diff --git a/integration/helpers/fixtures.ts b/integration/helpers/fixtures.ts new file mode 100644 index 0000000000..ab644568ae --- /dev/null +++ b/integration/helpers/fixtures.ts @@ -0,0 +1,138 @@ +import { ChildProcess } from "node:child_process"; +import * as fs from "node:fs/promises"; +import { fileURLToPath } from "node:url"; + +import { test as base } from "@playwright/test"; +import { + execa, + ExecaError, + type Options, + parseCommandString, + type ResultPromise, +} from "execa"; +import * as Path from "pathe"; + +import type { TemplateName } from "./vite.js"; + +declare module "@playwright/test" { + interface Page { + errors: Error[]; + } +} + +const __filename = fileURLToPath(import.meta.url); +const ROOT = Path.join(__filename, "../../.."); +const TMP = Path.join(ROOT, ".tmp/integration"); +const templatePath = (templateName: string) => + Path.resolve(ROOT, "integration/helpers", templateName); + +type Edits = Record string)>; + +async function applyEdits(cwd: string, edits: Edits) { + const promises = Object.entries(edits).map(async ([file, transform]) => { + const filepath = Path.join(cwd, file); + await fs.writeFile( + filepath, + typeof transform === "function" + ? transform(await fs.readFile(filepath, "utf8")) + : transform, + "utf8", + ); + return; + }); + await Promise.all(promises); +} + +export const test = base.extend<{ + template: TemplateName; + files: Edits; + cwd: string; + edit: (edits: Edits) => Promise; + $: ( + command: string, + options?: Pick, + ) => ResultPromise<{ reject: false }> & { + buffer: { stdout: string; stderr: string }; + }; +}>({ + template: ["vite-6-template", { option: true }], + files: [{}, { option: true }], + page: async ({ page }, use) => { + page.errors = []; + page.on("pageerror", (error: Error) => page.errors.push(error)); + await use(page); + }, + + cwd: async ({ template, files }, use, testInfo) => { + await fs.mkdir(TMP, { recursive: true }); + const cwd = await fs.mkdtemp(Path.join(TMP, template + "-")); + testInfo.attach("cwd", { body: cwd }); + + await fs.cp(templatePath(template), cwd, { + errorOnExist: true, + recursive: true, + }); + + await applyEdits(cwd, files); + + await use(cwd); + }, + + edit: async ({ cwd }, use) => { + await use(async (edits) => applyEdits(cwd, edits)); + }, + + $: async ({ cwd }, use) => { + const spawn = execa({ + cwd, + env: { + NO_COLOR: "1", + FORCE_COLOR: "0", + }, + reject: false, + }); + + let testHasEnded = false; + const processes: Array = []; + const unexpectedErrors: Array = []; + + await use((command, options = {}) => { + const [file, ...args] = parseCommandString(command); + + const p = spawn(file, args, options); + if (p instanceof ChildProcess) { + processes.push(p); + } + + p.then((result) => { + if (!(result instanceof Error)) return result; + + // Once the test has ended, this process will be killed as part of its teardown resulting in an ExecaError. + // We only care about surfacing errors that occurred during test execution, not during teardown. + const expectedError = testHasEnded && result instanceof ExecaError; + if (expectedError) return result; + unexpectedErrors.push(result); + }); + + const buffer = { stdout: "", stderr: "" }; + p.stdout?.on("data", (data) => (buffer.stdout += data.toString())); + p.stderr?.on("data", (data) => (buffer.stderr += data.toString())); + return Object.assign(p, { buffer }); + }); + + testHasEnded = true; + processes.forEach((p) => p.kill()); + + // Throw any unexpected errors that occurred during test execution + if (unexpectedErrors.length > 0) { + const errorMessage = + unexpectedErrors.length === 1 + ? `Unexpected process error: ${unexpectedErrors[0].message}` + : `${unexpectedErrors.length} unexpected process errors:\n${unexpectedErrors.map((e, i) => `${i + 1}. ${e.message}`).join("\n")}`; + + const error = new Error(errorMessage); + error.stack = unexpectedErrors[0].stack; + throw error; + } + }, +}); diff --git a/integration/package.json b/integration/package.json index 824fed2598..fb81ff14d5 100644 --- a/integration/package.json +++ b/integration/package.json @@ -25,7 +25,7 @@ "cheerio": "^1.0.0-rc.12", "cross-spawn": "^7.0.3", "dedent": "^0.7.0", - "execa": "^5.1.1", + "execa": "^9.6.0", "express": "^4.19.2", "get-port": "^5.1.1", "glob": "8.0.3", diff --git a/integration/typegen-test.ts b/integration/typegen-test.ts index 9bcd7ac928..9ffcc5e183 100644 --- a/integration/typegen-test.ts +++ b/integration/typegen-test.ts @@ -1,31 +1,16 @@ -import { spawnSync } from "node:child_process"; -import { mkdirSync, renameSync } from "node:fs"; -import { readFile, writeFile } from "node:fs/promises"; -import * as path from "node:path"; +import fs from "node:fs/promises"; -import { expect, test } from "@playwright/test"; -import dedent from "dedent"; +import tsx from "dedent"; +import * as Path from "pathe"; -import { createProject } from "./helpers/vite"; +import { test } from "./helpers/fixtures"; -const tsx = dedent; - -const nodeBin = process.argv[0]; -const reactRouterBin = "node_modules/@react-router/dev/dist/cli/index.js"; -const tscBin = "node_modules/typescript/bin/tsc"; - -function typecheck(cwd: string) { - const typegen = spawnSync(nodeBin, [reactRouterBin, "typegen"], { cwd }); - expect(typegen.stdout.toString()).toBe(""); - expect(typegen.stderr.toString()).toBe(""); - expect(typegen.status).toBe(0); - - return spawnSync(nodeBin, [tscBin], { cwd }); -} - -const viteConfig = ({ rsc }: { rsc: boolean } = { rsc: false }) => { +const viteConfig = ({ rsc }: { rsc: boolean }) => { + const reactRouterImportSpecifier = rsc + ? "unstable_reactRouterRSC as reactRouter" + : "reactRouter"; return tsx` - import { ${rsc ? "unstable_reactRouterRSC as reactRouter" : "reactRouter"} } from "@react-router/dev/vite"; + import { ${reactRouterImportSpecifier} } from "@react-router/dev/vite"; export default { plugins: [reactRouter()], @@ -33,19 +18,22 @@ const viteConfig = ({ rsc }: { rsc: boolean } = { rsc: false }) => { `; }; -const expectType = tsx` - export type Expect = T - - export type Equal = - (() => T extends X ? 1 : 2) extends - (() => T extends Y ? 1 : 2) ? true : false -`; +test.use({ + files: { + "vite.config.ts": viteConfig({ rsc: false }), + "app/expect-type.ts": tsx` + export type Expect = T + + export type Equal = + (() => T extends X ? 1 : 2) extends + (() => T extends Y ? 1 : 2) ? true : false + `, + }, +}); test.describe("typegen", () => { - test("basic", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, + test("basic", async ({ edit, $ }) => { + await edit({ "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -68,237 +56,208 @@ test.describe("typegen", () => { } `, }); - - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + await $("pnpm typecheck"); }); - test.describe("params", () => { - test("repeated", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; - - export default [ - route("only-required/:id/:id", "routes/only-required.tsx"), - route("only-optional/:id?/:id?", "routes/only-optional.tsx"), - route("optional-then-required/:id?/:id", "routes/optional-then-required.tsx"), - route("required-then-optional/:id/:id?", "routes/required-then-optional.tsx"), - ] satisfies RouteConfig; - `, - "app/routes/only-required.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/only-required" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/only-optional.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/only-optional" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/optional-then-required.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/optional-then-required" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/required-then-optional.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/required-then-optional" - - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - - test("splat", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; + test("repeated params", async ({ edit, $ }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; - export default [ - route("splat/*", "routes/splat.tsx") - ] satisfies RouteConfig; - `, - "app/routes/splat.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/splat" + export default [ + route("only-required/:id/:id", "routes/only-required.tsx"), + route("only-optional/:id?/:id?", "routes/only-optional.tsx"), + route("optional-then-required/:id?/:id", "routes/optional-then-required.tsx"), + route("required-then-optional/:id/:id?", "routes/required-then-optional.tsx"), + ] satisfies RouteConfig; + `, + "app/routes/only-required.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/only-required" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/only-optional.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/only-optional" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/optional-then-required.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/optional-then-required" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/required-then-optional.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/required-then-optional" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, }); + await $("pnpm typecheck"); + }); - test("with extension", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; + test("params with extension", async ({ edit, $ }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; - export default [ - route(":lang.xml", "routes/param-with-ext.tsx"), - route(":user?.pdf", "routes/optional-param-with-ext.tsx"), - ] satisfies RouteConfig; - `, - "app/routes/param-with-ext.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/param-with-ext" + export default [ + route(":lang.xml", "routes/param-with-ext.tsx"), + route(":user?.pdf", "routes/optional-param-with-ext.tsx"), + ] satisfies RouteConfig; + `, + "app/routes/param-with-ext.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/param-with-ext" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/optional-param-with-ext.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/optional-param-with-ext" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/optional-param-with-ext.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/optional-param-with-ext" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, }); + await $("pnpm typecheck"); + }); - test("normalized params", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route, layout } from "@react-router/dev/routes"; + test("normalized param", async ({ edit, $ }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig, route, layout } from "@react-router/dev/routes"; - export default [ - route("parent/:p", "routes/parent.tsx", [ - route("route/:r", "routes/route.tsx", [ - route("child1/:c1a/:c1b", "routes/child1.tsx"), - route("child2/:c2a/:c2b", "routes/child2.tsx") - ]), + export default [ + route("parent/:p", "routes/parent.tsx", [ + route("route/:r", "routes/route.tsx", [ + route("child1/:c1a/:c1b", "routes/child1.tsx"), + route("child2/:c2a/:c2b", "routes/child2.tsx") ]), - layout("routes/layout.tsx", [ - route("in-layout1/:id", "routes/in-layout1.tsx"), - route("in-layout2/:id/:other", "routes/in-layout2.tsx") - ]) - ] satisfies RouteConfig; - `, - "app/routes/parent.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/parent" + ]), + layout("routes/layout.tsx", [ + route("in-layout1/:id", "routes/in-layout1.tsx"), + route("in-layout2/:id/:other", "routes/in-layout2.tsx") + ]) + ] satisfies RouteConfig; + `, + "app/routes/parent.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/parent" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/route.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/route" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/route.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/route" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/child1.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/child1" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/child1.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/child1" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/child2.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/child2" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/child2.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/child2" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/layout.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/layout" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/layout.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/layout" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/in-layout1.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/in-layout1" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/in-layout1.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/in-layout1" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - "app/routes/in-layout2.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/in-layout2" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + "app/routes/in-layout2.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/in-layout2" - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return null - } - `, - }); + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, + }); + await $("pnpm typecheck"); + }); + + test("splat", async ({ edit, $ }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("splat/*", "routes/splat.tsx") + ] satisfies RouteConfig; + `, + "app/routes/splat.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/splat" - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return null + } + `, }); + await $("pnpm typecheck"); }); - test("clientLoader.hydrate = true", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, + test("clientLoader.hydrate = true", async ({ edit, $ }) => { + await edit({ "app/routes/_index.tsx": tsx` import type { Expect, Equal } from "../expect-type" import type { Route } from "./+types/_index" @@ -322,16 +281,11 @@ test.describe("typegen", () => { } `, }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + await $("pnpm typecheck"); }); - test("clientLoader data should not be serialized", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, + test("clientLoader data should not be serialized", async ({ edit, $ }) => { + await edit({ "app/routes/_index.tsx": tsx` import { useRouteLoaderData } from "react-router" @@ -352,360 +306,67 @@ test.describe("typegen", () => { } `, }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + await $("pnpm typecheck"); }); - test.describe("server-first route component detection", async () => { - test.describe("ServerComponent export", async () => { - test("when RSC Framework Mode plugin is present", async () => { - const cwd = await await createProject({ - "vite.config.ts": viteConfig({ rsc: true }), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; + test("custom app dir", async ({ cwd, edit, $ }) => { + await edit({ + "react-router.config.ts": tsx` + export default { + appDirectory: "src/myapp", + } + `, + "app/routes/products.$id.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/products.$id" - export default [ - route("server-component/:id", "routes/server-component.tsx") - ] satisfies RouteConfig; - `, - "app/routes/server-component.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/server-component" + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { planet: "world" } + } - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return { server: "server" } - } + export default function Component({ loaderData }: Route.ComponentProps) { + type Test = Expect> + return

Hello, {loaderData.planet}!

+ } + `, + }); + await fs.mkdir(Path.join(cwd, "src")); + await fs.rename(Path.join(cwd, "app"), Path.join(cwd, "src/myapp")); + await $("pnpm typecheck"); + }); - export function clientLoader() { - return { client: "client" } - } + test("matches", async ({ edit, $ }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; - export function action() { - return { server: "server" } - } + export default [ + route("parent1/:parent1", "routes/parent1.tsx", [ + route("parent2/:parent2", "routes/parent2.tsx", [ + route("current", "routes/current.tsx") + ]) + ]) + ] satisfies RouteConfig; + `, + "app/routes/parent1.tsx": tsx` + import { Outlet } from "react-router" - export function clientAction() { - return { client: "client" } - } + export function loader() { + return { parent1: 1 } + } - export function ServerComponent({ - loaderData, - actionData - }: Route.ComponentProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

ServerComponent

-

Loader data: {loaderData.server}

-

Action data: {actionData?.server}

- - ) - } - - export function ErrorBoundary({ - loaderData, - actionData - }: Route.ErrorBoundaryProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

ErrorBoundary

-

Loader data: {loaderData?.server}

-

Action data: {actionData?.server}

- - ) - } - - export function HydrateFallback({ - loaderData, - actionData - }: Route.HydrateFallbackProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

HydrateFallback

-

Loader data: {loaderData?.server}

-

Action data: {actionData?.server}

- - ) - } - `, - }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - - test("when RSC Framework Mode plugin is not present", async () => { - const cwd = await await createProject({ - "vite.config.ts": viteConfig({ rsc: false }), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; - - export default [ - route("server-component/:id", "routes/server-component.tsx") - ] satisfies RouteConfig; - `, - "app/routes/server-component.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/server-component" - - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return { server: "server" } - } - - export function clientLoader() { - return { client: "client" } - } - - export function action() { - return { server: "server" } - } - - export function clientAction() { - return { client: "client" } - } - - // This export is not used in standard Framework Mode. This is just - // to test that the typegen is unaffected by this export outside of - // RSC Framework Mode. - export function ServerComponent({ - loaderData, - actionData - }: Route.ComponentProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

ServerComponent (unused)

-

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

- {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} - - ) - } - - export function ErrorBoundary({ - loaderData, - actionData - }: Route.ErrorBoundaryProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

ErrorBoundary

- {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} - {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} - - ) - } - - export function HydrateFallback({ - loaderData, - actionData - }: Route.HydrateFallbackProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

HydrateFallback

- {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} - {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} - - ) - } - `, - }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - }); - - test.describe("default export", async () => { - async function createClientFirstRouteProject({ rsc }: { rsc: boolean }) { - return await await createProject({ - "vite.config.ts": viteConfig({ rsc }), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; - - export default [ - route("client-component/:id", "routes/client-component.tsx") - ] satisfies RouteConfig; - `, - "app/routes/client-component.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/client-component" - - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return { server: "server" } - } - - export function clientLoader() { - return { client: "client" } - } - - export function action() { - return { server: "server" } - } - - export function clientAction() { - return { client: "client" } - } - - export default function ClientComponent({ - loaderData, - actionData - }: Route.ComponentProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

default (Component)

-

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

- {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} - - ) - } - - export function ErrorBoundary({ - loaderData, - actionData - }: Route.ErrorBoundaryProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

ErrorBoundary

- {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} - {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} - - ) - } - - export function HydrateFallback({ - loaderData, - actionData - }: Route.HydrateFallbackProps) { - type TestLoaderData = Expect> - type TestActionData = Expect> - - return ( - <> -

HydrateFallback

- {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} - {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} - - ) - } - `, - }); - } - - test("when RSC Framework Mode plugin is present", async () => { - const cwd = await createClientFirstRouteProject({ rsc: true }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - - test("when RSC Framework Mode plugin is not present", async () => { - const cwd = await createClientFirstRouteProject({ rsc: false }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - }); - }); - - test("custom app dir", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "react-router.config.ts": tsx` - export default { - appDirectory: "src/myapp", - } - `, - "app/expect-type.ts": expectType, - "app/routes/products.$id.tsx": tsx` - import type { Expect, Equal } from "../expect-type" - import type { Route } from "./+types/products.$id" - - export function loader({ params }: Route.LoaderArgs) { - type Test = Expect> - return { planet: "world" } - } - - export default function Component({ loaderData }: Route.ComponentProps) { - type Test = Expect> - return

Hello, {loaderData.planet}!

- } - `, - }); - mkdirSync(path.join(cwd, "src")); - renameSync(path.join(cwd, "app"), path.join(cwd, "src/myapp")); - - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - - test("matches", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, - "app/routes.ts": tsx` - import { type RouteConfig, route } from "@react-router/dev/routes"; - - export default [ - route("parent1/:parent1", "routes/parent1.tsx", [ - route("parent2/:parent2", "routes/parent2.tsx", [ - route("current", "routes/current.tsx") - ]) - ]) - ] satisfies RouteConfig; - `, - "app/routes/parent1.tsx": tsx` - import { Outlet } from "react-router" - - export function loader() { - return { parent1: 1 } - } - - export default function Component() { - return ( -
-

Parent1

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

Parent1

+ +
+ ) + } + `, + "app/routes/parent2.tsx": tsx` + import { Outlet } from "react-router" export function loader() { return { parent2: 2 } @@ -764,16 +425,11 @@ test.describe("typegen", () => { } `, }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + await $("pnpm typecheck"); }); - test("route files with absolute paths", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, + test("route files with absolute paths", async ({ edit, $ }) => { + await edit({ "app/routes.ts": tsx` import path from "node:path"; import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -797,17 +453,11 @@ test.describe("typegen", () => { } `, }); - - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + await $("pnpm typecheck"); }); - test("href", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, + test("href", async ({ edit, $ }) => { + await edit({ "app/routes.ts": tsx` import path from "node:path"; import { type RouteConfig, route } from "@react-router/dev/routes"; @@ -855,101 +505,23 @@ test.describe("typegen", () => { // @ts-expect-error href("/optional-param") - // @ts-expect-error - href("/optional-param/:opt", { opt: "hello" }) - href("/optional-param/:opt?") - href("/optional-param/:opt?", { opt: "hello" }) - - href("/leading-and-trailing-slash") - // @ts-expect-error - href("/leading-and-trailing-slash/") - - export default function Component() {} - `, - }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - - test.describe("virtual:react-router/server-build", async () => { - test("static import matches 'createRequestHandler' argument type", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/routes.ts": tsx` - import { type RouteConfig } from "@react-router/dev/routes"; - export default [] satisfies RouteConfig; - `, - "app/handler.ts": tsx` - import { createRequestHandler } from "react-router"; - import * as serverBuild from "virtual:react-router/server-build"; - export default createRequestHandler(serverBuild); - `, - }); - - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); - - test("works with tsconfig 'moduleDetection' set to 'force'", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/routes.ts": tsx` - import { type RouteConfig } from "@react-router/dev/routes"; - export default [] satisfies RouteConfig; - `, - "app/handler.ts": tsx` - import { createRequestHandler } from "react-router"; - import * as serverBuild from "virtual:react-router/server-build"; - export default createRequestHandler(serverBuild); - `, - }); - - const tsconfig = JSON.parse( - await readFile(path.join(cwd, "tsconfig.json"), "utf-8"), - ); - tsconfig.compilerOptions.moduleDetection = "force"; - await writeFile( - path.join(cwd, "tsconfig.json"), - JSON.stringify(tsconfig), - "utf-8", - ); - - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); - }); + // @ts-expect-error + href("/optional-param/:opt", { opt: "hello" }) + href("/optional-param/:opt?") + href("/optional-param/:opt?", { opt: "hello" }) - test("dynamic import matches 'createRequestHandler' function argument type", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/routes.ts": tsx` - import { type RouteConfig } from "@react-router/dev/routes"; - export default [] satisfies RouteConfig; - `, - "app/handler.ts": tsx` - import { createRequestHandler } from "react-router"; - export default createRequestHandler( - () => import("virtual:react-router/server-build") - ); - `, - }); + href("/leading-and-trailing-slash") + // @ts-expect-error + href("/leading-and-trailing-slash/") - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + export default function Component() {} + `, }); + await $("pnpm typecheck"); }); - test("reuse route file at multiple paths", async () => { - const cwd = await createProject({ - "vite.config.ts": viteConfig(), - "app/expect-type.ts": expectType, + test("reuse route file at multiple paths", async ({ edit, $ }) => { + await edit({ "app/routes.ts": tsx` import { type RouteConfig, route } from "@react-router/dev/routes"; export default [ @@ -1021,10 +593,344 @@ test.describe("typegen", () => { } `, }); + await $("pnpm typecheck"); + }); + + test.describe("virtual:react-router/server-build", async () => { + test("static import matches 'createRequestHandler' argument type", async ({ + edit, + $, + }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig } from "@react-router/dev/routes"; + export default [] satisfies RouteConfig; + `, + "app/handler.ts": tsx` + import { createRequestHandler } from "react-router"; + import * as serverBuild from "virtual:react-router/server-build"; + export default createRequestHandler(serverBuild); + `, + }); + await $("pnpm typecheck"); + }); + + test("works with tsconfig 'moduleDetection' set to 'force'", async ({ + edit, + $, + }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig } from "@react-router/dev/routes"; + export default [] satisfies RouteConfig; + `, + "app/handler.ts": tsx` + import { createRequestHandler } from "react-router"; + import * as serverBuild from "virtual:react-router/server-build"; + export default createRequestHandler(serverBuild); + `, + "tsconfig.json": (contents) => { + const tsconfig = JSON.parse(contents); + tsconfig.compilerOptions.moduleDetection = "force"; + return JSON.stringify(tsconfig, null, 2); + }, + }); + await $("pnpm typecheck"); + }); + + test("dynamic import matches 'createRequestHandler' function argument type", async ({ + edit, + $, + }) => { + await edit({ + "app/routes.ts": tsx` + import { type RouteConfig } from "@react-router/dev/routes"; + export default [] satisfies RouteConfig; + `, + "app/handler.ts": tsx` + import { createRequestHandler } from "react-router"; + export default createRequestHandler( + () => import("virtual:react-router/server-build") + ); + `, + }); + await $("pnpm typecheck"); + }); + }); + + test.describe("server-first route component detection", () => { + test.describe("ServerComponent export", () => { + test("when RSC Framework Mode plugin is present", async ({ edit, $ }) => { + await edit({ + "vite.config.ts": viteConfig({ rsc: true }), + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("server-component/:id", "routes/server-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/server-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/server-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function clientLoader() { + return { client: "client" } + } + + export function action() { + return { server: "server" } + } + + export function clientAction() { + return { client: "client" } + } + + export function ServerComponent({ + loaderData, + actionData + }: Route.ComponentProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ServerComponent

+

Loader data: {loaderData.server}

+

Action data: {actionData?.server}

+ + ) + } + + export function ErrorBoundary({ + loaderData, + actionData + }: Route.ErrorBoundaryProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ErrorBoundary

+

Loader data: {loaderData?.server}

+

Action data: {actionData?.server}

+ + ) + } + + export function HydrateFallback({ + loaderData, + actionData + }: Route.HydrateFallbackProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

HydrateFallback

+

Loader data: {loaderData?.server}

+

Action data: {actionData?.server}

+ + ) + } + `, + }); + await $("pnpm typecheck"); + }); + + test("when RSC Framework Mode plugin is not present", async ({ + edit, + $, + }) => { + await edit({ + "vite.config.ts": viteConfig({ rsc: false }), + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("server-component/:id", "routes/server-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/server-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/server-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function clientLoader() { + return { client: "client" } + } + + export function action() { + return { server: "server" } + } + + export function clientAction() { + return { client: "client" } + } + + // This export is not used in standard Framework Mode. This is just + // to test that the typegen is unaffected by this export outside of + // RSC Framework Mode. + export function ServerComponent({ + loaderData, + actionData + }: Route.ComponentProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ServerComponent (unused)

+

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

+ {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function ErrorBoundary({ + loaderData, + actionData + }: Route.ErrorBoundaryProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ErrorBoundary

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function HydrateFallback({ + loaderData, + actionData + }: Route.HydrateFallbackProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

HydrateFallback

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + `, + }); + await $("pnpm typecheck"); + }); + }); - const proc = typecheck(cwd); - expect(proc.stdout.toString()).toBe(""); - expect(proc.stderr.toString()).toBe(""); - expect(proc.status).toBe(0); + test.describe("default export", () => { + const clientFirstRouteFiles = { + "app/routes.ts": tsx` + import { type RouteConfig, route } from "@react-router/dev/routes"; + + export default [ + route("client-component/:id", "routes/client-component.tsx") + ] satisfies RouteConfig; + `, + "app/routes/client-component.tsx": tsx` + import type { Expect, Equal } from "../expect-type" + import type { Route } from "./+types/client-component" + + export function loader({ params }: Route.LoaderArgs) { + type Test = Expect> + return { server: "server" } + } + + export function clientLoader() { + return { client: "client" } + } + + export function action() { + return { server: "server" } + } + + export function clientAction() { + return { client: "client" } + } + + export default function ClientComponent({ + loaderData, + actionData + }: Route.ComponentProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

default (Component)

+

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

+ {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function ErrorBoundary({ + loaderData, + actionData + }: Route.ErrorBoundaryProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

ErrorBoundary

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + + export function HydrateFallback({ + loaderData, + actionData + }: Route.HydrateFallbackProps) { + type TestLoaderData = Expect> + type TestActionData = Expect> + + return ( + <> +

HydrateFallback

+ {loaderData &&

Loader data: {"server" in loaderData ? loaderData.server : loaderData.client}

} + {actionData &&

Action data: {"server" in actionData ? actionData.server : actionData.client}

} + + ) + } + `, + }; + + test("when RSC Framework Mode plugin is present", async ({ edit, $ }) => { + await edit({ + "vite.config.ts": viteConfig({ rsc: true }), + ...clientFirstRouteFiles, + }); + await $("pnpm typecheck"); + }); + + test("when RSC Framework Mode plugin is not present", async ({ + edit, + $, + }) => { + await edit({ + "vite.config.ts": viteConfig({ rsc: false }), + ...clientFirstRouteFiles, + }); + await $("pnpm typecheck"); + }); + }); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 182ceea34b..b1ed9597b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -217,8 +217,8 @@ importers: specifier: ^0.7.0 version: 0.7.0 execa: - specifier: ^5.1.1 - version: 5.1.1 + specifier: ^9.6.0 + version: 9.6.0 express: specifier: ^4.19.2 version: 4.21.2 @@ -598,7 +598,7 @@ importers: version: 3.0.1(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)) vite-tsconfig-paths: specifier: ^4.2.1 - version: 4.3.2(typescript@5.4.5)(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)) + version: 4.3.2(typescript@5.4.5)(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)(terser@5.15.0)) integration/helpers/vite-6-template: dependencies: @@ -1161,7 +1161,7 @@ importers: version: 0.4.30(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(vite@6.2.5(@types/node@20.11.30)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.19.3)(yaml@2.8.0)) esbuild-register: specifier: ^3.6.0 - version: 3.6.0(esbuild@0.25.0) + version: 3.6.0(esbuild@0.25.4) execa: specifier: 5.1.1 version: 5.1.1 @@ -1573,7 +1573,7 @@ importers: version: 5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)(terser@5.15.0) vite-tsconfig-paths: specifier: ^4.2.1 - version: 4.3.2(typescript@5.4.5)(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)) + version: 4.3.2(typescript@5.4.5)(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)(terser@5.15.0)) playground/framework-vite-7-beta: dependencies: @@ -4561,6 +4561,9 @@ packages: '@rushstack/eslint-patch@1.10.1': resolution: {integrity: sha512-S3Kq8e7LqxkA9s7HKLqXGTGck1uwis5vAXan3FnU5yw1Ec5hsSGnq4s/UCaSqABPOnOTg7zASLyst7+ohgWexg==} + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@shikijs/engine-oniguruma@3.8.1': resolution: {integrity: sha512-KGQJZHlNY7c656qPFEQpIoqOuC4LrxjyNndRdzk5WKB/Ie87+NJCF1xo9KkOUxwxylk7rT6nhlZyTGTC4fCe1g==} @@ -4588,6 +4591,10 @@ packages: '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + '@sinonjs/commons@2.0.0': resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==} @@ -6376,6 +6383,10 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} + execa@9.6.0: + resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} + engines: {node: ^18.19.0 || >=20.5.0} + exit-hook@2.2.1: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} @@ -6438,6 +6449,10 @@ packages: picomatch: optional: true + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6584,6 +6599,10 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + get-symbol-description@1.0.2: resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} engines: {node: '>= 0.4'} @@ -6767,6 +6786,10 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -6988,6 +7011,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + is-string@1.0.7: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} @@ -7004,6 +7031,10 @@ packages: resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} engines: {node: '>= 0.4'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -8004,6 +8035,10 @@ packages: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -8149,6 +8184,10 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + parse-statements@1.0.11: resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} @@ -8180,6 +8219,10 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -8318,6 +8361,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + printable-characters@1.0.42: resolution: {integrity: sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==} @@ -9064,6 +9111,10 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -9388,6 +9439,10 @@ packages: resolution: {integrity: sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==} engines: {node: '>=4'} + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + unified@10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} @@ -9878,6 +9933,10 @@ packages: resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + youch@3.3.4: resolution: {integrity: sha512-UeVBXie8cA35DS6+nBkls68xaBBXCye0CNznrhszZjTbRVnJKQuNsyLKBTTL4ln1o1rh2PKtv35twV7irj5SEg==} @@ -12878,6 +12937,8 @@ snapshots: '@rushstack/eslint-patch@1.10.1': {} + '@sec-ant/readable-stream@0.4.1': {} + '@shikijs/engine-oniguruma@3.8.1': dependencies: '@shikijs/types': 3.8.1 @@ -12908,6 +12969,8 @@ snapshots: '@sinclair/typebox@0.27.8': {} + '@sindresorhus/merge-streams@4.0.0': {} + '@sinonjs/commons@2.0.0': dependencies: type-detect: 4.0.8 @@ -14784,6 +14847,13 @@ snapshots: transitivePeerDependencies: - supports-color + esbuild-register@3.6.0(esbuild@0.25.4): + dependencies: + debug: 4.4.1 + esbuild: 0.25.4 + transitivePeerDependencies: + - supports-color + esbuild@0.19.12: optionalDependencies: '@esbuild/aix-ppc64': 0.19.12 @@ -15256,6 +15326,21 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 + execa@9.6.0: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + exit-hook@2.2.1: {} exit@0.1.2: {} @@ -15348,6 +15433,10 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.0.4 @@ -15502,6 +15591,11 @@ snapshots: get-stream@6.0.1: {} + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + get-symbol-description@1.0.2: dependencies: call-bind: 1.0.7 @@ -15748,6 +15842,8 @@ snapshots: human-signals@2.1.0: {} + human-signals@8.0.1: {} + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -15929,6 +16025,8 @@ snapshots: is-stream@2.0.1: {} + is-stream@4.0.1: {} + is-string@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -15945,6 +16043,8 @@ snapshots: dependencies: which-typed-array: 1.1.15 + is-unicode-supported@2.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.0.2: @@ -17543,6 +17643,11 @@ snapshots: dependencies: path-key: 3.1.1 + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -17725,6 +17830,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-ms@4.0.0: {} + parse-statements@1.0.11: {} parse5-htmlparser2-tree-adapter@7.0.0: @@ -17750,6 +17857,8 @@ snapshots: path-key@3.1.1: {} + path-key@4.0.0: {} + path-parse@1.0.7: {} path-scurry@1.10.2: @@ -17868,6 +17977,10 @@ snapshots: ansi-styles: 5.2.0 react-is: 19.1.0 + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + printable-characters@1.0.42: {} proc-log@3.0.0: {} @@ -18710,6 +18823,8 @@ snapshots: strip-final-newline@2.0.0: {} + strip-final-newline@4.0.0: {} + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -19056,6 +19171,8 @@ snapshots: unicode-property-aliases-ecmascript@2.0.0: {} + unicorn-magic@0.3.0: {} + unified@10.1.2: dependencies: '@types/unist': 2.0.10 @@ -19362,7 +19479,7 @@ snapshots: - supports-color - typescript - vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)): + vite-tsconfig-paths@4.3.2(typescript@5.4.5)(vite@5.1.3(@types/node@22.14.0)(lightningcss@1.30.1)(terser@5.15.0)): dependencies: debug: 4.4.1 globrex: 0.1.2 @@ -19703,6 +19820,8 @@ snapshots: yoctocolors-cjs@2.1.2: {} + yoctocolors@2.1.2: {} + youch@3.3.4: dependencies: cookie: 0.7.2