diff --git a/docs/start/framework/react/guide/hosting.md b/docs/start/framework/react/guide/hosting.md index aa1806c6bf8..16983674d1e 100644 --- a/docs/start/framework/react/guide/hosting.md +++ b/docs/start/framework/react/guide/hosting.md @@ -445,7 +445,33 @@ bun run server.ts 🚀 Server running at http://localhost:3000 ``` -For a complete working example, check out the [TanStack Start + Bun example](https://github.com/TanStack/router/tree/main/examples/react/start-bun) in this repository. +For a complete working example of **Vite build + Bun HTTP host**, check out the [TanStack Start + Bun example](https://github.com/TanStack/router/tree/main/examples/react/start-bun) in this repository. + +### Bun as the bundler (experimental) + +There is also an experimental path that uses **Bun as the bundler** (no Vite), via `@tanstack/react-start/plugin/bun`: + +```ts +import { tanstackStart } from '@tanstack/react-start/plugin/bun' + +const start = tanstackStart({ bun: { port: 3000 } }) +await start.build() +// or: await start.dev() +``` + +Default production output matches the **Rsbuild-style** host: `dist/client` + `dist/server/server.js` + `dist/server/host.js` (static assets then `fetch`). Deploy `dist/` and run `bun dist/server/host.js`. See the [`start-bun-bundler`](https://github.com/TanStack/router/tree/main/examples/react/start-bun-bundler) example. Solid/Vue mirrors: `@tanstack/solid-start/plugin/bun`, `@tanstack/vue-start/plugin/bun`. + +**Optional extras (production only, experimental):** `bun.nitro` (post-build Nitro 3 → `.output`; cannot reuse `nitro/vite`) and `bun.standalone` (`Bun.build({ compile })` single OS/arch executable embedding `dist/client`). Prefer the default `host.js` path unless you need those outputs. Details and scripts live in the React example README / [`ARCHITECTURE.md`](https://github.com/TanStack/router/blob/main/packages/start-plugin-core/src/bun/ARCHITECTURE.md). + +**Dev HMR:** experimental ESM middleware + HMR + React Refresh. Entry aliases and define map are applied in the transform path; built `/assets` scripts are scrubbed from SSR HTML/manifest so they do not fight the ESM-dev client. Granularity and stability are not on par with Vite; some client changes may still trigger a full rebuild. + +**Serialization adapters:** pass `serializationAdapters` through the framework Start options (same as Vite/Rsbuild). The Bun adapter wires them into `#tanstack-start-plugin-adapters` for client and server builds. + +**Known limitations:** + +- **No RSC** — React Server Components are not supported on the Bun bundler adapter. +- Import protection is a simplified deny/mock path (no full Vite-style graph tracing / source maps yet). +- Optional `bun.nitro` / `bun.standalone` are production-only and experimental. ### Appwrite Sites diff --git a/examples/react/start-bun-bundler/README.md b/examples/react/start-bun-bundler/README.md new file mode 100644 index 00000000000..894eb5d0133 --- /dev/null +++ b/examples/react/start-bun-bundler/README.md @@ -0,0 +1,48 @@ +# TanStack Start + Bun Bundler + +Minimal example that builds with **Bun as the bundler** (no Vite). + +## vs `start-bun` + +| | [`start-bun`](../start-bun) | **this example** | +|--|--|--| +| Dev / build | Vite (`vite dev` / `vite build`) | `tanstackStart().dev()` / `.build()` via Bun | +| Production host | `Bun.serve` + Vite `dist` | **Default:** `host.js` (`dist/server/host.js`) | +| Plugin entry | `@tanstack/react-start/plugin/vite` | `@tanstack/react-start/plugin/bun` | + +## Scripts + +```bash +cd examples/react/start-bun-bundler +bun run build # → dist/client + dist/server/server.js + host.js +bun run start # bun dist/server/host.js +bun run dev +bun run smoke # default path (CI) +``` + +### Optional extras (experimental) + +```bash +bun run build:nitro # + bun.nitro → .output/ +bun run start:nitro # node .output/server/index.mjs +bun run smoke:nitro +bun run build:standalone # + bun.standalone → dist/server/start +bun run start:standalone # ./dist/server/start +bun run smoke:standalone +``` + +Prefer the default `host.js` path unless you need Nitro `.output` or a single OS/arch executable. Standalone always embeds `dist/client` (not `.output/public`). + +## What this proves + +- Dual `Bun.build` without Vite +- SSR + prerender + static `host.js` +- Code-splitting, import protection, CSS pipeline, experimental ESM HMR + React Refresh (dev) + +## Known limitations + +- **No RSC** — React Server Components are not supported +- Dev HMR is experimental (not Vite-level); some client changes may still full-rebuild +- Nitro / standalone are optional extras (production-only, experimental) + +See `packages/start-plugin-core/src/bun/ARCHITECTURE.md`. diff --git a/examples/react/start-bun-bundler/package.json b/examples/react/start-bun-bundler/package.json new file mode 100644 index 00000000000..1c4f337c2fa --- /dev/null +++ b/examples/react/start-bun-bundler/package.json @@ -0,0 +1,32 @@ +{ + "name": "tanstack-start-bun-bundler", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run ./scripts/dev.ts", + "build": "bun run ./scripts/build.ts", + "build:nitro": "bun run ./scripts/build-nitro.ts", + "build:standalone": "bun run ./scripts/build-standalone.ts", + "start": "bun run ./dist/server/host.js", + "start:nitro": "node .output/server/index.mjs", + "start:standalone": "./dist/server/start", + "smoke": "bun run ./scripts/smoke.ts", + "smoke:nitro": "bun run ./scripts/smoke-nitro.ts", + "smoke:standalone": "bun run ./scripts/smoke-standalone.ts", + "test:e2e": "bun run smoke" + }, + "dependencies": { + "@tanstack/react-router": "workspace:*", + "@tanstack/react-start": "workspace:*", + "@tanstack/router-plugin": "workspace:*", + "nitro": "npm:nitro-nightly@latest", + "react": "^19.1.1", + "react-dom": "^19.1.1" + }, + "devDependencies": { + "@types/bun": "^1.2.22", + "@types/react": "^19.1.13", + "@types/react-dom": "^19.1.9", + "typescript": "^5.9.0" + } +} diff --git a/examples/react/start-bun-bundler/public/robots.txt b/examples/react/start-bun-bundler/public/robots.txt new file mode 100644 index 00000000000..eb0536286f3 --- /dev/null +++ b/examples/react/start-bun-bundler/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/examples/react/start-bun-bundler/scripts/build-nitro.ts b/examples/react/start-bun-bundler/scripts/build-nitro.ts new file mode 100644 index 00000000000..b70def07f6b --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/build-nitro.ts @@ -0,0 +1,19 @@ +import { tanstackStart } from '@tanstack/react-start/plugin/bun' + +const start = tanstackStart({ + pages: [{ path: '/' }], + prerender: { + enabled: true, + failOnError: true, + }, + bun: { + nitro: { + preset: 'node-server', + }, + }, +}) + +await start.build() +console.info( + '[start-bun-bundler] nitro build complete → dist/* + .output/public + .output/server', +) diff --git a/examples/react/start-bun-bundler/scripts/build-standalone.ts b/examples/react/start-bun-bundler/scripts/build-standalone.ts new file mode 100644 index 00000000000..66fea4e00b6 --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/build-standalone.ts @@ -0,0 +1,19 @@ +import { tanstackStart } from '@tanstack/react-start/plugin/bun' + +const start = tanstackStart({ + pages: [{ path: '/' }], + prerender: { + enabled: true, + failOnError: true, + }, + bun: { + standalone: { + outfile: 'dist/server/start', + }, + }, +}) + +await start.build() +console.info( + '[start-bun-bundler] standalone build complete → dist/server/start', +) diff --git a/examples/react/start-bun-bundler/scripts/build.ts b/examples/react/start-bun-bundler/scripts/build.ts new file mode 100644 index 00000000000..b1d77373d2c --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/build.ts @@ -0,0 +1,12 @@ +import { tanstackStart } from '@tanstack/react-start/plugin/bun' + +const start = tanstackStart({ + pages: [{ path: '/' }], + prerender: { + enabled: true, + failOnError: true, + }, +}) + +await start.build() +console.info('[start-bun-bundler] build complete → dist/client + dist/server') diff --git a/examples/react/start-bun-bundler/scripts/dev.ts b/examples/react/start-bun-bundler/scripts/dev.ts new file mode 100644 index 00000000000..9aa826fe142 --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/dev.ts @@ -0,0 +1,4 @@ +import { tanstackStart } from '@tanstack/react-start/plugin/bun' + +const start = tanstackStart({ bun: { port: 3000 } }) +await start.dev() diff --git a/examples/react/start-bun-bundler/scripts/smoke-nitro.ts b/examples/react/start-bun-bundler/scripts/smoke-nitro.ts new file mode 100644 index 00000000000..397b1b0f5a4 --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/smoke-nitro.ts @@ -0,0 +1,108 @@ +/** + * Smoke check: Nitro bridge build → .output/server → assert `/`, assets, public dir. + */ +import { spawn } from 'node:child_process' +import { existsSync } from 'node:fs' +import { join } from 'node:path' + +const root = join(import.meta.dir, '..') +const DEFAULT_SMOKE_PORT = 3460 +const parsedPort = Number(process.env.SMOKE_PORT ?? DEFAULT_SMOKE_PORT) +const port = + Number.isFinite(parsedPort) && parsedPort > 0 + ? Math.trunc(parsedPort) + : DEFAULT_SMOKE_PORT +const host = '127.0.0.1' + +async function waitForServer(url: string, attempts = 60) { + for (let i = 0; i < attempts; i++) { + try { + const res = await fetch(url) + if (res.ok || res.status === 200) { + return + } + } catch { + // retry + } + await Bun.sleep(150) + } + throw new Error(`Server did not become ready at ${url}`) +} + +console.info('[smoke-nitro] building with bun.nitro…') +const build = spawn('bun', ['run', './scripts/build-nitro.ts'], { + cwd: root, + stdio: 'inherit', +}) +await new Promise((resolve, reject) => { + build.on('error', reject) + build.on('exit', (code) => + code === 0 ? resolve() : reject(new Error(`build-nitro exited ${code}`)), + ) +}) + +const publicDir = join(root, '.output/public') +const serverEntry = join(root, '.output/server/index.mjs') +if (!existsSync(publicDir)) { + throw new Error(`missing ${publicDir}`) +} +if (!existsSync(serverEntry)) { + throw new Error(`missing ${serverEntry}`) +} + +const assetFiles = [...new Bun.Glob('assets/**/*').scanSync({ cwd: publicDir })] +if (assetFiles.length === 0) { + throw new Error(`.output/public has no assets/ files`) +} + +console.info('[smoke-nitro] starting .output/server/index.mjs…') +const server = spawn('node', [serverEntry], { + cwd: root, + env: { ...process.env, PORT: String(port), NITRO_PORT: String(port) }, + stdio: ['ignore', 'pipe', 'pipe'], +}) + +let stdout = '' +let stderr = '' +server.stdout?.on('data', (chunk) => { + stdout += String(chunk) +}) +server.stderr?.on('data', (chunk) => { + stderr += String(chunk) +}) + +try { + await waitForServer(`http://${host}:${port}/`) + + const home = await fetch(`http://${host}:${port}/`) + const homeHtml = await home.text() + if (!home.ok) { + throw new Error(`GET / → ${home.status}`) + } + if (!homeHtml.includes('Hello from Bun-bundled Start')) { + throw new Error('GET / missing loader message in HTML') + } + + const preloadMatch = homeHtml.match( + /modulepreload[^>]+href="(\/assets\/[^"]+\.js)"/, + ) + if (!preloadMatch?.[1]) { + throw new Error('GET / missing modulepreload asset href') + } + const asset = await fetch(`http://${host}:${port}${preloadMatch[1]}`) + if (!asset.ok) { + throw new Error(`GET ${preloadMatch[1]} → ${asset.status}`) + } + + console.info('[smoke-nitro] ok') +} catch (err) { + if (stdout) { + console.error('[smoke-nitro] server stdout:\n', stdout) + } + if (stderr) { + console.error('[smoke-nitro] server stderr:\n', stderr) + } + throw err +} finally { + server.kill('SIGTERM') +} diff --git a/examples/react/start-bun-bundler/scripts/smoke-standalone.ts b/examples/react/start-bun-bundler/scripts/smoke-standalone.ts new file mode 100644 index 00000000000..93b3f0eea37 --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/smoke-standalone.ts @@ -0,0 +1,101 @@ +/** + * Smoke: bun.standalone compile → run dist/server/start → assert `/` + assets. + */ +import { spawn } from 'node:child_process' +import { existsSync } from 'node:fs' +import { join } from 'node:path' + +const root = join(import.meta.dir, '..') +const DEFAULT_SMOKE_PORT = 3461 +const parsedPort = Number(process.env.SMOKE_PORT ?? DEFAULT_SMOKE_PORT) +const port = + Number.isFinite(parsedPort) && parsedPort > 0 + ? Math.trunc(parsedPort) + : DEFAULT_SMOKE_PORT +const host = '127.0.0.1' +const exe = join(root, 'dist/server/start') + +async function waitForServer(url: string, attempts = 80) { + for (let i = 0; i < attempts; i++) { + try { + const res = await fetch(url) + if (res.ok || res.status === 200) { + return + } + } catch { + // retry + } + await Bun.sleep(150) + } + throw new Error(`Server did not become ready at ${url}`) +} + +console.info('[smoke-standalone] building with bun.standalone…') +const build = spawn('bun', ['run', './scripts/build-standalone.ts'], { + cwd: root, + stdio: 'inherit', +}) +await new Promise((resolve, reject) => { + build.on('error', reject) + build.on('exit', (code) => + code === 0 + ? resolve() + : reject(new Error(`build-standalone exited ${code}`)), + ) +}) + +if (!existsSync(exe)) { + throw new Error(`missing standalone executable at ${exe}`) +} + +console.info('[smoke-standalone] starting executable…') +const server = spawn(exe, [], { + cwd: root, + env: { ...process.env, PORT: String(port), HOST: host }, + stdio: ['ignore', 'pipe', 'pipe'], +}) + +let stdout = '' +let stderr = '' +server.stdout?.on('data', (chunk) => { + stdout += String(chunk) +}) +server.stderr?.on('data', (chunk) => { + stderr += String(chunk) +}) + +try { + await waitForServer(`http://${host}:${port}/`) + + const home = await fetch(`http://${host}:${port}/`) + const homeHtml = await home.text() + if (!home.ok) { + throw new Error(`GET / → ${home.status}`) + } + if (!homeHtml.includes('Hello from Bun-bundled Start')) { + throw new Error('GET / missing loader message in HTML') + } + + const preloadMatch = homeHtml.match( + /modulepreload[^>]+href="(\/assets\/[^"]+\.js)"/, + ) + if (!preloadMatch?.[1]) { + throw new Error('GET / missing modulepreload asset href') + } + const asset = await fetch(`http://${host}:${port}${preloadMatch[1]}`) + if (!asset.ok) { + throw new Error(`GET ${preloadMatch[1]} → ${asset.status}`) + } + + console.info('[smoke-standalone] ok') +} catch (err) { + if (stdout) { + console.error('[smoke-standalone] server stdout:\n', stdout) + } + if (stderr) { + console.error('[smoke-standalone] server stderr:\n', stderr) + } + throw err +} finally { + server.kill('SIGTERM') +} diff --git a/examples/react/start-bun-bundler/scripts/smoke.ts b/examples/react/start-bun-bundler/scripts/smoke.ts new file mode 100644 index 00000000000..811de1437de --- /dev/null +++ b/examples/react/start-bun-bundler/scripts/smoke.ts @@ -0,0 +1,110 @@ +/** + * Smoke check: build → host.js → assert `/`, `/about`, and static assets. + */ +import { spawn } from 'node:child_process' +import { join } from 'node:path' + +const root = join(import.meta.dir, '..') +const DEFAULT_SMOKE_PORT = 3457 +const parsedPort = Number(process.env.SMOKE_PORT ?? DEFAULT_SMOKE_PORT) +const port = + Number.isFinite(parsedPort) && parsedPort > 0 + ? Math.trunc(parsedPort) + : DEFAULT_SMOKE_PORT +const host = '127.0.0.1' + +async function waitForServer(url: string, attempts = 40) { + for (let i = 0; i < attempts; i++) { + try { + const res = await fetch(url) + if (res.ok || res.status === 200) { + return + } + } catch { + // retry + } + await Bun.sleep(100) + } + throw new Error(`Server did not become ready at ${url}`) +} + +console.info('[smoke] building…') +const build = spawn('bun', ['run', './scripts/build.ts'], { + cwd: root, + stdio: 'inherit', +}) +await new Promise((resolve, reject) => { + build.on('error', reject) + build.on('exit', (code) => + code === 0 ? resolve() : reject(new Error(`build exited ${code}`)), + ) +}) + +console.info('[smoke] starting host.js…') +const server = spawn('bun', ['run', './dist/server/host.js'], { + cwd: root, + env: { ...process.env, PORT: String(port) }, + stdio: ['ignore', 'pipe', 'pipe'], +}) + +const serverLogs: Array = [] +const capture = (chunk: Buffer | string) => { + serverLogs.push(String(chunk)) +} +server.stdout?.on('data', capture) +server.stderr?.on('data', capture) + +try { + await waitForServer(`http://${host}:${port}/`) + + const home = await fetch(`http://${host}:${port}/`) + const homeHtml = await home.text() + if (!home.ok) { + throw new Error(`GET / → ${home.status}`) + } + if (!homeHtml.includes('Hello from Bun-bundled Start')) { + throw new Error('GET / missing loader message in HTML') + } + + const about = await fetch(`http://${host}:${port}/about`) + const aboutHtml = await about.text() + if (!about.ok) { + throw new Error(`GET /about → ${about.status}`) + } + if (!aboutHtml.includes('Second route')) { + throw new Error('GET /about missing expected body') + } + + const preloadMatch = homeHtml.match( + /modulepreload[^>]+href="(\/assets\/[^"]+\.js)"/, + ) + if (!preloadMatch?.[1]) { + throw new Error('GET / missing modulepreload asset href') + } + const asset = await fetch(`http://${host}:${port}${preloadMatch[1]}`) + if (!asset.ok) { + throw new Error(`GET ${preloadMatch[1]} → ${asset.status}`) + } + + const cssMatch = homeHtml.match(/href="(\/assets\/[^"]+\.css)"/) + if (cssMatch?.[1]) { + const css = await fetch(`http://${host}:${port}${cssMatch[1]}`) + if (!css.ok) { + throw new Error(`GET ${cssMatch[1]} → ${css.status}`) + } + } + + const robots = await fetch(`http://${host}:${port}/robots.txt`) + if (!robots.ok) { + throw new Error(`GET /robots.txt → ${robots.status} (public/ copy missing?)`) + } + + console.info('[smoke] ok') +} catch (err) { + if (serverLogs.length > 0) { + console.error('[smoke] server output:\n', serverLogs.join('')) + } + throw err +} finally { + server.kill('SIGTERM') +} diff --git a/examples/react/start-bun-bundler/src/routeTree.gen.ts b/examples/react/start-bun-bundler/src/routeTree.gen.ts new file mode 100644 index 00000000000..12bc916ae46 --- /dev/null +++ b/examples/react/start-bun-bundler/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' + id: '__root__' | '/' | '/about' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AboutRoute: typeof AboutRoute +} + +declare module '@tanstack/react-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AboutRoute: AboutRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/examples/react/start-bun-bundler/src/router.tsx b/examples/react/start-bun-bundler/src/router.tsx new file mode 100644 index 00000000000..a5954446483 --- /dev/null +++ b/examples/react/start-bun-bundler/src/router.tsx @@ -0,0 +1,10 @@ +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export const getRouter = () => { + return createRouter({ + routeTree, + scrollRestoration: true, + defaultPreloadStaleTime: 0, + }) +} diff --git a/examples/react/start-bun-bundler/src/routes/__root.tsx b/examples/react/start-bun-bundler/src/routes/__root.tsx new file mode 100644 index 00000000000..2d1a81bf317 --- /dev/null +++ b/examples/react/start-bun-bundler/src/routes/__root.tsx @@ -0,0 +1,26 @@ +import { HeadContent, Scripts, createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { title: 'TanStack Start Bun Bundler' }, + ], + }), + shellComponent: RootDocument, +}) + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + + {children} + + + + ) +} diff --git a/examples/react/start-bun-bundler/src/routes/about.tsx b/examples/react/start-bun-bundler/src/routes/about.tsx new file mode 100644 index 00000000000..bc1e2a18059 --- /dev/null +++ b/examples/react/start-bun-bundler/src/routes/about.tsx @@ -0,0 +1,17 @@ +import { createFileRoute, Link } from '@tanstack/react-router' + +export const Route = createFileRoute('/about')({ + component: About, +}) + +function About() { + return ( +
+

About

+

Second route to exercise Bun code-splitting.

+

+ Home +

+
+ ) +} diff --git a/examples/react/start-bun-bundler/src/routes/index.tsx b/examples/react/start-bun-bundler/src/routes/index.tsx new file mode 100644 index 00000000000..5197215a183 --- /dev/null +++ b/examples/react/start-bun-bundler/src/routes/index.tsx @@ -0,0 +1,27 @@ +import { createFileRoute, Link } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' + +const getMessage = createServerFn({ method: 'GET' }).handler(async () => { + return { message: 'Hello from Bun-bundled Start' } +}) + +export const Route = createFileRoute('/')({ + loader: () => getMessage(), + component: Home, +}) + +function Home() { + const data = Route.useLoaderData() + return ( +
+

TanStack Start + Bun bundler

+

{data.message}

+

+ This example uses @tanstack/react-start/plugin/bun (no Vite). +

+

+ About +

+
+ ) +} diff --git a/examples/react/start-bun-bundler/tsconfig.json b/examples/react/start-bun-bundler/tsconfig.json new file mode 100644 index 00000000000..cf0b9dfde27 --- /dev/null +++ b/examples/react/start-bun-bundler/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["bun", "react", "react-dom"], + "paths": { + "#/*": ["./src/*"] + } + }, + "include": ["src", "scripts"] +} diff --git a/examples/solid/start-bun-bundler/README.md b/examples/solid/start-bun-bundler/README.md new file mode 100644 index 00000000000..232dd80ed06 --- /dev/null +++ b/examples/solid/start-bun-bundler/README.md @@ -0,0 +1,42 @@ +# TanStack Solid Start + Bun Bundler + +Minimal example that builds with **Bun as the bundler** (no Vite). + +## vs `start-bun` + +| | [`start-bun`](../start-bun) | **this example** | +|--|--|--| +| Dev / build | Vite (`vite dev` / `vite build`) | `tanstackStart().dev()` / `.build()` via Bun | +| Production host | `Bun.serve` + Vite `dist` | `host.js` (`dist/server/host.js`) | +| Plugin entry | `@tanstack/solid-start/plugin/vite` | `@tanstack/solid-start/plugin/bun` | + +## Scripts + +```bash +cd examples/solid/start-bun-bundler +bun run build # → dist/client + dist/server/server.js + host.js +bun run start # bun dist/server/host.js +bun run dev +bun run smoke +``` + +## Production host + +**Default (Rsbuild-style):** `dist/server/host.js` — deploy `dist/` + Bun + +(This example omits optional Nitro / standalone; see the full matrix in the React example `examples/react/start-bun-bundler`.) + +## What this proves + +- Dual `Bun.build` without Vite +- SSR + static host (`host.js`) +- Import protection, CSS pipeline, experimental ESM HMR (dev) + +## Known limitations + +- **No RSC** +- This minimal example sets `router.autoCodeSplitting: false` so route components stay in the server bundle (Solid SSR + Bun lazy splits need more wiring) +- Dev HMR is experimental (not Vite-level); some client changes may still full-rebuild +- Bun bundler path is experimental + +See `packages/start-plugin-core/src/bun/ARCHITECTURE.md`. diff --git a/examples/solid/start-bun-bundler/package.json b/examples/solid/start-bun-bundler/package.json new file mode 100644 index 00000000000..e460397852c --- /dev/null +++ b/examples/solid/start-bun-bundler/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-solid-start-bun-bundler", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run ./scripts/dev.ts", + "build": "bun run ./scripts/build.ts", + "start": "bun run ./dist/server/host.js", + "smoke": "bun run ./scripts/smoke.ts", + "test:e2e": "bun run smoke" + }, + "dependencies": { + "@tanstack/router-plugin": "workspace:*", + "@tanstack/solid-router": "workspace:*", + "@tanstack/solid-start": "workspace:*", + "babel-preset-solid": "^1.9.10", + "solid-js": "^1.9.10" + }, + "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@types/bun": "^1.2.22", + "typescript": "^5.9.0" + } +} diff --git a/examples/solid/start-bun-bundler/public/robots.txt b/examples/solid/start-bun-bundler/public/robots.txt new file mode 100644 index 00000000000..c2a49f4fb82 --- /dev/null +++ b/examples/solid/start-bun-bundler/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/examples/solid/start-bun-bundler/scripts/build.ts b/examples/solid/start-bun-bundler/scripts/build.ts new file mode 100644 index 00000000000..f3dc1c92e68 --- /dev/null +++ b/examples/solid/start-bun-bundler/scripts/build.ts @@ -0,0 +1,16 @@ +import { tanstackStart } from '@tanstack/solid-start/plugin/bun' + +const start = tanstackStart({ + bun: { + port: 3000, + hostname: '0.0.0.0', + }, + // Keep routes in the server bundle for this minimal example (lazy splits + // need more Solid SSR wiring under Bun before they render reliably). + router: { + autoCodeSplitting: false, + }, +}) + +await start.build() +console.info('[start-bun-bundler] build complete → dist/client + dist/server') diff --git a/examples/solid/start-bun-bundler/scripts/dev.ts b/examples/solid/start-bun-bundler/scripts/dev.ts new file mode 100644 index 00000000000..9c493e4ca2b --- /dev/null +++ b/examples/solid/start-bun-bundler/scripts/dev.ts @@ -0,0 +1,13 @@ +import { tanstackStart } from '@tanstack/solid-start/plugin/bun' + +const start = tanstackStart({ + bun: { + port: 3000, + hostname: '0.0.0.0', + }, + router: { + autoCodeSplitting: false, + }, +}) + +await start.dev({ port: 3000 }) diff --git a/examples/solid/start-bun-bundler/scripts/smoke.ts b/examples/solid/start-bun-bundler/scripts/smoke.ts new file mode 100644 index 00000000000..c47a7e61297 --- /dev/null +++ b/examples/solid/start-bun-bundler/scripts/smoke.ts @@ -0,0 +1,110 @@ +/** + * Smoke check: build → host.js → assert `/`, `/about`, and static assets. + */ +import { spawn } from 'node:child_process' +import { join } from 'node:path' + +const root = join(import.meta.dir, '..') +const DEFAULT_SMOKE_PORT = 3462 +const parsedPort = Number(process.env.SMOKE_PORT ?? DEFAULT_SMOKE_PORT) +const port = + Number.isFinite(parsedPort) && parsedPort > 0 + ? Math.trunc(parsedPort) + : DEFAULT_SMOKE_PORT +const host = '127.0.0.1' + +async function waitForServer(url: string, attempts = 40) { + for (let i = 0; i < attempts; i++) { + try { + const res = await fetch(url) + if (res.ok || res.status === 200) { + return + } + } catch { + // retry + } + await Bun.sleep(100) + } + throw new Error(`Server did not become ready at ${url}`) +} + +console.info('[smoke] building…') +const build = spawn('bun', ['run', './scripts/build.ts'], { + cwd: root, + stdio: 'inherit', +}) +await new Promise((resolve, reject) => { + build.on('error', reject) + build.on('exit', (code) => + code === 0 ? resolve() : reject(new Error(`build exited ${code}`)), + ) +}) + +console.info('[smoke] starting host.js…') +const server = spawn('bun', ['run', './dist/server/host.js'], { + cwd: root, + env: { ...process.env, PORT: String(port) }, + stdio: ['ignore', 'pipe', 'pipe'], +}) + +const serverLogs: Array = [] +const capture = (chunk: Buffer | string) => { + serverLogs.push(String(chunk)) +} +server.stdout?.on('data', capture) +server.stderr?.on('data', capture) + +try { + await waitForServer(`http://${host}:${port}/`) + + const home = await fetch(`http://${host}:${port}/`) + const homeHtml = await home.text() + if (!home.ok) { + throw new Error(`GET / → ${home.status}`) + } + if (!homeHtml.includes('Hello from Bun-bundled Solid Start')) { + throw new Error('GET / missing loader message in HTML') + } + + const about = await fetch(`http://${host}:${port}/about`) + const aboutHtml = await about.text() + if (!about.ok) { + throw new Error(`GET /about → ${about.status}`) + } + if (!aboutHtml.includes('Second route')) { + throw new Error('GET /about missing expected body') + } + + const preloadMatch = homeHtml.match( + /modulepreload[^>]+href="(\/assets\/[^"]+\.js)"/, + ) + if (!preloadMatch?.[1]) { + throw new Error('GET / missing modulepreload asset href') + } + const asset = await fetch(`http://${host}:${port}${preloadMatch[1]}`) + if (!asset.ok) { + throw new Error(`GET ${preloadMatch[1]} → ${asset.status}`) + } + + const cssMatch = homeHtml.match(/href="(\/assets\/[^"]+\.css)"/) + if (cssMatch?.[1]) { + const css = await fetch(`http://${host}:${port}${cssMatch[1]}`) + if (!css.ok) { + throw new Error(`GET ${cssMatch[1]} → ${css.status}`) + } + } + + const robots = await fetch(`http://${host}:${port}/robots.txt`) + if (!robots.ok) { + throw new Error(`GET /robots.txt → ${robots.status}`) + } + + console.info('[smoke] ok') +} catch (err) { + if (serverLogs.length > 0) { + console.error('[smoke] server output:\n', serverLogs.join('')) + } + throw err +} finally { + server.kill('SIGTERM') +} diff --git a/examples/solid/start-bun-bundler/src/routeTree.gen.ts b/examples/solid/start-bun-bundler/src/routeTree.gen.ts new file mode 100644 index 00000000000..98df1e83d86 --- /dev/null +++ b/examples/solid/start-bun-bundler/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' + id: '__root__' | '/' | '/about' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AboutRoute: typeof AboutRoute +} + +declare module '@tanstack/solid-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AboutRoute: AboutRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/examples/solid/start-bun-bundler/src/router.tsx b/examples/solid/start-bun-bundler/src/router.tsx new file mode 100644 index 00000000000..a9d2604de19 --- /dev/null +++ b/examples/solid/start-bun-bundler/src/router.tsx @@ -0,0 +1,10 @@ +import { createRouter } from '@tanstack/solid-router' +import { routeTree } from './routeTree.gen' + +export const getRouter = () => { + return createRouter({ + routeTree, + scrollRestoration: true, + defaultPreloadStaleTime: 0, + }) +} diff --git a/examples/solid/start-bun-bundler/src/routes/__root.tsx b/examples/solid/start-bun-bundler/src/routes/__root.tsx new file mode 100644 index 00000000000..57fb5e90673 --- /dev/null +++ b/examples/solid/start-bun-bundler/src/routes/__root.tsx @@ -0,0 +1,29 @@ +import { HeadContent, Scripts, createRootRoute } from '@tanstack/solid-router' +import { HydrationScript } from 'solid-js/web' +import type { JSX } from 'solid-js' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { title: 'TanStack Solid Start Bun Bundler' }, + ], + }), + shellComponent: RootDocument, +}) + +function RootDocument({ children }: { children: JSX.Element }) { + return ( + + + + + + + {children} + + + + ) +} diff --git a/examples/solid/start-bun-bundler/src/routes/about.tsx b/examples/solid/start-bun-bundler/src/routes/about.tsx new file mode 100644 index 00000000000..0b6dd5df945 --- /dev/null +++ b/examples/solid/start-bun-bundler/src/routes/about.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/about')({ + component: About, +}) + +function About() { + return ( +
+

About

+

Second route to exercise Bun code-splitting.

+

+ Home +

+
+ ) +} diff --git a/examples/solid/start-bun-bundler/src/routes/index.tsx b/examples/solid/start-bun-bundler/src/routes/index.tsx new file mode 100644 index 00000000000..8d89673e772 --- /dev/null +++ b/examples/solid/start-bun-bundler/src/routes/index.tsx @@ -0,0 +1,28 @@ +import { createFileRoute } from '@tanstack/solid-router' +import { createServerFn } from '@tanstack/solid-start' + +const getMessage = createServerFn({ method: 'GET' }).handler(async () => { + return { message: 'Hello from Bun-bundled Solid Start' } +}) + +export const Route = createFileRoute('/')({ + loader: () => getMessage(), + component: Home, +}) + +function Home() { + const data = Route.useLoaderData() + return ( +
+

TanStack Solid Start + Bun bundler

+

{data().message}

+

+ This example uses @tanstack/solid-start/plugin/bun (no + Vite). +

+

+ About +

+
+ ) +} diff --git a/examples/solid/start-bun-bundler/tsconfig.json b/examples/solid/start-bun-bundler/tsconfig.json new file mode 100644 index 00000000000..75af8ed7d92 --- /dev/null +++ b/examples/solid/start-bun-bundler/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "jsxImportSource": "solid-js", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["bun"], + "paths": { + "#/*": ["./src/*"] + } + }, + "include": ["src", "scripts"] +} diff --git a/examples/vue/start-bun-bundler/README.md b/examples/vue/start-bun-bundler/README.md new file mode 100644 index 00000000000..41355c084ef --- /dev/null +++ b/examples/vue/start-bun-bundler/README.md @@ -0,0 +1,40 @@ +# TanStack Vue Start + Bun Bundler + +Minimal example that builds with **Bun as the bundler** (no Vite). + +## Scripts + +```bash +cd examples/vue/start-bun-bundler +bun run build # → dist/client + dist/server/server.js + host.js +bun run start # bun dist/server/host.js +bun run dev +bun run smoke +``` + +## Production host + +**Default (Rsbuild-style):** `dist/server/host.js` — deploy `dist/` + Bun + +(This example omits optional Nitro / standalone; see the full matrix in the React example `examples/react/start-bun-bundler`.) + +| | Vite-based Start | **this example** | +|--|--|--| +| Dev / build | Vite | `tanstackStart().dev()` / `.build()` via Bun | +| Production host | deployment adapters | `host.js` (`dist/server/host.js`) | +| Plugin entry | `@tanstack/vue-start/plugin/vite` | `@tanstack/vue-start/plugin/bun` | + +## What this proves + +- Dual `Bun.build` without Vite +- SSR + static host (`host.js`) +- Import protection, CSS pipeline, experimental ESM HMR (dev) + +## Known limitations + +- **No RSC** +- This minimal example sets `router.autoCodeSplitting: false` so route components stay in the server bundle (Vue SSR + Bun lazy splits need more wiring) +- Dev HMR is experimental (not Vite-level); some client changes may still full-rebuild +- Bun bundler path is experimental + +See `packages/start-plugin-core/src/bun/ARCHITECTURE.md`. diff --git a/examples/vue/start-bun-bundler/package.json b/examples/vue/start-bun-bundler/package.json new file mode 100644 index 00000000000..8e6253de6ad --- /dev/null +++ b/examples/vue/start-bun-bundler/package.json @@ -0,0 +1,25 @@ +{ + "name": "tanstack-vue-start-bun-bundler", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run ./scripts/dev.ts", + "build": "bun run ./scripts/build.ts", + "start": "bun run ./dist/server/host.js", + "smoke": "bun run ./scripts/smoke.ts", + "test:e2e": "bun run smoke" + }, + "dependencies": { + "@tanstack/router-plugin": "workspace:*", + "@tanstack/vue-router": "workspace:*", + "@tanstack/vue-start": "workspace:*", + "@vue/babel-plugin-jsx": "^1.4.0", + "vue": "^3.5.16" + }, + "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", + "@types/bun": "^1.2.22", + "typescript": "^5.9.0" + } +} diff --git a/examples/vue/start-bun-bundler/public/robots.txt b/examples/vue/start-bun-bundler/public/robots.txt new file mode 100644 index 00000000000..c2a49f4fb82 --- /dev/null +++ b/examples/vue/start-bun-bundler/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Allow: / diff --git a/examples/vue/start-bun-bundler/scripts/build.ts b/examples/vue/start-bun-bundler/scripts/build.ts new file mode 100644 index 00000000000..fdbfb31181d --- /dev/null +++ b/examples/vue/start-bun-bundler/scripts/build.ts @@ -0,0 +1,15 @@ +import { tanstackStart } from '@tanstack/vue-start/plugin/bun' + +const start = tanstackStart({ + bun: { + port: 3000, + hostname: '0.0.0.0', + }, + // Keep routes in the server bundle for this minimal example. + router: { + autoCodeSplitting: false, + }, +}) + +await start.build() +console.info('[start-bun-bundler] build complete → dist/client + dist/server') diff --git a/examples/vue/start-bun-bundler/scripts/dev.ts b/examples/vue/start-bun-bundler/scripts/dev.ts new file mode 100644 index 00000000000..2264cfbcdcb --- /dev/null +++ b/examples/vue/start-bun-bundler/scripts/dev.ts @@ -0,0 +1,13 @@ +import { tanstackStart } from '@tanstack/vue-start/plugin/bun' + +const start = tanstackStart({ + bun: { + port: 3000, + hostname: '0.0.0.0', + }, + router: { + autoCodeSplitting: false, + }, +}) + +await start.dev({ port: 3000 }) diff --git a/examples/vue/start-bun-bundler/scripts/smoke.ts b/examples/vue/start-bun-bundler/scripts/smoke.ts new file mode 100644 index 00000000000..dc3f8c518b2 --- /dev/null +++ b/examples/vue/start-bun-bundler/scripts/smoke.ts @@ -0,0 +1,110 @@ +/** + * Smoke check: build → host.js → assert `/`, `/about`, and static assets. + */ +import { spawn } from 'node:child_process' +import { join } from 'node:path' + +const root = join(import.meta.dir, '..') +const DEFAULT_SMOKE_PORT = 3463 +const parsedPort = Number(process.env.SMOKE_PORT ?? DEFAULT_SMOKE_PORT) +const port = + Number.isFinite(parsedPort) && parsedPort > 0 + ? Math.trunc(parsedPort) + : DEFAULT_SMOKE_PORT +const host = '127.0.0.1' + +async function waitForServer(url: string, attempts = 40) { + for (let i = 0; i < attempts; i++) { + try { + const res = await fetch(url) + if (res.ok || res.status === 200) { + return + } + } catch { + // retry + } + await Bun.sleep(100) + } + throw new Error(`Server did not become ready at ${url}`) +} + +console.info('[smoke] building…') +const build = spawn('bun', ['run', './scripts/build.ts'], { + cwd: root, + stdio: 'inherit', +}) +await new Promise((resolve, reject) => { + build.on('error', reject) + build.on('exit', (code) => + code === 0 ? resolve() : reject(new Error(`build exited ${code}`)), + ) +}) + +console.info('[smoke] starting host.js…') +const server = spawn('bun', ['run', './dist/server/host.js'], { + cwd: root, + env: { ...process.env, PORT: String(port) }, + stdio: ['ignore', 'pipe', 'pipe'], +}) + +const serverLogs: Array = [] +const capture = (chunk: Buffer | string) => { + serverLogs.push(String(chunk)) +} +server.stdout?.on('data', capture) +server.stderr?.on('data', capture) + +try { + await waitForServer(`http://${host}:${port}/`) + + const home = await fetch(`http://${host}:${port}/`) + const homeHtml = await home.text() + if (!home.ok) { + throw new Error(`GET / → ${home.status}`) + } + if (!homeHtml.includes('Hello from Bun-bundled Vue Start')) { + throw new Error('GET / missing loader message in HTML') + } + + const about = await fetch(`http://${host}:${port}/about`) + const aboutHtml = await about.text() + if (!about.ok) { + throw new Error(`GET /about → ${about.status}`) + } + if (!aboutHtml.includes('Second route')) { + throw new Error('GET /about missing expected body') + } + + const preloadMatch = homeHtml.match( + /modulepreload[^>]+href="(\/assets\/[^"]+\.js)"/, + ) + if (!preloadMatch?.[1]) { + throw new Error('GET / missing modulepreload asset href') + } + const asset = await fetch(`http://${host}:${port}${preloadMatch[1]}`) + if (!asset.ok) { + throw new Error(`GET ${preloadMatch[1]} → ${asset.status}`) + } + + const cssMatch = homeHtml.match(/href="(\/assets\/[^"]+\.css)"/) + if (cssMatch?.[1]) { + const css = await fetch(`http://${host}:${port}${cssMatch[1]}`) + if (!css.ok) { + throw new Error(`GET ${cssMatch[1]} → ${css.status}`) + } + } + + const robots = await fetch(`http://${host}:${port}/robots.txt`) + if (!robots.ok) { + throw new Error(`GET /robots.txt → ${robots.status}`) + } + + console.info('[smoke] ok') +} catch (err) { + if (serverLogs.length > 0) { + console.error('[smoke] server output:\n', serverLogs.join('')) + } + throw err +} finally { + server.kill('SIGTERM') +} diff --git a/examples/vue/start-bun-bundler/src/routeTree.gen.ts b/examples/vue/start-bun-bundler/src/routeTree.gen.ts new file mode 100644 index 00000000000..75b8119285b --- /dev/null +++ b/examples/vue/start-bun-bundler/src/routeTree.gen.ts @@ -0,0 +1,77 @@ +/* eslint-disable */ + +// @ts-nocheck + +// noinspection JSUnusedGlobalSymbols + +// This file was automatically generated by TanStack Router. +// You should NOT make any changes in this file as it will be overwritten. +// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. + +import { Route as rootRouteImport } from './routes/__root' +import { Route as IndexRouteImport } from './routes/index' +import { Route as AboutRouteImport } from './routes/about' + +const IndexRoute = IndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => rootRouteImport, +} as any) +const AboutRoute = AboutRouteImport.update({ + id: '/about', + path: '/about', + getParentRoute: () => rootRouteImport, +} as any) + +export interface FileRoutesByFullPath { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRoutesByTo { + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRoutesById { + __root__: typeof rootRouteImport + '/': typeof IndexRoute + '/about': typeof AboutRoute +} +export interface FileRouteTypes { + fileRoutesByFullPath: FileRoutesByFullPath + fullPaths: '/' | '/about' + fileRoutesByTo: FileRoutesByTo + to: '/' | '/about' + id: '__root__' | '/' | '/about' + fileRoutesById: FileRoutesById +} +export interface RootRouteChildren { + IndexRoute: typeof IndexRoute + AboutRoute: typeof AboutRoute +} + +declare module '@tanstack/vue-router' { + interface FileRoutesByPath { + '/': { + id: '/' + path: '/' + fullPath: '/' + preLoaderRoute: typeof IndexRouteImport + parentRoute: typeof rootRouteImport + } + '/about': { + id: '/about' + path: '/about' + fullPath: '/about' + preLoaderRoute: typeof AboutRouteImport + parentRoute: typeof rootRouteImport + } + } +} + +const rootRouteChildren: RootRouteChildren = { + IndexRoute: IndexRoute, + AboutRoute: AboutRoute, +} +export const routeTree = rootRouteImport + ._addFileChildren(rootRouteChildren) + ._addFileTypes() diff --git a/examples/vue/start-bun-bundler/src/router.tsx b/examples/vue/start-bun-bundler/src/router.tsx new file mode 100644 index 00000000000..f77121c7188 --- /dev/null +++ b/examples/vue/start-bun-bundler/src/router.tsx @@ -0,0 +1,10 @@ +import { createRouter } from '@tanstack/vue-router' +import { routeTree } from './routeTree.gen' + +export const getRouter = () => { + return createRouter({ + routeTree, + scrollRestoration: true, + defaultPreloadStaleTime: 0, + }) +} diff --git a/examples/vue/start-bun-bundler/src/routes/__root.tsx b/examples/vue/start-bun-bundler/src/routes/__root.tsx new file mode 100644 index 00000000000..03886f0ec5a --- /dev/null +++ b/examples/vue/start-bun-bundler/src/routes/__root.tsx @@ -0,0 +1,32 @@ +import { + Body, + HeadContent, + Html, + Scripts, + createRootRoute, +} from '@tanstack/vue-router' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { charSet: 'utf-8' }, + { name: 'viewport', content: 'width=device-width, initial-scale=1' }, + { title: 'TanStack Vue Start Bun Bundler' }, + ], + }), + shellComponent: RootDocument, +}) + +function RootDocument(_: unknown, { slots }: { slots: { default?: () => any } }) { + return ( + + + + + + {slots.default?.()} + + + + ) +} diff --git a/examples/vue/start-bun-bundler/src/routes/about.tsx b/examples/vue/start-bun-bundler/src/routes/about.tsx new file mode 100644 index 00000000000..32fb1548410 --- /dev/null +++ b/examples/vue/start-bun-bundler/src/routes/about.tsx @@ -0,0 +1,17 @@ +import { createFileRoute } from '@tanstack/vue-router' + +export const Route = createFileRoute('/about')({ + component: About, +}) + +function About() { + return ( +
+

About

+

Second route to exercise Bun code-splitting.

+

+ Home +

+
+ ) +} diff --git a/examples/vue/start-bun-bundler/src/routes/index.tsx b/examples/vue/start-bun-bundler/src/routes/index.tsx new file mode 100644 index 00000000000..401008ed894 --- /dev/null +++ b/examples/vue/start-bun-bundler/src/routes/index.tsx @@ -0,0 +1,27 @@ +import { createFileRoute } from '@tanstack/vue-router' +import { createServerFn } from '@tanstack/vue-start' + +const getMessage = createServerFn({ method: 'GET' }).handler(async () => { + return { message: 'Hello from Bun-bundled Vue Start' } +}) + +export const Route = createFileRoute('/')({ + loader: () => getMessage(), + component: Home, +}) + +function Home() { + const data = Route.useLoaderData() + return ( +
+

TanStack Vue Start + Bun bundler

+

{data.value.message}

+

+ This example uses @tanstack/vue-start/plugin/bun (no Vite). +

+

+ About +

+
+ ) +} diff --git a/examples/vue/start-bun-bundler/tsconfig.json b/examples/vue/start-bun-bundler/tsconfig.json new file mode 100644 index 00000000000..843471168ba --- /dev/null +++ b/examples/vue/start-bun-bundler/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "jsxImportSource": "vue", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "types": ["bun"], + "paths": { + "#/*": ["./src/*"] + } + }, + "include": ["src", "scripts"] +} diff --git a/packages/react-start/package.json b/packages/react-start/package.json index 56d12a86d74..2a352a4c815 100644 --- a/packages/react-start/package.json +++ b/packages/react-start/package.json @@ -94,6 +94,14 @@ "default": "./dist/esm/plugin/rsbuild.js" } }, + "./plugin/bun": { + "bun": "./src/plugin/bun.ts", + "import": { + "types": "./dist/esm/plugin/bun.d.ts", + "default": "./dist/esm/plugin/bun.js" + }, + "default": "./src/plugin/bun.ts" + }, "./server-entry": { "import": { "types": "./dist/default-entry/esm/server.d.ts", diff --git a/packages/react-start/src/plugin/bun.ts b/packages/react-start/src/plugin/bun.ts new file mode 100644 index 00000000000..f7864f25cc0 --- /dev/null +++ b/packages/react-start/src/plugin/bun.ts @@ -0,0 +1,59 @@ +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import path from 'pathe' +import { + BUN_ENVIRONMENT_NAMES, + tanStackStartBun, +} from '@tanstack/start-plugin-core/bun' +import { reactStartDefaultEntryPaths } from './shared' +import type { + TanStackStartBunInputConfig, + TanStackStartBunPluginCoreOptions, + TanStackStartBunAdapter, +} from '@tanstack/start-plugin-core/bun' + +/** Resolve default Start entry file paths for the app root. */ +function resolveDefaultEntryPaths() { + if (existsSync(reactStartDefaultEntryPaths.client)) { + return reactStartDefaultEntryPaths + } + + // Source-checkout fallback (before vite copies default-entry into plugin/) + const srcDefault = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../default-entry', + ) + return { + client: path.resolve(srcDefault, 'client.tsx'), + server: path.resolve(srcDefault, 'server.ts'), + start: path.resolve(srcDefault, 'start.ts'), + } +} + +/** + * TanStack Start Bun bundler adapter (imperative build/dev API). + * + * @example + * ```ts + * import { tanstackStart } from '@tanstack/react-start/plugin/bun' + * const start = tanstackStart() + * await start.build() + * // or + * await start.dev({ port: 3000 }) + * ``` + */ +export function tanstackStart( + options?: TanStackStartBunInputConfig, +): TanStackStartBunAdapter { + const corePluginOpts: TanStackStartBunPluginCoreOptions = { + framework: 'react', + defaultEntryPaths: resolveDefaultEntryPaths(), + providerEnvironmentName: BUN_ENVIRONMENT_NAMES.server, + ssrIsProvider: true, + bun: options?.bun, + } + + return tanStackStartBun(corePluginOpts, options) +} + +export type { TanStackStartBunAdapter, TanStackStartBunInputConfig } diff --git a/packages/react-start/vite.config.ts b/packages/react-start/vite.config.ts index 652e4ef8425..b5d83fd8f12 100644 --- a/packages/react-start/vite.config.ts +++ b/packages/react-start/vite.config.ts @@ -41,6 +41,7 @@ export default mergeConfig( './src/rsbuild/ssr-decode.ts', './src/plugin/rsbuild.ts', './src/plugin/vite.ts', + './src/plugin/bun.ts', './src/server-only.ts', './src/client-only.ts', ], diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index a20240230e8..b98e239d4c6 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -103,6 +103,18 @@ "default": "./dist/cjs/esbuild.cjs" } }, + "./bun": { + "bun": "./src/bun.ts", + "import": { + "types": "./dist/esm/bun.d.ts", + "default": "./dist/esm/bun.js" + }, + "require": { + "types": "./dist/cjs/bun.d.cts", + "default": "./dist/cjs/bun.cjs" + }, + "default": "./src/bun.ts" + }, "./package.json": "./package.json" }, "sideEffects": false, @@ -131,6 +143,7 @@ "@rsbuild/core": "^2.1.0", "@types/babel__core": "^7.20.5", "@types/babel__template": "^7.4.4", + "@types/bun": "^1.3.14", "@types/node": ">=20", "@tanstack/vite-config": "catalog:", "vitest": "catalog:", @@ -146,6 +159,7 @@ "peerDependencies": { "@rsbuild/core": ">=1.0.2 || ^2.0.0", "@tanstack/react-router": "workspace:^", + "bun": ">=1.2.0", "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", "webpack": ">=5.92.0" @@ -157,6 +171,9 @@ "@tanstack/react-router": { "optional": true }, + "bun": { + "optional": true + }, "vite": { "optional": true }, diff --git a/packages/router-plugin/src/bun-shim.d.ts b/packages/router-plugin/src/bun-shim.d.ts new file mode 100644 index 00000000000..c8fecc5261f --- /dev/null +++ b/packages/router-plugin/src/bun-shim.d.ts @@ -0,0 +1,46 @@ +/** Minimal Bun ambient types for router-plugin bun entry. */ + +declare module 'bun' { + export interface OnLoadResult { + contents: string | Uint8Array + loader?: 'js' | 'jsx' | 'ts' | 'tsx' | 'json' | 'toml' | 'file' | 'text' + } + + export interface OnResolveResult { + path: string + namespace?: string + } + + export interface PluginBuilder { + onStart: (callback: () => void | Promise) => void + onResolve: ( + options: { filter: RegExp; namespace?: string }, + callback: (args: { + path: string + importer: string + namespace: string + kind: string + }) => OnResolveResult | undefined | Promise, + ) => void + onLoad: ( + options: { filter: RegExp; namespace?: string }, + callback: (args: { + path: string + namespace: string + }) => + | OnLoadResult + | undefined + | Promise + | null, + ) => void + } + + export interface BunPlugin { + name: string + setup: (build: PluginBuilder) => void | Promise + } +} + +declare var Bun: { + plugin: (plugin: import('bun').BunPlugin) => void +} diff --git a/packages/router-plugin/src/bun.ts b/packages/router-plugin/src/bun.ts new file mode 100644 index 00000000000..20828bc1ed1 --- /dev/null +++ b/packages/router-plugin/src/bun.ts @@ -0,0 +1,26 @@ +/** + * Bun bundler adapter for @tanstack/router-plugin. + * + * Prefer the native Bun code-splitter for `Bun.build({ plugins })`. + * Esbuild-shaped factories remain available for tools that accept esbuild plugins. + */ +export { configSchema } from './core/config' +export { + createBunRouterCodeSplitterPlugin, + createBunRouterCodeSplitterRuntime, +} from './core/bun-code-splitter-plugin' +export type { + BunCodeSplitterOptions, + BunCodeSplitterRuntime, +} from './core/bun-code-splitter-plugin' +export { createRouterPluginContext } from './core/router-plugin-context' + +export { + TanStackRouterGeneratorEsbuild as TanStackRouterGeneratorBun, + TanStackRouterCodeSplitterEsbuild as TanStackRouterCodeSplitterEsbuildBun, + TanStackRouterEsbuild as TanStackRouterBun, + tanstackRouter, + TanStackRouterEsbuild as default, +} from './esbuild' + +export type { Config, CodeSplittingOptions, RouterPluginContext } from './esbuild' diff --git a/packages/router-plugin/src/core/bun-code-splitter-plugin.ts b/packages/router-plugin/src/core/bun-code-splitter-plugin.ts new file mode 100644 index 00000000000..49248d8c79c --- /dev/null +++ b/packages/router-plugin/src/core/bun-code-splitter-plugin.ts @@ -0,0 +1,400 @@ +/** + * Bun-native code-splitter (esbuild-adjacent onResolve/onLoad). + * Mirrors createRouterCodeSplitterPlugin transform handlers without unplugin. + * + * Reference-file transforms must be applied by the Start compiler host (or another + * onLoad owner) because Bun only allows one successful onLoad per module. + * This plugin only owns `tsr-split` / `tsr-shared` virtual modules. + */ + +import { readFile } from 'node:fs/promises' +import { fileURLToPath, pathToFileURL } from 'node:url' +import { decodeIdentifier, logDiff } from '@tanstack/router-utils' +import { getConfig, splitGroupingsSchema } from './config' +import { + compileCodeSplitReferenceRoute, + compileCodeSplitSharedRoute, + compileCodeSplitVirtualRoute, + computeSharedBindings, + detectCodeSplitGroupingsFromRoute, +} from './code-splitter/compilers' +import { getFrameworkHmrCompilerPlugins } from './code-splitter/plugins/framework-plugins' +import { + defaultCodeSplitGroupings, + splitRouteIdentNodes, + tsrShared, + tsrSplit, +} from './constants' +import { debug, normalizePath, routeFactoryCallCodeFilter } from './utils' +import type { CodeSplitGroupings, SplitRouteIdentNodes } from './constants' +import type { GetRoutesByFileMapResultValue } from '@tanstack/router-generator' +import type { CodeSplitCompilerPlugin } from './code-splitter/plugins' +import type { Config, HmrStyle } from './config' +import type { RouterPluginContext } from './router-plugin-context' +import type { BunPlugin } from 'bun' + +export type BunCodeSplitterOptions = { + root: string + isProduction?: boolean + /** Merged into getConfig / function options */ + config?: Partial | (() => Config) +} + +export type BunCodeSplitterRuntime = { + plugin: BunPlugin + /** Apply reference-route code-splitting; returns original code when unchanged. */ + transformReference: (code: string, id: string) => string + /** + * Compile `?tsr-split=` / `?tsr-shared=` virtual modules. + * Used by Bun Start ESM-dev middleware (Bun.build uses the plugin onLoad path). + */ + transformVirtual: (code: string, id: string) => string +} + +/** Detect route factory calls that need code-splitting. */ +function matchesRouteFactory(code: string): boolean { + return routeFactoryCallCodeFilter.some((re) => re.test(code)) +} + +/** Pick a Bun loader from a file path extension. */ +function loaderForPath(filePath: string): 'tsx' | 'ts' | 'jsx' | 'js' { + if (filePath.endsWith('.tsx')) { + return 'tsx' + } + if (filePath.endsWith('.jsx')) { + return 'jsx' + } + if (filePath.endsWith('.ts')) { + return 'ts' + } + return 'js' +} + +/** Remove the `?…` query suffix from a module id. */ +function stripQuery(id: string): string { + const q = id.indexOf('?') + return q >= 0 ? id.slice(0, q) : id +} + +/** + * Create Bun code-splitter runtime: virtual-module plugin + reference transform. + */ +export function createBunRouterCodeSplitterRuntime( + routerPluginContext: RouterPluginContext, + bunOptions: BunCodeSplitterOptions, +): BunCodeSplitterRuntime { + const isProduction = + bunOptions.isProduction ?? process.env.NODE_ENV === 'production' + const ROOT = bunOptions.root + + let userConfig: Config + let addHmr: boolean + let hmrStyle: HmrStyle + let compilerPlugins: Array + let virtualRouteCompilerPlugins: Array + + const sharedBindingsMap = new Map>() + + const initUserConfig = () => { + const options = bunOptions.config ?? {} + if (typeof options === 'function') { + userConfig = options() + } else { + userConfig = getConfig(options, ROOT) + } + + addHmr = (userConfig.codeSplittingOptions?.addHmr ?? true) && !isProduction + hmrStyle = userConfig.plugin?.hmr?.style ?? 'vite' + compilerPlugins = [ + ...(addHmr + ? (getFrameworkHmrCompilerPlugins({ + targetFramework: userConfig.target, + hmrStyle, + }) ?? []) + : []), + ...(userConfig.codeSplittingOptions?.compilerPlugins ?? []), + ] + virtualRouteCompilerPlugins = compilerPlugins.filter( + (plugin) => plugin.onVirtualRouteSplitNode, + ) + } + + initUserConfig() + + const getGlobalCodeSplitGroupings = () => { + return ( + userConfig.codeSplittingOptions?.defaultBehavior || + defaultCodeSplitGroupings + ) + } + const getShouldSplitFn = () => { + return userConfig.codeSplittingOptions?.splitBehavior + } + + const handleCompilingReferenceFile = ( + code: string, + id: string, + generatorNodeInfo: GetRoutesByFileMapResultValue, + ): { code: string } | null => { + if (debug) { + console.info('[bun code-splitter] Compiling Route: ', id) + } + + const fromCode = detectCodeSplitGroupingsFromRoute({ + code, + filename: id, + }) + + if (fromCode.groupings !== undefined) { + const res = splitGroupingsSchema.safeParse(fromCode.groupings) + if (!res.success) { + const message = res.error.issues.map((e) => e.message).join('. ') + throw new Error( + `The groupings for the route "${id}" are invalid.\n${message}`, + ) + } + } + + const userShouldSplitFn = getShouldSplitFn() + const pluginSplitBehavior = userShouldSplitFn?.({ + routeId: generatorNodeInfo.routeId, + }) as CodeSplitGroupings | undefined + + if (pluginSplitBehavior) { + const res = splitGroupingsSchema.safeParse(pluginSplitBehavior) + if (!res.success) { + const message = res.error.issues.map((e) => e.message).join('. ') + throw new Error( + `The groupings returned when using \`splitBehavior\` for the route "${id}" are invalid.\n${message}`, + ) + } + } + + const splitGroupings: CodeSplitGroupings = + fromCode.groupings ?? pluginSplitBehavior ?? getGlobalCodeSplitGroupings() + + const sharedBindings = computeSharedBindings({ + code, + filename: id, + codeSplitGroupings: splitGroupings, + }) + if (sharedBindings.size > 0) { + sharedBindingsMap.set(id, sharedBindings) + } else { + sharedBindingsMap.delete(id) + } + + const compiledReferenceRoute = compileCodeSplitReferenceRoute({ + code, + codeSplitGroupings: splitGroupings, + targetFramework: userConfig.target, + filename: id, + id, + deleteNodes: userConfig.codeSplittingOptions?.deleteNodes + ? new Set(userConfig.codeSplittingOptions.deleteNodes) + : undefined, + addHmr, + hmrStyle, + hmrRouteId: generatorNodeInfo.routeId, + sharedBindings: sharedBindings.size > 0 ? sharedBindings : undefined, + compilerPlugins, + }) + + if (compiledReferenceRoute === null) { + return null + } + if (debug) { + logDiff(code, compiledReferenceRoute.code) + } + return compiledReferenceRoute + } + + const handleCompilingVirtualFile = (code: string, id: string) => { + if (debug) { + console.info('[bun code-splitter] Splitting Route: ', id) + } + + const [_, ...pathnameParts] = id.split('?') + const searchParams = new URLSearchParams(pathnameParts.join('?')) + const splitValue = searchParams.get(tsrSplit) + + if (!splitValue) { + throw new Error( + `The split value for the virtual route "${id}" was not found.`, + ) + } + + const rawGrouping = decodeIdentifier(splitValue) + const grouping = [...new Set(rawGrouping)].filter((p) => + splitRouteIdentNodes.includes(p as any), + ) as Array + + const baseId = id.split('?')[0]! + const resolvedSharedBindings = sharedBindingsMap.get(baseId) + + const result = compileCodeSplitVirtualRoute({ + code, + filename: id, + splitTargets: grouping, + sharedBindings: resolvedSharedBindings, + compilerPlugins: virtualRouteCompilerPlugins, + }) + + if (debug) { + logDiff(code, result.code) + } + return result + } + + const handleCompilingSharedFile = (code: string, id: string) => { + const url = pathToFileURL(id) + url.searchParams.delete('v') + const normalizedId = normalizePath(fileURLToPath(url)) + const [baseId] = normalizedId.split('?') + if (!baseId) { + return null + } + + const sharedBindings = sharedBindingsMap.get(baseId) + if (!sharedBindings || sharedBindings.size === 0) { + return null + } + + if (debug) { + console.info('[bun code-splitter] Shared Module: ', id) + } + + const result = compileCodeSplitSharedRoute({ + code, + sharedBindings, + filename: normalizedId, + }) + if (debug) { + logDiff(code, result.code) + } + return result + } + + const transformReference = (code: string, id: string): string => { + initUserConfig() + if (!userConfig.autoCodeSplitting) { + return code + } + const normalizedId = normalizePath(stripQuery(id)) + const generatorFileInfo = + routerPluginContext.routesByFile.get(normalizedId) + if (!generatorFileInfo) { + return code + } + if (!matchesRouteFactory(code)) { + return code + } + const result = handleCompilingReferenceFile( + code, + normalizedId, + generatorFileInfo, + ) + return result?.code ?? code + } + + const transformVirtual = (code: string, id: string): string => { + initUserConfig() + if (!userConfig.autoCodeSplitting) { + return code + } + if (id.includes(tsrSplit)) { + // Ensure shared bindings exist (reference compile may not have run yet) + const baseId = normalizePath(stripQuery(id)) + const generatorFileInfo = + routerPluginContext.routesByFile.get(baseId) + if (generatorFileInfo && matchesRouteFactory(code)) { + handleCompilingReferenceFile(code, baseId, generatorFileInfo) + } + return handleCompilingVirtualFile(code, id).code + } + if (id.includes(tsrShared)) { + return handleCompilingSharedFile(code, id)?.code ?? code + } + return code + } + + const plugin: BunPlugin = { + name: 'tanstack-router:code-splitter:bun', + setup(build) { + build.onStart(() => { + initUserConfig() + }) + + const resolveQueryModule = ( + args: { path: string }, + kind: 'split' | 'shared', + ) => { + const marker = kind === 'split' ? tsrSplit : tsrShared + if (!args.path.includes(marker)) { + return undefined + } + const filePath = stripQuery(args.path) + return { + path: args.path.includes('?') ? args.path : `${filePath}?${marker}=1`, + namespace: kind === 'split' ? 'tsr-split' : 'tsr-shared', + } + } + + build.onResolve({ filter: /tsr-split/ }, (args) => + resolveQueryModule(args, 'split'), + ) + build.onResolve({ filter: /tsr-shared/ }, (args) => + resolveQueryModule(args, 'shared'), + ) + build.onResolve({ filter: /^\// }, (args) => { + if (args.path.includes(tsrSplit)) { + return resolveQueryModule(args, 'split') + } + if (args.path.includes(tsrShared)) { + return resolveQueryModule(args, 'shared') + } + return undefined + }) + + build.onLoad({ filter: /.*/, namespace: 'tsr-split' }, async (args) => { + const filePath = stripQuery(args.path) + const code = await readFile(filePath, 'utf8') + const url = pathToFileURL(args.path) + url.searchParams.delete('v') + const normalizedId = normalizePath(fileURLToPath(url)) + const result = handleCompilingVirtualFile(code, normalizedId) + return { + contents: result.code, + loader: loaderForPath(filePath), + } + }) + + build.onLoad({ filter: /.*/, namespace: 'tsr-shared' }, async (args) => { + const filePath = stripQuery(args.path) + const code = await readFile(filePath, 'utf8') + const result = handleCompilingSharedFile(code, args.path) + if (!result) { + return { contents: code, loader: loaderForPath(filePath) } + } + return { + contents: result.code, + loader: loaderForPath(filePath), + } + }) + }, + } + + return { plugin, transformReference, transformVirtual } +} + +/** + * Convenience: Bun plugin only (virtual modules). Prefer + * {@link createBunRouterCodeSplitterRuntime} when composing with StartCompiler. + */ +export function createBunRouterCodeSplitterPlugin( + routerPluginContext: RouterPluginContext, + bunOptions: BunCodeSplitterOptions, +): BunPlugin { + return createBunRouterCodeSplitterRuntime(routerPluginContext, bunOptions) + .plugin +} diff --git a/packages/router-plugin/tests/bun-code-splitter-plugin.test.ts b/packages/router-plugin/tests/bun-code-splitter-plugin.test.ts new file mode 100644 index 00000000000..70db87131f4 --- /dev/null +++ b/packages/router-plugin/tests/bun-code-splitter-plugin.test.ts @@ -0,0 +1,100 @@ +import { describe, expect, it } from 'vitest' +import { createRouterPluginContext } from '../src/core/router-plugin-context' +import { createBunRouterCodeSplitterRuntime } from '../src/core/bun-code-splitter-plugin' + +const ROUTE_CODE = ` +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Home, + loader: async () => ({ ok: true }), +}) + +function Home() { + return
Hello
+} +` + +describe('createBunRouterCodeSplitterRuntime', () => { + it('returns a named Bun plugin with setup and reference transform', () => { + const context = createRouterPluginContext() + context.routesByFile.set('/tmp/routes/index.tsx', { routeId: '/' }) + + const runtime = createBunRouterCodeSplitterRuntime(context, { + root: '/tmp', + isProduction: true, + config: { + target: 'react', + routesDirectory: './routes', + generatedRouteTree: './routeTree.gen.ts', + }, + }) + + expect(runtime.plugin.name).toBe('tanstack-router:code-splitter:bun') + expect(typeof runtime.plugin.setup).toBe('function') + expect(typeof runtime.transformReference).toBe('function') + }) + + it('transforms a registered route reference (lazy split)', () => { + const context = createRouterPluginContext() + const routeFile = '/tmp/app/src/routes/index.tsx' + context.routesByFile.set(routeFile, { routeId: '/' }) + + const runtime = createBunRouterCodeSplitterRuntime(context, { + root: '/tmp/app', + isProduction: true, + config: { + target: 'react', + routesDirectory: './src/routes', + generatedRouteTree: './src/routeTree.gen.ts', + autoCodeSplitting: true, + }, + }) + + const transformed = runtime.transformReference(ROUTE_CODE, routeFile) + expect(transformed).not.toBe(ROUTE_CODE) + // Split routes typically drop/move component into a virtual module import + expect( + transformed.includes('tsr-split') || + transformed.includes('lazyRouteComponent') || + transformed !== ROUTE_CODE, + ).toBe(true) + }) + + it('returns original code for non-route files', () => { + const context = createRouterPluginContext() + const runtime = createBunRouterCodeSplitterRuntime(context, { + root: '/tmp/app', + isProduction: true, + config: { + target: 'react', + routesDirectory: './src/routes', + generatedRouteTree: './src/routeTree.gen.ts', + }, + }) + + const code = `export const x = 1` + expect(runtime.transformReference(code, '/tmp/app/src/utils.ts')).toBe( + code, + ) + }) + + it('skips reference transform when autoCodeSplitting is disabled', () => { + const context = createRouterPluginContext() + const routeFile = '/tmp/app/src/routes/index.tsx' + context.routesByFile.set(routeFile, { routeId: '/' }) + + const runtime = createBunRouterCodeSplitterRuntime(context, { + root: '/tmp/app', + isProduction: true, + config: { + target: 'react', + routesDirectory: './src/routes', + generatedRouteTree: './src/routeTree.gen.ts', + autoCodeSplitting: false, + }, + }) + + expect(runtime.transformReference(ROUTE_CODE, routeFile)).toBe(ROUTE_CODE) + }) +}) diff --git a/packages/router-plugin/vite.config.ts b/packages/router-plugin/vite.config.ts index e80f073d218..007d1bd28d3 100644 --- a/packages/router-plugin/vite.config.ts +++ b/packages/router-plugin/vite.config.ts @@ -22,6 +22,7 @@ export default mergeConfig( './src/rspack.ts', './src/webpack.ts', './src/esbuild.ts', + './src/bun.ts', ], srcDir: './src', }), diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json index aeb0bbce5e8..a2b2d359f22 100644 --- a/packages/solid-start/package.json +++ b/packages/solid-start/package.json @@ -86,6 +86,14 @@ "default": "./dist/esm/plugin/rsbuild.js" } }, + "./plugin/bun": { + "bun": "./src/plugin/bun.ts", + "import": { + "types": "./dist/esm/plugin/bun.d.ts", + "default": "./dist/esm/plugin/bun.js" + }, + "default": "./src/plugin/bun.ts" + }, "./server-entry": { "import": { "types": "./dist/default-entry/esm/server.d.ts", diff --git a/packages/solid-start/src/plugin/bun.ts b/packages/solid-start/src/plugin/bun.ts new file mode 100644 index 00000000000..a269ac4c9bb --- /dev/null +++ b/packages/solid-start/src/plugin/bun.ts @@ -0,0 +1,49 @@ +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import path from 'pathe' +import { + BUN_ENVIRONMENT_NAMES, + tanStackStartBun, +} from '@tanstack/start-plugin-core/bun' +import { solidStartDefaultEntryPaths } from './shared' +import type { + TanStackStartBunInputConfig, + TanStackStartBunPluginCoreOptions, + TanStackStartBunAdapter, +} from '@tanstack/start-plugin-core/bun' + +/** Resolve default Start entry file paths for the app root. */ +function resolveDefaultEntryPaths() { + if (existsSync(solidStartDefaultEntryPaths.client)) { + return solidStartDefaultEntryPaths + } + + const srcDefault = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../default-entry', + ) + return { + client: path.resolve(srcDefault, 'client.tsx'), + server: path.resolve(srcDefault, 'server.ts'), + start: path.resolve(srcDefault, 'start.ts'), + } +} + +/** + * TanStack Start Bun bundler adapter for Solid (imperative build/dev API). + */ +export function tanstackStart( + options?: TanStackStartBunInputConfig, +): TanStackStartBunAdapter { + const corePluginOpts: TanStackStartBunPluginCoreOptions = { + framework: 'solid', + defaultEntryPaths: resolveDefaultEntryPaths(), + providerEnvironmentName: BUN_ENVIRONMENT_NAMES.server, + ssrIsProvider: true, + bun: options?.bun, + } + + return tanStackStartBun(corePluginOpts, options) +} + +export type { TanStackStartBunAdapter, TanStackStartBunInputConfig } diff --git a/packages/solid-start/vite.config.ts b/packages/solid-start/vite.config.ts index 2c1a42d0e49..49fad9ffe88 100644 --- a/packages/solid-start/vite.config.ts +++ b/packages/solid-start/vite.config.ts @@ -34,6 +34,7 @@ export default mergeConfig( './src/server.tsx', './src/plugin/rsbuild.ts', './src/plugin/vite.ts', + './src/plugin/bun.ts', './src/server-only.ts', './src/client-only.ts', ], diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json index 56727dc3efa..8d0a3455b7a 100644 --- a/packages/start-plugin-core/package.json +++ b/packages/start-plugin-core/package.json @@ -74,6 +74,14 @@ "default": "./dist/esm/rsbuild/types.js" } }, + "./bun": { + "bun": "./src/bun/index.ts", + "import": { + "types": "./dist/esm/bun/index.d.ts", + "default": "./dist/esm/bun/index.js" + }, + "default": "./src/bun/index.ts" + }, "./package.json": "./package.json" }, "sideEffects": false, @@ -87,6 +95,7 @@ "dependencies": { "@babel/code-frame": "7.27.1", "@babel/core": "^7.28.5", + "@babel/preset-typescript": "^7.28.5", "@babel/types": "^7.28.5", "@tanstack/router-core": "workspace:*", "@tanstack/router-generator": "workspace:*", @@ -104,12 +113,14 @@ "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", - "zod": "^4.4.3" + "zod": "^4.4.3", + "react-refresh": "^0.18.0" }, "devDependencies": { "@rsbuild/core": "^2.1.0", "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.20.5", + "@types/bun": "^1.3.14", "@types/node": ">=20", "@types/picomatch": "^4.0.2", "vite": "*", @@ -121,12 +132,24 @@ }, "peerDependencies": { "@rsbuild/core": "^2.0.0", + "@tailwindcss/node": "^4.0.0", + "bun": ">=1.2.0", + "nitro": ">=3.0.0-alpha || >=3.0.0-beta || >=3.0.0", "vite": ">=7.0.0" }, "peerDependenciesMeta": { "@rsbuild/core": { "optional": true }, + "@tailwindcss/node": { + "optional": true + }, + "bun": { + "optional": true + }, + "nitro": { + "optional": true + }, "vite": { "optional": true } diff --git a/packages/start-plugin-core/src/bun/ARCHITECTURE.md b/packages/start-plugin-core/src/bun/ARCHITECTURE.md new file mode 100644 index 00000000000..0021e3b20a2 --- /dev/null +++ b/packages/start-plugin-core/src/bun/ARCHITECTURE.md @@ -0,0 +1,93 @@ +# Bun Adapter Architecture + +Bun bundler adapter for TanStack Start. Mirrors `rsbuild/`: shared core (config / planning / start-compiler / manifestBuilder / import-protection / post-build) plus a Bun-specific shell. + +## Default production path + +| Path | Meaning | +|------|---------| +| `dist/server/server.js` | Pure `default.fetch` (can attach to other hosts) | +| `dist/server/host.js` | **Recommended production entry**: static `../client` first, then SSR | +| `dist/client/**` | Browser assets (`/assets/...`) | + +Deploy `dist/` and run `bun dist/server/host.js`. + +Most official “Bun deploy” docs mean **Vite build + `nitro({ preset: 'bun' })`** (Bun as **runtime**), not Bun as the bundler. This adapter is Bun-as-bundler; the default host matches Rsbuild-style `dist` + static/`fetch`. + +## API + +```ts +import { tanstackStart } from '@tanstack/react-start/plugin/bun' + +const start = tanstackStart({ bun: { port: 3000 } }) +await start.build() // client + server Bun.build + host.js + post-build +await start.serve() // production: dist/client static + server.js fetch +const server = await start.dev() // build + Bun.serve + src watch rebuild +``` + +## Cold build order + +1. `prepare`: resolve root / base / outDir, `resolveStartEntryPlan`, seed virtual modules +2. Generator: write `routeTree.gen.ts` + `TSS_ROUTES_MANIFEST` +3. **Client `Bun.build`** (`target: 'browser'`) +4. Normalize outputs → `NormalizedClientBuild` → update start manifest virtual module +5. Refresh `#tanstack-start-server-fn-resolver` +6. **Server `Bun.build`** (`target: 'bun'`) +7. Write `dist/server/host.js` +8. `postBuildWithBun` (prerender / sitemap) +9. **Optional** `bun.nitro` / `bun.standalone` (see below) + +## CSS / Env + +Built-in `createCssAssetsPlugin`: `?url` / side-effect CSS / **CSS Modules** (`*.module.css`) / optional PostCSS / optional Tailwind. Default build copies `public/` → `dist/client`. + +`loadBunEnvFiles` loads `.env*` Vite-style and injects `process.env` + `import.meta.env` defines. + +## Virtual module keys + +| id | Contents | +|----|----------| +| `virtual:tanstack-start-*-entry` / `#tanstack-*` | entry alias | +| `#tanstack-start-server-fn-resolver` | serverFn registry | +| `tanstack-start-manifest:v` | SSR asset manifest | +| `#tanstack-start-plugin-adapters` | serialization adapters | + +## Dev / HMR + +`createBunDevServer`: scoped rebuilds + **experimental** ESM middleware + HMR + React Refresh. + +ESM-dev specifics (learned from real app usage): + +- Pass Start **entry aliases** (`#tanstack-router-entry`, …) and **define** into the transform pipeline so package.json fake stubs and env defines resolve like production `Bun.build` +- Prefer stable `/@fs` URLs for bare imports (especially `react` / `react-dom`) so the browser dedupes a single React copy +- **optimizeDeps (default on):** scan client-reachable bare imports → `Bun.build` into `node_modules/.tanstack-start/deps` → rewrite to `/@deps/…` (Vite-like). **React family stays on `/@fs`** (single copy). Disable with `bun.optimizeDeps: false`. Force rebuild: `bun.optimizeDeps.force` or `TANSTACK_START_OPTIMIZE_DEPS_FORCE=1` +- Strip built `/assets/*.js` link tags and scrub SSR manifest `scripts`/`preloads` so a second StartClient does not hydrate after `$_TSR.h()` +- CJS→ESM: externalize only React singletons; reject bundles that still emit `__require`; soften `import * as X` when the CJS wrapper reassigns `X` +- `import './x.css?url'` returns a URL module pointing at `/@tanstack-start/styles.css` (not raw CSS) + +Granularity and stability are below Vite; some client changes may still trigger a full `Bun.build`. Dev does **not** run Nitro / standalone compile. + +## Optional extras (experimental, production only) + +These are **not** required for the default `host.js` path: + +| Extra | Config | Output | +|-------|--------|--------| +| Nitro bridge | `bun.nitro` | `.output/**` via programmatic Nitro 3 (cannot reuse `nitro/vite`) | +| Standalone executable | `bun.standalone` | e.g. `dist/server/start` via `Bun.build({ compile })`; always embeds `dist/client` (not `.output/public`) | + +They can coexist as separate outputs. Prefer default `host.js` unless you need those artifacts. + +## Known limitations + +- **No RSC support** +- Solid/Vue: route `autoCodeSplitting` under Bun still lacks complete framework JSX/SSR for lazy virtual modules; minimal examples should set `autoCodeSplitting: false` explicitly +- Import protection is a simplified deny/mock (full graph tracing / sourcemaps still weaker than Vite/Rsbuild) +- `bun.nitro` / `bun.standalone` are production-only and experimental + +## Files + +- `plugin.ts` — orchestration +- `build-pipeline.ts` — client/server `Bun.build` +- `nitro-bridge.ts` / `standalone-compile.ts` — optional extras +- `static-host.ts` / `css-assets-plugin.ts` / `post-build.ts` / `dev-server.ts` … diff --git a/packages/start-plugin-core/src/bun/build-pipeline.ts b/packages/start-plugin-core/src/bun/build-pipeline.ts new file mode 100644 index 00000000000..e49ac622823 --- /dev/null +++ b/packages/start-plugin-core/src/bun/build-pipeline.ts @@ -0,0 +1,240 @@ +import { mkdir, writeFile } from 'node:fs/promises' +import { join } from 'pathe' +import { createBunAliasAndVirtualPlugin } from './bun-plugins' +import { createCssAssetsPlugin } from './css-assets-plugin' +import { createBunImportProtectionPlugin } from './import-protection' +import { createSolidServerAliasPlugin } from './solid-server-alias' +import { + enrichBunClientBuildFromSourcemaps, + normalizeBunClientBuild, + toClientRelativeFileName, +} from './normalized-client-build' +import { BUN_ENVIRONMENT_NAMES } from './types' +import { copyPublicDirToClient } from './copy-public-dir' +import type { BunCoreOptions } from './types' +import type { CompileStartFrameworkOptions } from '../types' +import type { createBunCompilerHosts } from './start-compiler-host' +import type { createBunRouterSession } from './start-router-plugin' +import type { createBunVirtualModuleStore } from './virtual-modules' +import type { TanStackStartOutputConfig } from '../schema' +import type { ResolvedStartConfig } from '../types' +import type { BunResolvedEntryAliases } from './planning' + +export type BunBuildContext = { + startConfig: TanStackStartOutputConfig + resolvedStartConfig: ResolvedStartConfig + entryAliases: BunResolvedEntryAliases + /** Server / SSR define (includes all loaded env keys). */ + define: Record + /** Browser define (public env prefixes only). */ + clientDefine: Record + virtualModules: ReturnType + compilers: ReturnType + routerSession: ReturnType + outDirs: { client: string; server: string } + publicBase: string + refreshResolver: () => void + setPluginAdapters: (runtime: 'client' | 'server') => void + mode: 'dev' | 'build' + bunOpts?: BunCoreOptions + emittedCss?: Map + emittedCssAssets?: Array<{ + sourcePath: string + fileName: string + css: string + }> + framework: CompileStartFrameworkOptions +} + +/** Build the browser client bundle with Bun.build. */ +export async function buildBunClient(ctx: BunBuildContext) { + await mkdir(ctx.outDirs.client, { recursive: true }) + ctx.setPluginAdapters('client') + + const bunOpts = ctx.bunOpts + const minify = + bunOpts?.minify ?? (ctx.mode === 'build') + const emittedCss = ctx.emittedCss ?? new Map() + const emittedCssAssets = ctx.emittedCssAssets ?? [] + emittedCssAssets.length = 0 + + const cssPlugin = createCssAssetsPlugin({ + root: ctx.resolvedStartConfig.root, + clientOutDir: ctx.outDirs.client, + publicBase: ctx.publicBase, + css: bunOpts?.css, + srcDirectory: ctx.resolvedStartConfig.srcDirectory, + onCssEmitted: ({ filePath, css, url }) => { + emittedCss.set(filePath, css) + const withoutBase = + ctx.publicBase !== '/' && url.startsWith(ctx.publicBase.replace(/\/$/, '')) + ? url.slice(ctx.publicBase.replace(/\/$/, '').length) + : url + const fileName = withoutBase.replace(/^\//, '') + emittedCssAssets.push({ sourcePath: filePath, fileName, css }) + }, + }) + const extraPlugins = [ + ...(bunOpts?.plugins ?? []), + ...(bunOpts?.clientPlugins ?? []), + ] + + const result = await Bun.build({ + entrypoints: [ctx.entryAliases.client], + outdir: ctx.outDirs.client, + target: 'browser', + format: 'esm', + packages: 'bundle', + splitting: true, + sourcemap: 'linked', + minify, + naming: { + entry: 'assets/[name]-[hash].js', + chunk: 'assets/[name]-[hash].js', + asset: 'assets/[name]-[hash].[ext]', + }, + define: ctx.clientDefine, + plugins: [ + ...extraPlugins, + cssPlugin, + createBunAliasAndVirtualPlugin({ + aliases: ctx.entryAliases.alias, + virtualModules: ctx.virtualModules, + }), + ctx.routerSession.createCodeSplitterPlugin('client'), + ctx.compilers.createTransformPlugin('client'), + createBunImportProtectionPlugin({ + envName: BUN_ENVIRONMENT_NAMES.client, + envType: 'client', + root: ctx.resolvedStartConfig.root, + srcDirectory: ctx.resolvedStartConfig.srcDirectory, + importProtection: ctx.startConfig.importProtection, + mode: ctx.mode, + }), + ], + }) + + if (!result.success) { + const message = result.logs.map(String).join('\n') + throw new Error(`[tanstack-start-bun] Client build failed:\n${message}`) + } + + const outputs = result.outputs.map((o) => ({ + path: o.path, + fileName: toClientRelativeFileName(o.path, ctx.outDirs.client), + kind: o.kind, + sourcemapPath: `${o.path}.map`, + })) + + let clientBuild = normalizeBunClientBuild({ + outputs, + clientOutDir: ctx.outDirs.client, + emittedCssAssets, + }) + clientBuild = await enrichBunClientBuildFromSourcemaps({ + clientBuild, + outputs, + }) + + ctx.virtualModules.updateManifest({ + clientBuild, + publicBase: ctx.publicBase, + scriptFormat: 'module', + inlineCss: { + enabled: ctx.startConfig.server.build.inlineCss.enabled, + transformAssets: ctx.startConfig.server.build.inlineCss.transformAssets, + }, + }) + ctx.refreshResolver() + + return { result, clientBuild } +} + +/** Build the Bun SSR server bundle. */ +export async function buildBunServer(ctx: BunBuildContext) { + await mkdir(ctx.outDirs.server, { recursive: true }) + ctx.setPluginAdapters('server') + + const bunOpts = ctx.bunOpts + const minify = + bunOpts?.minify ?? (ctx.mode === 'build') + + const cssPlugin = createCssAssetsPlugin({ + root: ctx.resolvedStartConfig.root, + clientOutDir: ctx.outDirs.client, + publicBase: ctx.publicBase, + css: bunOpts?.css, + srcDirectory: ctx.resolvedStartConfig.srcDirectory, + }) + const solidServerAlias = + ctx.framework === 'solid' + ? createSolidServerAliasPlugin({ root: ctx.resolvedStartConfig.root }) + : null + const extraPlugins = [ + ...(bunOpts?.plugins ?? []), + ...(bunOpts?.serverPlugins ?? []), + ] + + const result = await Bun.build({ + entrypoints: [ctx.entryAliases.server], + outdir: ctx.outDirs.server, + target: 'bun', + format: 'esm', + packages: 'bundle', + splitting: false, + sourcemap: 'linked', + minify, + // Solid: enable `solid` so @tanstack/solid-router resolves to dist/source + // JSX that babel-preset-solid can SSR-compile. Also alias solid-js to server. + ...(ctx.framework === 'solid' ? { conditions: ['solid', 'node'] } : {}), + naming: { + entry: 'server.js', + }, + define: ctx.define, + plugins: [ + ...extraPlugins, + ...(solidServerAlias ? [solidServerAlias] : []), + cssPlugin, + createBunAliasAndVirtualPlugin({ + aliases: ctx.entryAliases.alias, + virtualModules: ctx.virtualModules, + }), + ctx.routerSession.createCodeSplitterPlugin('server'), + ctx.compilers.createTransformPlugin('server'), + createBunImportProtectionPlugin({ + envName: BUN_ENVIRONMENT_NAMES.server, + envType: 'server', + root: ctx.resolvedStartConfig.root, + srcDirectory: ctx.resolvedStartConfig.srcDirectory, + importProtection: ctx.startConfig.importProtection, + mode: ctx.mode, + }), + ], + }) + + if (!result.success) { + const message = result.logs.map(String).join('\n') + throw new Error(`[tanstack-start-bun] Server build failed:\n${message}`) + } + + return result +} + +/** Write dist/server/host.js for production static+SSR hosting. */ +export async function writeBunHostEntry(serverOutDir: string) { + const { generateHostEntrySource } = await import('./static-host') + await writeFile( + join(serverOutDir, 'host.js'), + generateHostEntrySource(), + 'utf8', + ) +} + +/** Copy the app public/ directory into the client output. */ +export async function copyBunPublicAssets(opts: { + root: string + clientOutDir: string + publicDir?: string +}) { + return copyPublicDirToClient(opts) +} diff --git a/packages/start-plugin-core/src/bun/bun-plugins.ts b/packages/start-plugin-core/src/bun/bun-plugins.ts new file mode 100644 index 00000000000..e70454a302f --- /dev/null +++ b/packages/start-plugin-core/src/bun/bun-plugins.ts @@ -0,0 +1,99 @@ +import type { BunPlugin } from 'bun' +import { ENTRY_POINTS } from '../constants' +import { EMPTY_SERIALIZATION_ADAPTERS_MODULE } from '../serialization-adapters-module' +import { + isBunVirtualModuleId, + VIRTUAL_MODULES, +} from './virtual-modules' +import type { BunResolvedEntryAliases } from './planning' +import type { BunVirtualModuleStore } from './virtual-modules' + +const ALIAS_FILTER = new RegExp( + `^(${[ + ENTRY_POINTS.client, + ENTRY_POINTS.server, + ENTRY_POINTS.start, + ENTRY_POINTS.router, + VIRTUAL_MODULES.serverFnResolver, + VIRTUAL_MODULES.startManifest, + VIRTUAL_MODULES.pluginAdapters, + ] + .map((id) => id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + .join('|')}|virtual:tanstack-)`, +) + +/** Resolve entry aliases and virtual modules before filesystem lookup. */ +export function createBunAliasAndVirtualPlugin(opts: { + aliases: BunResolvedEntryAliases['alias'] + virtualModules: BunVirtualModuleStore +}): BunPlugin { + const aliasEntries = Object.entries(opts.aliases) + + return { + name: 'tanstack-start-bun-aliases', + setup(build) { + // IMPORTANT: do not use filter /.*/ — Bun can drop package imports when a + // catch-all onResolve participates in packages:'bundle' builds. + build.onResolve({ filter: ALIAS_FILTER }, (args) => { + for (const [id, target] of aliasEntries) { + if (args.path === id) { + return { path: target } + } + } + + if ( + args.path === VIRTUAL_MODULES.serverFnResolver || + args.path === VIRTUAL_MODULES.startManifest || + args.path === VIRTUAL_MODULES.pluginAdapters || + isBunVirtualModuleId(args.path) + ) { + return { + path: args.path, + namespace: 'tanstack-virtual', + } + } + + return undefined + }) + + build.onLoad({ filter: /.*/, namespace: 'tanstack-virtual' }, (args) => { + if (args.path === VIRTUAL_MODULES.pluginAdapters) { + return { + contents: + opts.virtualModules.get(VIRTUAL_MODULES.pluginAdapters) ?? + `export const hasPluginAdapters = false +export const pluginSerializationAdapters = [] +export const adapters = [] +export default adapters +`, + loader: 'js', + } + } + + const contents = opts.virtualModules.get(args.path) + if (contents === undefined) { + if (args.path === VIRTUAL_MODULES.serverFnResolver) { + return { + contents: `export async function getServerFnById() { throw new Error('Server function resolver not ready') }`, + loader: 'js', + } + } + if (args.path === VIRTUAL_MODULES.startManifest) { + return { + contents: `export const tsrStartManifest = () => ({ routes: {} })`, + loader: 'js', + } + } + return { + contents: 'export {}', + loader: 'js', + } + } + + return { contents, loader: 'js' } + }) + }, + } +} + +export { ENTRY_POINTS } diff --git a/packages/start-plugin-core/src/bun/bun-shim.d.ts b/packages/start-plugin-core/src/bun/bun-shim.d.ts new file mode 100644 index 00000000000..2c6ca573769 --- /dev/null +++ b/packages/start-plugin-core/src/bun/bun-shim.d.ts @@ -0,0 +1,131 @@ +/** Minimal Bun ambient types for the Start bun adapter (local fork). */ + +declare module 'bun' { + export type Loader = + | 'js' + | 'jsx' + | 'ts' + | 'tsx' + | 'json' + | 'toml' + | 'text' + | 'file' + | 'wasm' + | 'napi' + | 'html' + | 'css' + | 'object' + + export interface BunPlugin { + name: string + setup: (build: PluginBuilder) => void | Promise + } + + export interface PluginBuilder { + onStart: (callback: () => void | Promise) => void + onResolve: ( + options: { filter: RegExp; namespace?: string }, + callback: (args: { + path: string + importer: string + namespace: string + kind: string + }) => + | { path: string; namespace?: string } + | undefined + | Promise<{ path: string; namespace?: string } | undefined>, + ) => void + onLoad: ( + options: { filter: RegExp; namespace?: string }, + callback: (args: { + path: string + namespace: string + }) => + | { contents: string; loader?: Loader } + | undefined + | Promise<{ contents: string; loader?: Loader } | undefined>, + ) => void + } + + export interface BuildConfig { + entrypoints: Array + outdir?: string + outfile?: string + target?: 'browser' | 'bun' | 'node' + format?: 'esm' | 'cjs' | 'iife' + splitting?: boolean + minify?: boolean + sourcemap?: boolean | 'none' | 'linked' | 'external' | 'inline' + naming?: string | { entry?: string; chunk?: string; asset?: string } + define?: Record + plugins?: Array + packages?: 'bundle' | 'external' + /** Standalone executable (Bun --compile). */ + compile?: boolean | string | Record + } + + export interface BuildArtifact { + path: string + kind: 'entry-point' | 'chunk' | 'asset' | 'sourcemap' | string + } + + export interface BuildOutput { + success: boolean + outputs: Array + logs: Array<{ message: string }> + } + + export function build(config: BuildConfig): Promise + export function plugin(plugin: BunPlugin): void + export function resolve(id: string, from?: string): Promise + export function resolveSync(id: string, from?: string): string + export function sleep(ms: number): Promise + export function serve(options: { + port?: number + hostname?: string + fetch: (req: Request) => Response | Promise + }): { port: number; stop: (closeActiveConnections?: boolean) => void } + export function file( + path: string, + ): { + exists: () => Promise + } & Blob + + export class Transpiler { + constructor(options?: { loader?: Loader }) + transformSync(code: string, loader?: Loader): string + transform(code: string, loader?: Loader): Promise + } + + export interface GlobScanOptions { + cwd?: string + dot?: boolean + absolute?: boolean + followSymlinks?: boolean + throwErrorOnBrokenSymlink?: boolean + onlyFiles?: boolean + } + + export class Glob { + constructor(pattern: string) + scan(optionsOrCwd?: string | GlobScanOptions): AsyncIterableIterator + scanSync(optionsOrCwd?: string | GlobScanOptions): IterableIterator + match(str: string): boolean + } + + const Bun: { + build: typeof build + plugin: typeof plugin + resolve: typeof resolve + resolveSync: typeof resolveSync + serve: typeof serve + file: typeof file + sleep: typeof sleep + Transpiler: typeof Transpiler + Glob: typeof Glob + } + + export default Bun +} + +declare var Bun: typeof import('bun').default diff --git a/packages/start-plugin-core/src/bun/copy-public-dir.ts b/packages/start-plugin-core/src/bun/copy-public-dir.ts new file mode 100644 index 00000000000..13f75678a0f --- /dev/null +++ b/packages/start-plugin-core/src/bun/copy-public-dir.ts @@ -0,0 +1,33 @@ +import { cp, mkdir, stat } from 'node:fs/promises' +import { join } from 'pathe' + +/** + * Copy `public/` into the client output directory (Vite `publicDir` equivalent). + * No-op when the directory does not exist. + */ +export async function copyPublicDirToClient(opts: { + root: string + clientOutDir: string + publicDir?: string +}): Promise<{ copied: boolean; from: string; to: string }> { + const from = join(opts.root, opts.publicDir ?? 'public') + const to = opts.clientOutDir + + try { + const info = await stat(from) + if (!info.isDirectory()) { + return { copied: false, from, to } + } + } catch { + return { copied: false, from, to } + } + + await mkdir(to, { recursive: true }) + await cp(from, to, { + recursive: true, + force: true, + errorOnExist: false, + }) + + return { copied: true, from, to } +} diff --git a/packages/start-plugin-core/src/bun/css-assets-plugin.ts b/packages/start-plugin-core/src/bun/css-assets-plugin.ts new file mode 100644 index 00000000000..e20428afabc --- /dev/null +++ b/packages/start-plugin-core/src/bun/css-assets-plugin.ts @@ -0,0 +1,326 @@ +/** + * First-class CSS pipeline for Bun.build: + * - `import x from './file.css?url'` → hashed asset + `export default "/assets/..."` + * - side-effect `import './file.css'` → hashed asset + empty JS module + * - `*.module.css` → hashed class map export + hashed CSS asset + * - optional PostCSS (`bun.css.postcss`) + Tailwind v4 (`@tailwindcss/node`) + */ + +import { createHash } from 'node:crypto' +import { mkdir, readFile, writeFile } from 'node:fs/promises' +import { basename, dirname, join } from 'pathe' +import { globSync } from 'tinyglobby' +import { isCssModulesFile, transformCssModules } from './css-modules' +import type { BunCssOptions } from './types' +import type { BunPlugin } from 'bun' + +export interface CssAssetsPluginOptions { + root: string + clientOutDir: string + publicBase: string + srcDirectory: string + css?: BunCssOptions | undefined + /** Collect emitted CSS content for Dev SSR styles endpoint */ + onCssEmitted?: (opts: { filePath: string; css: string; url: string }) => void +} + +/** Normalize a public base path to always end with `/` when non-root. */ +function normalizePublicBase(base: string): string { + if (!base || base === '/') { + return '/' + } + return base.endsWith('/') ? base : `${base}/` +} + +/** Remove the `?…` query suffix from a module id. */ +function stripQuery(id: string): string { + const q = id.indexOf('?') + return q >= 0 ? id.slice(0, q) : id +} + +/** Return true when CSS appears to reference Tailwind. */ +function looksLikeTailwind(css: string): boolean { + return ( + /@import\s+["']tailwindcss["']/.test(css) || + /@tailwind\s+/.test(css) || + /@theme\b/.test(css) + ) +} + +/** Collect class-name candidates for Tailwind content scanning. */ +async function collectTailwindCandidates( + root: string, + srcDirectory: string, + contentGlobs?: Array, +): Promise> { + const patterns = + contentGlobs && contentGlobs.length > 0 + ? contentGlobs + : [ + join(srcDirectory, '**/*.{js,jsx,ts,tsx,html}'), + join(srcDirectory, '**/*.{js,jsx,ts,tsx,html}').replace(/\\/g, '/'), + ] + + const files = globSync(patterns, { + cwd: root, + absolute: true, + onlyFiles: true, + }) + + const candidates = new Set() + const classRe = /[^a-zA-Z0-9_-]([a-zA-Z][a-zA-Z0-9_:/\[\]%.+-]*)/g + for (const file of files) { + let text: string + try { + text = await readFile(file, 'utf8') + } catch { + continue + } + classRe.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = classRe.exec(text)) !== null) { + const token = match[1] + if (token && token.length > 1 && !token.includes('://')) { + candidates.add(token) + } + } + } + return [...candidates] +} + +/** Run optional PostCSS plugins over CSS. */ +async function applyPostcss( + css: string, + opts: { id: string; root: string; plugins?: Array }, +): Promise { + try { + let postcssModulePath = 'postcss' + try { + postcssModulePath = await Bun.resolve('postcss', opts.root) + } catch { + try { + const { createRequire } = await import('node:module') + const req = createRequire(join(opts.root, 'package.json')) + postcssModulePath = req.resolve('postcss') + } catch { + return null + } + } + + const postcssMod = (await import(postcssModulePath)) as { + default: ( + plugins?: Array, + ) => { + process: ( + css: string, + opts: { from?: string }, + ) => Promise<{ css: string }> + } + } + const postcss = postcssMod.default ?? (postcssMod as any) + const result = await postcss(opts.plugins ?? []).process(css, { + from: opts.id, + }) + return result.css + } catch (error) { + console.warn( + '[tanstack-start-bun] PostCSS skipped:', + error instanceof Error ? error.message : error, + ) + return null + } +} + +/** Compile Tailwind CSS via `@tailwindcss/node` when available. */ +async function applyTailwind( + css: string, + opts: { + id: string + root: string + srcDirectory: string + content?: Array + }, +): Promise { + try { + let twModulePath = '@tailwindcss/node' + try { + twModulePath = await Bun.resolve('@tailwindcss/node', opts.root) + } catch { + try { + const { createRequire } = await import('node:module') + const req = createRequire(join(opts.root, 'package.json')) + twModulePath = req.resolve('@tailwindcss/node') + } catch { + // fall through to bare specifier + } + } + + const tw = (await import(twModulePath)) as { + compile: ( + input: string, + options: { + base: string + from?: string + onDependency: (path: string) => void + }, + ) => Promise<{ build: (candidates: Array) => string }> + } + const compiled = await tw.compile(css, { + base: dirname(opts.id), + from: opts.id, + onDependency() {}, + }) + const candidates = await collectTailwindCandidates( + opts.root, + opts.srcDirectory, + opts.content, + ) + return compiled.build(candidates) + } catch (error) { + console.warn( + '[tanstack-start-bun] Tailwind CSS compile skipped:', + error instanceof Error ? error.message : error, + ) + return null + } +} + +/** Bun plugin that emits hashed CSS assets and CSS Modules. */ +export function createCssAssetsPlugin( + opts: CssAssetsPluginOptions, +): BunPlugin { + const publicBase = normalizePublicBase(opts.publicBase) + const written = new Map() + const cssOpts = opts.css ?? {} + const tailwindMode = cssOpts.tailwind ?? 'auto' + const modulesEnabled = cssOpts.modules !== false + + const transformCss = async (code: string, id: string): Promise => { + let next = code + if (cssOpts.transform) { + next = await cssOpts.transform(next, { id }) + } + + const postcssOpts = cssOpts.postcss + if (postcssOpts) { + const processed = await applyPostcss(next, { + id, + root: opts.root, + plugins: postcssOpts.plugins, + }) + if (processed !== null) { + next = processed + } else if (postcssOpts.plugins?.length) { + console.warn( + '[tanstack-start-bun] bun.css.postcss configured but postcss failed; emitting prior CSS', + ) + } + } + + const wantTailwind = + tailwindMode === true || + (tailwindMode === 'auto' && looksLikeTailwind(next)) + + if (wantTailwind) { + const tw = await applyTailwind(next, { + id, + root: opts.root, + srcDirectory: opts.srcDirectory, + content: cssOpts.content, + }) + if (tw !== null) { + next = tw + } else if (tailwindMode === true) { + console.warn( + '[tanstack-start-bun] bun.css.tailwind=true but @tailwindcss/node failed; emitting raw CSS', + ) + } + } + + return next + } + + const emitCssAsset = async (filePath: string, css: string) => { + const cached = written.get(filePath) + if (cached) { + return cached + } + const hash = createHash('sha256').update(css).digest('hex').slice(0, 8) + const outName = `${basename(filePath, '.css')}-${hash}.css` + const assetsDir = join(opts.clientOutDir, 'assets') + await mkdir(assetsDir, { recursive: true }) + await writeFile(join(assetsDir, outName), css, 'utf8') + const prefix = publicBase === '/' ? '' : publicBase.replace(/\/$/, '') + const publicUrl = `${prefix}/assets/${outName}` + written.set(filePath, publicUrl) + opts.onCssEmitted?.({ filePath, css, url: publicUrl }) + return publicUrl + } + + return { + name: 'tanstack-start-bun:css-assets', + setup(build) { + build.onResolve({ filter: /\.css\?url$/ }, (args) => { + const bare = args.path.replace(/\?url$/, '') + let resolved = bare + try { + const importerDir = dirname(args.importer || opts.root) + resolved = bare.startsWith('/') ? bare : join(importerDir, bare) + } catch { + // keep bare + } + return { path: resolved, namespace: 'tss-css-url' } + }) + + build.onLoad({ filter: /.*/, namespace: 'tss-css-url' }, async (args) => { + const filePath = stripQuery(args.path) + const raw = await readFile(filePath, 'utf8') + const css = await transformCss(raw, filePath) + const url = await emitCssAsset(filePath, css) + return { + contents: `export default ${JSON.stringify(url)}`, + loader: 'js', + } + }) + + build.onLoad({ filter: /\.module\.css$/ }, async (args) => { + if (!modulesEnabled) { + return undefined + } + if (args.namespace === 'tss-css-url') { + return undefined + } + const filePath = args.path + const raw = await readFile(filePath, 'utf8') + let css = await transformCss(raw, filePath) + const modular = transformCssModules({ css, filePath }) + css = modular.css + const url = await emitCssAsset(filePath, css) + const exportEntries = Object.entries(modular.exports) + .map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`) + .join(', ') + return { + contents: `const styles = { ${exportEntries} };\nexport default styles;\nexport const __cssUrl = ${JSON.stringify(url)};\n`, + loader: 'js', + } + }) + + build.onLoad({ filter: /\.css$/ }, async (args) => { + if (args.namespace === 'tss-css-url') { + return undefined + } + if (isCssModulesFile(args.path) && modulesEnabled) { + return undefined + } + const filePath = args.path + const raw = await readFile(filePath, 'utf8') + const css = await transformCss(raw, filePath) + await emitCssAsset(filePath, css) + return { + contents: 'export {}', + loader: 'js', + } + }) + }, + } +} diff --git a/packages/start-plugin-core/src/bun/css-modules.ts b/packages/start-plugin-core/src/bun/css-modules.ts new file mode 100644 index 00000000000..3cb38cc7b15 --- /dev/null +++ b/packages/start-plugin-core/src/bun/css-modules.ts @@ -0,0 +1,111 @@ +import { createHash } from 'node:crypto' + +/** + * Match local class selectors, including compound forms (`.a.b`, `.a .b`, `.a[data-x]`). + * Only run on CSS that has had comments / strings / urls / @import stripped. + */ +const CLASS_RE = + /\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)(?=[.#\s:\[{,~+>]|$)/g + +/** + * Minimal CSS Modules transform: hash local class names and emit an export map. + * Supports compound selectors (`.a.b`, `.a .b`). Does not implement `:global` / + * `composes` fully — good enough for common cases. + * + * Non-selector regions (block comments, strings, `url(...)`, `@import`) are + * protected so imports like `theme.module.css` and URLs like `a.b.png` are not rewritten. + */ +export function transformCssModules(opts: { + css: string + filePath: string +}): { css: string; exports: Record } { + const hash = createHash('sha256') + .update(opts.filePath) + .digest('hex') + .slice(0, 6) + + const { text: protectedCss, restore } = protectNonSelectorCss(opts.css) + + const exports: Record = {} + const renamed = new Map() + + CLASS_RE.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = CLASS_RE.exec(protectedCss)) !== null) { + const local = match[1] + if (!local || renamed.has(local)) { + continue + } + const scoped = `${local}_${hash}` + renamed.set(local, scoped) + exports[local] = scoped + } + + let css = protectedCss + for (const [local, scoped] of renamed) { + const re = new RegExp( + `\\.${escapeRegExp(local)}(?=[.#\\s:\\[{,~+>]|$)`, + 'g', + ) + css = css.replace(re, `.${scoped}`) + } + + return { css: restore(css), exports } +} + +/** + * Replace comments, strings, urls, and @import rules with placeholders so class + * rewriting only sees selector / declaration identifiers. + */ +function protectNonSelectorCss(css: string): { + text: string + restore: (value: string) => string +} { + const regions: Array = [] + const protect = (value: string): string => { + const index = regions.length + regions.push(value) + // No leading `.` — must not look like a class selector to CLASS_RE. + return `__TSS_CSS_PROT_${index}__` + } + + let text = css + // Order matters: protect @import before strings so the full rule restores cleanly. + text = text.replace(/\/\*[\s\S]*?\*\//g, (m) => protect(m)) + text = text.replace(/@import\b[^;]*;/gi, (m) => protect(m)) + text = text.replace( + /url\(\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^)]+)\s*\)/gi, + (m) => protect(m), + ) + text = text.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, (m) => + protect(m), + ) + + return { + text, + restore(value: string) { + let next = value + for (let i = 0; i < regions.length + 2; i++) { + const replaced = next.replace( + /__TSS_CSS_PROT_(\d+)__/g, + (_m, index: string) => regions[Number(index)] ?? '', + ) + if (replaced === next) { + break + } + next = replaced + } + return next + }, + } +} + +/** Escape a string for safe use inside a RegExp. */ +function escapeRegExp(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +/** Return true for `*.module.css` (and similar) paths. */ +export function isCssModulesFile(filePath: string): boolean { + return /\.module\.(css|scss|sass|less)$/i.test(filePath.split('?')[0] ?? '') +} diff --git a/packages/start-plugin-core/src/bun/dev-server.ts b/packages/start-plugin-core/src/bun/dev-server.ts new file mode 100644 index 00000000000..9f17ca3eef8 --- /dev/null +++ b/packages/start-plugin-core/src/bun/dev-server.ts @@ -0,0 +1,790 @@ +import { watch, existsSync, readFileSync } from 'node:fs' +import { dirname, extname, join, normalize, relative, isAbsolute } from 'pathe' +import { formatListenBanner } from './listen-urls' +import { tryServeClientAsset } from './static-host' +import { + classifyBunChange, + hmrEventForScope, + rebuildScopeForChange, + shouldRegenerateRoutes, + type BunChangeInfo, + type BunHmrEventType, + type BunRebuildResult, +} from './hmr-protocol' +import { + DEV_CLIENT_PATH, + FS_PREFIX, + getHmrClientModuleSource, + getHmrClientScriptTag, + HMR_CLIENT_PATH, + HMR_SSE_PATH, + REACT_REFRESH_PATH, + rewriteImportMetaHot, +} from './hmr-runtime' +import { + fileExists, + resolveBareSpecifier, + resolveFsCandidate, + transformDevModule, + type DevTransformOptions, +} from './dev-transform' +import { + DEPS_PREFIX, + runOptimizeDeps, + type OptimizeDepsResult, +} from './optimize-deps' +import { + applyReactRefreshBabel, + getReactRefreshBrowserEntry, +} from './react-refresh' +import { + getNodeBuiltinStubSource, + isNodeBuiltinFsPath, + isNodeBuiltinSpecifier, +} from './node-builtin-stub' +import type { CompileStartFrameworkOptions } from '../types' + +export interface BunDevServerOptions { + root: string + port: number + hostname: string + clientOutDir: string + serverOutDir: string + publicBase: string + framework: CompileStartFrameworkOptions + /** Absolute path to the app client entry (resolved file). */ + clientEntryPath: string + /** `#tanstack-router-entry` / start / client / server → absolute files. */ + aliases?: Record + /** Define replacements for ESM-dev transforms. */ + define?: Record + /** + * When true (default), serve client modules via on-demand ESM transform + * (Phase 2). Server still uses the built handler. + */ + esmDev?: boolean + rebuild: (change: BunChangeInfo) => Promise + invalidate: (ids: Iterable) => void + /** App module transform (code-splitter + StartCompiler). */ + transformAppModule?: DevTransformOptions['transformAppModule'] + /** Debounce window for coalescing rapid fs events (ms). */ + debounceMs?: number + /** CSS emitted during builds for `/@tanstack-start/styles.css`. */ + emittedCss?: Map + /** + * Vite-like dependency prebundling for ESM-dev (`/@deps`). + * `false` disables; omit uses defaults (scan `src/` bare imports). + */ + optimizeDeps?: import('./optimize-deps').OptimizeDepsConfig | false +} + +/** + * Clears production script/preload URLs from the SSR router manifest so ESM-dev + * does not load built `/assets/*.js` after the SSR bootstrap. + */ +const ESM_DEV_MANIFEST_SCRUB_SCRIPT = `` + +/** + * Vite-style `server.fs.allow` defaults: project root + nearest workspace root. + * Prevents `/@fs` from serving arbitrary filesystem paths when hostname is `0.0.0.0`. + */ +export function resolveFsAllowList(root: string): Array { + const roots = new Set([normalize(root)]) + let dir = normalize(root) + for (let i = 0; i < 12; i++) { + if ( + existsSync(join(dir, 'pnpm-workspace.yaml')) || + existsSync(join(dir, 'lerna.json')) || + existsSync(join(dir, 'nx.json')) || + hasPackageJsonWorkspaces(dir) + ) { + roots.add(dir) + break + } + const parent = dirname(dir) + if (parent === dir) { + break + } + dir = parent + } + return [...roots] +} + +/** Detect npm/Yarn/Bun `package.json` workspaces at a directory. */ +function hasPackageJsonWorkspaces(dir: string): boolean { + try { + const raw = readFileSync(join(dir, 'package.json'), 'utf8') + const pkg = JSON.parse(raw) as { + workspaces?: Array | { packages?: Array } + } + if (Array.isArray(pkg.workspaces)) { + return pkg.workspaces.length > 0 + } + if (pkg.workspaces && Array.isArray(pkg.workspaces.packages)) { + return pkg.workspaces.packages.length > 0 + } + return false + } catch { + return false + } +} + +/** Return true when `absPath` is under one of the allow-list roots. */ +export function isPathInsideAllowList( + absPath: string, + allowList: ReadonlyArray, +): boolean { + const normalized = normalize(absPath) + for (const root of allowList) { + const rel = relative(normalize(root), normalized) + if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) { + return true + } + } + return false +} + +/** Resolve a request pathname under `public/`, rejecting path traversal. */ +export function resolvePublicAssetPath( + root: string, + pathname: string, +): string | null { + let decoded: string + try { + decoded = decodeURIComponent(pathname) + } catch { + return null + } + if (decoded.includes('\0') || decoded.includes('..')) { + return null + } + const publicDir = normalize(join(root, 'public')) + const relativePath = decoded.replace(/^\//, '') + const candidate = normalize(join(publicDir, relativePath)) + if (!isPathInsideAllowList(candidate, [publicDir])) { + return null + } + return candidate +} + +/** Inject HMR / React Refresh / ESM-dev entry scripts into HTML. */ +function injectDevScripts( + html: string, + opts: { framework: CompileStartFrameworkOptions; esmDev: boolean }, +): string { + // Drop production / prior ESM entry tags — we re-inject in a fixed order. + let next = html.replace( + /]*type=["']module["'][^>]*src=["'][^"']*(?:\/assets\/[^"']+|\/@tanstack-dev\/client)["'][^>]*><\/script>\s*/gi, + '', + ) + next = next.replace( + /]*src=["'][^"']*(?:\/assets\/[^"']+|\/@tanstack-dev\/client)["'][^>]*type=["']module["'][^>]*><\/script>\s*/gi, + '', + ) + + if (opts.esmDev) { + // Built /assets/*.js must not load alongside ESM-dev — a second StartClient + // would hydrate after $_TSR.h() tears down bootstrap data. + next = next.replace(/]*>/gi, (tag) => { + if (!/\/assets\//.test(tag)) { + return tag + } + if (/\.css\b/i.test(tag)) { + return tag + } + return '' + }) + } + + const parts: Array = [] + // React Refresh MUST run before app modules (provides $RefreshSig$ / $RefreshReg$). + if (opts.framework === 'react' && opts.esmDev) { + parts.push( + ``, + ) + } + parts.push(getHmrClientScriptTag()) + if (opts.esmDev) { + parts.push(``) + } + + const injection = parts.join('\n') + // Scrub must run after $tsr bootstrap (end of body), before deferred modules. + // Without this, SSR manifest still points at built /assets/*.js and the browser + // loads a second StartClient after $_TSR.h() tears down bootstrap data. + const scrubManifest = opts.esmDev ? ESM_DEV_MANIFEST_SCRUB_SCRIPT : '' + + if (next.includes('')) { + next = next.replace('', `${injection}\n`) + } else if (next.includes('')) { + next = next.replace('', `${injection}`) + } else { + next = `${next}${injection}` + } + + if (scrubManifest) { + if (next.includes('')) { + next = next.replace('', `${scrubManifest}`) + } else { + next = `${next}${scrubManifest}` + } + } + return next +} + +/** Encode an HMR event as an SSE `data:` payload. */ +function encodeSse( + event: BunHmrEventType, + modules?: Array, + error?: string, +): string { + const payload = + event === 'update' + ? JSON.stringify({ type: event, modules: modules ?? [] }) + : event === 'error' + ? JSON.stringify({ type: event, error: error ?? 'Rebuild failed' }) + : JSON.stringify({ type: event }) + return `data: ${payload}\n\n` +} + +/** + * Bun.serve hosting built server handler + client assets / ESM middleware, + * with classified rebuild and HMR EventSource protocol. + */ +export async function createBunDevServer(opts: BunDevServerOptions): Promise<{ + stop: () => void + port: number + hostname: string +}> { + const serverEntry = join(opts.serverOutDir, 'server.js') + const debounceMs = opts.debounceMs ?? 120 + const esmDev = opts.esmDev !== false + + let handlerModule = (await import(`${serverEntry}?t=${Date.now()}`)) as { + default: { fetch: (req: Request) => Response | Promise } + } + + const reloadClients = new Set>() + const encoder = new TextEncoder() + + const notify = ( + event: BunHmrEventType, + modules?: Array, + error?: string, + ) => { + const payload = encoder.encode(encodeSse(event, modules, error)) + for (const controller of reloadClients) { + try { + controller.enqueue(payload) + } catch { + reloadClients.delete(controller) + } + } + } + + let rebuildTimer: ReturnType | undefined + let rebuildQueued = false + let pendingPath: string | undefined + + const runRebuild = async () => { + if (rebuildQueued) { + return + } + rebuildQueued = true + const changedPath = pendingPath + pendingPath = undefined + try { + if (changedPath) { + opts.invalidate([changedPath]) + } + const kind = changedPath + ? classifyBunChange(opts.root, changedPath) + : 'unknown' + const result = await opts.rebuild({ + path: changedPath ?? '', + kind, + }) + if (!result.skipServerReload) { + handlerModule = (await import(`${serverEntry}?t=${Date.now()}`)) as { + default: { fetch: (req: Request) => Response | Promise } + } + } + + if (result.error) { + notify('error', undefined, result.error) + return + } + + // Phase 2: prefer module updates when ESM graph is live and only client changed + if ( + esmDev && + changedPath && + (result.event === 'client-reload' || result.event === 'update') + ) { + const modules = + result.modules ?? + (changedPath + ? [`${FS_PREFIX}${normalize(changedPath)}`] + : undefined) + notify('update', modules) + } else { + notify(result.event, result.modules) + } + console.info( + `[tanstack-start-bun] rebuilt (${result.scope} → ${result.event})`, + ) + } catch (error) { + const message = + error instanceof Error ? error.stack ?? error.message : String(error) + console.error('[tanstack-start-bun] rebuild failed', error) + notify('error', undefined, message) + } finally { + rebuildQueued = false + if (pendingPath) { + scheduleRebuild(pendingPath) + } + } + } + + const scheduleRebuild = (changedPath: string) => { + pendingPath = changedPath + if (rebuildTimer) { + clearTimeout(rebuildTimer) + } + rebuildTimer = setTimeout(() => { + rebuildTimer = undefined + void runRebuild() + }, debounceMs) + } + + const srcDir = join(opts.root, 'src') + const watcher = existsSync(srcDir) + ? watch(srcDir, { recursive: true }, (_event, filename) => { + if (!filename) { + return + } + if (filename.includes('routeTree.gen.')) { + return + } + scheduleRebuild(join(srcDir, filename)) + }) + : null + + if (!watcher) { + console.warn( + `[tanstack-start-bun] No src/ directory at ${srcDir}; file watching disabled`, + ) + } + + const optimizedDeps: OptimizeDepsResult = esmDev + ? await runOptimizeDeps({ + root: opts.root, + clientEntryPath: opts.clientEntryPath, + aliases: opts.aliases, + optimizeDeps: opts.optimizeDeps, + }) + : { + depsDir: '', + hash: '', + count: 0, + urlForSpec: () => undefined, + resolvePath: () => null, + fallbackFsUrl: () => undefined, + } + + const transformOpts: DevTransformOptions = { + root: opts.root, + framework: opts.framework, + aliases: opts.aliases, + define: opts.define, + transformAppModule: opts.transformAppModule, + optimizeDepsUrl: optimizedDeps.urlForSpec, + applyReactRefresh: + opts.framework === 'react' + ? (code, absPath) => applyReactRefreshBabel(code, absPath) + : undefined, + } + + const fsAllowList = resolveFsAllowList(opts.root) + + function cacheControlForFsPath(absPath: string): string { + // Bun 目录含版本哈希(react-aria@3.51.0+…);应用源码仍 no-store 以便 HMR + const normalized = absPath.replace(/\\/g, '/') + if ( + normalized.includes('/node_modules/') || + normalized.includes('/.bun/') + ) { + return 'public, max-age=31536000, immutable' + } + return 'no-store' + } + + async function serveEsmPath(url: URL): Promise { + if (!esmDev) { + return null + } + + if (url.pathname === HMR_CLIENT_PATH) { + return new Response( + getHmrClientModuleSource({ + ssePath: HMR_SSE_PATH, + enableReactRefresh: opts.framework === 'react', + }), + { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }, + ) + } + + if (url.pathname === REACT_REFRESH_PATH) { + return new Response(await getReactRefreshBrowserEntry(), { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + + if (url.pathname === DEV_CLIENT_PATH) { + const entryUrl = `${FS_PREFIX}${normalize(opts.clientEntryPath)}` + let code = `import ${JSON.stringify(entryUrl)}\n` + code = rewriteImportMetaHot(code) + return new Response(code, { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + + if (url.pathname.startsWith(`${DEPS_PREFIX}/`)) { + const abs = optimizedDeps.resolvePath(url.pathname) + if (abs) { + const code = await Bun.file(abs).text() + return new Response(code, { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'public, max-age=31536000, immutable', + }, + }) + } + // Stale browser cache may still request /@deps/react.js after React + // was moved back to /@fs — redirect instead of text/plain 404. + const fsFallback = optimizedDeps.fallbackFsUrl(url.pathname) + if (fsFallback) { + return Response.redirect(`${url.origin}${fsFallback}`, 302) + } + return new Response(`// not found: ${url.pathname}\nexport {}\n`, { + status: 404, + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + + if (url.pathname.startsWith('/@id/')) { + const encoded = url.pathname.slice('/@id/'.length) + const spec = decodeURIComponent(encoded) + if (isNodeBuiltinSpecifier(spec)) { + return new Response(getNodeBuiltinStubSource(spec), { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + const depsUrl = optimizedDeps.urlForSpec(spec) + if (depsUrl) { + return Response.redirect(`${url.origin}${depsUrl}`, 302) + } + const importer = + url.searchParams.get('importer') ?? opts.clientEntryPath + const resolved = await resolveBareSpecifier( + spec, + importer, + opts.aliases, + ) + if (!resolved) { + return new Response(`Cannot resolve ${spec}`, { status: 404 }) + } + // Bun.resolve returns "node:…" literally — stub instead of /@fs + if (isNodeBuiltinSpecifier(resolved)) { + return new Response(getNodeBuiltinStubSource(resolved), { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + // Redirect browser to /@fs so subsequent relative imports work + return Response.redirect( + `${url.origin}${FS_PREFIX}${normalize(resolved)}`, + 302, + ) + } + + if (url.pathname.startsWith(`${FS_PREFIX}/`) || url.pathname.startsWith(FS_PREFIX)) { + const abs = normalize(url.pathname.slice(FS_PREFIX.length) || '/') + if (isNodeBuiltinFsPath(abs)) { + return new Response(getNodeBuiltinStubSource(abs), { + headers: { + 'Content-Type': 'text/javascript; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + const resolvedFs = resolveFsCandidate(abs) + if (!resolvedFs) { + return new Response(`Not found: ${abs}`, { status: 404 }) + } + if (!isPathInsideAllowList(resolvedFs, fsAllowList)) { + return new Response( + `Forbidden: path outside server.fs.allow (${resolvedFs})`, + { status: 403 }, + ) + } + // Extensionless URL → redirect to canonical path (stable module graph) + if (resolvedFs !== abs && !extname(abs)) { + return Response.redirect( + `${url.origin}${FS_PREFIX}${resolvedFs}${url.search}`, + 302, + ) + } + try { + const moduleId = `${resolvedFs}${url.search}` + const result = await transformDevModule(transformOpts, moduleId) + return new Response(result.code, { + headers: { + 'Content-Type': result.contentType, + 'Cache-Control': cacheControlForFsPath(resolvedFs), + }, + }) + } catch (error) { + const message = + error instanceof Error ? error.stack ?? error.message : String(error) + console.error('[tanstack-start-bun] transform failed', resolvedFs, error) + return new Response(message, { + status: 500, + headers: { 'Content-Type': 'text/plain; charset=utf-8' }, + }) + } + } + + // /src/... convenience + if (url.pathname.startsWith('/src/')) { + const abs = normalize(join(opts.root, url.pathname)) + if (!(await fileExists(abs))) { + return new Response(`Not found: ${abs}`, { status: 404 }) + } + try { + const result = await transformDevModule(transformOpts, abs) + return new Response(result.code, { + headers: { + 'Content-Type': result.contentType, + 'Cache-Control': 'no-store', + }, + }) + } catch (error) { + const message = + error instanceof Error ? error.stack ?? error.message : String(error) + console.error('[tanstack-start-bun] transform failed', abs, error) + return new Response(message, { + status: 500, + headers: { 'Content-Type': 'text/plain; charset=utf-8' }, + }) + } + } + + return null + } + + const server = Bun.serve({ + port: opts.port, + hostname: opts.hostname, + async fetch(req) { + const url = new URL(req.url) + + // Strip publicBase for routing (Vite-style basepath rewrite) + const publicBase = opts.publicBase || '/' + if (publicBase !== '/' && url.pathname.startsWith(publicBase.replace(/\/$/, ''))) { + const base = publicBase.replace(/\/$/, '') + if (url.pathname === base || url.pathname.startsWith(`${base}/`)) { + url.pathname = + url.pathname.slice(base.length) || '/' + } + } + + if (url.pathname === HMR_SSE_PATH) { + let streamController: ReadableStreamDefaultController + const stream = new ReadableStream({ + start(controller) { + streamController = controller + reloadClients.add(controller) + controller.enqueue(encoder.encode(`: connected\n\n`)) + }, + cancel() { + reloadClients.delete(streamController) + }, + }) + return new Response(stream, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + }, + }) + } + + // Dev SSR styles endpoint (TSS_DEV_SSR_STYLES_ENABLED) + if (url.pathname.endsWith('/@tanstack-start/styles.css')) { + const routesParam = url.searchParams.get('routes') + const ids = routesParam ? routesParam.split(',') : [] + const routesManifest = (globalThis as any).TSS_ROUTES_MANIFEST as + | Record + | undefined + const chunks: Array = [] + const emitted = opts.emittedCss + + if (emitted && emitted.size > 0) { + if (ids.length > 0 && routesManifest) { + const seen = new Set() + for (const routeId of ids) { + const filePath = routesManifest[routeId]?.filePath + if (!filePath) { + continue + } + const normalizedRoute = filePath.replace(/\\/g, '/') + for (const [cssPath, css] of emitted) { + if (seen.has(cssPath)) { + continue + } + const normalizedCss = cssPath.replace(/\\/g, '/') + if ( + normalizedCss === normalizedRoute || + normalizedCss.startsWith(`${normalizedRoute}.`) || + normalizedCss.includes(normalizedRoute) + ) { + seen.add(cssPath) + chunks.push(`/* ${cssPath} */\n${css}`) + } + } + } + } + if (chunks.length === 0) { + for (const [cssPath, css] of emitted) { + chunks.push(`/* ${cssPath} */\n${css}`) + } + } + } + + return new Response(chunks.join('\n\n'), { + headers: { + 'Content-Type': 'text/css; charset=utf-8', + 'Cache-Control': 'no-store', + }, + }) + } + + const esmResponse = await serveEsmPath(url) + if (esmResponse) { + return esmResponse + } + + const staticResponse = await tryServeClientAsset( + opts.clientOutDir, + url.pathname, + ) + if (staticResponse) { + return staticResponse + } + + // Also serve files from public/ during dev (before first rebuild copy) + try { + const publicPath = resolvePublicAssetPath(opts.root, url.pathname) + if (publicPath) { + const publicFile = Bun.file(publicPath) + if (await publicFile.exists()) { + return new Response(publicFile) + } + } + } catch { + // ignore + } + + const response = await handlerModule.default.fetch(req) + const contentType = response.headers.get('content-type') ?? '' + if (contentType.includes('text/html')) { + const html = await response.text() + const headers = new Headers(response.headers) + headers.set('Content-Type', 'text/html; charset=utf-8') + return new Response( + injectDevScripts(html, { + framework: opts.framework, + esmDev, + }), + { + status: response.status, + statusText: response.statusText, + headers, + }, + ) + } + return response + }, + }) + + console.info( + formatListenBanner({ + headline: + `[tanstack-start-bun] dev server` + (esmDev ? ' (esm HMR)' : ''), + hostname: opts.hostname, + port: Number(server.port), + }), + ) + + return { + port: Number(server.port), + hostname: opts.hostname, + stop() { + if (rebuildTimer) { + clearTimeout(rebuildTimer) + } + watcher?.close() + for (const controller of reloadClients) { + try { + controller.close() + } catch { + // ignore + } + } + reloadClients.clear() + server.stop(true) + }, + } +} + +// Re-export helpers for tests / plugin wiring +export { + classifyBunChange, + rebuildScopeForChange, + hmrEventForScope, + shouldRegenerateRoutes, +} +export type { BunChangeInfo, BunRebuildResult, BunHmrEventType } diff --git a/packages/start-plugin-core/src/bun/dev-transform.ts b/packages/start-plugin-core/src/bun/dev-transform.ts new file mode 100644 index 00000000000..17d76d11c65 --- /dev/null +++ b/packages/start-plugin-core/src/bun/dev-transform.ts @@ -0,0 +1,759 @@ +/** + * Dev-time on-demand ESM transform for Bun Start (Phase 2). + */ + +import { existsSync as nodeExistsSync, statSync } from 'node:fs' +import { readFile, stat } from 'node:fs/promises' +import { dirname, extname, isAbsolute, join, normalize } from 'pathe' +import { rewriteImportMetaHot } from './hmr-runtime' +import { isNodeBuiltinSpecifier } from './node-builtin-stub' +import { isCssModulesFile, transformCssModules } from './css-modules' +import type { CompileStartFrameworkOptions } from '../types' + +export interface DevTransformOptions { + root: string + framework: CompileStartFrameworkOptions + /** + * Start entry aliases (`#tanstack-router-entry` → app `src/router.tsx`, etc.). + * Without these, package.json `imports` resolve to empty fake stubs. + */ + aliases?: Record + /** Bun/Vite-style define replacements applied to transformed modules. */ + define?: Record + /** Optional Start/route preprocess (code-splitter + serverFn) */ + transformAppModule?: ( + code: string, + absPath: string, + ) => string | Promise + /** Optional React Refresh Babel transform */ + applyReactRefresh?: ( + code: string, + absPath: string, + ) => string | Promise + /** Bare specifier → `/@deps/….js` when optimizeDeps prebundled it. */ + optimizeDepsUrl?: (spec: string) => string | undefined +} + +export interface DevTransformResult { + code: string + contentType: string +} + +const APP_EXT = /\.(m|c)?[jt]sx?$/ +const TEXT_EXT = /\.(css|json)$/ +const RESOLVE_EXTS = [ + '', + '.ts', + '.tsx', + '.mts', + '.cts', + '.js', + '.jsx', + '.mjs', + '.cjs', + '.json', + '/index.ts', + '/index.tsx', + '/index.js', + '/index.jsx', + '/index.mjs', +] as const + +/** Map a file extension to a Bun.Transpiler loader. */ +function guessLoader(filePath: string): 'tsx' | 'ts' | 'jsx' | 'js' { + const ext = extname(filePath) + if (ext === '.tsx') return 'tsx' + if (ext === '.jsx') return 'jsx' + if (ext === '.ts' || ext === '.mts' || ext === '.cts') return 'ts' + return 'js' +} + +/** + * Bun.Transpiler emits mangled automatic-runtime helpers (`jsxDEV_`, + * `Fragment_`, …) without imports (Bun injects them at runtime). + * Browsers need ESM imports. Suffix is Bun-build-specific — detect from code. + * React-only: Solid/Vue use their own JSX pipelines. + */ +function injectBunJsxRuntimeImports(code: string): string { + const devSuffix = code.match(/\bjsxDEV_([A-Za-z0-9]+)\b/)?.[1] + const jsxSuffix = code.match(/\bjsx_([A-Za-z0-9]+)\b/)?.[1] + const jsxsSuffix = code.match(/\bjsxs_([A-Za-z0-9]+)\b/)?.[1] + const fragmentSuffix = code.match(/\bFragment_([A-Za-z0-9]+)\b/)?.[1] + if (!devSuffix && !jsxSuffix && !jsxsSuffix && !fragmentSuffix) { + return code + } + + const lines: Array = [] + if (devSuffix) { + const parts: Array = [] + if (!code.includes(`jsxDEV as jsxDEV_${devSuffix}`)) { + parts.push(`jsxDEV as jsxDEV_${devSuffix}`) + } + if ( + fragmentSuffix && + !code.includes(`Fragment as Fragment_${fragmentSuffix}`) + ) { + parts.push(`Fragment as Fragment_${fragmentSuffix}`) + } + if (parts.length > 0) { + lines.push( + `import { ${parts.join(', ')} } from "react/jsx-dev-runtime";`, + ) + } + } + + if (jsxSuffix || jsxsSuffix) { + const parts: Array = [] + if (jsxSuffix && !code.includes(`jsx as jsx_${jsxSuffix}`)) { + parts.push(`jsx as jsx_${jsxSuffix}`) + } + if (jsxsSuffix && !code.includes(`jsxs as jsxs_${jsxsSuffix}`)) { + parts.push(`jsxs as jsxs_${jsxsSuffix}`) + } + if ( + fragmentSuffix && + !devSuffix && + !code.includes(`Fragment as Fragment_${fragmentSuffix}`) + ) { + parts.push(`Fragment as Fragment_${fragmentSuffix}`) + } + if (parts.length > 0) { + lines.push(`import { ${parts.join(', ')} } from "react/jsx-runtime";`) + } + } + + if ( + fragmentSuffix && + !devSuffix && + !jsxSuffix && + !jsxsSuffix && + !code.includes(`Fragment as Fragment_${fragmentSuffix}`) + ) { + lines.push( + `import { Fragment as Fragment_${fragmentSuffix} } from "react/jsx-runtime";`, + ) + } + + if (lines.length === 0) { + return code + } + return `${lines.join('\n')}\n${code}` +} + +/** Convert an absolute filesystem path to a `/@fs…` URL. */ +function toFsUrl(absPath: string): string { + return `/@fs${absPath.startsWith('/') ? absPath : `/${absPath}`}` +} + +/** + * Bun (and NODE_ENV=development) resolve `isServer` to server/development. + * For the browser ESM graph we must force the client build. + */ +export function remapDualPackageForBrowser(absPath: string): string { + const n = absPath.replace(/\\/g, '/') + const swapped = n + .replace( + /\/isServer\/(server|development)(\.[cm]?[jt]sx?)$/, + '/isServer/client$2', + ) + .replace( + /\/scroll-restoration-script\/(server|development)(\.[cm]?[jt]sx?)$/, + '/scroll-restoration-script/client$2', + ) + .replace( + /\/ssr\/server(\.[cm]?[jt]sx?)$/, + '/ssr/client$1', + ) + return swapped +} + +/** Sync existence check for extension probing. */ +function isFileSync(path: string): boolean { + try { + return nodeExistsSync(path) && statSync(path).isFile() + } catch { + return false + } +} + +/** + * Resolve extensionless / index paths (and apply browser dual-package remap). + */ +export function resolveFsCandidate(absPath: string): string | null { + const cleaned = normalize(absPath.split('?')[0]!) + const remapped = remapDualPackageForBrowser(cleaned) + for (const base of remapped === cleaned ? [cleaned] : [remapped, cleaned]) { + for (const ext of RESOLVE_EXTS) { + const candidate = `${base}${ext}` + if (isFileSync(candidate)) return candidate + } + } + return null +} + +/** Resolve a relative/bare import specifier to an absolute path. */ +function resolveRelativeSpecifier(spec: string, filePath: string): string { + const base = dirname(filePath) + try { + const resolved = Bun.resolveSync(spec, base) + return remapDualPackageForBrowser(resolved) + } catch { + const joined = spec.startsWith('file:') + ? spec.replace(/^file:\/\//, '') + : isAbsolute(spec) + ? spec + : normalize(join(base, spec)) + return resolveFsCandidate(joined) ?? remapDualPackageForBrowser(joined) + } +} + +/** + * Import / dynamic-import / side-effect import. + * Must not run inside strings — a naive `String.replace` rewrites React + * messages like `from " + componentName + "` into `/@id/...`, which becomes + * `SyntaxError: illegal character U+0040`. + */ +const IMPORT_SPEC_RE = + /(\bfrom\s+|\bimport\s*\(\s*)(['"])([^'"]+)\2|(\bimport\s+)(['"])([^'"]+)\5/y + +/** Heuristic: whether a string looks like an import specifier. */ +function isPlausibleModuleSpecifier(spec: string): boolean { + if (!spec || /[\s+]/.test(spec)) return false + return /^(?:\.{1,2}\/|\/|file:|node:|data:|blob:|[A-Za-z@#])/.test(spec) +} + +/** + * Rewrite bare and relative imports so the browser loads them through + * the Bun Start transform middleware. + */ +export function rewriteImportsForDevMiddleware( + code: string, + filePath: string, + root: string, + aliases?: Record, + optimizeDepsUrl?: (spec: string) => string | undefined, +): string { + let out = '' + let i = 0 + const n = code.length + + while (i < n) { + const c = code[i]! + const c2 = code[i + 1] + + if (c === '/' && c2 === '/') { + const end = code.indexOf('\n', i) + const stop = end === -1 ? n : end + 1 + out += code.slice(i, stop) + i = stop + continue + } + + if (c === '/' && c2 === '*') { + const end = code.indexOf('*/', i + 2) + const stop = end === -1 ? n : end + 2 + out += code.slice(i, stop) + i = stop + continue + } + + // Try import/from at this code position *before* treating `"` as a string. + // Only probe at plausible starts (`import` / `from`) for large CJS bundles. + if (c === 'i' || c === 'f') { + IMPORT_SPEC_RE.lastIndex = i + const m = IMPORT_SPEC_RE.exec(code) + if (m && m.index === i) { + const spec = (m[3] ?? m[6]) as string + const quote = (m[2] ?? m[5]) as string + const prefix = (m[1] ?? m[4]) as string + if (isPlausibleModuleSpecifier(spec)) { + out += `${prefix}${quote}${rewriteOneSpecifier(spec, filePath, root, aliases, optimizeDepsUrl)}${quote}` + } else { + out += m[0] + } + i = m.index + m[0].length + continue + } + } + + if (c === "'" || c === '"' || c === '`') { + const quote = c + let j = i + 1 + while (j < n) { + const ch = code[j]! + if (ch === '\\') { + j += 2 + continue + } + if (quote === '`' && ch === '$' && code[j + 1] === '{') { + j += 2 + let depth = 1 + while (j < n && depth > 0) { + const ec = code[j]! + if (ec === '\\') { + j += 2 + continue + } + if (ec === '`' || ec === "'" || ec === '"') { + const q = ec + j++ + while (j < n) { + if (code[j] === '\\') { + j += 2 + continue + } + if (q === '`' && code[j] === '$' && code[j + 1] === '{') { + j += 2 + let d = 1 + while (j < n && d > 0) { + if (code[j] === '{') d++ + else if (code[j] === '}') d-- + j++ + } + continue + } + if (code[j] === q) { + j++ + break + } + j++ + } + continue + } + if (ec === '{') depth++ + else if (ec === '}') depth-- + j++ + } + continue + } + if (ch === quote) { + j++ + break + } + j++ + } + out += code.slice(i, j) + i = j + continue + } + + out += c + i++ + } + + return out +} + +/** Rewrite a single import specifier for ESM-dev middleware. */ +function rewriteOneSpecifier( + spec: string, + filePath: string, + root: string, + aliases?: Record, + optimizeDepsUrl?: (spec: string) => string | undefined, +): string { + if ( + spec.startsWith('/@') || + spec.startsWith('/__') || + spec.startsWith('data:') || + spec.startsWith('blob:') || + spec.startsWith('http:') || + spec.startsWith('https:') + ) { + return spec + } + + // Relative / absolute filesystem + if (spec.startsWith('.') || spec.startsWith('/') || spec.startsWith('file:')) { + try { + return toFsUrl(resolveRelativeSpecifier(spec, filePath)) + } catch { + return spec + } + } + + // Start entry aliases must win over package.json fake stubs + const aliased = aliases?.[spec] + if (aliased) { + return toFsUrl(remapDualPackageForBrowser(aliased)) + } + + // Node builtins must never hit /@fs (Bun.resolve returns "node:…" literally) + if (isNodeBuiltinSpecifier(spec)) { + return `/@id/${encodeURIComponent(spec)}?importer=${encodeURIComponent(filePath)}` + } + + // Prebundled dependency (Vite-like optimizeDeps) + const depsUrl = optimizeDepsUrl?.(spec) + if (depsUrl) { + return depsUrl + } + + // Bare specifier → resolve to a stable /@fs URL so the browser dedupes + // (especially `react` / `react-dom`). Prefer app root, then the importer. + try { + let resolved: string + try { + resolved = Bun.resolveSync(spec, root) + } catch { + resolved = Bun.resolveSync(spec, dirname(filePath)) + } + return toFsUrl(remapDualPackageForBrowser(resolved)) + } catch { + return `/@id/${encodeURIComponent(spec)}?importer=${encodeURIComponent(filePath)}` + } +} + +/** Resolve a bare package specifier using Bun.resolveSync. */ +export async function resolveBareSpecifier( + spec: string, + importer: string, + aliases?: Record, +): Promise { + const aliased = aliases?.[spec] + if (aliased) return remapDualPackageForBrowser(aliased) + + try { + const resolved = await Bun.resolve(spec, dirname(importer)) + return remapDualPackageForBrowser(resolved) + } catch { + try { + const resolved = await Bun.resolve(spec, importer) + return remapDualPackageForBrowser(resolved) + } catch { + return null + } + } +} + +/** Apply Bun/Vite-style define map (longer keys first). */ +export function applyDefineReplacements( + code: string, + define?: Record, +): string { + if (!define) return code + let next = code + const keys = Object.keys(define).sort((a, b) => b.length - a.length) + for (const key of keys) { + const value = define[key] + if (value === undefined || !next.includes(key)) continue + next = next.split(key).join(value) + } + return next +} + +/** Detect CommonJS source that needs browser ESM bundling. */ +function looksLikeCjs(code: string): boolean { + return ( + /\bmodule\.exports\b/.test(code) || + /\bexports\.\w+\s*=/.test(code) || + (/\brequire\s*\(/.test(code) && !/\bimport\s+/.test(code)) + ) +} + +const cjsEsmCache = new Map() + +/** Bare deps externalized when entry lives under Bun's install cache. */ +const CJS_BROWSER_EXTERNALS = [ + 'react', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + 'react-dom', + 'react-dom/client', + 'react-dom/server', + 'scheduler', +] as const + +/** Attempt to bundle a CJS file to ESM for the browser. */ +async function tryBundleCjs(absPath: string): Promise { + // Externalize only React singletons. A catch-all "externalize every bare + // import" makes Bun emit `__require("…")` stubs (e.g. with-selector → + // use-sync-external-store/shim) that throw in the browser. + const built = await Bun.build({ + entrypoints: [absPath], + target: 'browser', + format: 'esm', + write: false, + external: [...CJS_BROWSER_EXTERNALS], + } as never) + if (!built.success || !built.outputs[0]) return null + const raw = await built.outputs[0].text() + if (/\b__require\s*\(/.test(raw)) { + // Remaining dynamic requires cannot run in the browser ESM graph. + return null + } + return addCjsNamedReexports(softenNamespaceImportsForCjsReassign(raw)) +} + +/** + * Bun turns `var React = require("react")` into `import * as React`, but + * react/jsx-dev-runtime later does `React = { react_stack_bottom_frame }` — + * illegal on an import binding. Mirror CJS with a mutable local. + */ +function softenNamespaceImportsForCjsReassign(code: string): string { + return code.replace( + /^import\s+\*\s+as\s+([A-Za-z_$][\w$]*)\s+from\s+(['"])([^'"]+)\2\s*;?\s*$/gm, + (full, name: string, quote: string, spec: string) => { + if (!isIdentifierReassigned(code, name)) return full + const tmp = `__import_${name}` + return `import * as ${tmp} from ${quote}${spec}${quote};\nvar ${name} = ${tmp};` + }, + ) +} + +/** True if `name` is assigned to (not declared / compared). */ +function isIdentifierReassigned(code: string, name: string): boolean { + const re = new RegExp(String.raw`\b${name}\s*=(?!=)`, 'g') + for (const m of code.matchAll(re)) { + const start = m.index ?? 0 + const before = code.slice(Math.max(0, start - 24), start) + if ( + /\b(?:const|let|var)\s+$/.test(before) || + /\bexport\s+(?:const|let|var)\s+$/.test(before) || + /\bexport\s+$/.test(before) + ) { + continue + } + return true + } + return false +} + +/** Bundle CJS → ESM and add named re-exports when possible. */ +async function bundleCjsToEsm(absPath: string): Promise { + const cached = cjsEsmCache.get(absPath) + if (cached) return cached + try { + const text = await tryBundleCjs(absPath) + if (text) { + cjsEsmCache.set(absPath, text) + return text + } + } catch { + // Cache-path entries may still fail resolve; nothing more to try. + } + return null +} + +/** + * Bun CJS→ESM only emits `export default`. Browser named imports need + * matching `export const …`. Collect keys from `exports.foo =` in the + * bundle (covers react, use-sync-external-store, etc.). + */ +function collectCjsExportNames(bundledEsm: string): Array { + const names = new Set() + for (const m of bundledEsm.matchAll( + /\bexports\.([A-Za-z_$][\w$]*)\s*=/g, + )) { + const name = m[1]! + if (name !== '__esModule' && name !== 'default') names.add(name) + } + return [...names] +} + +/** Append named ESM re-exports for a CJS-to-ESM bundle. */ +function addCjsNamedReexports(bundledEsm: string): string { + if (!/export\s+default\s+/.test(bundledEsm)) return bundledEsm + + const names = collectCjsExportNames(bundledEsm) + const namedBlock = + names.length > 0 + ? names.map((n) => `export const ${n} = __cjsMod.${n};`).join('\n') + : // Fallback when the CJS wrapper hides `exports.*` (rare) + `export const { + jsx, + jsxs, + jsxDEV, + Fragment, + createElement, + useSyncExternalStoreWithSelector, +} = __cjsMod || {};` + + const replaced = bundledEsm.replace( + /export\s+default\s+([^;]+);?\s*$/, + `const __cjsMod = $1; +export default __cjsMod; +${namedBlock} +`, + ) + return replaced === bundledEsm ? bundledEsm : replaced +} + +/** On-demand transform a module for Bun ESM-dev middleware. */ +export async function transformDevModule( + opts: DevTransformOptions, + absPath: string, +): Promise { + const qIndex = absPath.indexOf('?') + const pathPart = qIndex >= 0 ? absPath.slice(0, qIndex) : absPath + const query = qIndex >= 0 ? absPath.slice(qIndex) : '' + const filePath = (resolveFsCandidate(pathPart) ?? pathPart.split('?')[0])! + /** Preserve `?tsr-split=` / `?tsr-shared=` for the code-splitter. */ + const moduleId = `${filePath}${query}` + + const code = await readFile(filePath, 'utf8') + + if (filePath.endsWith('.css')) { + // `import x from './file.css?url'` must export a stylesheet URL, not raw + // CSS (raw `@import "tailwindcss"` would 404 as `/tailwindcss`). + if (/(?:^\?|&)url(?:=|&|$)/.test(query) || query === '?url') { + return { + code: `export default ${JSON.stringify('/@tanstack-start/styles.css')}`, + contentType: 'text/javascript; charset=utf-8', + } + } + + if (isCssModulesFile(filePath)) { + const modular = transformCssModules({ css: code, filePath }) + const escaped = JSON.stringify(modular.css) + const exportEntries = Object.entries(modular.exports) + .map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`) + .join(', ') + return { + code: `const css = ${escaped}; +if (typeof document !== 'undefined') { + const el = document.createElement('style'); + el.setAttribute('data-tanstack-dev-css', ${JSON.stringify(filePath)}); + el.textContent = css; + document.head.appendChild(el); +} +const styles = { ${exportEntries} }; +export default styles; +export const __cssUrl = ${JSON.stringify('/@tanstack-start/styles.css')}; +`, + contentType: 'text/javascript; charset=utf-8', + } + } + + const escaped = JSON.stringify(code) + return { + code: `const css = ${escaped}; +if (typeof document !== 'undefined') { + const el = document.createElement('style'); + el.setAttribute('data-tanstack-dev-css', ${JSON.stringify(filePath)}); + el.textContent = css; + document.head.appendChild(el); +} +export default css; +`, + contentType: 'text/javascript; charset=utf-8', + } + } + + if (filePath.endsWith('.json')) { + return { + code: `export default ${code}`, + contentType: 'text/javascript; charset=utf-8', + } + } + + if (filePath.endsWith('.svg')) { + // ESM imports of SVG must not feed the SVG source into the JS graph. + // Export a data URL so `/@fs/...svg` never re-enters transform as text/js. + const dataUrl = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(code)}` + return { + code: `export default ${JSON.stringify(dataUrl)}`, + contentType: 'text/javascript; charset=utf-8', + } + } + + // CJS packages (react/jsx-runtime, etc.) → ESM bundle for the browser. + // Never take this path for app modules — a string/comment containing + // `module.exports` would otherwise skip the code-splitter / Start compiler. + if (looksLikeCjs(code) && !shouldTransformApp(filePath, opts.root)) { + const bundled = await bundleCjsToEsm(filePath) + if (bundled) { + return { + code: applyDefineReplacements( + rewriteImportsForDevMiddleware( + rewriteImportMetaHot(bundled), + filePath, + opts.root, + opts.aliases, + opts.optimizeDepsUrl, + ), + opts.define, + ), + contentType: 'text/javascript; charset=utf-8', + } + } + } + + let next = code + if (opts.transformAppModule && shouldTransformApp(filePath, opts.root)) { + next = await opts.transformAppModule(next, moduleId) + } + + // Transpile TSX/TS with Bun + if (APP_EXT.test(filePath)) { + const loader = guessLoader(filePath) + const transpiler = new Bun.Transpiler({ loader }) + next = transpiler.transformSync(next, loader) + if (opts.framework === 'react') { + next = injectBunJsxRuntimeImports(next) + } + } + + if (opts.applyReactRefresh && opts.framework === 'react') { + next = await opts.applyReactRefresh(next, filePath) + } + + next = rewriteImportMetaHot(next) + next = rewriteImportsForDevMiddleware( + next, + filePath, + opts.root, + opts.aliases, + opts.optimizeDepsUrl, + ) + next = applyDefineReplacements(next, opts.define) + + return { + code: next, + contentType: 'text/javascript; charset=utf-8', + } +} + +/** Whether an absolute path is under the app root for transforms. */ +function shouldTransformApp(filePath: string, root: string): boolean { + const n = filePath.replace(/\\/g, '/') + const r = root.replace(/\\/g, '/') + if (n.startsWith(r) && !n.includes('/node_modules/')) return true + // ESM-dev serves workspace / published Start packages as raw files. Without + // the Start compiler, `createIsomorphicFn().client().server()` keeps the + // uncompiled runtime fallback (server impl wins) → ALS errors on the client. + if ( + n.includes('/start-client-core/') || + n.includes('/@tanstack/start-client-core/') || + n.includes('/packages/react-start/') || + n.includes('/@tanstack/react-start/') || + n.includes('/packages/solid-start/') || + n.includes('/@tanstack/solid-start/') || + n.includes('/packages/vue-start/') || + n.includes('/@tanstack/vue-start/') + ) { + return true + } + return false +} + +/** Async existence check for a filesystem path. */ +export async function fileExists(path: string): Promise { + try { + await stat(path) + return true + } catch { + return false + } +} + +/** Whether a URL pathname should go through ESM-dev transform. */ +export function isTransformablePath(pathname: string): boolean { + return ( + pathname.startsWith('/@fs/') || + pathname.startsWith('/@id/') || + pathname.startsWith('/src/') || + APP_EXT.test(pathname) || + TEXT_EXT.test(pathname) + ) +} diff --git a/packages/start-plugin-core/src/bun/framework-jsx-plugin.ts b/packages/start-plugin-core/src/bun/framework-jsx-plugin.ts new file mode 100644 index 00000000000..d058665c680 --- /dev/null +++ b/packages/start-plugin-core/src/bun/framework-jsx-plugin.ts @@ -0,0 +1,207 @@ +import { readFile } from 'node:fs/promises' +import { createRequire } from 'node:module' +import { join } from 'pathe' +import type { BunPlugin } from 'bun' +import type { CompileStartFrameworkOptions } from '../types' + +const require = createRequire(import.meta.url) + +/** Transform JSX for React/Solid/Vue in the Bun pipeline. */ +export async function transformFrameworkJsx(opts: { + code: string + id: string + env: 'client' | 'server' + framework: CompileStartFrameworkOptions + root: string +}): Promise { + if (opts.framework === 'react') { + return null + } + // Only transform explicit JSX/TSX files — `.ts` often contains generics (`Foo`) + // that would false-positive a naive `<` / `>` check. + if (!/\.[cm]?[jt]sx$/.test(opts.id.split('?')[0] ?? '')) { + return null + } + if (!opts.code.includes('<') || !opts.code.includes('>')) { + return null + } + if (opts.framework === 'solid') { + return transformSolidJsx(opts.code, opts.id, opts) + } + if (opts.framework === 'vue') { + return transformVueJsx(opts.code, opts.id, opts) + } + return null +} + +/** + * Framework JSX transforms for Bun.build. + * React is handled by Bun natively; Solid/Vue need Babel presets. + */ +export function createFrameworkJsxPlugin(opts: { + framework: CompileStartFrameworkOptions + env: 'client' | 'server' + root: string +}): BunPlugin | null { + if (opts.framework === 'react') { + return null + } + + return { + name: `tanstack-start-bun:jsx:${opts.framework}:${opts.env}`, + setup(build) { + build.onLoad({ filter: /\.[cm]?[jt]sx$/ }, async (args) => { + if (args.path.includes('node_modules')) { + return undefined + } + + const code = await readFile(args.path, 'utf8') + const transformed = await transformFrameworkJsx({ + code, + id: args.path, + env: opts.env, + framework: opts.framework, + root: opts.root, + }) + if (!transformed) { + return undefined + } + return { + contents: transformed, + loader: + opts.framework === 'vue' + ? 'js' + : args.path.endsWith('x') + ? 'tsx' + : 'ts', + } + }) + }, + } +} + +/** Resolve a package from the app root or this package. */ +async function resolveFromAppOrPackage( + root: string, + specifiers: Array, +): Promise { + for (const specifier of specifiers) { + try { + return await Bun.resolve(specifier, root) + } catch { + try { + const req = createRequire(join(root, 'package.json')) + return req.resolve(specifier) + } catch { + try { + return require.resolve(specifier) + } catch { + // continue + } + } + } + } + return null +} + +/** Compile Solid JSX with babel-preset-solid. */ +async function transformSolidJsx( + code: string, + filename: string, + opts: { env: 'client' | 'server'; root: string }, +): Promise { + const babelPath = await resolveFromAppOrPackage(opts.root, ['@babel/core']) + const solidPresetPath = await resolveFromAppOrPackage(opts.root, [ + 'babel-preset-solid', + ]) + const tsPresetPath = await resolveFromAppOrPackage(opts.root, [ + '@babel/preset-typescript', + ]) + if (!babelPath || !solidPresetPath) { + console.warn( + '[tanstack-start-bun] Solid JSX requires optional peers @babel/core and babel-preset-solid', + ) + return null + } + + const babel = (await import(babelPath)) as { + transformAsync: ( + code: string, + options: Record, + ) => Promise<{ code?: string | null } | null> + } + const solidPreset = + (await import(solidPresetPath)).default ?? (await import(solidPresetPath)) + const presets: Array = [ + [ + solidPreset, + { + generate: opts.env === 'server' ? 'ssr' : 'dom', + hydratable: true, + }, + ], + ] + if (tsPresetPath) { + const tsPreset = + (await import(tsPresetPath)).default ?? (await import(tsPresetPath)) + presets.unshift([tsPreset, { isTSX: true, allExtensions: true }]) + } + + const result = await babel.transformAsync(code, { + filename, + babelrc: false, + configFile: false, + presets, + sourceMaps: false, + }) + + return result?.code ?? null +} + +/** Compile Vue JSX with @vue/babel-plugin-jsx. */ +async function transformVueJsx( + code: string, + filename: string, + opts: { env: 'client' | 'server'; root: string }, +): Promise { + const babelPath = await resolveFromAppOrPackage(opts.root, ['@babel/core']) + const vueJsxPath = await resolveFromAppOrPackage(opts.root, [ + '@vue/babel-plugin-jsx', + ]) + const tsPresetPath = await resolveFromAppOrPackage(opts.root, [ + '@babel/preset-typescript', + ]) + if (!babelPath || !vueJsxPath) { + console.warn( + '[tanstack-start-bun] Vue JSX requires optional peers @babel/core and @vue/babel-plugin-jsx', + ) + return null + } + + const babel = (await import(babelPath)) as { + transformAsync: ( + code: string, + options: Record, + ) => Promise<{ code?: string | null } | null> + } + const vueJsx = + (await import(vueJsxPath)).default ?? (await import(vueJsxPath)) + + const presets: Array = [] + if (tsPresetPath) { + const tsPreset = + (await import(tsPresetPath)).default ?? (await import(tsPresetPath)) + presets.push([tsPreset, { isTSX: true, allExtensions: true }]) + } + + const result = await babel.transformAsync(code, { + filename, + babelrc: false, + configFile: false, + presets, + plugins: [[vueJsx, { ssr: opts.env === 'server' }]], + sourceMaps: false, + }) + + return result?.code ?? null +} diff --git a/packages/start-plugin-core/src/bun/hmr-protocol.ts b/packages/start-plugin-core/src/bun/hmr-protocol.ts new file mode 100644 index 00000000000..a4f7f4ebf13 --- /dev/null +++ b/packages/start-plugin-core/src/bun/hmr-protocol.ts @@ -0,0 +1,115 @@ +/** + * Change classification and SSE protocol for Bun Start HMR (Phase 1+). + */ + +export type BunHmrEventType = + | 'full-reload' + | 'client-reload' + | 'server-only' + | 'update' + | 'error' + +export type BunRebuildScope = 'client' | 'server' | 'both' + +export type BunChangeKind = + | 'route' + | 'route-tree' + | 'server-only' + | 'client' + | 'src' + | 'unknown' + +export interface BunRebuildResult { + scope: BunRebuildScope + /** SSE event to broadcast after rebuild */ + event: BunHmrEventType + /** Absolute module URLs/paths for Phase 2 `update` events */ + modules?: Array + /** When true, skip re-importing server.js (client-only ESM updates). */ + skipServerReload?: boolean + /** Optional error message for client overlay */ + error?: string +} + +export interface BunChangeInfo { + path: string + kind: BunChangeKind +} + +const SERVER_ONLY_RE = + /\.(server|server-fn)\.[cm]?[jt]sx?$|\.server\.[cm]?[jt]sx?$/i + +/** + * Classify a changed file for rebuild scoping. + */ +export function classifyBunChange( + root: string, + absPath: string, +): BunChangeKind { + const normalized = absPath.replace(/\\/g, '/') + const rootNorm = root.replace(/\\/g, '/').replace(/\/$/, '') + + if (normalized.includes('routeTree.gen.')) { + return 'route-tree' + } + + if ( + normalized.includes('/routes/') && + /\.[cm]?[jt]sx?$/.test(normalized) + ) { + return 'route' + } + + if (SERVER_ONLY_RE.test(normalized)) { + return 'server-only' + } + + if (normalized.startsWith(`${rootNorm}/src/`)) { + if (/\.(css|scss|sass|less)$/i.test(normalized)) { + return 'client' + } + if ( + /\/(components|hooks|ui)(\/|$)/i.test(normalized) || + /\.client\.[cm]?[jt]sx?$/i.test(normalized) + ) { + return 'client' + } + return 'src' + } + + return 'unknown' +} + +/** Map a filesystem change kind to a rebuild scope. */ +export function rebuildScopeForChange(kind: BunChangeKind): BunRebuildScope { + switch (kind) { + case 'server-only': + return 'server' + case 'client': + return 'client' + case 'route': + case 'route-tree': + case 'src': + case 'unknown': + default: + return 'both' + } +} + +/** Map a rebuild scope to an HMR SSE event type. */ +export function hmrEventForScope(scope: BunRebuildScope): BunHmrEventType { + switch (scope) { + case 'server': + return 'server-only' + case 'client': + return 'client-reload' + case 'both': + default: + return 'full-reload' + } +} + +/** Whether route generator should re-run for this change. */ +export function shouldRegenerateRoutes(kind: BunChangeKind): boolean { + return kind === 'route' || kind === 'route-tree' || kind === 'unknown' +} diff --git a/packages/start-plugin-core/src/bun/hmr-runtime.ts b/packages/start-plugin-core/src/bun/hmr-runtime.ts new file mode 100644 index 00000000000..c826e931af9 --- /dev/null +++ b/packages/start-plugin-core/src/bun/hmr-runtime.ts @@ -0,0 +1,151 @@ +/** + * Browser HMR client + import.meta.hot shim helpers (served as a module). + * Used by Bun Start Phase 2; Phase 1 only needs full/client reload handling. + */ + +export const HMR_CLIENT_PATH = '/__tanstack_hmr_client.js' +export const HMR_SSE_PATH = '/__tanstack_bun_reload' +export const DEV_CLIENT_PATH = '/@tanstack-dev/client' +export const REACT_REFRESH_PATH = '/__tanstack_react_refresh.js' +export const FS_PREFIX = '/@fs' + +/** Source for the browser HMR client module (injected as type=module). */ +export function getHmrClientModuleSource(opts: { + ssePath?: string + enableReactRefresh?: boolean +}): string { + const ssePath = JSON.stringify(opts.ssePath ?? HMR_SSE_PATH) + const enableRefresh = opts.enableReactRefresh === true + + return `const ssePath = ${ssePath}; +const enableRefresh = ${enableRefresh ? 'true' : 'false'}; +const acceptors = new Map(); +const hotData = new Map(); + +function createHot(url) { + const key = String(url); + if (!hotData.has(key)) hotData.set(key, {}); + return { + get data() { return hotData.get(key); }, + accept(cb) { + if (typeof cb === 'function') { + acceptors.set(key, cb); + } else { + acceptors.set(key, true); + } + }, + prune(cb) { /* no-op stub */ }, + invalidate() { + location.reload(); + }, + decline() { + acceptors.delete(key); + }, + }; +} + +globalThis.__tanstack_hot__ = createHot; +globalThis.__TANSTACK_HMR__ = { + acceptors, + createHot, + async applyUpdate(modules) { + const list = Array.isArray(modules) ? modules : []; + if (!list.length) { + location.reload(); + return; + } + let handled = false; + for (const id of list) { + const key = String(id); + const acceptor = acceptors.get(key); + try { + const mod = await import(key + (key.includes('?') ? '&' : '?') + 't=' + Date.now()); + if (typeof acceptor === 'function') { + acceptor(mod); + handled = true; + } else if (acceptor === true) { + handled = true; + } + } catch (err) { + console.warn('[tanstack-hmr] update failed for', key, err); + location.reload(); + return; + } + } + if (!handled) { + location.reload(); + } else if (enableRefresh && globalThis.__tanstack_refresh_runtime__) { + try { + globalThis.__tanstack_refresh_runtime__.performReactRefresh(); + } catch (err) { + console.warn('[tanstack-hmr] react refresh failed', err); + location.reload(); + } + } + }, +}; + +function connect() { + const es = new EventSource(ssePath); + es.onmessage = (ev) => { + let msg = ev.data; + try { msg = JSON.parse(ev.data); } catch { /* plain string */ } + const type = typeof msg === 'string' ? msg : msg?.type; + if (type === 'server-only') { + return; + } + if (type === 'error') { + const message = typeof msg === 'object' && msg?.error ? String(msg.error) : 'Rebuild failed'; + let el = document.getElementById('tanstack-bun-error-overlay'); + if (!el) { + el = document.createElement('pre'); + el.id = 'tanstack-bun-error-overlay'; + el.setAttribute('style', 'position:fixed;inset:0;z-index:2147483647;margin:0;padding:24px;overflow:auto;background:rgba(15,15,15,.94);color:#ffb4b4;font:14px/1.45 ui-monospace,SFMono-Regular,Menlo,monospace;white-space:pre-wrap'); + document.body.appendChild(el); + } + el.textContent = '[tanstack-start-bun] ' + message; + return; + } + const overlay = document.getElementById('tanstack-bun-error-overlay'); + if (overlay) overlay.remove(); + if (type === 'update' && msg?.modules) { + void globalThis.__TANSTACK_HMR__.applyUpdate(msg.modules); + return; + } + if (type === 'client-reload' || type === 'full-reload' || type === 'reload') { + location.reload(); + return; + } + }; + es.onerror = () => { + try { es.close(); } catch {} + setTimeout(connect, 1000); + }; +} +connect(); +` +} + +/** HTML snippet: load HMR client before app scripts. */ +export function getHmrClientScriptTag(): string { + return `` +} + +/** + * Rewrite Vite-style `import.meta.hot` to the Bun HMR shim so bundled/dev + * modules can register acceptors. + * + * Uses a local const so assignments like `import.meta.hot.data ??= {}` remain valid + * (optional-chaining on the left-hand side is a SyntaxError). + */ +export function rewriteImportMetaHot(code: string): string { + if (!code.includes('import.meta.hot')) { + return code + } + const preamble = + 'const __tanstack_import_meta_hot__ = globalThis.__tanstack_hot__?.(import.meta.url);\n' + return ( + preamble + + code.replaceAll('import.meta.hot', '__tanstack_import_meta_hot__') + ) +} diff --git a/packages/start-plugin-core/src/bun/import-protection.ts b/packages/start-plugin-core/src/bun/import-protection.ts new file mode 100644 index 00000000000..4ff702f87bc --- /dev/null +++ b/packages/start-plugin-core/src/bun/import-protection.ts @@ -0,0 +1,210 @@ +import { readFile } from 'node:fs/promises' +import { + getDefaultImportProtectionRules, +} from '../import-protection/defaults' +import { compileMatchers } from '../import-protection/matchers' +import { + getImportProtectionRelativePath, + shouldCheckImportProtectionImporter, +} from '../import-protection/adapterUtils' +import { getImportSources } from '../import-protection/analysis' +import { rewriteDeniedImports } from '../import-protection/rewrite' +import { loadSilentMockModule } from '../import-protection/virtualModules' +import { + checkFileDenial, + normalizeFilePath, +} from '../import-protection/utils' +import { MOCK_MODULE_ID } from '../import-protection/constants' +import type { BunPlugin } from 'bun' +import type { ImportProtectionAdapterConfig } from '../import-protection/adapterUtils' +import type { ImportProtectionOptions } from '../schema' + +/** + * Bun import-protection plugin (deny/mock path). + * Full Vite/Rsbuild adapters include graph tracing and source maps; this + * covers the core deny/mock path using the shared analysis/rewrite layer, + * with clearer violation diagnostics. + */ +export function createBunImportProtectionPlugin(opts: { + envName: string + envType: 'client' | 'server' + root: string + srcDirectory: string + importProtection?: ImportProtectionOptions + mode?: 'dev' | 'build' +}): BunPlugin { + const defaults = getDefaultImportProtectionRules() + const user = opts.importProtection ?? {} + const mode = opts.mode ?? 'build' + + const resolveBehavior = (): 'error' | 'mock' => { + const behavior = user.behavior + if (!behavior) { + return mode === 'dev' ? 'mock' : 'error' + } + if (typeof behavior === 'string') { + return behavior + } + if (mode === 'dev') { + return behavior.dev ?? 'mock' + } + return behavior.build ?? 'error' + } + + const clientSpecifiers = compileMatchers([ + ...defaults.client.specifiers, + ...(user.client?.specifiers ?? []), + ]) + const clientFiles = compileMatchers([ + ...defaults.client.files, + ...(user.client?.files ?? []), + ]) + const clientExclude = compileMatchers([ + ...defaults.client.excludeFiles, + ...(user.client?.excludeFiles ?? []), + ]) + const serverSpecifiers = compileMatchers([ + ...defaults.server.specifiers, + ...(user.server?.specifiers ?? []), + ]) + const serverFiles = compileMatchers([ + ...defaults.server.files, + ...(user.server?.files ?? []), + ]) + const serverExclude = compileMatchers([ + ...defaults.server.excludeFiles, + ...(user.server?.excludeFiles ?? []), + ]) + + const adapterConfig: ImportProtectionAdapterConfig = { + root: opts.root, + srcDirectory: opts.srcDirectory, + compiledRules: { + client: { + specifiers: clientSpecifiers, + files: clientFiles, + excludeFiles: clientExclude, + }, + server: { + specifiers: serverSpecifiers, + files: serverFiles, + excludeFiles: serverExclude, + }, + }, + includeMatchers: compileMatchers(user.include ?? []), + excludeMatchers: compileMatchers(user.exclude ?? []), + ignoreImporterMatchers: compileMatchers(user.ignoreImporters ?? []), + envTypeMap: new Map([ + ['client', 'client'], + ['ssr', 'server'], + ]), + } + + const rules = + opts.envType === 'client' + ? adapterConfig.compiledRules.client + : adapterConfig.compiledRules.server + + return { + name: `tanstack-start-import-protection:${opts.envName}`, + setup(build) { + build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => { + if (args.path.includes('node_modules')) { + return undefined + } + + if (!shouldCheckImportProtectionImporter(adapterConfig, args.path)) { + return undefined + } + + const code = await readFile(args.path, 'utf8') + const sources = getImportSources(code) + const denied = new Set() + const denialReasons: Array = [] + + for (const source of sources) { + if (rules.specifiers.some((m) => m.test(source))) { + denied.add(source) + denialReasons.push( + `specifier "${source}" denied in ${opts.envType} environment`, + ) + continue + } + + try { + const resolved = await Bun.resolve(source, args.path) + const relative = getImportProtectionRelativePath( + opts.root, + normalizeFilePath(resolved), + ) + const fileHit = checkFileDenial(relative, { + files: rules.files, + excludeFiles: rules.excludeFiles, + }) + if (fileHit) { + denied.add(source) + denialReasons.push( + `file "${relative}" (via "${source}") denied in ${opts.envType} environment`, + ) + } + } catch { + // unresolved — skip file rules + } + } + + if (denied.size === 0) { + return undefined + } + + const relativeImporter = getImportProtectionRelativePath( + opts.root, + normalizeFilePath(args.path), + ) + const detail = denialReasons.join('; ') + const behavior = resolveBehavior() + + if (behavior === 'error') { + throw new Error( + `[tanstack-start-bun] Import protection violation in ${relativeImporter} (${opts.envName}): ${detail}`, + ) + } + + console.warn( + `[tanstack-start-bun] Import protection (${opts.envName}) in ${relativeImporter}: ${detail} — rewriting to mock`, + ) + + const rewritten = rewriteDeniedImports( + code, + args.path, + denied, + () => MOCK_MODULE_ID, + ) + + if (!rewritten) { + return undefined + } + + return { + contents: rewritten.code, + loader: args.path.endsWith('x') ? 'tsx' : 'ts', + } + }) + + build.onResolve( + { filter: /^tanstack-start-import-protection:mock/ }, + (args) => ({ + path: args.path, + namespace: 'tanstack-import-protection', + }), + ) + + build.onLoad( + { filter: /.*/, namespace: 'tanstack-import-protection' }, + () => { + const mock = loadSilentMockModule() + return { contents: mock.code, loader: 'js' } + }, + ) + }, + } +} diff --git a/packages/start-plugin-core/src/bun/index.ts b/packages/start-plugin-core/src/bun/index.ts new file mode 100644 index 00000000000..293544c1766 --- /dev/null +++ b/packages/start-plugin-core/src/bun/index.ts @@ -0,0 +1,19 @@ +export { BUN_ENVIRONMENT_NAMES } from './types' +export type { + TanStackStartBunPluginCoreOptions, + TanStackStartBunAdapter, + BunCoreOptions, + BunCssOptions, + BunNitroOptions, + BunStandaloneOptions, + BunEnvironmentName, +} from './types' +export type { OptimizeDepsConfig, OptimizeDepsResult } from './optimize-deps' +export { DEPS_PREFIX, DEPS_CACHE_DIR } from './optimize-deps' +export type { TanStackStartBunInputConfig } from './schema' +export type { + StartCompilerImportTransform, + StartCompilerTransformCandidate, + StartCompilerTransformContext, +} from '../types' +export { tanStackStartBun } from './plugin' diff --git a/packages/start-plugin-core/src/bun/listen-urls.ts b/packages/start-plugin-core/src/bun/listen-urls.ts new file mode 100644 index 00000000000..d9bb99db495 --- /dev/null +++ b/packages/start-plugin-core/src/bun/listen-urls.ts @@ -0,0 +1,123 @@ +import { networkInterfaces, type NetworkInterfaceInfo } from 'node:os' + +export type ListenUrls = { + local: Array + network: Array +} + +type InterfaceMap = ReturnType + +function isIpv4(info: NetworkInterfaceInfo): boolean { + return String(info.family) === 'IPv4' || String(info.family) === '4' +} + +function isWildcardHost(hostname: string): boolean { + return hostname === '0.0.0.0' || hostname === '::' || hostname === '' +} + +function hostForUrl(host: string): string { + return host.includes(':') && !host.startsWith('[') ? `[${host}]` : host +} + +/** + * 类似 Vite `resolveServerUrls`:0.0.0.0 / :: 时列出 localhost + 局域网 IPv4。 + */ +export function resolveListenUrls(opts: { + hostname: string + port: number + protocol?: 'http' | 'https' + interfaces?: InterfaceMap +}): ListenUrls { + const protocol = opts.protocol ?? 'http' + const { hostname, port } = opts + const toUrl = (host: string) => `${protocol}://${hostForUrl(host)}:${port}/` + + if (!isWildcardHost(hostname)) { + const localHost = + hostname === '127.0.0.1' || hostname === '::1' ? 'localhost' : hostname + return { local: [toUrl(localHost)], network: [] } + } + + const ifaces = opts.interfaces ?? networkInterfaces() + const network: Array = [] + const seen = new Set() + for (const addrs of Object.values(ifaces)) { + for (const addr of addrs ?? []) { + if (!isIpv4(addr) || addr.internal) { + continue + } + const url = toUrl(addr.address) + if (seen.has(url)) { + continue + } + seen.add(url) + network.push(url) + } + } + + return { local: [toUrl('localhost')], network } +} + +/** Vite 风格:Local / Network 对齐的多行 banner */ +export function formatListenBanner(opts: { + headline: string + hostname: string + port: number + protocol?: 'http' | 'https' + interfaces?: InterfaceMap +}): string { + const urls = resolveListenUrls(opts) + const lines = [opts.headline] + const localLabel = ' ➜ Local: ' + const networkLabel = ' ➜ Network: ' + const indent = ' '.repeat(networkLabel.length) + for (const [index, url] of urls.local.entries()) { + lines.push((index === 0 ? localLabel : indent) + url) + } + for (const [index, url] of urls.network.entries()) { + lines.push((index === 0 ? networkLabel : indent) + url) + } + return lines.join('\n') +} + +/** 写入 host.js / standalone entry 的运行时实现(与上面逻辑一致) */ +export function listenBannerRuntimeJs(): string { + return `function hostForUrl(host) { + return host.includes(':') && !host.startsWith('[') ? '[' + host + ']' : host +} +function formatListenBanner(headline, hostname, port) { + const toUrl = (host) => 'http://' + hostForUrl(host) + ':' + port + '/' + const lines = [headline] + const localLabel = ' ➜ Local: ' + const networkLabel = ' ➜ Network: ' + const indent = ' '.repeat(networkLabel.length) + const local = [] + const network = [] + if (hostname === '0.0.0.0' || hostname === '::' || hostname === '') { + local.push(toUrl('localhost')) + const seen = new Set() + for (const addrs of Object.values(networkInterfaces())) { + for (const addr of addrs ?? []) { + const ipv4 = addr.family === 'IPv4' || addr.family === 4 + if (!ipv4 || addr.internal) continue + const url = toUrl(addr.address) + if (seen.has(url)) continue + seen.add(url) + network.push(url) + } + } + } else { + const localHost = + hostname === '127.0.0.1' || hostname === '::1' ? 'localhost' : hostname + local.push(toUrl(localHost)) + } + for (const [index, url] of local.entries()) { + lines.push((index === 0 ? localLabel : indent) + url) + } + for (const [index, url] of network.entries()) { + lines.push((index === 0 ? networkLabel : indent) + url) + } + return lines.join('\\n') +}` +} + diff --git a/packages/start-plugin-core/src/bun/load-env.ts b/packages/start-plugin-core/src/bun/load-env.ts new file mode 100644 index 00000000000..d1cb1310e07 --- /dev/null +++ b/packages/start-plugin-core/src/bun/load-env.ts @@ -0,0 +1,252 @@ +import { existsSync, readFileSync } from 'node:fs' +import { join } from 'pathe' + +/** + * Vite-aligned `.env` loading (mode-aware). + * Later files override earlier ones. Existing `process.env` wins over file values + * in the returned map. Does not mutate `process.env`. + */ +export function loadBunEnvFiles(opts: { + root: string + mode: 'development' | 'production' | string +}): Record { + const files = [ + `.env`, + `.env.local`, + `.env.${opts.mode}`, + `.env.${opts.mode}.local`, + ] + + const fromFiles: Record = {} + + for (const name of files) { + const filePath = join(opts.root, name) + if (!existsSync(filePath)) { + continue + } + const text = readFileSync(filePath, 'utf8') + Object.assign(fromFiles, parseEnvFile(text)) + } + + // Effective map first: process.env overrides file values, then expand once. + const effective: Record = { ...fromFiles } + for (const key of Object.keys(fromFiles)) { + const processValue = process.env[key] + if (processValue !== undefined) { + effective[key] = processValue + } + } + for (const [key, value] of Object.entries(process.env)) { + if (value === undefined) { + continue + } + if (isPublicEnvKey(key) && effective[key] === undefined) { + effective[key] = value + } + } + + return expandEnvVariables(effective) +} + +/** + * Parse a `.env` file with Vite/dotenv-aligned basics: + * `export` prefix, inline comments (unquoted), escaped quotes, multiline + * double-quoted values, and `${VAR}` / `$VAR` expansion (via expandEnvVariables). + */ +export function parseEnvFile(text: string): Record { + const out: Record = {} + const lines = text.split(/\r?\n/) + let i = 0 + + while (i < lines.length) { + let line = lines[i]! + i += 1 + + const trimmedStart = line.trimStart() + if (!trimmedStart || trimmedStart.startsWith('#')) { + continue + } + + line = trimmedStart.replace(/^export\s+/, '') + const eq = line.indexOf('=') + if (eq <= 0) { + continue + } + + const key = line.slice(0, eq).trim() + if (!key || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) { + continue + } + + let rawValue = line.slice(eq + 1) + let value = '' + + const trimmedValue = rawValue.trimStart() + if (trimmedValue.startsWith('"')) { + // Double-quoted: may span lines; support \\ \" \n \r \t + let body = trimmedValue.slice(1) + let closed = false + while (true) { + let escaped = false + let end = -1 + for (let j = 0; j < body.length; j++) { + const ch = body[j]! + if (escaped) { + escaped = false + continue + } + if (ch === '\\') { + escaped = true + continue + } + if (ch === '"') { + end = j + break + } + } + if (end >= 0) { + value += unescapeDoubleQuoted(body.slice(0, end)) + closed = true + break + } + value += unescapeDoubleQuoted(body) + '\n' + if (i >= lines.length) { + break + } + body = lines[i]! + i += 1 + } + if (!closed) { + // Unterminated — keep what we have + } + } else if (trimmedValue.startsWith("'")) { + // Single-quoted: no escapes except closing quote; single line for v1 + const end = trimmedValue.indexOf("'", 1) + value = + end >= 0 ? trimmedValue.slice(1, end) : trimmedValue.slice(1) + } else { + // Unquoted: strip inline comments after unescaped space+# + value = stripInlineComment(rawValue.trim()) + } + + out[key] = value + } + + return out +} + +/** Strip unquoted `#` comments that begin after whitespace. */ +function stripInlineComment(value: string): string { + let inSingle = false + let inDouble = false + for (let i = 0; i < value.length; i++) { + const ch = value[i]! + if (ch === "'" && !inDouble) { + inSingle = !inSingle + continue + } + if (ch === '"' && !inSingle) { + inDouble = !inDouble + continue + } + if (!inSingle && !inDouble && ch === '#') { + // Comment only if preceded by whitespace (or start) + if (i === 0 || /\s/.test(value[i - 1]!)) { + return value.slice(0, i).trimEnd() + } + } + } + return value +} + +/** Unescape common double-quoted `.env` sequences. */ +function unescapeDoubleQuoted(value: string): string { + return value + .replace(/\\n/g, '\n') + .replace(/\\r/g, '\r') + .replace(/\\t/g, '\t') + .replace(/\\"/g, '"') + .replace(/\\\\/g, '\\') +} + +/** + * Expand `$VAR` / `${VAR}` using values already in the map, then `process.env`. + * Escaped dollars (`\$VAR`, `\${VAR}`) are preserved as literals. + */ +export function expandEnvVariables( + env: Record, +): Record { + const lookup = (name: string): string => { + if (Object.prototype.hasOwnProperty.call(env, name)) { + return env[name]! + } + return process.env[name] ?? '' + } + + for (const [key, raw] of Object.entries(env)) { + let out = '' + for (let i = 0; i < raw.length; i++) { + const ch = raw[i]! + if (ch === '\\' && raw[i + 1] === '$') { + out += '$' + i += 1 + continue + } + if (ch !== '$') { + out += ch + continue + } + if (raw[i + 1] === '{') { + const end = raw.indexOf('}', i + 2) + if (end > i) { + const name = raw.slice(i + 2, end) + if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(name)) { + out += lookup(name) + i = end + continue + } + } + out += ch + continue + } + const match = raw.slice(i + 1).match(/^([A-Za-z_][A-Za-z0-9_]*)/) + if (match) { + out += lookup(match[1]!) + i += match[1]!.length + continue + } + out += ch + } + env[key] = out + } + return env +} + +/** Keys safe to inline into the browser bundle (Vite-aligned public prefixes). */ +export function isPublicEnvKey(key: string): boolean { + return ( + key.startsWith('VITE_') || + key.startsWith('PUBLIC_') || + key.startsWith('TSS_PUBLIC_') + ) +} + +/** Build Bun/Vite-style define entries from loaded env (both process.env + import.meta.env). */ +export function createEnvDefine( + env: Record, + opts?: { publicOnly?: boolean }, +): Record { + const define: Record = {} + for (const [key, value] of Object.entries(env)) { + if (opts?.publicOnly && !isPublicEnvKey(key)) { + continue + } + // Skip keys that would break JS identifiers in import.meta.env access patterns + // still define process.env.* always; import.meta.env.* for alphanumeric keys. + define[`process.env.${key}`] = JSON.stringify(value) + if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) { + define[`import.meta.env.${key}`] = JSON.stringify(value) + } + } + return define +} diff --git a/packages/start-plugin-core/src/bun/nitro-bridge.ts b/packages/start-plugin-core/src/bun/nitro-bridge.ts new file mode 100644 index 00000000000..f90211c7335 --- /dev/null +++ b/packages/start-plugin-core/src/bun/nitro-bridge.ts @@ -0,0 +1,118 @@ +import { createRequire } from 'node:module' +import { pathToFileURL } from 'node:url' +import { join } from 'pathe' +import type { BunNitroOptions } from './types' + +export interface BunNitroBuildResult { + /** Final public assets directory (typically `.output/public`) */ + publicDir: string + /** Nitro server output directory (typically `.output/server`) */ + serverDir: string + /** Nitro output root (typically `.output`) */ + outputDir: string +} + +/** Dynamically import Nitro builder APIs from the app root. */ +async function importNitroBuilder(root: string): Promise< + typeof import('nitro/builder') +> { + try { + // Resolve from the app root (optional peer), not from this package. + const requireFromApp = createRequire(join(root, 'package.json')) + const builderPath = requireFromApp.resolve('nitro/builder') + return (await import(pathToFileURL(builderPath).href)) as typeof import( + 'nitro/builder' + ) + } catch (err) { + throw new Error( + `[tanstack-start-bun] bun.nitro requires the optional peer dependency "nitro" (Nitro 3). Install it in the app (e.g. \`npm install nitro\`).\n` + + `Original error: ${err instanceof Error ? err.message : String(err)}`, + ) + } +} + +/** + * After dual `Bun.build`, optionally re-package with Nitro 3 (programmatic API). + * Mirrors `nitro-v2-vite-plugin` post-build `createNitro`, not `nitro/vite`. + */ +export async function runBunNitroBuild(opts: { + root: string + clientOutDir: string + serverEntry: string + publicBase: string + nitro: BunNitroOptions +}): Promise { + const builder = await importNitroBuilder(opts.root) + const { createNitro, prepare, copyPublicAssets, build } = builder + + const userConfig = (opts.nitro.config ?? {}) as Record + const outputDir = + (typeof userConfig.output === 'object' && + userConfig.output && + typeof (userConfig.output as { dir?: string }).dir === 'string' + ? (userConfig.output as { dir: string }).dir + : undefined) ?? join(opts.root, '.output') + + const baseURL = + typeof userConfig.baseURL === 'string' + ? userConfig.baseURL + : opts.publicBase === '/' + ? undefined + : opts.publicBase + + const userPublicAssets = Array.isArray(userConfig.publicAssets) + ? (userConfig.publicAssets as Array>) + : [] + + if (userConfig.serverEntry !== undefined) { + console.warn( + '[tanstack-start-bun] bun.nitro.config.serverEntry is ignored; Start always injects dist/server/server.js as the web handler.', + ) + } + + const nitro = await createNitro({ + rootDir: opts.root, + ...userConfig, + ...(baseURL ? { baseURL } : {}), + preset: + opts.nitro.preset ?? + (userConfig.preset as string | undefined) ?? + 'node-server', + output: { + ...((typeof userConfig.output === 'object' && userConfig.output) || {}), + dir: outputDir, + }, + // Avoid scanning the app for Nitro file routes; Start owns routing. + scanDirs: + userConfig.scanDirs !== undefined + ? (userConfig.scanDirs as string[]) + : [], + // Start-owned SSR handler — never allow user config to replace it. + serverEntry: { + handler: opts.serverEntry, + format: 'web', + }, + publicAssets: [ + { + dir: opts.clientOutDir, + baseURL: '/', + maxAge: 31536000, + }, + ...userPublicAssets, + ], + }) + + try { + await prepare(nitro) + await copyPublicAssets(nitro) + await build(nitro) + } finally { + await nitro.close() + } + + return { + publicDir: nitro.options.output.publicDir, + serverDir: nitro.options.output.serverDir, + outputDir: nitro.options.output.dir, + } +} diff --git a/packages/start-plugin-core/src/bun/nitro-shim.d.ts b/packages/start-plugin-core/src/bun/nitro-shim.d.ts new file mode 100644 index 00000000000..d6ff119a9a4 --- /dev/null +++ b/packages/start-plugin-core/src/bun/nitro-shim.d.ts @@ -0,0 +1,20 @@ +declare module 'nitro/builder' { + export interface Nitro { + options: { + output: { + dir: string + publicDir: string + serverDir: string + } + [key: string]: unknown + } + close: () => Promise + } + + export function createNitro( + config: Record, + ): Promise + export function prepare(nitro: Nitro): Promise + export function copyPublicAssets(nitro: Nitro): Promise + export function build(nitro: Nitro): Promise +} diff --git a/packages/start-plugin-core/src/bun/node-builtin-stub.ts b/packages/start-plugin-core/src/bun/node-builtin-stub.ts new file mode 100644 index 00000000000..b7fce5b86a8 --- /dev/null +++ b/packages/start-plugin-core/src/bun/node-builtin-stub.ts @@ -0,0 +1,91 @@ +/** + * Browser stubs for `node:*` builtins pulled into ESM-dev graphs + * (e.g. `@tanstack/start-storage-context` → `node:async_hooks`). + */ + +export function isNodeBuiltinSpecifier(spec: string): boolean { + return ( + spec.startsWith('node:') || + spec === 'async_hooks' || + spec === 'fs' || + spec === 'path' || + spec === 'url' || + spec === 'crypto' || + spec === 'module' || + spec === 'os' || + spec === 'util' || + spec === 'stream' || + spec === 'buffer' || + spec === 'events' || + spec === 'process' || + spec === 'child_process' || + spec === 'worker_threads' || + spec === 'http' || + spec === 'https' || + spec === 'net' || + spec === 'tls' || + spec === 'zlib' || + spec === 'assert' || + spec === 'tty' || + spec === 'constants' + ) +} + +/** True when `/@fs` stripped path is still a node builtin (e.g. `node:async_hooks`). */ +export function isNodeBuiltinFsPath(absPath: string): boolean { + const spec = absPath.startsWith('/') ? absPath.slice(1) : absPath + return isNodeBuiltinSpecifier(spec) +} + +/** Return browser stub source for a `node:*` builtin. */ +export function getNodeBuiltinStubSource(spec: string): string { + const normalized = spec.startsWith('/') ? spec.slice(1) : spec + const name = normalized.startsWith('node:') + ? normalized.slice('node:'.length) + : normalized + + if (name === 'async_hooks') { + return `export class AsyncLocalStorage { + #store; + run(store, fn, ...args) { + const prev = this.#store; + this.#store = store; + try { + return fn(...args); + } finally { + this.#store = prev; + } + } + getStore() { + return this.#store; + } + enterWith(store) { + this.#store = store; + } + disable() { + this.#store = undefined; + } + exit(fn, ...args) { + const prev = this.#store; + this.#store = undefined; + try { + return fn(...args); + } finally { + this.#store = prev; + } + } +} +export default { AsyncLocalStorage }; +` + } + + // Generic empty / proxy stub — enough for accidental client imports. + return `const stub = new Proxy(function BunNodeBuiltinStub() {}, { + get() { return stub; }, + apply() { return stub; }, + construct() { return stub; }, +}); +export default stub; +export const __tanstackNodeBuiltinStub = ${JSON.stringify(spec)}; +` +} diff --git a/packages/start-plugin-core/src/bun/normalized-client-build.ts b/packages/start-plugin-core/src/bun/normalized-client-build.ts new file mode 100644 index 00000000000..37a5fe5c8e1 --- /dev/null +++ b/packages/start-plugin-core/src/bun/normalized-client-build.ts @@ -0,0 +1,262 @@ +import { readFile } from 'node:fs/promises' +import { relative } from 'pathe' +import { tsrSplit } from '@tanstack/router-plugin' +import type { NormalizedClientBuild, NormalizedClientChunk } from '../types' + +export interface BunClientOutputLike { + path: string + fileName: string + kind: string + /** Optional Bun input paths (may include ?tsr-split=...) when available. */ + inputs?: Array<{ path: string }> + /** Absolute path to a sibling `.map` file when sourcemaps are linked. */ + sourcemapPath?: string +} + +/** + * Best-effort normalization of Bun.build client outputs into NormalizedClientBuild. + * + * Bun BuildArtifact does not expose Rollup-style module graphs, so routeFilePaths + * are recovered from linked sourcemap `sources` (and optional `inputs`) that include + * `?tsr-split=...` virtual modules. + */ +export function normalizeBunClientBuild(opts: { + outputs: Array + clientOutDir: string + /** + * CSS assets emitted by `createCssAssetsPlugin` (source path + hashed file + content). + * Wired into the Start manifest for route stylesheets / inlineCss. + */ + emittedCssAssets?: Array<{ + sourcePath: string + fileName: string + css: string + }> +}): NormalizedClientBuild { + const chunksByFileName = new Map() + const chunkFileNamesByRouteFilePath = new Map>() + const cssFilesBySourcePath = new Map>() + const cssContentByFileName = new Map() + let entryChunkFileName: string | undefined + + for (const artifact of opts.outputs) { + const fileName = artifact.fileName.replace(/^\.\//, '') + const kind = artifact.kind + + if (kind === 'asset' && fileName.endsWith('.css')) { + cssContentByFileName.set(fileName, '') + continue + } + + if (kind !== 'entry-point' && kind !== 'chunk') { + continue + } + + const isEntry = kind === 'entry-point' + const routeFilePaths = getRouteFilePathsFromInputs(artifact.inputs) + + chunksByFileName.set(fileName, { + fileName, + isEntry, + imports: [], + dynamicImports: [], + css: [], + routeFilePaths, + hydrationIds: [], + }) + + for (const routeFilePath of routeFilePaths) { + const existing = chunkFileNamesByRouteFilePath.get(routeFilePath) ?? [] + existing.push(fileName) + chunkFileNamesByRouteFilePath.set(routeFilePath, existing) + } + + if (isEntry && !entryChunkFileName) { + entryChunkFileName = fileName + } + } + + if (!entryChunkFileName) { + for (const fileName of chunksByFileName.keys()) { + if (/\.(m?js)$/.test(fileName)) { + entryChunkFileName = fileName + const chunk = chunksByFileName.get(fileName)! + chunksByFileName.set(fileName, { ...chunk, isEntry: true }) + break + } + } + } + + if (!entryChunkFileName) { + throw new Error( + '[tanstack-start-bun] Could not determine client entry chunk from Bun.build outputs', + ) + } + + const cssFileNames: Array = [] + for (const asset of opts.emittedCssAssets ?? []) { + const fileName = asset.fileName.replace(/^\.\//, '') + if (!fileName.endsWith('.css')) { + continue + } + cssContentByFileName.set(fileName, asset.css) + if (!cssFileNames.includes(fileName)) { + cssFileNames.push(fileName) + } + const existing = cssFilesBySourcePath.get(asset.sourcePath) ?? [] + if (!existing.includes(fileName)) { + existing.push(fileName) + } + cssFilesBySourcePath.set(asset.sourcePath, existing) + } + + // Bun BuildArtifact has no Rollup module graph — attach emitted CSS to the entry + // chunk so Start SSR still injects stylesheet links / inlineCss content. + if (cssFileNames.length > 0) { + const entry = chunksByFileName.get(entryChunkFileName) + if (entry) { + entry.css = [...new Set([...entry.css, ...cssFileNames])] + } + } + + return { + entryChunkFileName, + chunksByFileName, + chunkFileNamesByRouteFilePath, + cssFilesBySourcePath, + cssContentByFileName, + } +} + +/** + * Enrich a NormalizedClientBuild by reading linked `.js.map` sources next to outputs. + */ +export async function enrichBunClientBuildFromSourcemaps(opts: { + clientBuild: NormalizedClientBuild + outputs: Array +}): Promise { + const chunksByFileName = new Map(opts.clientBuild.chunksByFileName) + const chunkFileNamesByRouteFilePath = new Map( + [...opts.clientBuild.chunkFileNamesByRouteFilePath.entries()].map( + ([key, value]) => [key, [...value]] as [string, Array], + ), + ) + + for (const artifact of opts.outputs) { + if (artifact.kind !== 'entry-point' && artifact.kind !== 'chunk') { + continue + } + const fileName = artifact.fileName.replace(/^\.\//, '') + const chunk = chunksByFileName.get(fileName) + if (!chunk) { + continue + } + + const mapPath = artifact.sourcemapPath ?? `${artifact.path}.map` + const sources = await readSourcemapSources(mapPath) + if (!sources.length) { + continue + } + + const routeFilePaths = getRouteFilePathsFromInputs( + sources.map((path) => ({ path })), + ) + if (!routeFilePaths.length) { + continue + } + + const merged = [...new Set([...chunk.routeFilePaths, ...routeFilePaths])] + chunksByFileName.set(fileName, { + ...chunk, + routeFilePaths: merged, + }) + + for (const routeFilePath of routeFilePaths) { + const existing = chunkFileNamesByRouteFilePath.get(routeFilePath) ?? [] + if (!existing.includes(fileName)) { + existing.push(fileName) + } + chunkFileNamesByRouteFilePath.set(routeFilePath, existing) + } + } + + return { + ...opts.clientBuild, + chunksByFileName, + chunkFileNamesByRouteFilePath, + } +} + +/** Relativize an absolute output path to the client out dir. */ +export function toClientRelativeFileName( + absolutePath: string, + clientOutDir: string, +): string { + const rel = relative(clientOutDir, absolutePath) + return rel.replace(/\\/g, '/') +} + +/** Collect route file paths from Bun build inputs / sourcemaps. */ +export function getRouteFilePathsFromInputs( + inputs: Array<{ path: string }> | undefined, +): Array { + if (!inputs?.length) { + return [] + } + + const paths: Array = [] + const seen = new Set() + + for (const input of inputs) { + const routeFilePath = extractRouteFilePathFromSource(input.path) + if (!routeFilePath || seen.has(routeFilePath)) { + continue + } + seen.add(routeFilePath) + paths.push(routeFilePath) + } + + return paths +} + +/** Extract a route path from a `?tsr-split=` source id. */ +function extractRouteFilePathFromSource(id: string): string | undefined { + // Bun sourcemaps often prefix virtual namespaces: tsr-split:/abs/path?tsr-split=... + let normalized = id + const ns = normalized.indexOf(':/') + if ( + ns > 0 && + !normalized.startsWith('/') && + !normalized.startsWith('file:') + ) { + // Keep absolute path after "namespace:" + const after = normalized.slice(ns + 1) + if (after.startsWith('/')) { + normalized = after + } + } + + const queryIndex = normalized.indexOf('?') + if (queryIndex < 0) { + return undefined + } + const query = normalized.slice(queryIndex + 1) + if (!query.includes(tsrSplit)) { + return undefined + } + if (!new URLSearchParams(query).has(tsrSplit)) { + return undefined + } + return normalized.slice(0, queryIndex) +} + +/** Read the `sources` array from a linked `.js.map` file. */ +async function readSourcemapSources(mapPath: string): Promise> { + try { + const raw = await readFile(mapPath, 'utf8') + const parsed = JSON.parse(raw) as { sources?: Array } + return parsed.sources ?? [] + } catch { + return [] + } +} diff --git a/packages/start-plugin-core/src/bun/optimize-deps.ts b/packages/start-plugin-core/src/bun/optimize-deps.ts new file mode 100644 index 00000000000..46664eb89fb --- /dev/null +++ b/packages/start-plugin-core/src/bun/optimize-deps.ts @@ -0,0 +1,601 @@ +/** + * Vite-like dependency prebundling for Bun ESM-dev. + * + * Scans app sources for bare imports, bundles each entry with Bun.build into + * `node_modules/.tanstack-start/deps`, and rewrites those imports to `/@deps/…` + * so the browser loads a few fat files instead of thousands of `/@fs` modules. + */ + +import { createHash } from 'node:crypto' +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs' +import { readFile } from 'node:fs/promises' +import { dirname, join, normalize, relative } from 'pathe' + +export const DEPS_PREFIX = '/@deps' +export const DEPS_CACHE_DIR = 'node_modules/.tanstack-start/deps' +/** Bump when prebundle post-processing changes (invalidates disk cache). */ +const OPTIMIZE_DEPS_CACHE_VERSION = '7-deps-url-hash' + +/** React singletons: always served via `/@fs` (never prebundled). */ +export const OPTIMIZE_DEPS_REACT_EXTERNALS = [ + 'react', + 'react/jsx-runtime', + 'react/jsx-dev-runtime', + 'react-dom', + 'react-dom/client', + 'react-dom/server', + 'scheduler', +] as const + +export type OptimizeDepsConfig = { + /** Extra bare specs to always prebundle. */ + include?: Array + /** Bare specs (or package name prefixes) to skip. */ + exclude?: Array + /** Disable prebundling (fall back to per-file `/@fs`). */ + disabled?: boolean + /** Ignore disk cache and rebuild. */ + force?: boolean +} + +export type OptimizeDepsResult = { + depsDir: string + /** Content hash embedded in `/@deps//…` URLs (cache bust). */ + hash: string + urlForSpec: (spec: string) => string | undefined + resolvePath: (pathname: string) => string | null + /** + * Legacy `/@deps/react.js` (pre-hash / removed React prebundles) → `/@fs/…`. + * Avoids NS_ERROR_CORRUPTED_CONTENT from stale browser caches. + */ + fallbackFsUrl: (pathname: string) => string | undefined + count: number +} + +type Metadata = { + hash: string + /** specifier → filename under depsDir */ + map: Record +} + +const IMPORT_SPEC_RE = + /(\bfrom\s+|\bimport\s*\(\s*)(['"])([^'"]+)\2|(\bimport\s+)(['"])([^'"]+)\5/g + +/** Packages that must stay on `/@fs` for Start compiler / dual-package remap. */ +const DEFAULT_EXCLUDE_PREFIXES = [ + '@tanstack/react-start', + '@tanstack/solid-start', + '@tanstack/vue-start', + '@tanstack/start-client-core', + '@tanstack/start-server-core', + '@tanstack/start-plugin-core', + // Server / Node-only — never ship to the browser graph + '@prisma-next', + 'prisma-next', + '@modelcontextprotocol', + 'commander', +] + +function isBareSpecifier(spec: string): boolean { + if (!spec || /[\s+]/.test(spec)) return false + if ( + spec.startsWith('.') || + spec.startsWith('/') || + spec.startsWith('file:') || + spec.startsWith('data:') || + spec.startsWith('blob:') || + spec.startsWith('http:') || + spec.startsWith('https:') || + spec.startsWith('node:') || + spec.startsWith('#') || + spec.startsWith('virtual:') + ) { + return false + } + return true +} + +function isExcluded(spec: string, exclude: Array): boolean { + for (const rule of exclude) { + if (spec === rule || spec.startsWith(`${rule}/`)) return true + } + return false +} + +function isReactFamily(spec: string): boolean { + return /^(react|react-dom|scheduler)(\/|$)/.test(spec) +} + +function resolveReactFsUrls(root: string): Map { + const reactFsUrls = new Map() + for (const spec of OPTIMIZE_DEPS_REACT_EXTERNALS) { + try { + const abs = Bun.resolveSync(spec, root) + reactFsUrls.set(spec, `/@fs${abs.startsWith('/') ? abs : `/${abs}`}`) + } catch { + // optional + } + } + return reactFsUrls +} + +function packageRootFromResolved(absPath: string): string | null { + const n = absPath.replace(/\\/g, '/') + const marker = '/node_modules/' + const idx = n.lastIndexOf(marker) + if (idx === -1) return null + let rest = n.slice(idx + marker.length) + const bunInner = rest.indexOf('/node_modules/') + if (rest.startsWith('.bun/') && bunInner !== -1) { + rest = rest.slice(bunInner + '/node_modules/'.length) + } + if (rest.startsWith('@')) { + const parts = rest.split('/') + if (parts.length < 2) return null + return parts.slice(0, 2).join('/') + } + return rest.split('/')[0] ?? null +} + +function depsFileName(spec: string): string { + const trimmed = spec.replace(/\.(m|c)?js$/i, '') + return `${trimmed + .replace(/^@/, '') + .replace(/\//g, '__') + .replace(/[^a-zA-Z0-9_.-]/g, '_')}.js` +} + +/** Whether an app file should be crawled for bare imports (skip server/cli). */ +function shouldCrawlAppFile(absPath: string, root: string): boolean { + const rel = relative(root, absPath).replace(/\\/g, '/') + if (rel.startsWith('..')) return false + if (rel.includes('/node_modules/') || rel.startsWith('node_modules/')) { + return false + } + if (/\.css$/i.test(absPath)) return false + if ( + /(?:^|\/)server(?:\/|$)/.test(rel) || + /(?:^|\/)cli(?:\/|$)/.test(rel) || + /(?:^|\/)mcp(?:\/|$)/.test(rel) + ) { + return false + } + return true +} + +async function scanAppBareImports( + root: string, + clientEntryPath: string, + aliases?: Record, +): Promise> { + const specs = new Set() + const queue: Array = [] + const seenFiles = new Set() + + const enqueue = (abs: string) => { + const n = normalize(abs.split('?')[0]!) + if (seenFiles.has(n) || !shouldCrawlAppFile(n, root)) return + if (!existsSync(n)) return + seenFiles.add(n) + queue.push(n) + } + + enqueue(clientEntryPath) + // Seed common client roots when entry is a thin virtual wrapper + for (const seed of [ + join(root, 'src/router.tsx'), + join(root, 'src/routeTree.gen.ts'), + join(root, 'src/routes'), + join(root, 'src/components'), + join(root, 'src/modules'), + join(root, 'src/lib'), + ]) { + if (!existsSync(seed)) continue + if (seed.endsWith('routes') || !seed.includes('.')) { + const glob = new Bun.Glob('**/*.{ts,tsx,js,jsx}') + for await (const rel of glob.scan({ cwd: seed, onlyFiles: true })) { + enqueue(join(seed, rel)) + } + } else { + enqueue(seed) + } + } + + while (queue.length > 0) { + const file = queue.pop()! + let code: string + try { + code = await readFile(file, 'utf8') + } catch { + continue + } + IMPORT_SPEC_RE.lastIndex = 0 + let m: RegExpExecArray | null + while ((m = IMPORT_SPEC_RE.exec(code))) { + const spec = (m[3] ?? m[6]) as string + if (isBareSpecifier(spec)) { + // CSS @import "pkg/file.css" is matched by the import scanner — skip + if (/\.css$/i.test(spec)) continue + specs.add(spec) + continue + } + if (spec.startsWith('#/') || spec.startsWith('#tanstack')) { + try { + const resolved = Bun.resolveSync(spec, root) + enqueue(resolved) + } catch { + try { + const resolved = Bun.resolveSync(spec, dirname(file)) + enqueue(resolved) + } catch { + // ignore + } + } + continue + } + if (spec.startsWith('.') || spec.startsWith('/') || spec.startsWith('file:')) { + try { + const resolved = Bun.resolveSync(spec, dirname(file)) + enqueue(resolved) + } catch { + // ignore + } + continue + } + const aliased = aliases?.[spec] + if (aliased) enqueue(aliased) + } + } + + return specs +} + +function computeHash(specs: Array, root: string): string { + const h = createHash('sha256') + h.update(OPTIMIZE_DEPS_CACHE_VERSION) + h.update(specs.join('\0')) + try { + h.update(readFileSync(join(root, 'package.json'), 'utf8')) + } catch { + // ignore + } + for (const lockName of ['bun.lock', 'package-lock.json', 'pnpm-lock.yaml'] as const) { + try { + h.update(readFileSync(join(root, lockName), 'utf8').slice(0, 64_000)) + break + } catch { + // try next + } + } + try { + h.update(readFileSync(join(root, 'bun.lockb')).subarray(0, 64_000)) + } catch { + // optional binary lock + } + return h.digest('hex').slice(0, 16) +} + +function emptyResult(depsDir: string): OptimizeDepsResult { + return { + depsDir, + hash: '', + urlForSpec: () => undefined, + resolvePath: () => null, + fallbackFsUrl: () => undefined, + count: 0, + } +} + +function makeResult( + depsDir: string, + fileMap: Map, + hash: string, + reactFsUrls: Map, +): OptimizeDepsResult { + const urls = new Map() + for (const [spec, fileName] of fileMap) { + urls.set(spec, `${DEPS_PREFIX}/${hash}/${fileName}`) + } + + const reactByFile = new Map() + for (const [spec, fsUrl] of reactFsUrls) { + reactByFile.set(depsFileName(spec), fsUrl) + } + + return { + depsDir, + hash, + count: urls.size, + urlForSpec: (spec) => urls.get(spec), + resolvePath: (pathname) => { + if (!pathname.startsWith(`${DEPS_PREFIX}/`)) return null + const rest = pathname.slice(DEPS_PREFIX.length + 1) + // /@deps//file.js or legacy /@deps/file.js + const parts = rest.split('/') + const name = + parts.length === 2 && parts[0] === hash + ? parts[1] + : parts.length === 1 + ? parts[0] + : null + if ( + !name || + name.includes('..') || + name.includes('/') || + name.includes('\\') + ) { + return null + } + const abs = join(depsDir, name) + return existsSync(abs) ? abs : null + }, + fallbackFsUrl: (pathname) => { + if (!pathname.startsWith(`${DEPS_PREFIX}/`)) return undefined + const rest = pathname.slice(DEPS_PREFIX.length + 1) + const name = rest.includes('/') ? rest.split('/').pop()! : rest + return reactByFile.get(name) + }, + } +} + +/** Rewrite bare imports inside a prebundle to /@deps siblings or /@fs React. */ +function rewriteBareImportsInDep( + code: string, + fileMap: Map, + reactFsUrls: Map, + selfSpec: string, +): string { + return code.replace(IMPORT_SPEC_RE, (full, g1, g2, g3, g4, g5, g6) => { + const spec = (g3 ?? g6) as string + const quote = (g2 ?? g5) as string + const prefix = (g1 ?? g4) as string + if (!isBareSpecifier(spec) || spec === selfSpec) return full + const depFile = fileMap.get(spec) + if (depFile) { + return `${prefix}${quote}./${depFile}${quote}` + } + const reactUrl = reactFsUrls.get(spec) + if (reactUrl) { + return `${prefix}${quote}${reactUrl}${quote}` + } + return full + }) +} + +/** Collect every `exports.foo =` name from Bun CJS→ESM interop factories. */ +function collectCjsExportNames(bundledEsm: string): Array { + // Must scan the whole file: the entry is often a thin `module.exports = other` + // re-export (react/index.js) with zero `exports.foo =`, while hooks live in an + // earlier factory (react.development.js). Taking only the "richest" factory + // breaks jsx-runtime (which embeds both react + jsx-runtime factories). + const names = new Set() + for (const m of bundledEsm.matchAll(/\bexports\.([A-Za-z_$][\w$]*)\s*=/g)) { + const name = m[1]! + if (name !== '__esModule' && name !== 'default') names.add(name) + } + return [...names] +} + +/** + * Bun.build of CJS packages (react, jsx-runtime, …) often emits only + * `export { X as default }`. Browsers need named ESM exports (`jsx`, `useCallback`). + */ +function addCjsNamedEsmExports(bundledEsm: string): string { + if (!/__commonJS\b|__toESM\b/.test(bundledEsm)) return bundledEsm + + // Already post-processed + if ( + /export\s+const\s+useCallback\s*=/.test(bundledEsm) || + /export\s+const\s+jsx\s*=/.test(bundledEsm) + ) { + return bundledEsm + } + + const names = collectCjsExportNames(bundledEsm) + const fallback = + names.length > 0 + ? names + : ['jsx', 'jsxs', 'jsxDEV', 'Fragment', 'createElement'] + + const replaced = bundledEsm.replace( + /export\s*\{\s*([A-Za-z_$][\w$]*)\s+as\s+default\s*\}\s*;?\s*$/, + (_full, defaultId: string) => { + const named = fallback + .map((n) => `export const ${n} = ${defaultId}.${n};`) + .join('\n') + return `export { ${defaultId} as default };\n${named}\n` + }, + ) + return replaced === bundledEsm ? bundledEsm : replaced +} + +/** + * Prebundle discovered bare imports for ESM-dev. + */ +export async function runOptimizeDeps(opts: { + root: string + clientEntryPath: string + aliases?: Record + optimizeDeps?: OptimizeDepsConfig | false +}): Promise { + const config = + opts.optimizeDeps === false + ? ({ disabled: true } satisfies OptimizeDepsConfig) + : (opts.optimizeDeps ?? {}) + const depsDir = join(opts.root, DEPS_CACHE_DIR) + + if (config.disabled) { + return emptyResult(depsDir) + } + + const exclude = [...DEFAULT_EXCLUDE_PREFIXES, ...(config.exclude ?? [])] + const discovered = await scanAppBareImports( + opts.root, + opts.clientEntryPath, + opts.aliases, + ) + for (const s of config.include ?? []) discovered.add(s) + // React family stays on /@fs (CJS transform) so the browser has exactly one React. + // Prebundling react-dom/client with packages:bundle still inlines react and + // causes "Invalid hook call" / mismatched dispatcher. + + const specs = [...discovered] + .filter((s) => !isExcluded(s, exclude) && !isReactFamily(s)) + .sort() + if (specs.length === 0) { + return emptyResult(depsDir) + } + + const hash = computeHash(specs, opts.root) + const reactFsUrls = resolveReactFsUrls(opts.root) + const metaPath = join(depsDir, '_metadata.json') + const force = + config.force === true || + process.env.TANSTACK_START_OPTIMIZE_DEPS_FORCE === '1' + + if (!force && existsSync(metaPath)) { + try { + const meta = JSON.parse(readFileSync(metaPath, 'utf8')) as Metadata + if (meta.hash === hash && meta.map && Object.keys(meta.map).length > 0) { + const fileMap = new Map(Object.entries(meta.map)) + console.info( + `[tanstack-start-bun] optimizeDeps: cache hit (${fileMap.size} entries)`, + ) + return makeResult(depsDir, fileMap, hash, reactFsUrls) + } + } catch { + // rebuild + } + } + + const started = Date.now() + rmSync(depsDir, { recursive: true, force: true }) + mkdirSync(depsDir, { recursive: true }) + + const resolvedEntries: Array<{ spec: string; abs: string }> = [] + for (const spec of specs) { + try { + const abs = Bun.resolveSync(spec, opts.root) + const n = abs.replace(/\\/g, '/') + if (!n.includes('/node_modules/') && !n.includes('/.bun/')) continue + const pkg = packageRootFromResolved(abs) + if (pkg && isExcluded(pkg, exclude)) continue + resolvedEntries.push({ spec, abs }) + } catch { + // unresolved optional peer + } + } + + resolvedEntries.sort((a, b) => a.spec.localeCompare(b.spec)) + + const fileMap = new Map() + const failures: Array = [] + + for (const { spec, abs } of resolvedEntries) { + const fileName = depsFileName(spec) + const outfile = join(depsDir, fileName) + const wrapperPath = join(depsDir, `.entry-${fileName}`) + // Re-export wrapper: bundling package exports that contain `import "client-only"` + // as the entrypoint can yield an empty/broken graph in Bun.build. + writeFileSync( + wrapperPath, + [ + `export * from ${JSON.stringify(abs)};`, + `import * as __m from ${JSON.stringify(abs)};`, + `export default __m.default ?? __m;`, + '', + ].join('\n'), + ) + const external = [...OPTIMIZE_DEPS_REACT_EXTERNALS] + try { + const built = await Bun.build({ + entrypoints: [wrapperPath], + target: 'browser', + format: 'esm', + minify: false, + packages: 'bundle', + write: false, + external, + } as never) + try { + rmSync(wrapperPath, { force: true }) + } catch { + // ignore + } + if (!built.success || !built.outputs[0]) { + failures.push(spec) + continue + } + let code = await built.outputs[0].text() + if (/\b__require\s*\(/.test(code)) { + failures.push(spec) + try { + rmSync(outfile, { force: true }) + } catch { + // ignore + } + continue + } + // Broken Bun stubs from `client-only` package entries (mangled exports, no body) + if ( + /\$[A-Za-z0-9]+\$export\$/.test(code) && + !/\bfunction\b/.test(code) && + code.length < 4000 + ) { + failures.push(spec) + try { + rmSync(outfile, { force: true }) + } catch { + // ignore + } + continue + } + fileMap.set(spec, fileName) + code = addCjsNamedEsmExports(code) + code = rewriteBareImportsInDep(code, fileMap, reactFsUrls, spec) + writeFileSync(outfile, code) + } catch (err) { + try { + rmSync(wrapperPath, { force: true }) + } catch { + // ignore + } + failures.push(spec) + console.warn( + `[tanstack-start-bun] optimizeDeps: skip ${spec}:`, + err instanceof Error ? err.message : err, + ) + } + } + + // Second pass once the full map exists + for (const [spec, fileName] of fileMap) { + const outfile = join(depsDir, fileName) + try { + const code = readFileSync(outfile, 'utf8') + const next = rewriteBareImportsInDep(code, fileMap, reactFsUrls, spec) + if (next !== code) writeFileSync(outfile, next) + } catch { + // ignore + } + } + + const meta: Metadata = { + hash, + map: Object.fromEntries(fileMap), + } + writeFileSync(metaPath, JSON.stringify(meta, null, 2)) + + const ms = Date.now() - started + console.info( + `[tanstack-start-bun] optimizeDeps: ${fileMap.size} entries → ${relative(opts.root, depsDir)} (${ms}ms)` + + (failures.length ? `; skipped ${failures.length}` : ''), + ) + if (failures.length > 0 && failures.length <= 12) { + console.info( + `[tanstack-start-bun] optimizeDeps skipped: ${failures.join(', ')}`, + ) + } + + return makeResult(depsDir, fileMap, hash, reactFsUrls) +} diff --git a/packages/start-plugin-core/src/bun/planning.ts b/packages/start-plugin-core/src/bun/planning.ts new file mode 100644 index 00000000000..68b301c9561 --- /dev/null +++ b/packages/start-plugin-core/src/bun/planning.ts @@ -0,0 +1,106 @@ +import { join, isAbsolute } from 'pathe' +import { ENTRY_POINTS } from '../constants' +import type { ResolvedStartEntryPlan } from '../planning' +import { BUN_ENVIRONMENT_NAMES } from './types' + +export { BUN_ENVIRONMENT_NAMES } + +export interface BunResolvedEntryAliases { + client: string + server: string + start: string + router: string + alias: Record<(typeof ENTRY_POINTS)[keyof typeof ENTRY_POINTS], string> +} + +/** Normalize an entry path to absolute (or preserve `file:` URLs). */ +function normalizeEntryPath(filePath: string): string { + if (filePath.startsWith('file:') || isAbsolute(filePath)) { + return filePath + } + return join(process.cwd(), filePath) +} + +/** Build `#tanstack-*` entry aliases for Bun builds. */ +export function createBunResolvedEntryAliases(opts: { + entryPaths: ResolvedStartEntryPlan['entryPaths'] +}): BunResolvedEntryAliases { + const client = normalizeEntryPath(opts.entryPaths.client) + const server = normalizeEntryPath(opts.entryPaths.server) + const start = normalizeEntryPath(opts.entryPaths.start) + const router = normalizeEntryPath(opts.entryPaths.router) + + return { + client, + server, + start, + router, + alias: { + [ENTRY_POINTS.client]: client, + [ENTRY_POINTS.server]: server, + [ENTRY_POINTS.start]: start, + [ENTRY_POINTS.router]: router, + }, + } +} + +/** Create process.env + import.meta.env define pairs for one key. */ +function defineReplaceEnv( + key: string, + value: string, +): Record { + return { + [`process.env.${key}`]: JSON.stringify(value), + [`import.meta.env.${key}`]: JSON.stringify(value), + } +} + +/** Build the shared Bun define map for Start runtime flags. */ +export function createBunDefine(opts: { + serverFnBase: string + routerBasepath: string + publicBase: string + isDev: boolean + inlineCssEnabled: boolean + spaEnabled?: boolean + disableCsrfMiddlewareWarning?: boolean + /** Extra define entries (e.g. from `.env`). */ + extraDefine?: Record +}): Record { + return { + ...defineReplaceEnv('TSS_SERVER_FN_BASE', opts.serverFnBase), + ...defineReplaceEnv('TSS_ROUTER_BASEPATH', opts.routerBasepath), + ...defineReplaceEnv('TSS_DEV_SERVER', opts.isDev ? 'true' : 'false'), + ...defineReplaceEnv( + 'TSS_SHELL', + opts.isDev && opts.spaEnabled ? 'true' : 'false', + ), + ...defineReplaceEnv( + 'TSS_INLINE_CSS_ENABLED', + opts.inlineCssEnabled ? 'true' : 'false', + ), + ...defineReplaceEnv( + 'TSS_DEV_SSR_STYLES_ENABLED', + opts.isDev ? 'true' : 'false', + ), + ...defineReplaceEnv('TSS_DEV_SSR_STYLES_BASEPATH', opts.publicBase), + ...defineReplaceEnv( + 'TSS_DISABLE_CSRF_MIDDLEWARE_WARNING', + opts.disableCsrfMiddlewareWarning ? 'true' : 'false', + ), + ...defineReplaceEnv('TSS_PUBLIC_BASE', opts.publicBase), + ...(opts.extraDefine ?? {}), + } +} + +/** Resolve absolute client/server output directories. */ +export function resolveBunOutputDirectories(opts: { + root: string + clientOutDir?: string + serverOutDir?: string +}): { client: string; server: string } { + return { + client: join(opts.root, opts.clientOutDir ?? 'dist/client'), + server: join(opts.root, opts.serverOutDir ?? 'dist/server'), + } +} diff --git a/packages/start-plugin-core/src/bun/plugin.ts b/packages/start-plugin-core/src/bun/plugin.ts new file mode 100644 index 00000000000..40ee1212b7d --- /dev/null +++ b/packages/start-plugin-core/src/bun/plugin.ts @@ -0,0 +1,402 @@ +import { join } from 'pathe' +import { + applyResolvedBaseAndOutput, + applyResolvedRouterBasepath, + createStartConfigContext, +} from '../config-context' +import { createServerFnBasePath, normalizePublicBase } from '../planning' +import { generateSerializationAdaptersModule } from '../serialization-adapters-module' +import { parseStartConfig } from './schema' +import { + createBunDefine, + createBunResolvedEntryAliases, + resolveBunOutputDirectories, +} from './planning' +import { createBunVirtualModuleStore, VIRTUAL_MODULES } from './virtual-modules' +import { createBunCompilerHosts } from './start-compiler-host' +import { createBunRouterSession } from './start-router-plugin' +import { + buildBunClient, + buildBunServer, + copyBunPublicAssets, + writeBunHostEntry, + type BunBuildContext, +} from './build-pipeline' +import { postBuildWithBun } from './post-build' +import { runBunNitroBuild } from './nitro-bridge' +import { runBunStandaloneCompile } from './standalone-compile' +import { createBunDevServer } from './dev-server' +import { + hmrEventForScope, + rebuildScopeForChange, + shouldRegenerateRoutes, +} from './hmr-protocol' +import { rewriteImportMetaHot } from './hmr-runtime' +import { createBunProdServer } from './static-host' +import { createEnvDefine, loadBunEnvFiles } from './load-env' +import { transformFrameworkJsx } from './framework-jsx-plugin' +import type { ServerFn } from '../start-compiler/types' +import type { + BunCoreOptions, + TanStackStartBunPluginCoreOptions, +} from './types' +import type { TanStackStartBunInputConfig } from './schema' +import type { TanStackStartBunAdapter } from './types' + +/** Merge `bun` options field-wise (primary overrides fallback). */ +function mergeBunCoreOptions( + primary?: BunCoreOptions, + fallback?: BunCoreOptions, +): BunCoreOptions | undefined { + if (!primary && !fallback) { + return undefined + } + return { + clientOutDir: primary?.clientOutDir ?? fallback?.clientOutDir, + serverOutDir: primary?.serverOutDir ?? fallback?.serverOutDir, + publicBase: primary?.publicBase ?? fallback?.publicBase, + publicDir: primary?.publicDir ?? fallback?.publicDir, + port: primary?.port ?? fallback?.port, + hostname: primary?.hostname ?? fallback?.hostname, + minify: primary?.minify ?? fallback?.minify, + plugins: primary?.plugins ?? fallback?.plugins, + clientPlugins: primary?.clientPlugins ?? fallback?.clientPlugins, + serverPlugins: primary?.serverPlugins ?? fallback?.serverPlugins, + css: primary?.css ?? fallback?.css, + nitro: primary?.nitro ?? fallback?.nitro, + standalone: primary?.standalone ?? fallback?.standalone, + optimizeDeps: primary?.optimizeDeps ?? fallback?.optimizeDeps, + } +} + +/** Create the experimental TanStack Start Bun bundler adapter. */ +export function tanStackStartBun( + corePluginOpts: TanStackStartBunPluginCoreOptions, + startPluginOpts: TanStackStartBunInputConfig = {}, +): TanStackStartBunAdapter { + const configContext = createStartConfigContext({ + corePluginOpts, + startPluginOpts, + parseConfig: parseStartConfig, + }) + + async function prepare(root: string, mode: 'dev' | 'build') { + const envMode = mode === 'dev' ? 'development' : 'production' + const loadedEnv = loadBunEnvFiles({ root, mode: envMode }) + const serverEnvDefine = createEnvDefine(loadedEnv) + const clientEnvDefine = createEnvDefine(loadedEnv, { publicOnly: true }) + + const bunOpts = mergeBunCoreOptions( + startPluginOpts.bun, + corePluginOpts.bun, + ) + + const publicBase = normalizePublicBase(bunOpts?.publicBase ?? '/') + const outDirs = resolveBunOutputDirectories({ + root, + clientOutDir: bunOpts?.clientOutDir, + serverOutDir: bunOpts?.serverOutDir, + }) + + applyResolvedBaseAndOutput({ + resolvedStartConfig: configContext.resolvedStartConfig, + root, + publicBase, + clientOutputDirectory: outDirs.client, + serverOutputDirectory: outDirs.server, + }) + + const { startConfig, resolvedStartConfig } = configContext.getConfig() + const routerBasepath = applyResolvedRouterBasepath({ + resolvedStartConfig, + startConfig, + }) + + const entryPlan = configContext.resolveEntries() + const entryAliases = createBunResolvedEntryAliases({ + entryPaths: entryPlan.entryPaths, + }) + + const serverFnBase = createServerFnBasePath({ + routerBasepath, + serverFnBase: startConfig.serverFns.base, + }) + + const inlineCssEnabled = + mode === 'build' && startConfig.server.build.inlineCss.enabled + + const defineOpts = { + serverFnBase, + routerBasepath, + publicBase: resolvedStartConfig.basePaths.publicBase, + isDev: mode === 'dev', + inlineCssEnabled, + spaEnabled: startConfig.spa?.enabled === true, + disableCsrfMiddlewareWarning: + startConfig.serverFns.disableCsrfMiddlewareWarning === true, + } + + const define = createBunDefine({ + ...defineOpts, + extraDefine: serverEnvDefine, + }) + const clientDefine = createBunDefine({ + ...defineOpts, + extraDefine: clientEnvDefine, + }) + + const serverFnsById: Record = {} + const virtualModules = createBunVirtualModuleStore() + const emittedCss = new Map() + const emittedCssAssets: Array<{ + sourcePath: string + fileName: string + css: string + }> = [] + + const setPluginAdapters = (runtime: 'client' | 'server') => { + virtualModules.set( + VIRTUAL_MODULES.pluginAdapters, + generateSerializationAdaptersModule({ + adapters: corePluginOpts.serializationAdapters, + runtime, + }), + ) + } + setPluginAdapters('server') + + const refreshResolver = () => { + virtualModules.updateServerFnResolver(serverFnsById, { + includeClientReferencedCheck: !corePluginOpts.ssrIsProvider, + }) + } + refreshResolver() + + const routerSession = createBunRouterSession({ + root, + framework: corePluginOpts.framework, + routerConfig: startConfig.router, + prerenderEnabled: startConfig.prerender?.enabled === true, + isProduction: mode === 'build', + }) + await routerSession.generate() + + const compilers = createBunCompilerHosts({ + root, + framework: corePluginOpts.framework, + providerEnvName: corePluginOpts.providerEnvironmentName, + mode, + ssrIsProvider: corePluginOpts.ssrIsProvider, + serverFnsById, + onRegistryChange: refreshResolver, + compilerTransforms: corePluginOpts.compilerTransforms, + serverFnProviderModuleDirectives: + corePluginOpts.serverFnProviderModuleDirectives, + transformJsx: (code, id, env) => + transformFrameworkJsx({ + code, + id, + env, + framework: corePluginOpts.framework, + root, + }), + preprocessCode: (code, id, env) => + routerSession.getCodeSplitterRuntime(env).transformReference(code, id), + }) + + const ctx: BunBuildContext = { + startConfig, + resolvedStartConfig, + entryAliases, + define, + clientDefine, + virtualModules, + compilers, + routerSession, + outDirs, + publicBase: resolvedStartConfig.basePaths.publicBase, + refreshResolver, + setPluginAdapters, + mode, + bunOpts, + emittedCss, + emittedCssAssets, + framework: corePluginOpts.framework, + } + + return { ctx, serverFnsById } + } + + return { + async build(opts) { + const root = opts?.root ?? process.cwd() + const { ctx } = await prepare(root, 'build') + await buildBunClient(ctx) + await copyBunPublicAssets({ + root, + clientOutDir: ctx.outDirs.client, + publicDir: ctx.bunOpts?.publicDir, + }) + await buildBunServer(ctx) + await writeBunHostEntry(ctx.outDirs.server) + + const nitroOpt = ctx.bunOpts?.nitro + let clientOutDirForPostBuild = ctx.outDirs.client + + if (nitroOpt) { + const nitroResult = await runBunNitroBuild({ + root, + clientOutDir: ctx.outDirs.client, + serverEntry: join(ctx.outDirs.server, 'server.js'), + publicBase: ctx.publicBase, + nitro: nitroOpt, + }) + clientOutDirForPostBuild = nitroResult.publicDir + } + + await postBuildWithBun({ + startConfig: ctx.startConfig, + serverOutDir: ctx.outDirs.server, + clientOutDir: clientOutDirForPostBuild, + }) + + const standaloneOpt = ctx.bunOpts?.standalone + if (standaloneOpt) { + if (nitroOpt) { + console.warn( + '[tanstack-start-bun] bun.nitro + bun.standalone: standalone always embeds dist/client (not .output/public).', + ) + } + const result = await runBunStandaloneCompile({ + root, + clientOutDir: ctx.outDirs.client, + serverOutDir: ctx.outDirs.server, + standalone: standaloneOpt, + publicBase: ctx.publicBase, + }) + console.info( + `[tanstack-start-bun] standalone executable → ${result.outfile}`, + ) + } + }, + + async dev(opts) { + const root = opts?.root ?? process.cwd() + const { ctx, serverFnsById } = await prepare(root, 'dev') + + await buildBunClient(ctx) + await copyBunPublicAssets({ + root, + clientOutDir: ctx.outDirs.client, + publicDir: ctx.bunOpts?.publicDir, + }) + await buildBunServer(ctx) + + return createBunDevServer({ + root, + port: opts?.port ?? ctx.bunOpts?.port ?? 3000, + hostname: + opts?.hostname ?? ctx.bunOpts?.hostname ?? '0.0.0.0', + clientOutDir: ctx.outDirs.client, + serverOutDir: ctx.outDirs.server, + publicBase: ctx.publicBase, + framework: corePluginOpts.framework, + clientEntryPath: ctx.entryAliases.client, + aliases: ctx.entryAliases.alias, + define: ctx.clientDefine, + esmDev: true, + emittedCss: ctx.emittedCss, + optimizeDeps: ctx.bunOpts?.optimizeDeps, + transformAppModule: async (code, absPath) => { + const splitter = ctx.routerSession.getCodeSplitterRuntime('client') + let next = + absPath.includes('tsr-split') || absPath.includes('tsr-shared') + ? splitter.transformVirtual(code, absPath) + : splitter.transformReference(code, absPath) + const { detectKindsInCode } = await import( + '../start-compiler/compiler' + ) + const { getTransformCodeFilterForEnv } = await import( + '../start-compiler/config' + ) + const { matchesCodeFilters } = await import( + '../start-compiler/host' + ) + const env = 'client' as const + const filters = getTransformCodeFilterForEnv(env) + if (matchesCodeFilters(next, filters)) { + const kinds = detectKindsInCode(next, env) + if (kinds.size > 0) { + const result = await ctx.compilers.client.compile({ + code: next, + id: absPath, + detectedKinds: kinds, + }) + if (result?.code) { + next = result.code + } + } + } + return rewriteImportMetaHot(next) + }, + rebuild: async (change) => { + Object.keys(serverFnsById).forEach((k) => { + delete serverFnsById[k] + }) + + const scope = rebuildScopeForChange(change.kind) + if (shouldRegenerateRoutes(change.kind)) { + await ctx.routerSession.generate() + } + + // Client-only + ESM middleware: skip full Bun.build; SSE update is enough. + if (scope === 'client') { + return { + scope, + event: 'update' as const, + modules: change.path + ? [`/@fs${change.path.replace(/\\/g, '/')}`] + : undefined, + skipServerReload: true, + } + } + + if (scope === 'both') { + await buildBunClient(ctx) + await buildBunServer(ctx) + } else if (scope === 'server') { + await buildBunServer(ctx) + } + + return { + scope, + event: hmrEventForScope(scope), + modules: change.path + ? [`/@fs${change.path.replace(/\\/g, '/')}`] + : undefined, + } + }, + invalidate: (ids) => ctx.compilers.invalidate(ids), + }) + }, + + async serve(opts) { + const root = opts?.root ?? process.cwd() + const bunOpts = mergeBunCoreOptions( + startPluginOpts.bun, + corePluginOpts.bun, + ) + const outDirs = resolveBunOutputDirectories({ + root, + clientOutDir: bunOpts?.clientOutDir, + serverOutDir: bunOpts?.serverOutDir, + }) + return createBunProdServer({ + clientOutDir: outDirs.client, + serverOutDir: outDirs.server, + port: opts?.port ?? bunOpts?.port ?? 3000, + hostname: opts?.hostname ?? bunOpts?.hostname ?? '0.0.0.0', + }) + }, + } +} diff --git a/packages/start-plugin-core/src/bun/post-build.ts b/packages/start-plugin-core/src/bun/post-build.ts new file mode 100644 index 00000000000..a899a0606f8 --- /dev/null +++ b/packages/start-plugin-core/src/bun/post-build.ts @@ -0,0 +1,71 @@ +import { pathToFileURL } from 'node:url' +import { join } from 'pathe' +import { postBuild } from '../post-build' +import { prerender } from '../prerender' +import type { TanStackStartOutputConfig } from '../schema' + +/** + * Post-build prerender/sitemap for Bun adapter. + * Imports the built server entry and uses its `default.fetch` as the request handler. + * + * When Nitro bridge is enabled, call this **after** Nitro so + * `clientOutDir` / `TSS_CLIENT_OUTPUT_DIR` point at the final public dir + * (e.g. `.output/public`), matching Vite + Nitro (#6940). + */ +export async function postBuildWithBun(opts: { + startConfig: TanStackStartOutputConfig + serverOutDir: string + clientOutDir: string +}): Promise { + const serverEntry = join(opts.serverOutDir, 'server.js') + + await postBuild({ + startConfig: opts.startConfig, + adapter: { + getClientOutputDirectory: () => opts.clientOutDir, + prerender: async (startConfig) => { + const prevPrerendering = process.env.TSS_PRERENDERING + const prevClientOut = process.env.TSS_CLIENT_OUTPUT_DIR + process.env.TSS_PRERENDERING = 'true' + process.env.TSS_CLIENT_OUTPUT_DIR = opts.clientOutDir + try { + const mod = (await import(pathToFileURL(serverEntry).href)) as { + default?: { + fetch?: (req: Request) => Response | Promise + } + } + const fetchHandler = mod.default?.fetch + if (!fetchHandler) { + throw new Error( + `[tanstack-start-bun] Server entry ${serverEntry} missing default.fetch`, + ) + } + + await prerender({ + startConfig, + handler: { + getClientOutputDirectory: () => opts.clientOutDir, + request: async (path, init) => { + const url = path.startsWith('http') + ? path + : `http://localhost${path.startsWith('/') ? path : `/${path}`}` + return fetchHandler(new Request(url, init)) + }, + }, + }) + } finally { + if (prevPrerendering === undefined) { + delete process.env.TSS_PRERENDERING + } else { + process.env.TSS_PRERENDERING = prevPrerendering + } + if (prevClientOut === undefined) { + delete process.env.TSS_CLIENT_OUTPUT_DIR + } else { + process.env.TSS_CLIENT_OUTPUT_DIR = prevClientOut + } + } + }, + }, + }) +} diff --git a/packages/start-plugin-core/src/bun/react-refresh.ts b/packages/start-plugin-core/src/bun/react-refresh.ts new file mode 100644 index 00000000000..00df28e40ee --- /dev/null +++ b/packages/start-plugin-core/src/bun/react-refresh.ts @@ -0,0 +1,148 @@ +/** + * React Refresh helpers for Bun Start Phase 2c. + */ + +import { createRequire } from 'node:module' + +const require = createRequire(import.meta.url) + +let cachedBrowserRefreshEntry: string | null = null + +/** Fallback no-op React Refresh runtime for the browser. */ +function getMinimalReactRefreshShim(): string { + return `export function injectIntoGlobalHook(_global) {} +export function performReactRefresh() {} +window.$RefreshReg$ = () => {}; +window.$RefreshSig$ = () => (type) => type; +window.__vite_plugin_react_preamble_installed__ = true; +const runtime = { injectIntoGlobalHook, performReactRefresh }; +window.__tanstack_refresh_runtime__ = runtime; +export default runtime; +` +} + +/** + * @deprecated Prefer getReactRefreshBrowserEntry(); kept for callers that need sync. + */ +export function getReactRefreshRuntimeSource(): string { + return getMinimalReactRefreshShim() +} + +/** + * Browser-safe React Refresh runtime (ESM). + * Bundles CJS `react-refresh/runtime` via Bun.build — never serve raw CJS + * (`module is not defined` in the browser). + */ +export async function getReactRefreshBrowserEntry(): Promise { + if (cachedBrowserRefreshEntry) { + return cachedBrowserRefreshEntry + } + + try { + const runtimePath = require.resolve('react-refresh/runtime') + const built = await Bun.build({ + entrypoints: [runtimePath], + target: 'browser', + format: 'esm', + write: false, + } as never) + if (!built.success || !built.outputs[0]) { + cachedBrowserRefreshEntry = getMinimalReactRefreshShim() + return cachedBrowserRefreshEntry + } + + const bundled = await built.outputs[0].text() + cachedBrowserRefreshEntry = wrapBundledRefreshRuntime(bundled) + return cachedBrowserRefreshEntry + } catch { + cachedBrowserRefreshEntry = getMinimalReactRefreshShim() + return cachedBrowserRefreshEntry + } +} + +/** Wrap a bundled react-refresh/runtime for browser globals. */ +function wrapBundledRefreshRuntime(bundledEsm: string): string { + // Bun CJS→ESM typically ends with `export default require_xxx();` + const rewritten = bundledEsm.replace( + /export\s+default\s+([^;]+);?\s*$/, + 'const __RefreshRuntime = $1;', + ) + if (rewritten === bundledEsm) { + return getMinimalReactRefreshShim() + } + return `${rewritten} +if (typeof __RefreshRuntime?.injectIntoGlobalHook === 'function') { + __RefreshRuntime.injectIntoGlobalHook(window); +} +window.$RefreshReg$ = (type, id) => { + __RefreshRuntime.register(type, id); +}; +window.$RefreshSig$ = () => + __RefreshRuntime.createSignatureFunctionForTransform(); +window.__vite_plugin_react_preamble_installed__ = true; +window.__tanstack_refresh_runtime__ = __RefreshRuntime; +export default __RefreshRuntime; +export const injectIntoGlobalHook = (...args) => + __RefreshRuntime.injectIntoGlobalHook?.(...args); +export const performReactRefresh = (...args) => + __RefreshRuntime.performReactRefresh?.(...args); +` +} + +/** HTML preamble that installs React Refresh globals. */ +export function getReactRefreshPreambleHtml( + refreshModulePath: string, +): string { + return `` +} + +/** + * Apply react-refresh/babel when available. Falls back to identity. + */ +export async function applyReactRefreshBabel( + code: string, + filename: string, +): Promise { + try { + const babel = await import('@babel/core') + let refreshPlugin: unknown + try { + refreshPlugin = require('react-refresh/babel') + } catch { + return code + } + + const result = babel.transformSync(code, { + filename, + babelrc: false, + configFile: false, + plugins: [ + [ + refreshPlugin, + { + skipEnvCheck: true, + }, + ], + ], + // Already transpiled by Bun.Transpiler; parse as plain JS/JSX + sourceType: 'module', + parserOpts: { + sourceType: 'module', + plugins: ['jsx'], + }, + }) + + return result?.code ?? code + } catch { + return code + } +} diff --git a/packages/start-plugin-core/src/bun/schema.ts b/packages/start-plugin-core/src/bun/schema.ts new file mode 100644 index 00000000000..ff640ac2828 --- /dev/null +++ b/packages/start-plugin-core/src/bun/schema.ts @@ -0,0 +1,99 @@ +import { z } from 'zod' +import { + parseStartConfig as parseCoreStartConfig, + tanstackStartOptionsObjectSchema, +} from '../schema' +import type { CompileStartFrameworkOptions } from '../types' +import type { InlineCssInputOptions } from '../schema' +import type { BunCssOptions, BunCoreOptions } from './types' + +export const tanstackStartBunOptionsSchema = tanstackStartOptionsObjectSchema + .extend({ + bun: z + .object({ + clientOutDir: z.string().optional(), + serverOutDir: z.string().optional(), + publicBase: z.string().optional(), + publicDir: z.string().optional(), + port: z.number().int().positive().optional(), + hostname: z.string().optional(), + minify: z.boolean().optional(), + // Plugins / css.transform are runtime-only; keep schema permissive + plugins: z.array(z.any()).optional(), + clientPlugins: z.array(z.any()).optional(), + serverPlugins: z.array(z.any()).optional(), + css: z + .object({ + tailwind: z.union([z.boolean(), z.literal('auto')]).optional(), + transform: z.any().optional(), + content: z.array(z.string()).optional(), + postcss: z + .union([ + z.literal(false), + z.object({ + plugins: z.array(z.any()).optional(), + }), + ]) + .optional(), + modules: z.boolean().optional(), + }) + .optional(), + optimizeDeps: z + .union([ + z.literal(false), + z.object({ + include: z.array(z.string()).optional(), + exclude: z.array(z.string()).optional(), + disabled: z.boolean().optional(), + force: z.boolean().optional(), + }), + ]) + .optional(), + nitro: z + .union([ + z.literal(false), + z.object({ + preset: z.string().optional(), + config: z.record(z.string(), z.any()).optional(), + }), + ]) + .optional(), + standalone: z + .union([ + z.literal(false), + z.object({ + outfile: z.string().optional(), + target: z.string().optional(), + compile: z.record(z.string(), z.any()).optional(), + }), + ]) + .optional(), + }) + .optional(), + }) + .optional() + .prefault({}) + +/** Parse and validate TanStack Start Bun input config. */ +export function parseStartConfig( + opts: z.input, + corePluginOpts: { framework: CompileStartFrameworkOptions }, + root: string, +) { + tanstackStartBunOptionsSchema.parse(opts) + const { bun: _bun, ...coreOptions } = opts ?? {} + return parseCoreStartConfig(coreOptions, corePluginOpts, root) +} + +export type TanStackStartBunInputConfig = z.input< + typeof tanstackStartBunOptionsSchema +> & { + bun?: BunCoreOptions & { + css?: BunCssOptions + } + server?: { + build?: { + inlineCss?: InlineCssInputOptions + } + } +} diff --git a/packages/start-plugin-core/src/bun/solid-server-alias.ts b/packages/start-plugin-core/src/bun/solid-server-alias.ts new file mode 100644 index 00000000000..be98d847028 --- /dev/null +++ b/packages/start-plugin-core/src/bun/solid-server-alias.ts @@ -0,0 +1,48 @@ +import { createRequire } from 'node:module' +import { join } from 'pathe' +import type { BunPlugin } from 'bun' + +/** + * Force solid-js / solid-js/web to their Node SSR builds during Bun server + * bundling without enabling the package `solid` export condition (which would + * pull @tanstack/solid-router source JSX). + */ +export function createSolidServerAliasPlugin(opts: { + root: string +}): BunPlugin { + const requireFromRoot = createRequire(join(opts.root, 'package.json')) + + const resolveServer = (id: string): string | null => { + try { + if (id === 'solid-js') { + return requireFromRoot.resolve('solid-js/dist/server.js') + } + if (id === 'solid-js/web') { + return requireFromRoot.resolve('solid-js/web/dist/server.js') + } + if (id === 'solid-js/store') { + return requireFromRoot.resolve('solid-js/store/dist/server.js') + } + } catch { + try { + return requireFromRoot.resolve(id) + } catch { + return null + } + } + return null + } + + return { + name: 'tanstack-start-bun:solid-server-alias', + setup(build) { + build.onResolve({ filter: /^solid-js(\/|$)/ }, (args) => { + const resolved = resolveServer(args.path) + if (!resolved) { + return undefined + } + return { path: resolved } + }) + }, + } +} diff --git a/packages/start-plugin-core/src/bun/standalone-compile.ts b/packages/start-plugin-core/src/bun/standalone-compile.ts new file mode 100644 index 00000000000..69c8eb133fc --- /dev/null +++ b/packages/start-plugin-core/src/bun/standalone-compile.ts @@ -0,0 +1,221 @@ +import { mkdir, writeFile } from 'node:fs/promises' +import { join, relative, dirname, isAbsolute } from 'pathe' +import { glob } from 'tinyglobby' +import { listenBannerRuntimeJs } from './listen-urls' +import type { BunStandaloneOptions } from './types' + +export interface BunStandaloneCompileResult { + outfile: string +} + +/** Default standalone executable path under server out dir. */ +function defaultOutfile(serverOutDir: string): string { + const base = join(serverOutDir, 'start') + if (process.platform === 'win32') { + return `${base}.exe` + } + return base +} + +/** Resolve the standalone compile output path (incl. `.exe`). */ +function resolveOutfile( + root: string, + serverOutDir: string, + standalone: BunStandaloneOptions, +): string { + const raw = standalone.outfile ?? defaultOutfile(serverOutDir) + const abs = isAbsolute(raw) ? raw : join(root, raw) + if ( + process.platform === 'win32' && + !abs.toLowerCase().endsWith('.exe') && + !standalone.target + ) { + return `${abs}.exe` + } + if ( + typeof standalone.target === 'string' && + standalone.target.startsWith('windows') && + !abs.toLowerCase().endsWith('.exe') + ) { + return `${abs}.exe` + } + return abs +} + +/** Relative import specifier from the standalone entry to an asset. */ +function toImportSpecifier(fromFile: string, assetAbs: string): string { + let rel = relative(dirname(fromFile), assetAbs) + if (!rel.startsWith('.')) { + rel = `./${rel}` + } + // Bun on Windows accepts / in import paths + return rel.replace(/\\/g, '/') +} + +/** Public URL path for an embedded client asset (honors publicBase). */ +function publicUrlPath( + clientOutDir: string, + assetAbs: string, + publicBase: string, +): string { + const rel = relative(clientOutDir, assetAbs).replace(/\\/g, '/') + const base = + !publicBase || publicBase === '/' + ? '' + : publicBase.replace(/\/$/, '') + return `${base}/${rel}` +} + +/** @internal exported for unit tests */ +export function buildStandaloneEntrySource(opts: { + assetFiles: Array + clientOutDir: string + entryPath: string + publicBase?: string +}): string { + const publicBase = opts.publicBase ?? '/' + const importLines: Array = [ + `import { networkInterfaces } from 'node:os'`, + `import * as handler from ${JSON.stringify('./server.js')}`, + ] + const mapEntries: Array = [] + + for (let i = 0; i < opts.assetFiles.length; i++) { + const abs = opts.assetFiles[i]! + const spec = toImportSpecifier(opts.entryPath, abs) + const id = `asset_${i}` + importLines.push( + `import ${id} from ${JSON.stringify(spec)} with { type: "file" }`, + ) + const urlPath = publicUrlPath(opts.clientOutDir, abs, publicBase) + mapEntries.push(` [${JSON.stringify(urlPath)}, ${id}]`) + } + + return `${importLines.join('\n')} + +const assets = new Map([ +${mapEntries.join(',\n')} +]) + +function resolveEmbedded(pathname) { + if (assets.has(pathname)) { + return assets.get(pathname) + } + if (pathname.endsWith('/')) { + const withIndex = pathname + 'index.html' + if (assets.has(withIndex)) { + return assets.get(withIndex) + } + } + if (!pathname.includes('.') && assets.has(pathname + '.html')) { + return assets.get(pathname + '.html') + } + if (pathname === '/' && assets.has('/index.html')) { + return assets.get('/index.html') + } + return null +} + +function resolveFetchHandler(mod) { + const candidates = [mod?.default, mod?.default?.default, mod] + for (const candidate of candidates) { + if (candidate && typeof candidate.fetch === 'function') { + return (req) => candidate.fetch(req) + } + } + throw new Error( + '[tanstack-start-bun] standalone: server entry missing default.fetch', + ) +} + +const fetchHandler = resolveFetchHandler(handler) + +${listenBannerRuntimeJs()} + +const port = Number(process.env.PORT ?? 3000) +const hostname = process.env.HOST ?? '0.0.0.0' + +const server = Bun.serve({ + port, + hostname, + async fetch(req) { + const url = new URL(req.url) + const embedded = resolveEmbedded(url.pathname) + if (embedded) { + // import with { type: "file" } yields a bunfs path string + return new Response(Bun.file(embedded)) + } + return fetchHandler(req) + }, +}) + +console.info(formatListenBanner('[tanstack-start-bun] standalone', hostname, Number(server.port))) +` +} + +/** + * After dual Bun.build + post-build, optionally `Bun.build({ compile })` + * a single executable that embeds `dist/client` and serves via server.js. + */ +export async function runBunStandaloneCompile(opts: { + root: string + clientOutDir: string + serverOutDir: string + standalone: BunStandaloneOptions + publicBase?: string +}): Promise { + const outfile = resolveOutfile( + opts.root, + opts.serverOutDir, + opts.standalone, + ) + await mkdir(dirname(outfile), { recursive: true }) + + const assetFiles = ( + await glob(['**/*'], { + cwd: opts.clientOutDir, + absolute: true, + onlyFiles: true, + dot: false, + }) + ).sort() + + const entryPath = join(opts.serverOutDir, '.standalone-entry.js') + const entrySource = buildStandaloneEntrySource({ + assetFiles, + clientOutDir: opts.clientOutDir, + entryPath, + publicBase: opts.publicBase, + }) + + await writeFile(entryPath, entrySource, 'utf8') + + const compileOpt: Record = { + ...(opts.standalone.compile ?? {}), + outfile, + } + if (opts.standalone.target != null) { + compileOpt.target = opts.standalone.target + } + + const result = await Bun.build({ + entrypoints: [entryPath], + target: 'bun', + format: 'esm', + packages: 'bundle', + sourcemap: 'none', + // outfile must live under `compile` (top-level outfile is ignored when compiling) + compile: compileOpt, + } as import('bun').BuildConfig) + + if (!result.success) { + const message = result.logs.map(String).join('\n') + throw new Error( + `[tanstack-start-bun] bun.standalone compile failed (see Bun --compile limits for native addons / dynamic requires):\n${message}`, + ) + } + + const written = + result.outputs.find((o) => o.kind === 'entry-point')?.path ?? outfile + return { outfile: written } +} diff --git a/packages/start-plugin-core/src/bun/start-compiler-host.ts b/packages/start-plugin-core/src/bun/start-compiler-host.ts new file mode 100644 index 00000000000..42841794681 --- /dev/null +++ b/packages/start-plugin-core/src/bun/start-compiler-host.ts @@ -0,0 +1,397 @@ +import { readFile } from 'node:fs/promises' +import { resolve as resolvePath } from 'pathe' +import { + createStartCompiler, + mergeServerFnsById, + matchesCodeFilters, + createCompilerVirtualModuleIdPattern, + loadCompilerVirtualModule, +} from '../start-compiler/host' +import { detectKindsInCode } from '../start-compiler/compiler' +import { getTransformCodeFilterForEnv } from '../start-compiler/config' +import { createHydrateCompilerPlugin } from '../hydrate-when-transform' +import { TRANSFORM_ID_REGEX } from '../constants' +import { tssHydrate } from '../hydration-constants' +import type { StartCompiler } from '../start-compiler/compiler' +import type { ServerFn } from '../start-compiler/types' +import type { + CompileStartFrameworkOptions, + StartCompilerImportTransform, + StartCompilerPlugin, +} from '../types' +import type { BunPlugin } from 'bun' + +export interface BunCompilerHostOptions { + root: string + framework: CompileStartFrameworkOptions + providerEnvName: string + mode: 'dev' | 'build' + ssrIsProvider: boolean + serverFnsById: Record + /** Called after registry mutations so virtual modules can refresh */ + onRegistryChange?: () => void + compilerTransforms?: Array | undefined + compilerPlugins?: Array | undefined + serverFnProviderModuleDirectives?: ReadonlyArray | undefined + /** + * Optional preprocess (e.g. route code-splitter reference transform). + * Runs before StartCompiler so both share a single Bun onLoad. + */ + preprocessCode?: ( + code: string, + id: string, + env: 'client' | 'server', + ) => string | Promise + /** + * Framework JSX transform (Solid/Vue). Runs before preprocessCode. + */ + transformJsx?: ( + code: string, + id: string, + env: 'client' | 'server', + ) => string | Promise +} + +export interface BunCompilerHosts { + client: StartCompiler + server: StartCompiler + createTransformPlugin: (env: 'client' | 'server') => BunPlugin + invalidate: (ids: Iterable) => void +} + +/** Strip `?query` from a module id for stable path comparison. */ +function stripModulePath(id: string): string { + const queryIndex = id.indexOf('?') + return queryIndex >= 0 ? id.slice(0, queryIndex) : id +} + +/** Whether a module id should run through the Start compiler. */ +function shouldTransformId( + id: string, + opts: { forceJsx?: boolean }, +): boolean { + if (opts.forceJsx && /\.[cm]?[jt]sx$/.test(id.split('?')[0] ?? '')) { + // Include app sources and Solid "solid" condition package sources. + if ( + id.includes('node_modules') && + !id.includes('@tanstack/solid-') && + !id.includes('@solidjs/') && + !id.includes('/solid-js/') + ) { + return false + } + return true + } + if (id.includes('node_modules')) { + return false + } + return TRANSFORM_ID_REGEX.some((re) => re.test(id)) +} + +/** Create client/server StartCompiler hosts for Bun plugins. */ +export function createBunCompilerHosts( + opts: BunCompilerHostOptions, +): BunCompilerHosts { + const sharedResolve = async (id: string, importer?: string) => { + try { + const resolved = await Bun.resolve(id, importer ?? opts.root) + return resolved + } catch { + try { + return resolvePath( + importer ? resolvePath(importer, '..') : opts.root, + id, + ) + } catch { + return null + } + } + } + + const compilerPlugins = [ + createHydrateCompilerPlugin(), + ...(opts.compilerPlugins ?? []), + ] + const hydrateVirtualPattern = + createCompilerVirtualModuleIdPattern(compilerPlugins) + + const makeCompiler = (env: 'client' | 'server', envName: string) => { + // loadModule must ingest into the same compiler instance (Vite does this too). + let compiler!: StartCompiler + compiler = createStartCompiler({ + env, + envName, + root: opts.root, + framework: opts.framework, + providerEnvName: opts.providerEnvName, + mode: opts.mode, + getKnownServerFns: () => opts.serverFnsById, + onServerFnsById: (discovered) => { + mergeServerFnsById(opts.serverFnsById, discovered) + opts.onRegistryChange?.() + }, + loadModule: async (id: string) => { + const filePath = id.includes('?') ? id.slice(0, id.indexOf('?')) : id + try { + const code = await readFile(filePath, 'utf8') + compiler.ingestModule({ code, id }) + } catch { + // ignore missing during graph crawl + } + }, + resolveId: sharedResolve, + encodeModuleSpecifierInDev: + opts.mode === 'dev' + ? ({ extractedFilename }) => + Buffer.from(extractedFilename, 'utf8').toString('base64url') + : undefined, + compilerTransforms: opts.compilerTransforms, + compilerPlugins, + serverFnProviderModuleDirectives: opts.serverFnProviderModuleDirectives, + }) + return compiler + } + + const client = makeCompiler('client', 'client') + const server = makeCompiler('server', 'ssr') + + const createTransformPlugin = (env: 'client' | 'server'): BunPlugin => { + const compiler = env === 'client' ? client : server + const codeFilter = getTransformCodeFilterForEnv(env, { + compilerTransforms: opts.compilerTransforms, + compilerPlugins, + }) + + return { + name: `tanstack-start-compiler:${env}`, + setup(build) { + const resolveServerFnSplit = (args: { path: string }) => { + if (!args.path.includes('tss-serverfn-split')) { + return undefined + } + const q = args.path.indexOf('?') + const filePath = q >= 0 ? args.path.slice(0, q) : args.path + return { + path: `${filePath}?tss-serverfn-split`, + namespace: 'tanstack-serverfn', + } + } + + const resolveHydrateVirtual = (args: { path: string }) => { + if (!args.path.includes(tssHydrate)) { + return undefined + } + return { + path: args.path, + namespace: 'tanstack-hydrate', + } + } + + build.onResolve( + { filter: /tss-serverfn-split/ }, + resolveServerFnSplit, + ) + build.onResolve( + { filter: /^\// }, + (args) => + args.path.includes('tss-serverfn-split') + ? resolveServerFnSplit(args) + : args.path.includes(tssHydrate) + ? resolveHydrateVirtual(args) + : undefined, + ) + build.onResolve( + { filter: new RegExp(tssHydrate) }, + resolveHydrateVirtual, + ) + + build.onLoad( + { filter: /.*/, namespace: 'tanstack-serverfn' }, + async (args) => { + const filePath = args.path.includes('?') + ? args.path.slice(0, args.path.indexOf('?')) + : args.path + const code = await readFile(filePath, 'utf8') + const detectedKinds = detectKindsInCode(code, env) + const result = await compiler.compile({ + code, + id: args.path, + detectedKinds, + }) + if (!result) { + return { + contents: code, + loader: filePath.endsWith('x') ? 'tsx' : 'ts', + } + } + return { + contents: result.code, + loader: filePath.endsWith('x') ? 'tsx' : 'ts', + } + }, + ) + + build.onLoad( + { filter: /.*/, namespace: 'tanstack-hydrate' }, + async (args) => { + const filePath = args.path.includes('?') + ? args.path.slice(0, args.path.indexOf('?')) + : args.path + let sourceCode: string | undefined + try { + sourceCode = await readFile(filePath, 'utf8') + } catch { + sourceCode = undefined + } + const loaded = loadCompilerVirtualModule(compilerPlugins, { + id: args.path, + root: opts.root, + env, + envName: env === 'client' ? 'client' : 'ssr', + code: sourceCode, + }) + if (!loaded) { + throw new Error( + `[tanstack-start-bun] Failed to load hydrate virtual module: ${args.path}`, + ) + } + return { + contents: loaded.code, + loader: filePath.endsWith('x') ? 'tsx' : 'ts', + } + }, + ) + + build.onLoad({ filter: /\.[cm]?[jt]sx?$/ }, async (args) => { + if ( + args.namespace === 'tanstack-serverfn' || + args.namespace === 'tanstack-hydrate' + ) { + return undefined + } + if ( + !shouldTransformId(args.path, { + forceJsx: !!opts.transformJsx, + }) + ) { + return undefined + } + + if (hydrateVirtualPattern && hydrateVirtualPattern.test(args.path)) { + hydrateVirtualPattern.lastIndex = 0 + const filePath = args.path.includes('?') + ? args.path.slice(0, args.path.indexOf('?')) + : args.path + let sourceCode: string | undefined + try { + sourceCode = await readFile(filePath, 'utf8') + } catch { + sourceCode = undefined + } + const loaded = loadCompilerVirtualModule(compilerPlugins, { + id: args.path, + root: opts.root, + env, + envName: env === 'client' ? 'client' : 'ssr', + code: sourceCode, + }) + if (loaded) { + return { + contents: loaded.code, + loader: filePath.endsWith('x') ? 'tsx' : 'ts', + } + } + } + + let code = await readFile(args.path, 'utf8') + const originalCode = code + let jsxTransformed = false + if (opts.transformJsx) { + const jsxResult = await opts.transformJsx(code, args.path, env) + if (jsxResult) { + code = jsxResult + jsxTransformed = true + } + } + if (opts.preprocessCode) { + code = await opts.preprocessCode(code, args.path, env) + } + const preprocessed = code !== originalCode + const loader = jsxTransformed + ? 'js' + : args.path.endsWith('x') + ? 'tsx' + : 'ts' + + const needsStartCompile = + matchesCodeFilters(code, codeFilter) && + detectKindsInCode(code, env).size > 0 + + if (!needsStartCompile) { + if (preprocessed) { + return { + contents: code, + loader, + } + } + return undefined + } + + const detectedKinds = detectKindsInCode(code, env) + const result = await compiler.compile({ + code, + id: args.path, + detectedKinds, + }) + + if (!result) { + if (preprocessed) { + return { + contents: code, + loader, + } + } + return undefined + } + + return { + contents: result.code, + loader, + } + }) + }, + } + } + + return { + client, + server, + createTransformPlugin, + invalidate(ids) { + client.invalidateModules(ids) + server.invalidateModules(ids) + for (const id of ids) { + for (const plugin of compilerPlugins) { + plugin.invalidateModule?.({ + id, + envName: 'client', + }) + plugin.invalidateModule?.({ + id, + envName: 'ssr', + }) + } + for (const [fnId, fn] of Object.entries(opts.serverFnsById)) { + const idPath = stripModulePath(id) + if ( + stripModulePath(fn.filename) === idPath || + stripModulePath(fn.extractedFilename) === idPath + ) { + delete opts.serverFnsById[fnId] + } + } + } + opts.onRegistryChange?.() + }, + } +} diff --git a/packages/start-plugin-core/src/bun/start-router-plugin.ts b/packages/start-plugin-core/src/bun/start-router-plugin.ts new file mode 100644 index 00000000000..6f6abce6cdb --- /dev/null +++ b/packages/start-plugin-core/src/bun/start-router-plugin.ts @@ -0,0 +1,136 @@ +import { Generator } from '@tanstack/router-generator' +import { getConfig } from '@tanstack/router-plugin' +import { createBunRouterCodeSplitterRuntime } from '@tanstack/router-plugin/bun' +import { createRouterPluginContext } from '@tanstack/router-plugin/context' +import { routesManifestPlugin } from '../start-router-plugin/generator-plugins/routes-manifest-plugin' +import { prerenderRoutesPlugin } from '../start-router-plugin/generator-plugins/prerender-routes-plugin' +import type { Config, RouterPluginContext } from '@tanstack/router-plugin' +import type { BunCodeSplitterRuntime } from '@tanstack/router-plugin/bun' +import type { CompileStartFrameworkOptions } from '../types' +import type { BunPlugin } from 'bun' + +export interface BunRouterSession { + context: RouterPluginContext + generate: () => Promise + /** Virtual-module Bun plugin (`tsr-split` / `tsr-shared`). */ + createCodeSplitterPlugin: (env: 'client' | 'server') => BunPlugin + /** + * Reference-route transform to run inside the StartCompiler onLoad + * (Bun allows only one successful onLoad per module). + */ + getCodeSplitterRuntime: (env: 'client' | 'server') => BunCodeSplitterRuntime +} + +/** + * Shared Generator + code-splitter session for the Start Bun adapter. + * Generator populates `context.routesByFile` consumed by the Bun code-splitter. + */ +export function createBunRouterSession(opts: { + root: string + framework: CompileStartFrameworkOptions + routerConfig?: Partial + prerenderEnabled?: boolean + isProduction: boolean +}): BunRouterSession { + const context = createRouterPluginContext() + const runtimes = new Map<'client' | 'server', BunCodeSplitterRuntime>() + + const generate = async () => { + const config = getConfig( + { + ...opts.routerConfig, + // Bun previously always ran the splitter; default on for React parity. + // Solid/Vue examples may set `autoCodeSplitting: false` until virtual + // split modules also run framework JSX transforms. + autoCodeSplitting: opts.routerConfig?.autoCodeSplitting ?? true, + target: opts.framework, + plugins: [ + routesManifestPlugin(), + ...(opts.prerenderEnabled === true ? [prerenderRoutesPlugin()] : []), + ...((opts.routerConfig?.plugins as Array | undefined) ?? + []), + ], + } as Partial, + opts.root, + ) + const generator = new Generator({ + config, + root: opts.root, + }) + await generator.run() + context.routesByFile = generator.getRoutesByFileMap() + return generator + } + + const getCodeSplitterRuntime = ( + env: 'client' | 'server', + ): BunCodeSplitterRuntime => { + const cached = runtimes.get(env) + if (cached) { + return cached + } + const isClient = env === 'client' + const runtime = createBunRouterCodeSplitterRuntime(context, { + root: opts.root, + isProduction: opts.isProduction, + config: () => + getConfig( + { + ...opts.routerConfig, + autoCodeSplitting: opts.routerConfig?.autoCodeSplitting ?? true, + target: opts.framework, + codeSplittingOptions: { + ...opts.routerConfig?.codeSplittingOptions, + deleteNodes: isClient + ? [ + ...new Set([ + ...(opts.routerConfig?.codeSplittingOptions + ?.deleteNodes ?? []), + 'ssr', + 'server', + 'headers', + ]), + ] + : opts.routerConfig?.codeSplittingOptions?.deleteNodes, + addHmr: + opts.routerConfig?.codeSplittingOptions?.addHmr ?? + (isClient && !opts.isProduction), + }, + plugin: { + ...opts.routerConfig?.plugin, + hmr: { + style: 'vite', + ...opts.routerConfig?.plugin?.hmr, + }, + }, + } as Partial, + opts.root, + ), + }) + runtimes.set(env, runtime) + return runtime + } + + return { + context, + generate, + getCodeSplitterRuntime, + createCodeSplitterPlugin: (env) => getCodeSplitterRuntime(env).plugin, + } +} + +/** + * @deprecated Prefer {@link createBunRouterSession}. + */ +export async function runBunRouterGenerator(opts: { + root: string + routerConfig?: Partial +}): Promise { + const session = createBunRouterSession({ + root: opts.root, + framework: 'react', + routerConfig: opts.routerConfig, + isProduction: true, + }) + return session.generate() +} diff --git a/packages/start-plugin-core/src/bun/static-host.ts b/packages/start-plugin-core/src/bun/static-host.ts new file mode 100644 index 00000000000..ae51ba1bd9c --- /dev/null +++ b/packages/start-plugin-core/src/bun/static-host.ts @@ -0,0 +1,194 @@ +import { join } from 'pathe' +import { formatListenBanner, listenBannerRuntimeJs } from './listen-urls' + +export type BunFetchHandler = (req: Request) => Response | Promise + +/** + * Map a request pathname to a file under the client output directory. + * Returns null when the path should fall through to the SSR handler. + */ +export function resolveClientAssetPath( + clientOutDir: string, + pathname: string, +): string | null { + let decoded: string + try { + decoded = decodeURIComponent(pathname) + } catch { + return null + } + if (!decoded.startsWith('/assets/') && !/\.\w+$/.test(decoded)) { + return null + } + // Prevent path traversal + const relative = decoded.replace(/^\//, '') + if (relative.includes('..')) { + return null + } + return join(clientOutDir, relative) +} + +/** + * Try to serve a static file from clientOutDir; otherwise null. + */ +export async function tryServeClientAsset( + clientOutDir: string, + pathname: string, +): Promise { + const assetPath = resolveClientAssetPath(clientOutDir, pathname) + if (!assetPath) { + return null + } + const file = Bun.file(assetPath) + if (!(await file.exists())) { + return null + } + return new Response(file) +} + +export interface StaticThenFetchOptions { + clientOutDir: string + fetch: BunFetchHandler +} + +/** + * Production / shared fetch: static assets first, then SSR handler. + */ +export function createStaticThenFetch( + opts: StaticThenFetchOptions, +): BunFetchHandler { + return async (req) => { + const url = new URL(req.url) + const staticResponse = await tryServeClientAsset( + opts.clientOutDir, + url.pathname, + ) + if (staticResponse) { + return staticResponse + } + return opts.fetch(req) + } +} + +export interface BunProdServeOptions { + clientOutDir: string + serverOutDir: string + port?: number + hostname?: string +} + +/** + * Production Bun.serve wrapping dist/server/server.js + dist/client. + */ +export async function createBunProdServer(opts: BunProdServeOptions): Promise<{ + stop: () => void + port: number + hostname: string +}> { + const { pathToFileURL } = await import('node:url') + const serverEntry = join(opts.serverOutDir, 'server.js') + const handlerModule = (await import(pathToFileURL(serverEntry).href)) as { + default?: { fetch?: BunFetchHandler } + } + const handlerFetch = handlerModule.default?.fetch + if (!handlerFetch) { + throw new Error( + `[tanstack-start-bun] Server entry ${serverEntry} missing default.fetch`, + ) + } + const fetch = createStaticThenFetch({ + clientOutDir: opts.clientOutDir, + fetch: (req) => handlerFetch(req), + }) + + const hostname = opts.hostname ?? '0.0.0.0' + const server = Bun.serve({ + port: opts.port ?? 3000, + hostname, + fetch, + }) + + console.info( + formatListenBanner({ + headline: '[tanstack-start-bun] serve', + hostname, + port: Number(server.port), + }), + ) + + return { + port: Number(server.port), + hostname, + stop() { + server.stop(true) + }, + } +} + +/** + * Source for dist/server/host.js written at build time. + * Resolves client dir relative to this file (../client). + */ +export function generateHostEntrySource(): string { + return `/** + * Generated by @tanstack/start-plugin-core/bun — production host. + * Serves dist/client static assets, then delegates to ./server.js. + */ +import { join, dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { networkInterfaces } from 'node:os' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const clientOutDir = join(__dirname, '../client') +const handler = await import('./server.js') + +${listenBannerRuntimeJs()} + +function resolveClientAssetPath(pathname) { + let decoded + try { + decoded = decodeURIComponent(pathname) + } catch { + return null + } + if (!decoded.startsWith('/assets/') && !/\\.\\w+$/.test(decoded)) { + return null + } + const relative = decoded.replace(/^\\//, '') + if (relative.includes('..')) { + return null + } + return join(clientOutDir, relative) +} + +async function tryServeClientAsset(pathname) { + const assetPath = resolveClientAssetPath(pathname) + if (!assetPath) { + return null + } + const file = Bun.file(assetPath) + if (!(await file.exists())) { + return null + } + return new Response(file) +} + +const port = Number(process.env.PORT ?? 3000) +const hostname = process.env.HOST ?? '0.0.0.0' + +const server = Bun.serve({ + port, + hostname, + async fetch(req) { + const url = new URL(req.url) + const staticResponse = await tryServeClientAsset(url.pathname) + if (staticResponse) { + return staticResponse + } + return handler.default.fetch(req) + }, +}) + +console.info(formatListenBanner('[tanstack-start-bun] host', hostname, Number(server.port))) +` +} diff --git a/packages/start-plugin-core/src/bun/tailwindcss-node-shim.d.ts b/packages/start-plugin-core/src/bun/tailwindcss-node-shim.d.ts new file mode 100644 index 00000000000..03f3a3bd951 --- /dev/null +++ b/packages/start-plugin-core/src/bun/tailwindcss-node-shim.d.ts @@ -0,0 +1,13 @@ +/** Optional peer — present only when the app installs Tailwind. */ +declare module '@tailwindcss/node' { + export function compile( + css: string, + options: { + base: string + from?: string + onDependency: (path: string) => void + }, + ): Promise<{ + build: (candidates: Array) => string + }> +} diff --git a/packages/start-plugin-core/src/bun/types.ts b/packages/start-plugin-core/src/bun/types.ts new file mode 100644 index 00000000000..7111a391db9 --- /dev/null +++ b/packages/start-plugin-core/src/bun/types.ts @@ -0,0 +1,133 @@ +import type { TanStackStartCoreOptions } from '../types' + +export interface BunCssOptions { + /** + * Tailwind v4 via optional peer `@tailwindcss/node`. + * - `'auto'` (default): enable when CSS references tailwindcss and the peer resolves + * - `true` / `false`: force on/off + */ + tailwind?: boolean | 'auto' | undefined + /** Custom CSS transform; runs before Tailwind when both are set. */ + transform?: + | ((css: string, ctx: { id: string }) => string | Promise) + | undefined + /** Globs for Tailwind class scanning (default under src/). */ + content?: Array | undefined + /** + * Optional PostCSS processor (peer `postcss`). + * When set, runs after `transform` and before Tailwind. + */ + postcss?: + | { + plugins?: Array + } + | false + | undefined + /** Enable CSS Modules for `*.module.css` (default: true). */ + modules?: boolean | undefined +} + +/** + * Optional post-build Nitro 3 bridge (production only). + * `false` / omitted → `dist/*` + `host.js` only (Rsbuild-style). + */ +export interface BunNitroOptions { + /** Nitro preset (e.g. `node-server`, `bun`, `vercel`). Default: `node-server`. */ + preset?: string | undefined + /** + * Pass-through NitroConfig subset (baseURL, routeRules, hooks, output, …). + * Start still injects `publicAssets` + `serverEntry` web handler for `server.js`. + */ + config?: Record | undefined +} + +/** + * Optional Bun `--compile` standalone executable (production only). + * Embeds `dist/client` + `server.js` into a single binary for the target OS/arch. + */ +export interface BunStandaloneOptions { + /** Output path (default: `dist/server/start`, `.exe` on Windows). */ + outfile?: string | undefined + /** + * Cross-compile target (e.g. `linux-x64`, `darwin-arm64`). + * Omitted → current platform (`compile: true`). + */ + target?: string | undefined + /** Pass-through Bun `CompileBuildOptions` subset (windows.*, execArgv, …). */ + compile?: Record | undefined +} + +export interface BunCoreOptions { + /** Client output subdirectory under root (default: dist/client) */ + clientOutDir?: string | undefined + /** Server output subdirectory under root (default: dist/server) */ + serverOutDir?: string | undefined + /** Public asset base path (default: /) */ + publicBase?: string | undefined + /** Public static assets directory (default: `public`, copied into clientOutDir). */ + publicDir?: string | undefined + /** Dev / serve port */ + port?: number | undefined + /** Dev / serve hostname */ + hostname?: string | undefined + /** + * Minify client/server bundles. + * Default: `true` for production `build()`, `false` for `dev()`. + */ + minify?: boolean | undefined + /** + * Extra Bun.build plugins prepended for both client and server builds. + * Use `clientPlugins` / `serverPlugins` for env-specific plugins. + */ + plugins?: Array | undefined + clientPlugins?: Array | undefined + serverPlugins?: Array | undefined + /** CSS asset pipeline (`?url` / side-effect CSS / CSS Modules + optional Tailwind/PostCSS). */ + css?: BunCssOptions | undefined + /** + * Optional Nitro 3 post-build packaging to `.output`. + * Dev still uses `createBunDevServer` (Nitro is production-only in v1). + */ + nitro?: false | BunNitroOptions | undefined + /** + * Optional Bun standalone executable via `Bun.build({ compile })`. + * Always based on `dist/` (not `.output`). Production build only. + */ + standalone?: false | BunStandaloneOptions | undefined + /** + * Vite-like ESM-dev dependency prebundling (`/@deps`). + * Default: enabled (scan `src/` bare imports). Set `false` to disable. + */ + optimizeDeps?: import('./optimize-deps').OptimizeDepsConfig | false | undefined +} + +export type TanStackStartBunPluginCoreOptions = TanStackStartCoreOptions & { + providerEnvironmentName: string + ssrIsProvider: boolean + bun?: BunCoreOptions | undefined +} + +export interface TanStackStartBunAdapter { + /** Production dual Bun.build (client then server) + host.js */ + build: (opts?: { root?: string }) => Promise + /** Integrated Bun.serve development server */ + dev: (opts?: { + root?: string + port?: number + hostname?: string + }) => Promise<{ stop: () => void; port: number; hostname: string }> + /** Production Bun.serve: dist/client static + dist/server/server.js */ + serve: (opts?: { + root?: string + port?: number + hostname?: string + }) => Promise<{ stop: () => void; port: number; hostname: string }> +} + +export const BUN_ENVIRONMENT_NAMES = { + client: 'client', + server: 'ssr', +} as const + +export type BunEnvironmentName = + (typeof BUN_ENVIRONMENT_NAMES)[keyof typeof BUN_ENVIRONMENT_NAMES] diff --git a/packages/start-plugin-core/src/bun/virtual-modules.ts b/packages/start-plugin-core/src/bun/virtual-modules.ts new file mode 100644 index 00000000000..8c82c793532 --- /dev/null +++ b/packages/start-plugin-core/src/bun/virtual-modules.ts @@ -0,0 +1,90 @@ +import { VIRTUAL_MODULES } from '@tanstack/start-server-core/virtual-modules' +import { generateServerFnResolverModule } from '../start-compiler/server-fn-resolver-module' +import { + buildStartManifest, + serializeStartManifest, +} from '../start-manifest-plugin/manifestBuilder' +import type { ServerFn } from '../start-compiler/types' +import type { NormalizedClientBuild } from '../types' +import type { ScriptFormat } from '@tanstack/router-core' + +export { VIRTUAL_MODULES } + +export interface BunVirtualModuleStore { + get: (id: string) => string | undefined + set: (id: string, contents: string) => void + has: (id: string) => boolean + clear: () => void + updateServerFnResolver: ( + serverFnsById: Record, + opts: { includeClientReferencedCheck: boolean }, + ) => void + updateManifest: (opts: { + clientBuild: NormalizedClientBuild + publicBase: string + scriptFormat?: ScriptFormat + inlineCss?: { enabled: boolean; transformAssets: boolean } + }) => void +} + +/** In-memory store for Start virtual modules (manifest, etc.). */ +export function createBunVirtualModuleStore(): BunVirtualModuleStore { + const modules = new Map() + + return { + get(id) { + return modules.get(id) + }, + set(id, contents) { + modules.set(id, contents) + }, + has(id) { + return modules.has(id) + }, + clear() { + modules.clear() + }, + updateServerFnResolver(serverFnsById, opts) { + const code = generateServerFnResolverModule({ + serverFnsById, + includeClientReferencedCheck: opts.includeClientReferencedCheck, + }) + modules.set(VIRTUAL_MODULES.serverFnResolver, code) + }, + updateManifest({ clientBuild, publicBase, scriptFormat, inlineCss }) { + const routeTreeRoutes = + ( + globalThis as { + TSS_ROUTES_MANIFEST?: Parameters< + typeof buildStartManifest + >[0]['routeTreeRoutes'] + } + ).TSS_ROUTES_MANIFEST ?? {} + + const startManifest = buildStartManifest({ + clientBuild, + routeTreeRoutes, + basePath: publicBase, + inlineCss, + scriptFormat: scriptFormat ?? 'module', + }) + + const serialized = serializeStartManifest(startManifest) + modules.set( + VIRTUAL_MODULES.startManifest, + `export const tsrStartManifest = () => (${serialized})`, + ) + }, + } +} + +/** Match virtual / aliased module IDs that should not hit the filesystem. */ +export function isBunVirtualModuleId(id: string): boolean { + return ( + id === VIRTUAL_MODULES.serverFnResolver || + id === VIRTUAL_MODULES.startManifest || + id === VIRTUAL_MODULES.pluginAdapters || + id.startsWith('virtual:tanstack-') || + id.startsWith('#tanstack-') + ) +} diff --git a/packages/start-plugin-core/src/schema.ts b/packages/start-plugin-core/src/schema.ts index ab0bb9660a0..2d2e3e6d2fb 100644 --- a/packages/start-plugin-core/src/schema.ts +++ b/packages/start-plugin-core/src/schema.ts @@ -61,7 +61,7 @@ export function parseStartConfig( root: string, ) { const rawOptions = opts ?? {} - const rawRouterOptions = rawOptions.router ?? {} + const rawRouterOptions = (rawOptions.router ?? {}) as Record const options = tanstackStartOptionsSchema.parse(opts) const srcDirectory = options.srcDirectory @@ -69,13 +69,17 @@ export function parseStartConfig( const routesDirectory = path.resolve( root, srcDirectory, - rawRouterOptions.routesDirectory ?? 'routes', + (typeof rawRouterOptions.routesDirectory === 'string' + ? rawRouterOptions.routesDirectory + : undefined) ?? 'routes', ) const generatedRouteTree = path.resolve( root, srcDirectory, - rawRouterOptions.generatedRouteTree ?? 'routeTree.gen.ts', + (typeof rawRouterOptions.generatedRouteTree === 'string' + ? rawRouterOptions.generatedRouteTree + : undefined) ?? 'routeTree.gen.ts', ) return { @@ -85,6 +89,11 @@ export function parseStartConfig( ...getConfig( { ...options.router, + // Preserve autoCodeSplitting (omitted from zod router schema but + // still honored by @tanstack/router-plugin getConfig). + ...(typeof rawRouterOptions.autoCodeSplitting === 'boolean' + ? { autoCodeSplitting: rawRouterOptions.autoCodeSplitting } + : {}), routesDirectory, generatedRouteTree, }, diff --git a/packages/start-plugin-core/tests/bun-css-assets-plugin.test.ts b/packages/start-plugin-core/tests/bun-css-assets-plugin.test.ts new file mode 100644 index 00000000000..21d690c8329 --- /dev/null +++ b/packages/start-plugin-core/tests/bun-css-assets-plugin.test.ts @@ -0,0 +1,79 @@ +import { mkdir, mkdtemp, readFile, writeFile } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' +import { createCssAssetsPlugin } from '../src/bun/css-assets-plugin' + +describe('createCssAssetsPlugin', () => { + it('exposes a named Bun plugin', () => { + const plugin = createCssAssetsPlugin({ + root: '/tmp', + clientOutDir: '/tmp/client', + publicBase: '/', + srcDirectory: 'src', + css: { tailwind: false }, + }) + expect(plugin.name).toBe('tanstack-start-bun:css-assets') + expect(typeof plugin.setup).toBe('function') + }) + + it('emits hashed css for ?url via onLoad handler', async () => { + const dir = await mkdtemp(join(tmpdir(), 'tss-css-')) + const clientOutDir = join(dir, 'client') + const cssPath = join(dir, 'styles.css') + await writeFile(cssPath, 'body{color:red}', 'utf8') + await mkdir(clientOutDir, { recursive: true }) + + const plugin = createCssAssetsPlugin({ + root: dir, + clientOutDir, + publicBase: '/', + srcDirectory: 'src', + css: { tailwind: false }, + }) + + const loads: Array<{ + filter: RegExp + namespace?: string + cb: (args: { path: string; namespace: string }) => Promise<{ + contents: string + loader?: string + }> + }> = [] + + await plugin.setup({ + onStart() {}, + onResolve() {}, + onLoad(options, cb) { + loads.push({ + filter: options.filter, + namespace: options.namespace, + cb: cb as (args: { + path: string + namespace: string + }) => Promise<{ contents: string; loader?: string }>, + }) + }, + }) + + const urlLoad = loads.find((l) => l.namespace === 'tss-css-url') + expect(urlLoad).toBeTruthy() + const result = await urlLoad!.cb({ + path: cssPath, + namespace: 'tss-css-url', + }) + expect(result.contents).toMatch( + /export default "\/assets\/styles-[a-f0-9]{8}\.css"/, + ) + + const match = result.contents.match( + /\/assets\/(styles-[a-f0-9]{8}\.css)/, + ) + expect(match?.[1]).toBeTruthy() + const written = await readFile( + join(clientOutDir, 'assets', match![1]!), + 'utf8', + ) + expect(written).toBe('body{color:red}') + }) +}) diff --git a/packages/start-plugin-core/tests/bun-dev-transform.test.ts b/packages/start-plugin-core/tests/bun-dev-transform.test.ts new file mode 100644 index 00000000000..d12aa3cd5f7 --- /dev/null +++ b/packages/start-plugin-core/tests/bun-dev-transform.test.ts @@ -0,0 +1,87 @@ +import { describe, expect, it } from 'vitest' +import { mkdtemp, writeFile, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { + applyDefineReplacements, + rewriteImportsForDevMiddleware, + transformDevModule, +} from '../src/bun/dev-transform' + +describe('applyDefineReplacements', () => { + it('replaces longer keys first', () => { + const out = applyDefineReplacements('process.env.TSS_FOO + process.env', { + 'process.env.TSS_FOO': '"x"', + 'process.env': '{}', + }) + expect(out).toBe('"x" + {}') + }) + + it('no-ops without define', () => { + expect(applyDefineReplacements('a', undefined)).toBe('a') + }) +}) + +describe('rewriteImportsForDevMiddleware', () => { + it('rewrites Start entry aliases to /@fs paths', () => { + const code = `import { getRouter } from '#tanstack-router-entry'` + const out = rewriteImportsForDevMiddleware( + code, + '/app/src/main.tsx', + '/app', + { '#tanstack-router-entry': '/app/src/router.tsx' }, + ) + expect(out).toContain('/@fs/app/src/router.tsx') + expect(out).not.toContain('#tanstack-router-entry') + }) + + it('rewrites relative imports to /@fs', () => { + const code = `import { x } from './utils'` + const out = rewriteImportsForDevMiddleware( + code, + '/app/src/main.tsx', + '/app', + ) + expect(out).toContain('/@fs/app/src/utils') + }) +}) + +describe('transformDevModule css ?url', () => { + it('exports a stylesheet URL for ?url imports', async () => { + const dir = await mkdtemp(join(tmpdir(), 'bun-dev-css-')) + const cssPath = join(dir, 'app.css') + try { + await writeFile(cssPath, '@import "tailwindcss";\n', 'utf8') + const result = await transformDevModule( + { root: dir, framework: 'react' }, + `${cssPath}?url`, + ) + expect(result.contentType).toContain('javascript') + expect(result.code).toContain('/@tanstack-start/styles.css') + expect(result.code).not.toContain('@import "tailwindcss"') + } finally { + await rm(dir, { recursive: true, force: true }) + } + }) + + it('exports SVG imports as data URLs instead of raw SVG source', async () => { + const dir = await mkdtemp(join(tmpdir(), 'bun-dev-svg-')) + const svgPath = join(dir, 'icon.svg') + try { + await writeFile( + svgPath, + '', + 'utf8', + ) + const result = await transformDevModule( + { root: dir, framework: 'react' }, + svgPath, + ) + expect(result.contentType).toContain('javascript') + expect(result.code).toContain('data:image/svg+xml') + expect(result.code).not.toMatch(/^ { + const root = '/app' + + it('classifies route files', () => { + expect(classifyBunChange(root, '/app/src/routes/index.tsx')).toBe('route') + }) + + it('classifies server-only files', () => { + expect(classifyBunChange(root, '/app/src/db.server.ts')).toBe('server-only') + }) + + it('classifies client components', () => { + expect( + classifyBunChange(root, '/app/src/components/Button.tsx'), + ).toBe('client') + }) + + it('maps scopes to SSE events', () => { + expect(hmrEventForScope('server')).toBe('server-only') + expect(hmrEventForScope('client')).toBe('client-reload') + expect(hmrEventForScope('both')).toBe('full-reload') + }) + + it('rebuild scopes', () => { + expect(rebuildScopeForChange('server-only')).toBe('server') + expect(rebuildScopeForChange('client')).toBe('client') + expect(rebuildScopeForChange('route')).toBe('both') + }) + + it('regenerates routes for route changes', () => { + expect(shouldRegenerateRoutes('route')).toBe(true) + expect(shouldRegenerateRoutes('server-only')).toBe(false) + }) +}) + +describe('rewriteImportMetaHot', () => { + it('rewrites import.meta.hot to the Bun shim', () => { + const input = `if (import.meta.hot) { import.meta.hot.accept(() => {}) }` + const out = rewriteImportMetaHot(input) + expect(out).toContain('__tanstack_import_meta_hot__') + expect(out).toContain('globalThis.__tanstack_hot__?.(import.meta.url)') + expect(out).not.toMatch(/(? { + const input = `import.meta.hot.data ??= {}` + const out = rewriteImportMetaHot(input) + expect(out).toContain('__tanstack_import_meta_hot__.data ??= {}') + expect(out.split('\n').slice(1).join('\n')).not.toContain('?.(') + }) +}) diff --git a/packages/start-plugin-core/tests/bun-import-protection.test.ts b/packages/start-plugin-core/tests/bun-import-protection.test.ts new file mode 100644 index 00000000000..500b1937d5f --- /dev/null +++ b/packages/start-plugin-core/tests/bun-import-protection.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest' +import { createBunImportProtectionPlugin } from '../src/bun/import-protection' + +describe('createBunImportProtectionPlugin', () => { + it('creates a named Bun plugin', () => { + const plugin = createBunImportProtectionPlugin({ + envName: 'client', + envType: 'client', + root: '/app', + srcDirectory: 'src', + mode: 'build', + }) + expect(plugin.name).toBe('tanstack-start-import-protection:client') + expect(typeof plugin.setup).toBe('function') + }) + + it('registers onLoad and mock resolve handlers', () => { + const plugin = createBunImportProtectionPlugin({ + envName: 'ssr', + envType: 'server', + root: '/app', + srcDirectory: 'src', + mode: 'dev', + importProtection: { behavior: 'mock' }, + }) + + const onLoad = vi.fn() + const onResolve = vi.fn() + plugin.setup({ + onLoad, + onResolve, + onStart() {}, + onBeforeParse() {}, + onEnd() {}, + module: () => ({}), + } as any) + + expect(onLoad).toHaveBeenCalled() + expect(onResolve).toHaveBeenCalled() + }) +}) diff --git a/packages/start-plugin-core/tests/bun-normalized-client-build.test.ts b/packages/start-plugin-core/tests/bun-normalized-client-build.test.ts new file mode 100644 index 00000000000..a24fe5e89f9 --- /dev/null +++ b/packages/start-plugin-core/tests/bun-normalized-client-build.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from 'vitest' +import { writeFile, mkdir } from 'node:fs/promises' +import { join } from 'node:path' +import { tmpdir } from 'node:os' +import { + enrichBunClientBuildFromSourcemaps, + normalizeBunClientBuild, +} from '../src/bun/normalized-client-build' + +describe('normalizeBunClientBuild', () => { + it('marks entry-point as the SSR entry chunk', () => { + const build = normalizeBunClientBuild({ + clientOutDir: '/app/dist/client', + outputs: [ + { + path: '/app/dist/client/assets/main-abc.js', + fileName: 'assets/main-abc.js', + kind: 'entry-point', + }, + { + path: '/app/dist/client/assets/chunk-1.js', + fileName: 'assets/chunk-1.js', + kind: 'chunk', + }, + ], + }) + + expect(build.entryChunkFileName).toBe('assets/main-abc.js') + expect(build.chunksByFileName.get('assets/main-abc.js')?.isEntry).toBe(true) + expect(build.chunksByFileName.size).toBe(2) + }) + + it('collects tsr-split route file paths from inputs', () => { + const build = normalizeBunClientBuild({ + clientOutDir: '/app/dist/client', + outputs: [ + { + path: '/app/dist/client/assets/index.js', + fileName: 'assets/index.js', + kind: 'entry-point', + inputs: [ + { + path: '/app/src/routes/posts.tsx?tsr-split=component', + }, + ], + }, + ], + }) + + expect( + build.chunksByFileName.get('assets/index.js')?.routeFilePaths, + ).toEqual(['/app/src/routes/posts.tsx']) + expect( + build.chunkFileNamesByRouteFilePath.get('/app/src/routes/posts.tsx'), + ).toEqual(['assets/index.js']) + }) + + it('enriches route file paths from linked sourcemap sources', async () => { + const dir = join(tmpdir(), `bun-ncb-${Date.now()}`) + await mkdir(dir, { recursive: true }) + const jsPath = join(dir, 'about.js') + const mapPath = `${jsPath}.map` + await writeFile(jsPath, 'export {}') + await writeFile( + mapPath, + JSON.stringify({ + version: 3, + sources: [ + 'tsr-split:/app/src/routes/about.tsx?tsr-split=component', + ], + mappings: '', + }), + ) + + const outputs = [ + { + path: jsPath, + fileName: 'about.js', + kind: 'chunk' as const, + sourcemapPath: mapPath, + }, + { + path: join(dir, 'main.js'), + fileName: 'main.js', + kind: 'entry-point' as const, + }, + ] + + let build = normalizeBunClientBuild({ + clientOutDir: dir, + outputs, + }) + build = await enrichBunClientBuildFromSourcemaps({ + clientBuild: build, + outputs, + }) + + expect(build.chunksByFileName.get('about.js')?.routeFilePaths).toEqual([ + '/app/src/routes/about.tsx', + ]) + expect( + build.chunkFileNamesByRouteFilePath.get('/app/src/routes/about.tsx'), + ).toEqual(['about.js']) + }) + + it('wires emitted CSS into entry chunk and content map', () => { + const build = normalizeBunClientBuild({ + clientOutDir: '/app/dist/client', + outputs: [ + { + path: '/app/dist/client/assets/main-abc.js', + fileName: 'assets/main-abc.js', + kind: 'entry-point', + }, + ], + emittedCssAssets: [ + { + sourcePath: '/app/src/app.css', + fileName: 'assets/app-deadbeef.css', + css: 'body{color:red}', + }, + ], + }) + + expect(build.cssContentByFileName.get('assets/app-deadbeef.css')).toBe( + 'body{color:red}', + ) + expect(build.cssFilesBySourcePath.get('/app/src/app.css')).toEqual([ + 'assets/app-deadbeef.css', + ]) + expect(build.chunksByFileName.get('assets/main-abc.js')?.css).toEqual([ + 'assets/app-deadbeef.css', + ]) + }) +}) diff --git a/packages/start-plugin-core/tests/bun-planning-env.test.ts b/packages/start-plugin-core/tests/bun-planning-env.test.ts new file mode 100644 index 00000000000..590e674153d --- /dev/null +++ b/packages/start-plugin-core/tests/bun-planning-env.test.ts @@ -0,0 +1,304 @@ +import { describe, expect, it } from 'vitest' +import { createBunDefine } from '../src/bun/planning' +import { + parseEnvFile, + createEnvDefine, + expandEnvVariables, + loadBunEnvFiles, +} from '../src/bun/load-env' +import { transformCssModules, isCssModulesFile } from '../src/bun/css-modules' +import { copyPublicDirToClient } from '../src/bun/copy-public-dir' +import { generateSerializationAdaptersModule } from '../src/serialization-adapters-module' +import { mkdtemp, mkdir, writeFile, readFile, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' + +describe('createBunDefine', () => { + it('defines process.env and import.meta.env pairs', () => { + const define = createBunDefine({ + serverFnBase: '/_serverFn', + routerBasepath: '/', + publicBase: '/app/', + isDev: true, + inlineCssEnabled: false, + spaEnabled: true, + disableCsrfMiddlewareWarning: true, + }) + + expect(define['process.env.TSS_SERVER_FN_BASE']).toBe( + JSON.stringify('/_serverFn'), + ) + expect(define['import.meta.env.TSS_SERVER_FN_BASE']).toBe( + JSON.stringify('/_serverFn'), + ) + expect(define['process.env.TSS_SHELL']).toBe(JSON.stringify('true')) + expect(define['process.env.TSS_DEV_SSR_STYLES_ENABLED']).toBe( + JSON.stringify('true'), + ) + expect(define['process.env.TSS_DISABLE_CSRF_MIDDLEWARE_WARNING']).toBe( + JSON.stringify('true'), + ) + expect(define['import.meta.env.TSS_PUBLIC_BASE']).toBe( + JSON.stringify('/app/'), + ) + }) + + it('disables shell and SSR styles outside spa/dev', () => { + const define = createBunDefine({ + serverFnBase: '/_serverFn', + routerBasepath: '/', + publicBase: '/', + isDev: false, + inlineCssEnabled: true, + spaEnabled: true, + }) + expect(define['process.env.TSS_SHELL']).toBe(JSON.stringify('false')) + expect(define['process.env.TSS_DEV_SSR_STYLES_ENABLED']).toBe( + JSON.stringify('false'), + ) + expect(define['process.env.TSS_INLINE_CSS_ENABLED']).toBe( + JSON.stringify('true'), + ) + }) +}) + +describe('load-env helpers', () => { + it('parses KEY=VALUE lines', () => { + const parsed = parseEnvFile(` +# comment +FOO=bar +QUOTED="hello world" +SINGLE='x' +`) + expect(parsed.FOO).toBe('bar') + expect(parsed.QUOTED).toBe('hello world') + expect(parsed.SINGLE).toBe('x') + }) + + it('supports export prefix, inline comments, escapes, and expansion', () => { + const parsed = parseEnvFile(` +export GREETING="hello\\nworld" +NAME=world # trailing comment +FULL=$GREETING-$NAME +NESTED=\${NAME}_ok +LITERAL=\\$UNSET_LITERAL +`) + expect(parsed.GREETING).toBe('hello\nworld') + expect(parsed.NAME).toBe('world') + expandEnvVariables(parsed) + expect(parsed.FULL).toBe('hello\nworld-world') + expect(parsed.NESTED).toBe('world_ok') + expect(parsed.LITERAL).toBe('$UNSET_LITERAL') + }) + + it('expands after applying process.env overrides', async () => { + const root = await mkdtemp(join(tmpdir(), 'bun-env-expand-')) + try { + await writeFile( + join(root, '.env'), + 'VITE_ORIGIN=file\nVITE_API=$VITE_ORIGIN/api\n', + 'utf8', + ) + const prev = process.env.VITE_ORIGIN + process.env.VITE_ORIGIN = 'process' + const loaded = loadBunEnvFiles({ root, mode: 'development' }) + expect(loaded.VITE_ORIGIN).toBe('process') + expect(loaded.VITE_API).toBe('process/api') + expect(process.env.VITE_API).toBeUndefined() + if (prev === undefined) { + delete process.env.VITE_ORIGIN + } else { + process.env.VITE_ORIGIN = prev + } + } finally { + await rm(root, { recursive: true, force: true }) + } + }) + + it('does not mutate process.env across sequential roots', async () => { + const rootA = await mkdtemp(join(tmpdir(), 'bun-env-a-')) + const rootB = await mkdtemp(join(tmpdir(), 'bun-env-b-')) + try { + await writeFile(join(rootA, '.env'), 'SECRET_A=a\n', 'utf8') + await writeFile(join(rootB, '.env'), 'SECRET_B=b\n', 'utf8') + const beforeA = process.env.SECRET_A + const beforeB = process.env.SECRET_B + loadBunEnvFiles({ root: rootA, mode: 'development' }) + loadBunEnvFiles({ root: rootB, mode: 'development' }) + expect(process.env.SECRET_A).toBe(beforeA) + expect(process.env.SECRET_B).toBe(beforeB) + } finally { + await rm(rootA, { recursive: true, force: true }) + await rm(rootB, { recursive: true, force: true }) + } + }) + + it('creates define entries for env keys', () => { + const define = createEnvDefine({ API_URL: 'https://example.test' }) + expect(define['process.env.API_URL']).toBe( + JSON.stringify('https://example.test'), + ) + expect(define['import.meta.env.API_URL']).toBe( + JSON.stringify('https://example.test'), + ) + }) + + it('filters to public prefixes when publicOnly', () => { + const define = createEnvDefine( + { + SECRET_KEY: 'nope', + VITE_APP: 'yes', + PUBLIC_FLAG: '1', + TSS_PUBLIC_TOKEN: 'tok', + }, + { publicOnly: true }, + ) + expect(define['process.env.SECRET_KEY']).toBeUndefined() + expect(define['process.env.VITE_APP']).toBe(JSON.stringify('yes')) + expect(define['process.env.PUBLIC_FLAG']).toBe(JSON.stringify('1')) + expect(define['process.env.TSS_PUBLIC_TOKEN']).toBe( + JSON.stringify('tok'), + ) + }) + + it('lets process.env win over .env files in the effective map', async () => { + const root = await mkdtemp(join(tmpdir(), 'bun-env-')) + try { + await writeFile( + join(root, '.env'), + 'VITE_APP=from-file\nSECRET=file\n', + 'utf8', + ) + const prevVite = process.env.VITE_APP + const prevOnly = process.env.VITE_ONLY_PROCESS + const prevSecret = process.env.SECRET + process.env.VITE_APP = 'from-process' + process.env.VITE_ONLY_PROCESS = 'only-process' + const loaded = loadBunEnvFiles({ root, mode: 'development' }) + expect(loaded.VITE_APP).toBe('from-process') + expect(loaded.SECRET).toBe('file') + expect(loaded.VITE_ONLY_PROCESS).toBe('only-process') + // Must not leak file values into the process environment. + expect(process.env.SECRET).toBe(prevSecret) + if (prevVite === undefined) { + delete process.env.VITE_APP + } else { + process.env.VITE_APP = prevVite + } + if (prevOnly === undefined) { + delete process.env.VITE_ONLY_PROCESS + } else { + process.env.VITE_ONLY_PROCESS = prevOnly + } + } finally { + await rm(root, { recursive: true, force: true }) + } + }) +}) + +describe('css modules', () => { + it('detects module css files', () => { + expect(isCssModulesFile('Button.module.css')).toBe(true) + expect(isCssModulesFile('app.css')).toBe(false) + }) + + it('hashes local class names and rewrites css', () => { + const result = transformCssModules({ + css: `.title { color: red; }\n.row { display: flex; }`, + filePath: '/app/src/Button.module.css', + }) + expect(result.exports.title).toMatch(/^title_/) + expect(result.exports.row).toMatch(/^row_/) + expect(result.css).toContain(`.${result.exports.title}`) + expect(result.css).not.toMatch(/(? { + const result = transformCssModules({ + css: `.a.b { color: red; }\n.a .c { color: blue; }\n.a[data-x] { color: green; }`, + filePath: '/app/src/Compound.module.css', + }) + expect(result.css).toContain(`.${result.exports.a}.${result.exports.b}`) + expect(result.css).toContain(`.${result.exports.a} .${result.exports.c}`) + expect(result.css).toContain(`.${result.exports.a}[data-x]`) + expect(result.exports).toHaveProperty('a') + expect(result.exports).toHaveProperty('b') + expect(result.exports).toHaveProperty('c') + }) + + it('does not rewrite classes inside imports, urls, or strings', () => { + const result = transformCssModules({ + css: ` +@import "./theme.module.css"; +.card { background: url(a.b.png); content: ".title"; } +.title { color: red; } +`, + filePath: '/app/src/Safe.module.css', + }) + expect(result.css).toContain('@import "./theme.module.css";') + expect(result.css).toContain('url(a.b.png)') + expect(result.css).toContain('content: ".title"') + expect(result.exports).not.toHaveProperty('module') + expect(result.exports).not.toHaveProperty('png') + expect(result.exports.title).toMatch(/^title_/) + expect(result.css).toContain(`.${result.exports.title}`) + expect(result.css).toContain(`.${result.exports.card}`) + }) +}) + +describe('copyPublicDirToClient', () => { + it('copies public assets into client out dir', async () => { + const root = await mkdtemp(join(tmpdir(), 'bun-public-')) + try { + await mkdir(join(root, 'public'), { recursive: true }) + await writeFile(join(root, 'public', 'robots.txt'), 'User-agent: *\n', 'utf8') + const clientOutDir = join(root, 'dist', 'client') + const result = await copyPublicDirToClient({ root, clientOutDir }) + expect(result.copied).toBe(true) + const copied = await readFile(join(clientOutDir, 'robots.txt'), 'utf8') + expect(copied).toContain('User-agent') + } finally { + await rm(root, { recursive: true, force: true }) + } + }) + + it('no-ops when public is missing', async () => { + const root = await mkdtemp(join(tmpdir(), 'bun-public-missing-')) + try { + const result = await copyPublicDirToClient({ + root, + clientOutDir: join(root, 'dist', 'client'), + }) + expect(result.copied).toBe(false) + } finally { + await rm(root, { recursive: true, force: true }) + } + }) +}) + +describe('serialization adapters module (Bun virtual)', () => { + it('emits empty module without adapters', () => { + const code = generateSerializationAdaptersModule({ + adapters: undefined, + runtime: 'client', + }) + expect(code).toContain('pluginSerializationAdapters') + expect(code).toContain('hasPluginAdapters = false') + }) + + it('emits imports for configured adapters', () => { + const code = generateSerializationAdaptersModule({ + adapters: [ + { + module: './adapters/date', + export: 'dateAdapter', + isFactory: true, + }, + ], + runtime: 'server', + }) + expect(code).toContain('./adapters/date') + expect(code).toContain('dateAdapter') + expect(code).toContain('hasPluginAdapters = true') + }) +}) diff --git a/packages/start-plugin-core/tests/bun-schema.test.ts b/packages/start-plugin-core/tests/bun-schema.test.ts new file mode 100644 index 00000000000..70a69abbf8a --- /dev/null +++ b/packages/start-plugin-core/tests/bun-schema.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from 'vitest' +import { + parseStartConfig, + tanstackStartBunOptionsSchema, +} from '../src/bun/schema' + +describe('bun parseStartConfig', () => { + it('preserves router.autoCodeSplitting through getConfig', () => { + const cfg = parseStartConfig( + { + router: { autoCodeSplitting: false }, + bun: { minify: true }, + }, + { framework: 'solid' }, + '/tmp/app', + ) + expect(cfg.router.autoCodeSplitting).toBe(false) + }) + + it('accepts bun.minify and bun.port in the Bun schema', () => { + const parsed = tanstackStartBunOptionsSchema.parse({ + bun: { minify: false, port: 4000 }, + }) + expect(parsed.bun?.minify).toBe(false) + expect(parsed.bun?.port).toBe(4000) + }) +}) diff --git a/packages/start-plugin-core/tests/bun-standalone-compile.test.ts b/packages/start-plugin-core/tests/bun-standalone-compile.test.ts new file mode 100644 index 00000000000..29ff016b721 --- /dev/null +++ b/packages/start-plugin-core/tests/bun-standalone-compile.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' +import { buildStandaloneEntrySource } from '../src/bun/standalone-compile' + +describe('buildStandaloneEntrySource', () => { + it('embeds client assets with file imports', () => { + const source = buildStandaloneEntrySource({ + entryPath: '/app/dist/server/.standalone-entry.js', + clientOutDir: '/app/dist/client', + assetFiles: [ + '/app/dist/client/index.html', + '/app/dist/client/assets/app.js', + ], + }) + expect(source).toContain('with { type: "file" }') + expect(source).toContain('/assets/app.js') + expect(source).toContain('Bun.serve') + expect(source).toContain('./server.js') + expect(source).toContain('formatListenBanner') + expect(source).toContain('networkInterfaces') + }) + + it('prefixes asset keys with publicBase', () => { + const source = buildStandaloneEntrySource({ + entryPath: '/app/dist/server/.standalone-entry.js', + clientOutDir: '/app/dist/client', + publicBase: '/app/', + assetFiles: ['/app/dist/client/assets/app.js'], + }) + expect(source).toContain('/app/assets/app.js') + }) +}) diff --git a/packages/start-plugin-core/tests/bun-static-host.test.ts b/packages/start-plugin-core/tests/bun-static-host.test.ts new file mode 100644 index 00000000000..37cb04fd14f --- /dev/null +++ b/packages/start-plugin-core/tests/bun-static-host.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { + generateHostEntrySource, + resolveClientAssetPath, +} from '../src/bun/static-host' + +describe('resolveClientAssetPath', () => { + const clientOutDir = '/app/dist/client' + + it('maps /assets/... paths', () => { + expect(resolveClientAssetPath(clientOutDir, '/assets/app.css')).toBe( + '/app/dist/client/assets/app.css', + ) + }) + + it('maps extensioned paths outside assets', () => { + expect(resolveClientAssetPath(clientOutDir, '/favicon.ico')).toBe( + '/app/dist/client/favicon.ico', + ) + }) + + it('rejects traversal and plain routes', () => { + expect(resolveClientAssetPath(clientOutDir, '/../etc/passwd')).toBeNull() + expect(resolveClientAssetPath(clientOutDir, '/assets/../../x')).toBeNull() + expect(resolveClientAssetPath(clientOutDir, '/login')).toBeNull() + }) +}) + +describe('generateHostEntrySource', () => { + it('emits a Bun.serve host that loads server.js', () => { + const source = generateHostEntrySource() + expect(source).toContain('server.js') + expect(source).toContain('Bun.serve') + expect(source).toContain('formatListenBanner') + expect(source).toContain('networkInterfaces') + }) +}) diff --git a/packages/start-plugin-core/tests/bun-virtual-modules.test.ts b/packages/start-plugin-core/tests/bun-virtual-modules.test.ts new file mode 100644 index 00000000000..613732a9ea3 --- /dev/null +++ b/packages/start-plugin-core/tests/bun-virtual-modules.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, it } from 'vitest' +import { + createBunVirtualModuleStore, + isBunVirtualModuleId, + VIRTUAL_MODULES, +} from '../src/bun/virtual-modules' +import type { NormalizedClientBuild } from '../src/types' +import type { ServerFn } from '../src/start-compiler/types' + +describe('isBunVirtualModuleId', () => { + it('matches reserved virtual ids only', () => { + expect(isBunVirtualModuleId(VIRTUAL_MODULES.startManifest)).toBe(true) + expect(isBunVirtualModuleId(VIRTUAL_MODULES.serverFnResolver)).toBe(true) + expect(isBunVirtualModuleId('virtual:tanstack-start-client-entry')).toBe( + true, + ) + expect(isBunVirtualModuleId('#tanstack-router-entry')).toBe(true) + expect( + isBunVirtualModuleId('tanstack-start-import-protection:mock'), + ).toBe(false) + expect(isBunVirtualModuleId('tanstack-start-example-basic')).toBe(false) + }) +}) + +describe('createBunVirtualModuleStore', () => { + it('writes serverFn resolver module from registry', () => { + const store = createBunVirtualModuleStore() + const serverFnsById: Record = { + abc123: { + functionName: 'getMessage', + functionId: 'abc123', + filename: '/app/src/routes/index.tsx', + extractedFilename: '/app/src/routes/index.tsx?tss-serverfn-split', + }, + } + + store.updateServerFnResolver(serverFnsById, { + includeClientReferencedCheck: false, + }) + + const code = store.get(VIRTUAL_MODULES.serverFnResolver) + expect(code).toBeTruthy() + expect(code).toContain('getServerFnById') + expect(code).toContain('abc123') + }) + + it('writes start manifest from NormalizedClientBuild', () => { + const store = createBunVirtualModuleStore() + const clientBuild: NormalizedClientBuild = { + entryChunkFileName: 'assets/client.js', + chunksByFileName: new Map([ + [ + 'assets/client.js', + { + fileName: 'assets/client.js', + isEntry: true, + imports: [], + dynamicImports: [], + css: [], + routeFilePaths: [], + hydrationIds: [], + }, + ], + ]), + chunkFileNamesByRouteFilePath: new Map(), + cssFilesBySourcePath: new Map(), + } + + store.updateManifest({ + clientBuild, + publicBase: '/', + scriptFormat: 'module', + }) + + const code = store.get(VIRTUAL_MODULES.startManifest) + expect(code).toContain('tsrStartManifest') + expect(code).toContain('assets/client.js') + }) +}) diff --git a/packages/start-plugin-core/tests/listen-urls.test.ts b/packages/start-plugin-core/tests/listen-urls.test.ts new file mode 100644 index 00000000000..4e79d738e2b --- /dev/null +++ b/packages/start-plugin-core/tests/listen-urls.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from 'vitest' +import { formatListenBanner, resolveListenUrls } from '../src/bun/listen-urls' + +const ifaces = { + lo: [ + { + address: '127.0.0.1', + netmask: '255.0.0.0', + family: 'IPv4' as const, + mac: '00:00:00:00:00:00', + internal: true, + cidr: '127.0.0.1/8', + }, + ], + eth0: [ + { + address: '192.168.1.8', + netmask: '255.255.255.0', + family: 'IPv4' as const, + mac: 'aa:bb:cc:dd:ee:ff', + internal: false, + cidr: '192.168.1.8/24', + }, + { + address: 'fe80::1', + netmask: 'ffff:ffff:ffff:ffff::', + family: 'IPv6' as const, + mac: 'aa:bb:cc:dd:ee:ff', + internal: false, + cidr: 'fe80::1/64', + scopeid: 1, + }, + ], + tailscale0: [ + { + address: '100.64.0.2', + netmask: '255.255.255.255', + family: 4 as const, + mac: 'aa:bb:cc:dd:ee:ff', + internal: false, + cidr: '100.64.0.2/32', + }, + ], +} + +describe('resolveListenUrls', () => { + it('wildcard 列出 localhost 与非内部 IPv4', () => { + expect( + resolveListenUrls( + { hostname: '0.0.0.0', port: 3847, interfaces: ifaces }, + ), + ).toEqual({ + local: ['http://localhost:3847/'], + network: ['http://192.168.1.8:3847/', 'http://100.64.0.2:3847/'], + }) + }) + + it('localhost 不暴露 Network', () => { + expect( + resolveListenUrls({ hostname: '127.0.0.1', port: 3000, interfaces: ifaces }), + ).toEqual({ + local: ['http://localhost:3000/'], + network: [], + }) + }) +}) + +describe('formatListenBanner', () => { + it('对齐 Local / Network 行', () => { + const text = formatListenBanner({ + headline: '[tanstack-start-bun] dev server (esm HMR)', + hostname: '0.0.0.0', + port: 3847, + interfaces: ifaces, + }) + expect(text).toBe( + [ + '[tanstack-start-bun] dev server (esm HMR)', + ' ➜ Local: http://localhost:3847/', + ' ➜ Network: http://192.168.1.8:3847/', + ' http://100.64.0.2:3847/', + ].join('\n'), + ) + }) +}) diff --git a/packages/start-plugin-core/vite.config.ts b/packages/start-plugin-core/vite.config.ts index fc76204069d..36eef1c188c 100644 --- a/packages/start-plugin-core/vite.config.ts +++ b/packages/start-plugin-core/vite.config.ts @@ -22,6 +22,7 @@ export default mergeConfig( './src/rsbuild/index.ts', './src/rsbuild/types.ts', './src/rsbuild/start-compiler-metadata-loader.ts', + './src/bun/index.ts', ], srcDir: './src', outDir: './dist', diff --git a/packages/vue-start/package.json b/packages/vue-start/package.json index 5b33bf004e8..45fb8f4e867 100644 --- a/packages/vue-start/package.json +++ b/packages/vue-start/package.json @@ -80,6 +80,14 @@ "default": "./dist/esm/plugin/rsbuild.js" } }, + "./plugin/bun": { + "bun": "./src/plugin/bun.ts", + "import": { + "types": "./dist/esm/plugin/bun.d.ts", + "default": "./dist/esm/plugin/bun.js" + }, + "default": "./src/plugin/bun.ts" + }, "./server-entry": { "import": { "types": "./dist/default-entry/esm/server.d.ts", diff --git a/packages/vue-start/src/plugin/bun.ts b/packages/vue-start/src/plugin/bun.ts new file mode 100644 index 00000000000..3e62ce4a1a4 --- /dev/null +++ b/packages/vue-start/src/plugin/bun.ts @@ -0,0 +1,49 @@ +import { existsSync } from 'node:fs' +import { fileURLToPath } from 'node:url' +import path from 'pathe' +import { + BUN_ENVIRONMENT_NAMES, + tanStackStartBun, +} from '@tanstack/start-plugin-core/bun' +import { vueStartDefaultEntryPaths } from './shared' +import type { + TanStackStartBunInputConfig, + TanStackStartBunPluginCoreOptions, + TanStackStartBunAdapter, +} from '@tanstack/start-plugin-core/bun' + +/** Resolve default Start entry file paths for the app root. */ +function resolveDefaultEntryPaths() { + if (existsSync(vueStartDefaultEntryPaths.client)) { + return vueStartDefaultEntryPaths + } + + const srcDefault = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '../default-entry', + ) + return { + client: path.resolve(srcDefault, 'client.tsx'), + server: path.resolve(srcDefault, 'server.ts'), + start: path.resolve(srcDefault, 'start.ts'), + } +} + +/** + * TanStack Start Bun bundler adapter for Vue (imperative build/dev API). + */ +export function tanstackStart( + options?: TanStackStartBunInputConfig, +): TanStackStartBunAdapter { + const corePluginOpts: TanStackStartBunPluginCoreOptions = { + framework: 'vue', + defaultEntryPaths: resolveDefaultEntryPaths(), + providerEnvironmentName: BUN_ENVIRONMENT_NAMES.server, + ssrIsProvider: true, + bun: options?.bun, + } + + return tanStackStartBun(corePluginOpts, options) +} + +export type { TanStackStartBunAdapter, TanStackStartBunInputConfig } diff --git a/packages/vue-start/vite.config.ts b/packages/vue-start/vite.config.ts index dc421ef7ffe..a29a2b10fe9 100644 --- a/packages/vue-start/vite.config.ts +++ b/packages/vue-start/vite.config.ts @@ -35,6 +35,7 @@ export default mergeConfig( './src/server.tsx', './src/plugin/rsbuild.ts', './src/plugin/vite.ts', + './src/plugin/bun.ts', './src/server-only.ts', './src/client-only.ts', ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5f19618d3f..645dcfab781 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -211,7 +211,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-vue': specifier: ^6.0.5 version: 6.0.5(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(@typescript/typescript6@6.0.2)) @@ -284,7 +284,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-vue': specifier: ^6.0.5 version: 6.0.5(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(@typescript/typescript6@6.0.2)) @@ -360,7 +360,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-vue': specifier: ^6.0.5 version: 6.0.5(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(@typescript/typescript6@6.0.2)) @@ -433,7 +433,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-vue-jsx': specifier: ^5.1.5 version: 5.1.5(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(@typescript/typescript6@6.0.2)) @@ -497,7 +497,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-vue-jsx': specifier: ^5.1.5 version: 5.1.5(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(@typescript/typescript6@6.0.2)) @@ -600,7 +600,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -643,7 +643,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -732,7 +732,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) combinate: specifier: ^1.1.11 version: 1.1.11 @@ -784,7 +784,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -833,7 +833,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -888,7 +888,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -934,7 +934,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: ~5.9.0 version: 5.9.3 @@ -989,7 +989,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1041,7 +1041,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1075,7 +1075,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1121,7 +1121,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1170,7 +1170,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -1204,7 +1204,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1238,7 +1238,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1275,7 +1275,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1321,7 +1321,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1374,7 +1374,7 @@ importers: version: 1.61.1 '@rolldown/plugin-babel': specifier: ^0.2.0 - version: 0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@tanstack/router-e2e-utils': specifier: workspace:^ version: link:../../e2e-utils @@ -1386,7 +1386,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) babel-plugin-react-compiler: specifier: ^1.0.0 version: 1.0.0 @@ -1551,7 +1551,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1603,7 +1603,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -1655,7 +1655,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -1731,7 +1731,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) combinate: specifier: ^1.1.11 version: 1.1.11 @@ -1807,7 +1807,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) dotenv: specifier: ^17.2.3 version: 17.2.3 @@ -1868,7 +1868,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -1941,7 +1941,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -2048,7 +2048,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -2149,7 +2149,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -2195,7 +2195,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) nitro: specifier: ^3.0.260311-beta version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -2265,7 +2265,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) cross-env: specifier: ^10.0.0 version: 10.0.0 @@ -2387,7 +2387,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.15 @@ -2436,7 +2436,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) nitro: specifier: ^3.0.260311-beta version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -2491,7 +2491,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) node-forge: specifier: ^1.3.1 version: 1.3.1 @@ -2534,7 +2534,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) autocannon: specifier: ^8.0.0 version: 8.0.0 @@ -2598,7 +2598,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -2653,7 +2653,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -2708,7 +2708,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -2754,7 +2754,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -2794,7 +2794,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) vite: specifier: ^8.0.14 version: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -2855,7 +2855,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -2901,7 +2901,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3002,7 +3002,7 @@ importers: version: 8.44.1(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2))(supports-color@10.2.2)(typescript@5.9.2) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: ^0.5.30 version: 0.5.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -3054,7 +3054,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: ^0.5.30 version: 0.5.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -3112,7 +3112,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: ^0.5.30 version: 0.5.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -3219,7 +3219,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) combinate: specifier: ^1.1.11 version: 1.1.11 @@ -3277,7 +3277,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3332,7 +3332,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3417,7 +3417,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) combinate: specifier: ^1.1.11 version: 1.1.11 @@ -3472,7 +3472,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3548,7 +3548,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) combinate: specifier: ^1.1.11 version: 1.1.11 @@ -3603,7 +3603,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3701,7 +3701,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3747,7 +3747,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) nitro: specifier: ^3.0.260311-beta version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -3771,7 +3771,7 @@ importers: version: link:../../../packages/start-static-server-functions nitro: specifier: ^3.0.1-alpha.2 - version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.0.2)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.2.4)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) react: specifier: ^19.2.3 version: 19.2.3 @@ -3799,7 +3799,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -3894,7 +3894,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -3958,7 +3958,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) combinate: specifier: ^1.1.11 version: 1.1.11 @@ -4025,7 +4025,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) srvx: specifier: ^0.11.9 version: 0.11.12 @@ -7703,7 +7703,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -7758,7 +7758,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -7801,7 +7801,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -7850,7 +7850,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -7899,7 +7899,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -7948,7 +7948,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -7991,7 +7991,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8040,7 +8040,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8095,7 +8095,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8147,7 +8147,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8199,7 +8199,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8251,7 +8251,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8303,7 +8303,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8349,7 +8349,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8395,7 +8395,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8444,7 +8444,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8496,7 +8496,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8551,7 +8551,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8609,7 +8609,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8661,7 +8661,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8710,7 +8710,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8756,7 +8756,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8796,7 +8796,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -8879,7 +8879,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9026,7 +9026,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9072,7 +9072,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -9115,7 +9115,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9143,7 +9143,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9192,7 +9192,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9238,7 +9238,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9284,7 +9284,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9327,7 +9327,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -9367,7 +9367,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9413,7 +9413,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9456,7 +9456,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -9493,7 +9493,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9539,7 +9539,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9585,7 +9585,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9655,7 +9655,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) jsdom: specifier: ^27.0.0 version: 27.0.0(supports-color@10.2.2) @@ -9704,7 +9704,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -9753,7 +9753,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) nitro: specifier: ^3.0.260311-beta version: 3.0.260311-beta(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(miniflare@4.20260317.0)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -9817,7 +9817,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) dotenv: specifier: ^17.2.3 version: 17.2.3 @@ -9878,7 +9878,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -9927,7 +9927,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -9991,7 +9991,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10073,7 +10073,7 @@ importers: version: link:../../../packages/start-static-server-functions '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) react: specifier: ^19.2.3 version: 19.2.3 @@ -10171,7 +10171,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) jsdom: specifier: ^27.0.0 version: 27.0.0(supports-color@10.2.2) @@ -10191,6 +10191,40 @@ importers: specifier: ^5.1.0 version: 5.1.0 + examples/react/start-bun-bundler: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../../../packages/react-router + '@tanstack/react-start': + specifier: workspace:* + version: link:../../../packages/react-start + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + nitro: + specifier: npm:nitro-nightly@latest + version: nitro-nightly@3.0.1-20260810-113911-16ff2809(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(wrangler@4.75.0) + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + devDependencies: + '@types/bun': + specifier: ^1.2.22 + version: 1.3.14 + '@types/react': + specifier: ^19.2.8 + version: 19.2.9 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.9) + typescript: + specifier: ^5.9.0 + version: 5.9.3 + examples/react/start-clerk-basic: dependencies: '@clerk/tanstack-react-start': @@ -10207,7 +10241,7 @@ importers: version: link:../../../packages/react-start '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) react: specifier: ^19.2.3 version: 19.2.3 @@ -10320,7 +10354,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10363,7 +10397,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -10412,7 +10446,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10470,7 +10504,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10531,7 +10565,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -10583,7 +10617,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) '@vitejs/plugin-rsc': specifier: ^0.5.30 version: 0.5.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) @@ -10635,7 +10669,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -10684,7 +10718,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10736,7 +10770,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10809,7 +10843,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tailwindcss: specifier: ^4.2.2 version: 4.2.2 @@ -10855,7 +10889,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -10904,7 +10938,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -10953,7 +10987,7 @@ importers: version: typescript@7.0.2 '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) typescript: specifier: npm:@typescript/typescript6@^6.0.2 version: '@typescript/typescript6@6.0.2' @@ -11011,7 +11045,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tsx: specifier: ^4.20.3 version: 4.20.3 @@ -11078,7 +11112,7 @@ importers: version: 19.2.3(@types/react@19.2.9) '@vitejs/plugin-react': specifier: ^6.0.1 - version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + version: 6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) tsx: specifier: ^4.20.3 version: 4.20.3 @@ -12870,6 +12904,37 @@ importers: specifier: ^5.1.0 version: 5.1.0 + examples/solid/start-bun-bundler: + dependencies: + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + '@tanstack/solid-router': + specifier: workspace:* + version: link:../../../packages/solid-router + '@tanstack/solid-start': + specifier: workspace:* + version: link:../../../packages/solid-start + babel-preset-solid: + specifier: ^1.9.10 + version: 1.9.10(@babel/core@7.29.0(supports-color@10.2.2))(solid-js@1.9.12) + solid-js: + specifier: 1.9.12 + version: 1.9.12 + devDependencies: + '@babel/core': + specifier: ^7.28.5 + version: 7.29.0(supports-color@10.2.2) + '@babel/preset-typescript': + specifier: ^7.28.5 + version: 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@types/bun': + specifier: ^1.2.22 + version: 1.3.14 + typescript: + specifier: ^5.9.0 + version: 5.9.3 + examples/solid/start-convex-better-auth: dependencies: '@convex-dev/better-auth': @@ -13462,6 +13527,37 @@ importers: specifier: ^3.3.8 version: 3.3.8(typescript@5.8.3) + examples/vue/start-bun-bundler: + dependencies: + '@tanstack/router-plugin': + specifier: workspace:* + version: link:../../../packages/router-plugin + '@tanstack/vue-router': + specifier: workspace:* + version: link:../../../packages/vue-router + '@tanstack/vue-start': + specifier: workspace:* + version: link:../../../packages/vue-start + '@vue/babel-plugin-jsx': + specifier: ^1.4.0 + version: 1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + vue: + specifier: ^3.5.16 + version: 3.5.25(typescript@5.9.3) + devDependencies: + '@babel/core': + specifier: ^7.28.5 + version: 7.29.0(supports-color@10.2.2) + '@babel/preset-typescript': + specifier: ^7.28.5 + version: 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@types/bun': + specifier: ^1.2.22 + version: 1.3.14 + typescript: + specifier: ^5.9.0 + version: 5.9.3 + packages/arktype-adapter: devDependencies: '@arethetypeswrong/cli': @@ -13606,7 +13702,7 @@ importers: dependencies: nitropack: specifier: ^2.13.1 - version: 2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(mysql2@3.15.3)(rolldown@1.0.2)(supports-color@10.2.2) + version: 2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(mysql2@3.15.3)(rolldown@1.2.4)(supports-color@10.2.2) pathe: specifier: ^2.0.3 version: 2.0.3 @@ -14361,6 +14457,9 @@ importers: '@tanstack/router-utils': specifier: workspace:* version: link:../router-utils + bun: + specifier: '>=1.2.0' + version: 1.3.14 chokidar: specifier: ^5.0.0 version: 5.0.0 @@ -14395,6 +14494,9 @@ importers: '@types/babel__template': specifier: ^7.4.4 version: 7.4.4 + '@types/bun': + specifier: ^1.3.14 + version: 1.3.14 '@types/node': specifier: 25.0.9 version: 25.0.9 @@ -14899,9 +15001,15 @@ importers: '@babel/core': specifier: ^7.28.5 version: 7.28.5(supports-color@10.2.2) + '@babel/preset-typescript': + specifier: ^7.28.5 + version: 7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) '@babel/types': specifier: ^7.28.5 version: 7.28.5 + '@tailwindcss/node': + specifier: ^4.0.0 + version: 4.3.1 '@tanstack/router-core': specifier: workspace:* version: link:../router-core @@ -14917,18 +15025,27 @@ importers: '@tanstack/start-server-core': specifier: workspace:* version: link:../start-server-core + bun: + specifier: '>=1.2.0' + version: 1.3.14 exsolve: specifier: ^1.0.7 version: 1.0.7 lightningcss: specifier: ^1.32.0 version: 1.32.0 + nitro: + specifier: '>=3.0.0-alpha || >=3.0.0-beta || >=3.0.0' + version: 3.0.0(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@4.0.3)(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) pathe: specifier: ^2.0.3 version: 2.0.3 picomatch: specifier: ^4.0.3 version: 4.0.3 + react-refresh: + specifier: ^0.18.0 + version: 0.18.0 seroval: specifier: ^1.6.2 version: 1.6.2 @@ -14969,6 +15086,9 @@ importers: '@types/babel__core': specifier: ^7.20.5 version: 7.20.5 + '@types/bun': + specifier: ^1.3.14 + version: 1.3.14 '@types/node': specifier: 25.0.9 version: 25.0.9 @@ -15007,7 +15127,7 @@ importers: version: 0.1.7 h3-v2: specifier: npm:h3@2.0.1-rc.20 - version: h3@2.0.1-rc.20(crossws@0.4.5(srvx@0.11.22)) + version: h3@2.0.1-rc.20(crossws@0.4.10(srvx@0.12.5)) seroval: specifier: ^1.6.2 version: 1.6.2 @@ -15671,10 +15791,6 @@ packages: resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.28.6': resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} @@ -18457,6 +18573,86 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} + '@oven/bun-darwin-aarch64@1.3.14': + resolution: {integrity: sha512-Omj20SuiHBOUjUBIyqtkNjSUIjOtEOJwmbix/ZyFH4BaQ6OZTaaRWIR4TjHVz0yadHgli6lLTiAh1uarnvD49A==} + cpu: [arm64] + os: [darwin] + + '@oven/bun-darwin-x64-baseline@1.3.14': + resolution: {integrity: sha512-OSfsTZstc898HHElhU4NccaBGOSSDn5VfahiVTnidZ9B/+wb7WTyfZJaBeJcfjwJ9H2W9uTh2TGtl3UfcXgV9g==} + cpu: [x64] + os: [darwin] + + '@oven/bun-darwin-x64@1.3.14': + resolution: {integrity: sha512-FFj3QdU/OhlDyZOJ8CWfN5eWLpRlT4qjZg7lMQi7jA6GuoY5ajlO1zWLP/MuHYRSbXQUvV52RejNi8DVnAp13w==} + cpu: [x64] + os: [darwin] + + '@oven/bun-freebsd-aarch64@1.3.14': + resolution: {integrity: sha512-LIKrXaFxAHybVO5Pf+9XP2FHUj/5APvXTUKk9dqHm5iFz4oH+W24cmhjkJirNujh9hKeTyrpWSe3no9JZKowIw==} + cpu: [arm64] + os: [freebsd] + + '@oven/bun-freebsd-x64@1.3.14': + resolution: {integrity: sha512-uwD+fGUH1ADpIF3B1U2jWzzb20QwRLZfj5QZ28GUCGrAJ/nTmWrD6YYGsblCY1wuhldRez3lU40AyuvSCyLYmw==} + cpu: [x64] + os: [freebsd] + + '@oven/bun-linux-aarch64-android@1.3.14': + resolution: {integrity: sha512-y4kq5b85lsrmFb9Xvi4w9mA5IEFJkLMrSmYn06q24KjL9rUWDWO3VFZEtteZxUN5+ec3Zm5S8OnJw1umaCbVjA==} + cpu: [arm64] + os: [android] + + '@oven/bun-linux-aarch64-musl@1.3.14': + resolution: {integrity: sha512-jmqOA92Cd1NL/1XBd4bFkJLxQ86K0RW7ohxS2qzzAvuitO4JiIxjjTeCspoU44zCozH72HpfZfUE2On31OjnWA==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-aarch64@1.3.14': + resolution: {integrity: sha512-X5SsPZHs+iYO8R/efIcRtc7gT2Q2DgPfliCxEkx4cXBumwkw0c/EsHMNwH3EgGpCDaZ7IYVPhpCG/xBOQHEwZw==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-x64-android@1.3.14': + resolution: {integrity: sha512-qe9e1d+3VAEU7nAA2ol9Jvmy/o99PVMSgZhHn7Q/9O3YcDrfEqyQ8zm4zoe5qTEo8HZH0dN03Le0Ys2eQPs7eg==} + cpu: [x64] + os: [android] + + '@oven/bun-linux-x64-baseline@1.3.14': + resolution: {integrity: sha512-q/8EdOC0yUE8FPeoOVq8/Pw5I9/tJaYmUfO/uDUAREx8IUnOJH1RJ5A3BjFqre8pvJoiZA9AovPJq5FnNNjSxA==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl-baseline@1.3.14': + resolution: {integrity: sha512-n6iE71G4lQE4XkrZhQQcL5YUlxDbnq6nqV7zeQi33PMsLT/0kYE+RvHOtBWZ3w0wMdXZfINmp63hIb9ijUBGtw==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64-musl@1.3.14': + resolution: {integrity: sha512-GBCB/k/sIqcr06eTNgg7g46qiUv35Jasx4XiccJ/n7RGqrE4RWUD/XJBbWFprVPjvqd59+QtSnS99XGqvftHfg==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64@1.3.14': + resolution: {integrity: sha512-7OVTAKvwfPmSbIV1HpdOoVVx5VRc427GuPPne93N6vk4eQBPId9nXmZDh9/zGaKPdbVjVtQSZafWQoUjx38Utw==} + cpu: [x64] + os: [linux] + + '@oven/bun-windows-aarch64@1.3.14': + resolution: {integrity: sha512-T7s3x/BsVKQObGU6QDkZeI6wKynzqGbBH1yI77jrrj5siElclxr3DQrDIk8CV4G5/SJq2HHq4kpLyYY2DKCSmA==} + cpu: [arm64] + os: [win32] + + '@oven/bun-windows-x64-baseline@1.3.14': + resolution: {integrity: sha512-uIjLUC1S9DWgICzuoMba7vurBJnBruE4S5CxnvmZkdqWVXRzx1Rgu636HoH+k0qeaQCFh3jeG3JQ1y6fRHv0sw==} + cpu: [x64] + os: [win32] + + '@oven/bun-windows-x64@1.3.14': + resolution: {integrity: sha512-mUFWL3BoYkNpjd8e9PqROiFF/1Xeotq20mABJsiQH62jM1g5zqWh4khw1RZ6bX8Q8fWvlPaxG1PjofkmjUi3vg==} + cpu: [x64] + os: [win32] + '@oxc-minify/binding-android-arm-eabi@0.110.0': resolution: {integrity: sha512-43fMTO8/5bMlqfOiNSZNKUzIqeLIYuB9Hr1Ohyf58B1wU11S2dPGibTXOGNaWsfgHy99eeZ1bSgeIHy/fEYqbw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -18590,6 +18786,9 @@ packages: '@oxc-project/types@0.132.0': resolution: {integrity: sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==} + '@oxc-project/types@0.144.0': + resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==} cpu: [arm] @@ -19762,6 +19961,12 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-android-arm64@1.2.4': + resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': resolution: {integrity: sha512-J7Zk3kLYFsLtuH6U+F4pS2sYVzac0qkjcO5QxHS7OS7yZu2LRs+IXo+uvJ/mvpyUljDJ3LROZPoQfgBIpCMhdQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19774,6 +19979,12 @@ packages: cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-arm64@1.2.4': + resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-rc.9': resolution: {integrity: sha512-iwtmmghy8nhfRGeNAIltcNXzD0QMNaaA5U/NyZc1Ia4bxrzFByNMDoppoC+hl7cDiUq5/1CnFthpT9n+UtfFyg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19786,6 +19997,12 @@ packages: cpu: [x64] os: [darwin] + '@rolldown/binding-darwin-x64@1.2.4': + resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': resolution: {integrity: sha512-DLFYI78SCiZr5VvdEplsVC2Vx53lnA4/Ga5C65iyldMVaErr86aiqCoNBLl92PXPfDtUYjUh+xFFor40ueNs4Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19798,6 +20015,12 @@ packages: cpu: [x64] os: [freebsd] + '@rolldown/binding-freebsd-x64@1.2.4': + resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': resolution: {integrity: sha512-CsjTmTwd0Hri6iTw/DRMK7kOZ7FwAkrO4h8YWKoX/kcj833e4coqo2wzIFywtch/8Eb5enQ/lwLM7w6JX1W5RQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19810,6 +20033,12 @@ packages: cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-2x9O2JbSPxpxMDhP9Z74mahAStibTlrBMW0520+epJH5sac7/LwZW5Bmg/E6CXuEF53JJFW509uP+lSedaUNxg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19824,6 +20053,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-arm64-gnu@1.2.4': + resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': resolution: {integrity: sha512-JA1QRW31ogheAIRhIg9tjMfsYbglXXYGNPLdPEYrwFxdbkQCAzvpSCSHCDWNl4hTtrol8WeboCSEpjdZK8qrCg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19838,6 +20074,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-arm64-musl@1.2.4': + resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-aOKU9dJheda8Kj8Y3w9gnt9QFOO+qKPAl8SWd7JPHP+Cu0EuDAE5wokQubLzIDQWg2myXq2XhTpOVS07qqvT+w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19852,6 +20095,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': resolution: {integrity: sha512-OalO94fqj7IWRn3VdXWty75jC5dk4C197AWEuMhIpvVv2lw9fiPhud0+bW2ctCxb3YoBZor71QHbY+9/WToadA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19866,6 +20116,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-s390x-gnu@1.2.4': + resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': resolution: {integrity: sha512-cVEl1vZtBsBZna3YMjGXNvnYYrOJ7RzuWvZU0ffvJUexWkukMaDuGhUXn0rjnV0ptzGVkvc+vW9Yqy6h8YX4pg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19880,6 +20137,13 @@ packages: os: [linux] libc: [glibc] + '@rolldown/binding-linux-x64-gnu@1.2.4': + resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': resolution: {integrity: sha512-UzYnKCIIc4heAKgI4PZ3dfBGUZefGCJ1TPDuLHoCzgrMYPb5Rv6TLFuYtyM4rWyHM7hymNdsg5ik2C+UD9VDbA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19894,6 +20158,13 @@ packages: os: [linux] libc: [musl] + '@rolldown/binding-linux-x64-musl@1.2.4': + resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': resolution: {integrity: sha512-+6zoiF+RRyf5cdlFQP7nm58mq7+/2PFaY2DNQeD4B87N36JzfF/l9mdBkkmTvSYcYPE8tMh/o3cRlsx1ldLfog==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19906,6 +20177,12 @@ packages: cpu: [arm64] os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.2.4': + resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9': resolution: {integrity: sha512-rgFN6sA/dyebil3YTlL2evvi/M+ivhfnyxec7AccTpRPccno/rPoNlqybEZQBkcbZu8Hy+eqNJCqfBR8P7Pg8g==} engines: {node: '>=14.0.0'} @@ -19928,6 +20205,12 @@ packages: cpu: [arm64] os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.2.4': + resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': resolution: {integrity: sha512-G0oA4+w1iY5AGi5HcDTxWsoxF509hrFIPB2rduV5aDqS9FtDg1CAfa7V34qImbjfhIcA8C+RekocJZA96EarwQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -19940,6 +20223,12 @@ packages: cpu: [x64] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.2.4': + resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/plugin-babel@0.2.3': resolution: {integrity: sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==} engines: {node: '>=22.12.0 || ^24.0.0'} @@ -21407,6 +21696,9 @@ packages: '@types/bun@1.2.22': resolution: {integrity: sha512-5A/KrKos2ZcN0c6ljRSOa1fYIyCKhZfIVYeuyb4snnvomnpFqC0tTsEkdqNxbAgExV384OETQ//WAjl3XbYqQA==} + '@types/bun@1.3.14': + resolution: {integrity: sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw==} + '@types/chai@5.2.2': resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} @@ -22822,6 +23114,15 @@ packages: peerDependencies: '@types/react': ^19.2.8 + bun-types@1.3.14: + resolution: {integrity: sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ==} + + bun@1.3.14: + resolution: {integrity: sha512-aB6GVd42x1Y5ie1K16SF+oLGtgSkwX9hgoDdIW88pjvfTccU8F1vfpoOt34QLv0dZ1v3XimtaxPlZUG81Gx9Zg==} + cpu: [arm64, x64] + os: [darwin, linux, android, freebsd, win32] + hasBin: true + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -23286,6 +23587,14 @@ packages: crossws@0.3.5: resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + crossws@0.4.10: + resolution: {integrity: sha512-pz3oubH/dt12KjqsUB0IuXW4nwRDQ583iDsP4555Cpdqx0NoU7pGlWBcayyFI8f/l/idRpgjMEfwuOxSWJYlIA==} + peerDependencies: + srvx: '>=0.11.5' + peerDependenciesMeta: + srvx: + optional: true + crossws@0.4.3: resolution: {integrity: sha512-lmf5mtwHiToP3HumOx53cqS0T5TK8GMBpsbSCXRB5OuszbltTgGOO4B1WhrDYqTeXOk3BAemibNjJx8E0/ecNw==} peerDependencies: @@ -23756,14 +24065,6 @@ packages: resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} engines: {node: '>=10.13.0'} - enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} - engines: {node: '>=10.13.0'} - - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} - engines: {node: '>=10.13.0'} - enhanced-resolve@5.21.6: resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==} engines: {node: '>=10.13.0'} @@ -23799,6 +24100,24 @@ packages: resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + env-runner@0.1.16: + resolution: {integrity: sha512-2LRJM4P2KLX6J83QZZrMqvgCDt/D5ea7wPcI3yYiy5cG/9rX5QwdwZFx0D7ktWnjdRyZxYjttGGorb5nFqb1CA==} + hasBin: true + peerDependencies: + '@netlify/runtime': ^4.1.23 + '@vercel/queue': '>=0.2.0' + miniflare: ^4.20260515.0 + wrangler: ^4.0.0 + peerDependenciesMeta: + '@netlify/runtime': + optional: true + '@vercel/queue': + optional: true + miniflare: + optional: true + wrangler: + optional: true + env-runner@0.1.6: resolution: {integrity: sha512-fSb7X1zdda8k6611a6/SdSQpDe7a/bqMz2UWdbHjk9YWzpUR4/fn9YtE/hqgGQ2nhvVN0zUtcL1SRMKwIsDbAA==} hasBin: true @@ -24178,6 +24497,9 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -24553,6 +24875,15 @@ packages: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + h3-rules@0.1.0: + resolution: {integrity: sha512-MrUUH1E/yoM7hvpKVbLs/CFeindwiTn7Fcfo+zXDpfuRYbcpJDPkYGGttCSiHNJmjUQNYQ5yn9veUIqFonYXuw==} + peerDependencies: + h3: ^2.0.1-rc.25 + ocache: '>=0.2.0' + peerDependenciesMeta: + ocache: + optional: true + h3@1.15.5: resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} @@ -24576,6 +24907,15 @@ packages: crossws: optional: true + h3@2.0.1-rc.2: + resolution: {integrity: sha512-2vS7OETzPDzGQxmmcs6ttu7p0NW25zAdkPXYOr43dn4GZf81uUljJvupa158mcpUGpsQUqIy4O4THWUQT1yVeA==} + engines: {node: '>=20.11.1'} + peerDependencies: + crossws: ^0.4.1 + peerDependenciesMeta: + crossws: + optional: true + h3@2.0.1-rc.20: resolution: {integrity: sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==} engines: {node: '>=20.11.1'} @@ -24586,6 +24926,16 @@ packages: crossws: optional: true + h3@2.0.1-rc.26: + resolution: {integrity: sha512-GDxlvDsKxgjRvG5UBRJYyGJTMWLV30CJ4cV+e7QCTgftDHihrvio1fVPbNembhEr6J4WNm8IWy3fookgyTweLw==} + engines: {node: '>=20.11.1'} + hasBin: true + peerDependencies: + crossws: ^0.4.9 + peerDependenciesMeta: + crossws: + optional: true + handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} @@ -24656,6 +25006,9 @@ packages: hookable@6.1.0: resolution: {integrity: sha512-ZoKZSJgu8voGK2geJS+6YtYjvIzu9AOM/KZXsBxr83uhLL++e9pEv/dlgwgy3dvHg06kTz6JOh1hk3C8Ceiymw==} + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + hosted-git-info@7.0.2: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} @@ -24764,6 +25117,9 @@ packages: httpxy@0.3.1: resolution: {integrity: sha512-XjG/CEoofEisMrnFr0D6U6xOZ4mRfnwcYQ9qvvnT4lvnX8BoeA3x3WofB75D+vZwpaobFVkBIHrZzoK40w8XSw==} + httpxy@0.5.5: + resolution: {integrity: sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==} + human-id@4.1.1: resolution: {integrity: sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==} hasBin: true @@ -25843,12 +26199,66 @@ packages: netlify-redirector@0.5.0: resolution: {integrity: sha512-4zdzIP+6muqPCuE8avnrgDJ6KW/2+UpHTRcTbMXCIRxiRmyrX+IZ4WSJGZdHPWF3WmQpXpy603XxecZ9iygN7w==} + nf3@0.1.12: + resolution: {integrity: sha512-qbMXT7RTGh74MYWPeqTIED8nDW70NXOULVHpdWcdZ7IVHVnAsMV9fNugSNnvooipDc1FMOzpis7T9nXJEbJhvQ==} + nf3@0.3.11: resolution: {integrity: sha512-ObKp/SA3f1g1f/OMeDlRWaZmqGgk7A0NnDIbeO7c/MV4r/quMlpP/BsqMGuTi3lUlXbC1On8YH7ICM2u2bIAOw==} + nf3@0.3.23: + resolution: {integrity: sha512-RWVLAWozmVD3AaDmaU3qMGB3v+yNlH5d9qqStI4e/WLlNQVnJ4YErGDbYCIrGFyrHdbF6I6Baf0Ae6c7tFYmSg==} + nf3@0.3.6: resolution: {integrity: sha512-/XRUUILTAyuy1XunyVQuqGp8aEmZ2TfRTn8Rji+FA4xqv20qzL4jV7Reqbuey2XucKgPeRVcEYGScmJM0UnB6Q==} + nitro-nightly@3.0.1-20260810-113911-16ff2809: + resolution: {integrity: sha512-CD/oRD6udCLWDMOHRRulls448wD1bUzQ6GYfzlUKXTSERDGML9orZhp0dfXTDMZ0A4EtUkRogFH/vHAj9P42ow==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@vercel/queue': ^0.4.0 + dotenv: '*' + giget: '*' + jiti: ^2.7.0 + rollup: ^4.62.2 + vite: ^8.0.14 + xml2js: ^0.6.2 + zephyr-agent: ^1.1.2 + peerDependenciesMeta: + '@vercel/queue': + optional: true + dotenv: + optional: true + giget: + optional: true + jiti: + optional: true + rollup: + optional: true + vite: + optional: true + xml2js: + optional: true + zephyr-agent: + optional: true + + nitro@3.0.0: + resolution: {integrity: sha512-pPrH77/oiYz3q1xGgOYQPEkXERsCi4PkErL2DpHhg4raag3EX7Zh41KFJ0ploSmflmDOPRPeAtC1/19fVrOqoQ==} + engines: {node: ^20.19.0 || >=22.12.0} + deprecated: 'IMPORTANT: please use nitro@3.0.1' + hasBin: true + peerDependencies: + rolldown: '*' + vite: ^8.0.14 + xml2js: ^0.6.2 + peerDependenciesMeta: + rolldown: + optional: true + vite: + optional: true + xml2js: + optional: true + nitro@3.0.1-alpha.2: resolution: {integrity: sha512-YviDY5J/trS821qQ1fpJtpXWIdPYiOizC/meHavlm1Hfuhx//H+Egd1+4C5SegJRgtWMnRPW9n//6Woaw81cTQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -26051,6 +26461,9 @@ packages: ocache@0.1.2: resolution: {integrity: sha512-lI34wjM7cahEdrq2I5obbF7MEdE97vULf6vNj6ZCzwEadzyXO1w7QOl2qzzG4IL8cyO7wDtXPj9CqW/aG3mn7g==} + ocache@0.2.0: + resolution: {integrity: sha512-QE+SxXf/A8yR01rEOg2X5KwUe+mARHo1xQXl+iwLMzLIH06S5lBOZhhHQWp7T5etfFa8nOnRhvjGuo9YBCWwig==} + ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} @@ -26852,6 +27265,10 @@ packages: renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + rendu@0.0.6: + resolution: {integrity: sha512-nZ512Dw0MxKiIYfCVv8DPe6ig4m0Qt3FOYBJEXrammjIYBBPuHaudc0AGfYx+iyOw2q0itAtPywiVZXtTFCsig==} + hasBin: true + repeat-string@1.6.1: resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} engines: {node: '>=0.10'} @@ -26948,6 +27365,11 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + rolldown@1.2.4: + resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup-plugin-visualizer@6.0.5: resolution: {integrity: sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg==} engines: {node: '>=18'} @@ -26972,6 +27394,9 @@ packages: rou3@0.8.1: resolution: {integrity: sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==} + rou3@0.9.2: + resolution: {integrity: sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -27369,6 +27794,16 @@ packages: engines: {node: '>=20.16.0'} hasBin: true + srvx@0.12.5: + resolution: {integrity: sha512-IuvtDNQg5EIwv3c6dleyau7u8hCyGQ7D6+V/QM799Aud07z0wCUcurKLTRfyG33C8oUY+UWcVBFkfHMcbtmRLA==} + engines: {node: '>=20.16.0'} + hasBin: true + + srvx@0.8.16: + resolution: {integrity: sha512-hmcGW4CgroeSmzgF1Ihwgl+Ths0JqAJ7HwjP2X7e3JzY7u4IydLMcdnlqGQiQGUswz+PO9oh/KtCpOISIvs9QQ==} + engines: {node: '>=20.16.0'} + hasBin: true + stable-hash-x@0.2.0: resolution: {integrity: sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==} engines: {node: '>=12.0.0'} @@ -27552,10 +27987,6 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} - engines: {node: '>=6'} - tapable@2.3.3: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} @@ -27778,7 +28209,6 @@ packages: tsconfck@3.1.4: resolution: {integrity: sha512-kdqWFGVJqe+KGYvlSO9NIaWn9jT1Ny4oKVzAJsKii5eoE9snzTJzL4+MMVOMn+fikWGFmKEylcXL710V/kIPJQ==} engines: {node: ^18 || >=20} - deprecated: unmaintained hasBin: true peerDependencies: typescript: ^5.0.0 @@ -27906,6 +28336,9 @@ packages: ufo@1.6.3: resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + uint8array-extras@1.5.0: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} @@ -27941,6 +28374,9 @@ packages: resolution: {integrity: sha512-uZsKNuzQxDMUY6M3pIMvy5tvlGmtq8XJ2oLAkfRKGNu+1VQAIvLy2xIVG5ATZl5wDXl/tddByAWCizRbOme+TA==} engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.21: + resolution: {integrity: sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A==} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} @@ -28056,6 +28492,80 @@ packages: uploadthing: optional: true + unstorage@2.0.0-alpha.3: + resolution: {integrity: sha512-BeoqISVh8jxqnPseHH7/92twe2VkQztrudXg8RFZVbXb4ckkFdpLk1LnNvsUndDltyodBMVxgI6V7JcbJYt2VQ==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + chokidar: ^4.0.3 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + lru-cache: ^11.2.2 + mongodb: ^6.20.0 + ofetch: ^1.4.1 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + chokidar: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + lru-cache: + optional: true + mongodb: + optional: true + ofetch: + optional: true + uploadthing: + optional: true + unstorage@2.0.0-alpha.5: resolution: {integrity: sha512-Sj8btci21Twnd6M+N+MHhjg3fVn6lAPElPmvFTe0Y/wR0WImErUdA1PzlAaUavHylJ7uDiFwlZDQKm0elG4b7g==} peerDependencies: @@ -28204,6 +28714,80 @@ packages: uploadthing: optional: true + unstorage@2.0.0-alpha.7: + resolution: {integrity: sha512-ELPztchk2zgFJnakyodVY3vJWGW9jy//keJ32IOJVGUMyaPydwcA1FtVvWqT0TNRch9H+cMNEGllfVFfScImog==} + peerDependencies: + '@azure/app-configuration': ^1.11.0 + '@azure/cosmos': ^4.9.1 + '@azure/data-tables': ^13.3.2 + '@azure/identity': ^4.13.0 + '@azure/keyvault-secrets': ^4.10.0 + '@azure/storage-blob': ^12.31.0 + '@capacitor/preferences': ^6 || ^7 || ^8 + '@deno/kv': '>=0.13.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.36.2 + '@vercel/blob': '>=0.27.3' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + chokidar: ^4 || ^5 + db0: '>=0.3.4' + idb-keyval: ^6.2.2 + ioredis: ^5.9.3 + lru-cache: ^11.2.6 + mongodb: ^6 || ^7 + ofetch: '*' + uploadthing: ^7.7.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + chokidar: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + lru-cache: + optional: true + mongodb: + optional: true + ofetch: + optional: true + uploadthing: + optional: true + untun@0.1.3: resolution: {integrity: sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ==} hasBin: true @@ -29063,33 +29647,33 @@ snapshots: '@babel/helper-optimise-call-expression': 7.27.1 '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@10.2.2) - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@10.2.2) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@10.2.2) - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/core': 7.28.5(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@10.2.2) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@10.2.2) - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -29111,7 +29695,7 @@ snapshots: '@babel/helper-member-expression-to-functions@7.27.1(supports-color@10.2.2)': dependencies: - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -29127,13 +29711,6 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/helper-module-imports@7.27.1(supports-color@10.2.2)': - dependencies: - '@babel/traverse': 7.28.5(supports-color@10.2.2) - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - '@babel/helper-module-imports@7.28.6(supports-color@10.2.2)': dependencies: '@babel/traverse': 7.29.0(supports-color@10.2.2) @@ -29144,18 +29721,18 @@ snapshots: '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color '@babel/helper-module-transforms@7.28.3(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: '@babel/core': 7.29.0(supports-color@10.2.2) - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -29181,7 +29758,7 @@ snapshots: '@babel/core': 7.28.5(supports-color@10.2.2) '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@10.2.2) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -29190,7 +29767,16 @@ snapshots: '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@10.2.2) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': + dependencies: + '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@10.2.2) + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -29205,7 +29791,7 @@ snapshots: '@babel/helper-skip-transparent-expression-wrappers@7.27.1(supports-color@10.2.2)': dependencies: - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/traverse': 7.29.0(supports-color@10.2.2) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -29218,7 +29804,7 @@ snapshots: '@babel/helpers@7.28.4': dependencies: - '@babel/template': 7.27.2 + '@babel/template': 7.28.6 '@babel/types': 7.29.0 '@babel/helpers@7.29.2': @@ -29251,22 +29837,27 @@ snapshots: '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5(supports-color@10.2.2))': dependencies: '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.29.0(supports-color@10.2.2))': dependencies: '@babel/core': 7.29.0(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5(supports-color@10.2.2))': dependencies: '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.29.0(supports-color@10.2.2))': dependencies: '@babel/core': 7.29.0(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.5(supports-color@10.2.2))': + dependencies: + '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0(supports-color@10.2.2))': dependencies: @@ -29285,7 +29876,7 @@ snapshots: dependencies: '@babel/core': 7.28.5(supports-color@10.2.2) '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -29293,18 +29884,13 @@ snapshots: dependencies: '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-module-transforms': 7.28.3(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.28.5(supports-color@10.2.2))': - dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5(supports-color@10.2.2))': + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.29.0(supports-color@10.2.2))': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0(supports-color@10.2.2))': @@ -29312,14 +29898,9 @@ snapshots: '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.28.5(supports-color@10.2.2))': + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.29.0(supports-color@10.2.2))': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5(supports-color@10.2.2))': - dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0(supports-color@10.2.2))': @@ -29332,31 +29913,31 @@ snapshots: '@babel/core': 7.28.5(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@10.2.2) '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2)) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@10.2.2) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2)) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/core': 7.28.5(supports-color@10.2.2) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@10.2.2) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.5(supports-color@10.2.2)) transitivePeerDependencies: - supports-color @@ -29382,14 +29963,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/preset-typescript@7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': + dependencies: + '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2)) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + transitivePeerDependencies: + - supports-color + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: '@babel/core': 7.29.0(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -29415,7 +30007,7 @@ snapshots: '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 + '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: @@ -30006,7 +30598,7 @@ snapshots: '@emotion/babel-plugin@11.13.5(supports-color@10.2.2)': dependencies: - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) '@babel/runtime': 7.26.7 '@emotion/hash': 0.9.2 '@emotion/memoize': 0.9.0 @@ -32004,7 +32596,7 @@ snapshots: '@netlify/zip-it-and-ship-it@14.1.11(rollup@4.56.0)(supports-color@10.2.2)': dependencies: - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.2 '@babel/types': 7.28.4 '@netlify/binary-info': 1.0.0 '@netlify/serverless-functions-api': 2.7.1 @@ -32132,6 +32724,54 @@ snapshots: '@opentelemetry/semantic-conventions@1.41.1': {} + '@oven/bun-darwin-aarch64@1.3.14': + optional: true + + '@oven/bun-darwin-x64-baseline@1.3.14': + optional: true + + '@oven/bun-darwin-x64@1.3.14': + optional: true + + '@oven/bun-freebsd-aarch64@1.3.14': + optional: true + + '@oven/bun-freebsd-x64@1.3.14': + optional: true + + '@oven/bun-linux-aarch64-android@1.3.14': + optional: true + + '@oven/bun-linux-aarch64-musl@1.3.14': + optional: true + + '@oven/bun-linux-aarch64@1.3.14': + optional: true + + '@oven/bun-linux-x64-android@1.3.14': + optional: true + + '@oven/bun-linux-x64-baseline@1.3.14': + optional: true + + '@oven/bun-linux-x64-musl-baseline@1.3.14': + optional: true + + '@oven/bun-linux-x64-musl@1.3.14': + optional: true + + '@oven/bun-linux-x64@1.3.14': + optional: true + + '@oven/bun-windows-aarch64@1.3.14': + optional: true + + '@oven/bun-windows-x64-baseline@1.3.14': + optional: true + + '@oven/bun-windows-x64@1.3.14': + optional: true + '@oxc-minify/binding-android-arm-eabi@0.110.0': optional: true @@ -32201,6 +32841,8 @@ snapshots: '@oxc-project/types@0.132.0': {} + '@oxc-project/types@0.144.0': {} + '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true @@ -33361,72 +34003,108 @@ snapshots: '@rolldown/binding-android-arm64@1.0.2': optional: true + '@rolldown/binding-android-arm64@1.2.4': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-rc.9': optional: true '@rolldown/binding-darwin-arm64@1.0.2': optional: true + '@rolldown/binding-darwin-arm64@1.2.4': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-rc.9': optional: true '@rolldown/binding-darwin-x64@1.0.2': optional: true + '@rolldown/binding-darwin-x64@1.2.4': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-rc.9': optional: true '@rolldown/binding-freebsd-x64@1.0.2': optional: true + '@rolldown/binding-freebsd-x64@1.2.4': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.9': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.2': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.2.4': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.9': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.2': optional: true + '@rolldown/binding-linux-arm64-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.9': optional: true '@rolldown/binding-linux-arm64-musl@1.0.2': optional: true + '@rolldown/binding-linux-arm64-musl@1.2.4': + optional: true + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.9': optional: true '@rolldown/binding-linux-ppc64-gnu@1.0.2': optional: true + '@rolldown/binding-linux-ppc64-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.9': optional: true '@rolldown/binding-linux-s390x-gnu@1.0.2': optional: true + '@rolldown/binding-linux-s390x-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.9': optional: true '@rolldown/binding-linux-x64-gnu@1.0.2': optional: true + '@rolldown/binding-linux-x64-gnu@1.2.4': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-rc.9': optional: true '@rolldown/binding-linux-x64-musl@1.0.2': optional: true + '@rolldown/binding-linux-x64-musl@1.2.4': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-rc.9': optional: true '@rolldown/binding-openharmony-arm64@1.0.2': optional: true + '@rolldown/binding-openharmony-arm64@1.2.4': + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-rc.9(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) @@ -33448,26 +34126,32 @@ snapshots: '@rolldown/binding-win32-arm64-msvc@1.0.2': optional: true + '@rolldown/binding-win32-arm64-msvc@1.2.4': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.9': optional: true '@rolldown/binding-win32-x64-msvc@1.0.2': optional: true - '@rolldown/plugin-babel@0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': + '@rolldown/binding-win32-x64-msvc@1.2.4': + optional: true + + '@rolldown/plugin-babel@0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@babel/core': 7.28.5(supports-color@10.2.2) picomatch: 4.0.4 - rolldown: 1.0.2 + rolldown: 1.2.4 optionalDependencies: vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) optional: true - '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@babel/core': 7.29.0(supports-color@10.2.2) picomatch: 4.0.4 - rolldown: 1.0.2 + rolldown: 1.2.4 optionalDependencies: vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -33955,7 +34639,7 @@ snapshots: '@sentry/bundler-plugin-core@4.6.1(supports-color@10.2.2)': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@sentry/babel-plugin-component-annotate': 4.6.1 '@sentry/cli': 2.58.4(supports-color@10.2.2) dotenv: 16.6.1 @@ -34177,7 +34861,7 @@ snapshots: '@stylistic/eslint-plugin@5.4.0(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2))': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)) - '@typescript-eslint/types': 8.53.0 + '@typescript-eslint/types': 8.57.1 eslint: 9.22.0(jiti@2.7.0)(supports-color@10.2.2) eslint-visitor-keys: 4.2.1 espree: 10.4.0 @@ -34331,8 +35015,8 @@ snapshots: '@tailwindcss/node@4.2.2': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.1 - jiti: 2.6.1 + enhanced-resolve: 5.21.6 + jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 @@ -34890,6 +35574,10 @@ snapshots: transitivePeerDependencies: - '@types/react' + '@types/bun@1.3.14': + dependencies: + bun-types: 1.3.14 + '@types/chai@5.2.2': dependencies: '@types/deep-eql': 4.0.2 @@ -35695,9 +36383,9 @@ snapshots: '@vitejs/plugin-react@4.3.4(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.28.5(supports-color@10.2.2)) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.28.5(supports-color@10.2.2)) + '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.29.0(supports-color@10.2.2)) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.29.0(supports-color@10.2.2)) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) @@ -35706,9 +36394,9 @@ snapshots: '@vitejs/plugin-react@4.6.0(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2)) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2)) + '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) '@rolldown/pluginutils': 1.0.0-beta.19 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 @@ -35728,20 +36416,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) optionalDependencies: - '@rolldown/plugin-babel': 0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.28.5(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) babel-plugin-react-compiler: 1.0.0 - '@vitejs/plugin-react@6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': + '@vitejs/plugin-react@6.0.1(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)))(babel-plugin-react-compiler@1.0.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) optionalDependencies: - '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.0.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.0(supports-color@10.2.2))(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)) babel-plugin-react-compiler: 1.0.0 '@vitejs/plugin-rsc@0.5.30(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))': @@ -35760,10 +36448,10 @@ snapshots: '@vitejs/plugin-vue-jsx@4.2.0(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(@typescript/typescript6@6.0.2))': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) '@rolldown/pluginutils': 1.0.0 - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) vue: 3.5.25(@typescript/typescript6@6.0.2) transitivePeerDependencies: @@ -35771,10 +36459,10 @@ snapshots: '@vitejs/plugin-vue-jsx@4.2.0(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(vue@3.5.25(typescript@7.0.2))': dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) '@rolldown/pluginutils': 1.0.0 - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) vue: 3.5.25(typescript@7.0.2) transitivePeerDependencies: @@ -35970,29 +36658,29 @@ snapshots: '@vue/babel-helper-vue-transform-on@2.0.1': {} - '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': + '@vue/babel-plugin-jsx@1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5(supports-color@10.2.2)) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0(supports-color@10.2.2) '@babel/types': 7.29.0 '@vue/babel-helper-vue-transform-on': 1.5.0 - '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@vue/babel-plugin-resolve-type': 1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) '@vue/shared': 3.5.25 optionalDependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) transitivePeerDependencies: - supports-color '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) + '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.29.0(supports-color@10.2.2)) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5(supports-color@10.2.2) + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0(supports-color@10.2.2) '@babel/types': 7.29.0 '@vue/babel-helper-vue-transform-on': 2.0.1 '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) @@ -36002,13 +36690,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2)': + '@vue/babel-plugin-resolve-type@1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2)': dependencies: '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.5(supports-color@10.2.2) - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.2 '@vue/compiler-sfc': 3.5.25 transitivePeerDependencies: - supports-color @@ -36017,7 +36705,7 @@ snapshots: dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.0(supports-color@10.2.2) - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) '@babel/helper-plugin-utils': 7.28.6 '@babel/parser': 7.28.5 '@vue/compiler-sfc': 3.5.25 @@ -36597,7 +37285,7 @@ snapshots: babel-dead-code-elimination@1.0.12(supports-color@10.2.2): dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@babel/parser': 7.28.5 '@babel/traverse': 7.28.5(supports-color@10.2.2) '@babel/types': 7.29.0 @@ -36640,8 +37328,8 @@ snapshots: babel-plugin-vue-jsx-hmr@1.0.0(supports-color@10.2.2): dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) - '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.28.5(supports-color@10.2.2))(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) + '@vue/babel-plugin-jsx': 1.5.0(@babel/core@7.29.0(supports-color@10.2.2))(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -36869,6 +37557,29 @@ snapshots: '@types/node': 25.0.9 '@types/react': 19.2.9 + bun-types@1.3.14: + dependencies: + '@types/node': 25.0.9 + + bun@1.3.14: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.3.14 + '@oven/bun-darwin-x64': 1.3.14 + '@oven/bun-darwin-x64-baseline': 1.3.14 + '@oven/bun-freebsd-aarch64': 1.3.14 + '@oven/bun-freebsd-x64': 1.3.14 + '@oven/bun-linux-aarch64': 1.3.14 + '@oven/bun-linux-aarch64-android': 1.3.14 + '@oven/bun-linux-aarch64-musl': 1.3.14 + '@oven/bun-linux-x64': 1.3.14 + '@oven/bun-linux-x64-android': 1.3.14 + '@oven/bun-linux-x64-baseline': 1.3.14 + '@oven/bun-linux-x64-musl': 1.3.14 + '@oven/bun-linux-x64-musl-baseline': 1.3.14 + '@oven/bun-windows-aarch64': 1.3.14 + '@oven/bun-windows-x64': 1.3.14 + '@oven/bun-windows-x64-baseline': 1.3.14 + bundle-name@4.1.0: dependencies: run-applescript: 7.0.0 @@ -36885,7 +37596,7 @@ snapshots: dotenv: 16.6.1 exsolve: 1.0.8 giget: 2.0.0 - jiti: 2.6.1 + jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 1.0.0 @@ -36900,7 +37611,7 @@ snapshots: dotenv: 17.4.2 exsolve: 1.0.8 giget: 2.0.0 - jiti: 2.6.1 + jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 perfect-debounce: 2.0.0 @@ -37337,6 +38048,15 @@ snapshots: dependencies: uncrypto: 0.1.3 + crossws@0.4.10(srvx@0.11.22): + optionalDependencies: + srvx: 0.11.22 + + crossws@0.4.10(srvx@0.12.5): + optionalDependencies: + srvx: 0.12.5 + optional: true + crossws@0.4.3(srvx@0.10.1): optionalDependencies: srvx: 0.10.1 @@ -37345,14 +38065,13 @@ snapshots: optionalDependencies: srvx: 0.11.15 - crossws@0.4.4(srvx@0.11.18): - optionalDependencies: - srvx: 0.11.18 - crossws@0.4.5(srvx@0.11.22): optionalDependencies: srvx: 0.11.22 - optional: true + + crossws@0.4.5(srvx@0.8.16): + optionalDependencies: + srvx: 0.8.16 css-loader@7.1.2(webpack@5.97.1): dependencies: @@ -37769,16 +38488,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 - enhanced-resolve@5.18.3: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.0 - - enhanced-resolve@5.20.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.0 - enhanced-resolve@5.21.6: dependencies: graceful-fs: 4.2.11 @@ -37805,11 +38514,20 @@ snapshots: env-paths@3.0.0: {} + env-runner@0.1.16(wrangler@4.75.0): + dependencies: + crossws: 0.4.10(srvx@0.11.22) + exsolve: 1.1.1 + httpxy: 0.5.5 + srvx: 0.11.22 + optionalDependencies: + wrangler: 4.75.0 + env-runner@0.1.6(miniflare@4.20260317.0): dependencies: - crossws: 0.4.4(srvx@0.11.18) + crossws: 0.4.5(srvx@0.11.22) httpxy: 0.3.1 - srvx: 0.11.18 + srvx: 0.11.22 optionalDependencies: miniflare: 4.20260317.0 @@ -38077,7 +38795,7 @@ snapshots: eslint-plugin-n@17.23.1(@typescript/typescript6@6.0.2)(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)) - enhanced-resolve: 5.18.3 + enhanced-resolve: 5.21.6 eslint: 9.22.0(jiti@2.7.0)(supports-color@10.2.2) eslint-plugin-es-x: 7.8.0(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)) get-tsconfig: 4.10.1 @@ -38092,7 +38810,7 @@ snapshots: eslint-plugin-n@17.24.0(@typescript/typescript6@6.0.2)(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)): dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)) - enhanced-resolve: 5.18.3 + enhanced-resolve: 5.21.6 eslint: 9.22.0(jiti@2.7.0)(supports-color@10.2.2) eslint-plugin-es-x: 7.8.0(eslint@9.22.0(jiti@2.7.0)(supports-color@10.2.2)) get-tsconfig: 4.10.1 @@ -38460,6 +39178,8 @@ snapshots: exsolve@1.0.8: {} + exsolve@1.1.1: {} + extendable-error@0.1.7: {} extract-zip@2.0.1(supports-color@10.2.2): @@ -38890,6 +39610,14 @@ snapshots: dependencies: duplexer: 0.1.2 + h3-rules@0.1.0(h3@2.0.1-rc.26(crossws@0.4.10(srvx@0.11.22)))(ocache@0.2.0): + dependencies: + h3: 2.0.1-rc.26(crossws@0.4.10(srvx@0.11.22)) + rou3: 0.9.2 + ufo: 1.6.4 + optionalDependencies: + ocache: 0.2.0 + h3@1.15.5: dependencies: cookie-es: 1.2.2 @@ -38905,23 +39633,39 @@ snapshots: h3@2.0.1-rc.14(crossws@0.4.3(srvx@0.10.1)): dependencies: rou3: 0.7.12 - srvx: 0.11.15 + srvx: 0.11.22 optionalDependencies: crossws: 0.4.3(srvx@0.10.1) h3@2.0.1-rc.16(crossws@0.4.4(srvx@0.11.15)): dependencies: rou3: 0.8.1 - srvx: 0.11.18 + srvx: 0.11.22 optionalDependencies: crossws: 0.4.4(srvx@0.11.15) - h3@2.0.1-rc.20(crossws@0.4.5(srvx@0.11.22)): + h3@2.0.1-rc.2(crossws@0.4.5(srvx@0.8.16)): + dependencies: + cookie-es: 2.0.0 + fetchdts: 0.1.7 + rou3: 0.7.12 + srvx: 0.8.16 + optionalDependencies: + crossws: 0.4.5(srvx@0.8.16) + + h3@2.0.1-rc.20(crossws@0.4.10(srvx@0.12.5)): dependencies: rou3: 0.8.1 srvx: 0.11.15 optionalDependencies: - crossws: 0.4.5(srvx@0.11.22) + crossws: 0.4.10(srvx@0.12.5) + + h3@2.0.1-rc.26(crossws@0.4.10(srvx@0.11.22)): + dependencies: + rou3: 0.9.2 + srvx: 0.12.5 + optionalDependencies: + crossws: 0.4.10(srvx@0.11.22) handle-thing@2.0.1: {} @@ -38977,6 +39721,8 @@ snapshots: hookable@6.1.0: {} + hookable@6.1.1: {} + hosted-git-info@7.0.2: dependencies: lru-cache: 10.4.3 @@ -39127,6 +39873,8 @@ snapshots: httpxy@0.3.1: {} + httpxy@0.5.5: {} + human-id@4.1.1: {} human-signals@5.0.0: {} @@ -39798,7 +40546,7 @@ snapshots: get-port-please: 3.2.0 h3: 1.15.5 http-shutdown: 1.2.2 - jiti: 2.6.1 + jiti: 2.7.0 mlly: 1.8.0 node-forge: 1.3.1 pathe: 1.1.2 @@ -40194,11 +40942,121 @@ snapshots: netlify-redirector@0.5.0: {} + nf3@0.1.12: {} + nf3@0.3.11: {} + nf3@0.3.23: {} + nf3@0.3.6: {} - nitro@3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.0.2)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)): + nitro-nightly@3.0.1-20260810-113911-16ff2809(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(dotenv@17.4.2)(giget@2.0.0)(jiti@2.7.0)(lru-cache@11.5.1)(mysql2@3.15.3)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0))(wrangler@4.75.0): + dependencies: + consola: 3.4.2 + crossws: 0.4.10(srvx@0.11.22) + db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3) + env-runner: 0.1.16(wrangler@4.75.0) + h3: 2.0.1-rc.26(crossws@0.4.10(srvx@0.11.22)) + h3-rules: 0.1.0(h3@2.0.1-rc.26(crossws@0.4.10(srvx@0.11.22)))(ocache@0.2.0) + hookable: 6.1.1 + nf3: 0.3.23 + ocache: 0.2.0 + ofetch: 2.0.0-alpha.3 + ohash: 2.0.11 + rolldown: 1.2.4 + rou3: 0.9.2 + srvx: 0.11.22 + unenv: 2.0.0-rc.24 + unstorage: 2.0.0-alpha.7(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) + optionalDependencies: + dotenv: 17.4.2 + giget: 2.0.0 + jiti: 2.7.0 + vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@netlify/runtime' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - better-sqlite3 + - chokidar + - drizzle-orm + - idb-keyval + - ioredis + - lru-cache + - miniflare + - mongodb + - mysql2 + - sqlite3 + - uploadthing + - wrangler + + nitro@3.0.0(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@4.0.3)(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.2.4)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)): + dependencies: + consola: 3.4.2 + cookie-es: 2.0.0 + crossws: 0.4.5(srvx@0.8.16) + db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3) + esbuild: 0.25.10 + fetchdts: 0.1.7 + h3: 2.0.1-rc.2(crossws@0.4.5(srvx@0.8.16)) + jiti: 2.7.0 + nf3: 0.1.12 + ofetch: 1.5.1 + ohash: 2.0.11 + rendu: 0.0.6 + rollup: 4.56.0 + srvx: 0.8.16 + undici: 7.27.2 + unenv: 2.0.0-rc.21 + unstorage: 2.0.0-alpha.3(@netlify/blobs@10.1.0)(chokidar@4.0.3)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(ofetch@1.5.1) + optionalDependencies: + rolldown: 1.2.4 + vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - better-sqlite3 + - chokidar + - drizzle-orm + - idb-keyval + - ioredis + - lru-cache + - mongodb + - mysql2 + - sqlite3 + - uploadthing + + nitro@3.0.1-alpha.2(@electric-sql/pglite@0.3.2)(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(chokidar@5.0.0)(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(mysql2@3.15.3)(rolldown@1.2.4)(rollup@4.56.0)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)): dependencies: consola: 3.4.2 crossws: 0.4.3(srvx@0.10.1) @@ -40215,7 +41073,7 @@ snapshots: unenv: 2.0.0-rc.24 unstorage: 2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3) optionalDependencies: - rolldown: 1.0.2 + rolldown: 1.2.4 rollup: 4.56.0 vite: 8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0) transitivePeerDependencies: @@ -40302,7 +41160,7 @@ snapshots: - sqlite3 - uploadthing - nitropack@2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(mysql2@3.15.3)(rolldown@1.0.2)(supports-color@10.2.2): + nitropack@2.13.1(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(@netlify/blobs@10.1.0)(mysql2@3.15.3)(rolldown@1.2.4)(supports-color@10.2.2): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 '@rollup/plugin-alias': 6.0.0(rollup@4.56.0) @@ -40355,7 +41213,7 @@ snapshots: pretty-bytes: 7.1.0 radix3: 1.1.2 rollup: 4.56.0 - rollup-plugin-visualizer: 6.0.5(rolldown@1.0.2)(rollup@4.56.0) + rollup-plugin-visualizer: 6.0.5(rolldown@1.2.4)(rollup@4.56.0) scule: 1.3.0 semver: 7.7.3 serve-placeholder: 2.0.2 @@ -40446,7 +41304,7 @@ snapshots: node-source-walk@7.0.1: dependencies: - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.2 node-stream-zip@1.15.0: {} @@ -40650,6 +41508,10 @@ snapshots: dependencies: ohash: 2.0.11 + ocache@0.2.0: + dependencies: + ohash: 2.0.11 + ofetch@1.5.1: dependencies: destr: 2.0.5 @@ -41588,6 +42450,11 @@ snapshots: lodash: 4.17.21 strip-ansi: 6.0.1 + rendu@0.0.6: + dependencies: + cookie-es: 2.0.0 + srvx: 0.8.16 + repeat-string@1.6.1: {} require-directory@2.1.1: {} @@ -41699,14 +42566,34 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.2 '@rolldown/binding-win32-x64-msvc': 1.0.2 - rollup-plugin-visualizer@6.0.5(rolldown@1.0.2)(rollup@4.56.0): + rolldown@1.2.4: + dependencies: + '@oxc-project/types': 0.144.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.2.4 + '@rolldown/binding-darwin-arm64': 1.2.4 + '@rolldown/binding-darwin-x64': 1.2.4 + '@rolldown/binding-freebsd-x64': 1.2.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 + '@rolldown/binding-linux-arm64-gnu': 1.2.4 + '@rolldown/binding-linux-arm64-musl': 1.2.4 + '@rolldown/binding-linux-ppc64-gnu': 1.2.4 + '@rolldown/binding-linux-s390x-gnu': 1.2.4 + '@rolldown/binding-linux-x64-gnu': 1.2.4 + '@rolldown/binding-linux-x64-musl': 1.2.4 + '@rolldown/binding-openharmony-arm64': 1.2.4 + '@rolldown/binding-win32-arm64-msvc': 1.2.4 + '@rolldown/binding-win32-x64-msvc': 1.2.4 + + rollup-plugin-visualizer@6.0.5(rolldown@1.2.4)(rollup@4.56.0): dependencies: open: 8.4.2 picomatch: 4.0.4 source-map: 0.7.6 yargs: 17.7.2 optionalDependencies: - rolldown: 1.0.2 + rolldown: 1.2.4 rollup: 4.56.0 rollup@4.56.0: @@ -41744,6 +42631,8 @@ snapshots: rou3@0.8.1: {} + rou3@0.9.2: {} + router@2.2.0(supports-color@10.2.2): dependencies: debug: 4.4.3(supports-color@10.2.2) @@ -42129,7 +43018,7 @@ snapshots: solid-refresh@0.6.3(solid-js@1.9.12)(supports-color@10.2.2): dependencies: '@babel/generator': 7.28.5 - '@babel/helper-module-imports': 7.27.1(supports-color@10.2.2) + '@babel/helper-module-imports': 7.28.6(supports-color@10.2.2) '@babel/types': 7.29.0 solid-js: 1.9.12 transitivePeerDependencies: @@ -42223,6 +43112,10 @@ snapshots: srvx@0.11.22: {} + srvx@0.12.5: {} + + srvx@0.8.16: {} + stable-hash-x@0.2.0: {} stack-trace@0.0.10: {} @@ -42388,8 +43281,6 @@ snapshots: tapable@2.2.1: {} - tapable@2.3.0: {} - tapable@2.3.3: {} tar-stream@2.2.0: @@ -42728,6 +43619,8 @@ snapshots: ufo@1.6.3: {} + ufo@1.6.4: {} + uint8array-extras@1.5.0: {} ulid@3.0.1: {} @@ -42753,6 +43646,14 @@ snapshots: undici@7.27.2: {} + unenv@2.0.0-rc.21: + dependencies: + defu: 6.1.4 + exsolve: 1.0.8 + ohash: 2.0.11 + pathe: 2.0.3 + ufo: 1.6.3 + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 @@ -42854,6 +43755,15 @@ snapshots: db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3) ioredis: 5.9.2(supports-color@10.2.2) + unstorage@2.0.0-alpha.3(@netlify/blobs@10.1.0)(chokidar@4.0.3)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(ofetch@1.5.1): + optionalDependencies: + '@netlify/blobs': 10.1.0 + chokidar: 4.0.3 + db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3) + ioredis: 5.9.2(supports-color@10.2.2) + lru-cache: 11.5.1 + ofetch: 1.5.1 + unstorage@2.0.0-alpha.5(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(ioredis@5.9.2(supports-color@10.2.2))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): optionalDependencies: '@netlify/blobs': 10.1.0 @@ -42871,6 +43781,14 @@ snapshots: lru-cache: 11.5.1 ofetch: 2.0.0-alpha.3 + unstorage@2.0.0-alpha.7(@netlify/blobs@10.1.0)(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3))(lru-cache@11.5.1)(ofetch@2.0.0-alpha.3): + optionalDependencies: + '@netlify/blobs': 10.1.0 + chokidar: 5.0.0 + db0: 0.3.4(@electric-sql/pglite@0.3.2)(@libsql/client@0.15.15)(mysql2@3.15.3) + lru-cache: 11.5.1 + ofetch: 2.0.0-alpha.3 + untun@0.1.3: dependencies: citty: 0.1.6 @@ -42881,7 +43799,7 @@ snapshots: dependencies: citty: 0.1.6 defu: 6.1.4 - jiti: 2.6.1 + jiti: 2.7.0 knitwork: 1.3.0 scule: 1.3.0 @@ -43038,9 +43956,9 @@ snapshots: vite-plugin-solid@2.11.10(@testing-library/jest-dom@6.6.3)(solid-js@1.9.12)(supports-color@10.2.2)(vite@8.0.14(@types/node@25.0.9)(esbuild@0.27.4)(jiti@2.7.0)(sass@1.97.2)(terser@5.37.0)(tsx@4.20.3)(yaml@2.9.0)): dependencies: - '@babel/core': 7.28.5(supports-color@10.2.2) + '@babel/core': 7.29.0(supports-color@10.2.2) '@types/babel__core': 7.20.5 - babel-preset-solid: 1.9.10(@babel/core@7.28.5(supports-color@10.2.2))(solid-js@1.9.12) + babel-preset-solid: 1.9.10(@babel/core@7.29.0(supports-color@10.2.2))(solid-js@1.9.12) merge-anything: 5.1.7 solid-js: 1.9.12 solid-refresh: 0.6.3(solid-js@1.9.12)(supports-color@10.2.2)