From 8a09e261bff9fb29c44184bc79b8e54445751ca7 Mon Sep 17 00:00:00 2001 From: Youssef Benlemlih Date: Wed, 18 Dec 2024 22:19:11 +0100 Subject: [PATCH 01/14] docs: add custom link example for mantine (#3033) --- docs/framework/react/guide/custom-link.md | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/framework/react/guide/custom-link.md b/docs/framework/react/guide/custom-link.md index 3edd86396d..5db96a6277 100644 --- a/docs/framework/react/guide/custom-link.md +++ b/docs/framework/react/guide/custom-link.md @@ -159,3 +159,30 @@ export const CustomLink: LinkComponent = (props) => { return } ``` + +### Mantine example + +```tsx +import * as React from "react"; +import { createLink, LinkComponent } from "@tanstack/react-router"; +import { Anchor, AnchorProps } from "@mantine/core"; + +interface MantineAnchorProps extends Omit { + // Add any additional props you want to pass to the anchor +} + +const MantineLinkComponent = React.forwardRef< + HTMLAnchorElement, + MantineAnchorProps +>((props, ref) => { + return ; +}); + +const CreatedLinkComponent = createLink(MantineLinkComponent); + +export const CustomLink: LinkComponent = ( + props +) => { + return ; +}; +``` \ No newline at end of file From 253284a196404a2b3dceaeec4b93933ec9e13ff2 Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:20:21 +0000 Subject: [PATCH 02/14] ci: apply automated fixes --- docs/framework/react/guide/custom-link.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/framework/react/guide/custom-link.md b/docs/framework/react/guide/custom-link.md index 5db96a6277..28c9f06038 100644 --- a/docs/framework/react/guide/custom-link.md +++ b/docs/framework/react/guide/custom-link.md @@ -163,11 +163,11 @@ export const CustomLink: LinkComponent = (props) => { ### Mantine example ```tsx -import * as React from "react"; -import { createLink, LinkComponent } from "@tanstack/react-router"; -import { Anchor, AnchorProps } from "@mantine/core"; +import * as React from 'react' +import { createLink, LinkComponent } from '@tanstack/react-router' +import { Anchor, AnchorProps } from '@mantine/core' -interface MantineAnchorProps extends Omit { +interface MantineAnchorProps extends Omit { // Add any additional props you want to pass to the anchor } @@ -175,14 +175,14 @@ const MantineLinkComponent = React.forwardRef< HTMLAnchorElement, MantineAnchorProps >((props, ref) => { - return ; -}); + return +}) -const CreatedLinkComponent = createLink(MantineLinkComponent); +const CreatedLinkComponent = createLink(MantineLinkComponent) export const CustomLink: LinkComponent = ( - props + props, ) => { - return ; -}; -``` \ No newline at end of file + return +} +``` From 347544cc3b0dd755f0bd22a2643fd4549d4ef419 Mon Sep 17 00:00:00 2001 From: Manuel Schiller Date: Thu, 19 Dec 2024 03:31:35 +0100 Subject: [PATCH 03/14] fix(react-router): make sure full matches are passed into route functions (#3039) --- packages/react-router/src/router.ts | 54 ++++++++++++++++++----------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts index 33185f2499..f51c88f322 100644 --- a/packages/react-router/src/router.ts +++ b/packages/react-router/src/router.ts @@ -1224,6 +1224,16 @@ export class Router< const matches: Array = [] + const getParentContext = (parentMatch?: AnyRouteMatch) => { + const parentMatchId = parentMatch?.id + + const parentContext = !parentMatchId + ? ((this.options.context as any) ?? {}) + : (parentMatch.context ?? this.options.context ?? {}) + + return parentContext + } + matchedRoutes.forEach((route, index) => { // Take each matched route and resolve + validate its search params // This has to happen serially because each route's search params @@ -1361,17 +1371,6 @@ export class Router< } } - const headFnContent = route.options.head?.({ - matches, - match, - params: match.params, - loaderData: match.loaderData ?? undefined, - }) - - match.links = headFnContent?.links - match.scripts = headFnContent?.scripts - match.meta = headFnContent?.meta - // If it's already a success, update the headers // These may get updated again if the match is refreshed // due to being stale @@ -1389,11 +1388,7 @@ export class Router< // update the searchError if there is one match.searchError = searchError - const parentMatchId = parentMatch?.id - - const parentContext = !parentMatchId - ? ((this.options.context as any) ?? {}) - : (parentMatch.context ?? this.options.context ?? {}) + const parentContext = getParentContext(parentMatch) match.context = { ...parentContext, @@ -1401,13 +1396,23 @@ export class Router< ...match.__beforeLoadContext, } + matches.push(match) + }) + + matches.forEach((match, index) => { + const route = this.looseRoutesById[match.routeId]! + const existingMatch = this.getMatch(match.id) + // only execute `context` if we are not just building a location if (!existingMatch && opts?._buildLocation !== true) { + const parentMatch = matches[index - 1] + const parentContext = getParentContext(parentMatch) + // Update the match's context const contextFnContext: RouteContextOptions = { - deps: loaderDeps, + deps: match.loaderDeps, params: match.params, - context: match.context, + context: parentContext, location: next, navigate: (opts: any) => this.navigate({ ...opts, _fromLocation: next }), @@ -1428,10 +1433,19 @@ export class Router< } } - matches.push(match) + const headFnContent = route.options.head?.({ + matches, + match, + params: match.params, + loaderData: match.loaderData ?? undefined, + }) + + match.links = headFnContent?.links + match.scripts = headFnContent?.scripts + match.meta = headFnContent?.meta }) - return matches as any + return matches } getMatchedRoutes = (next: ParsedLocation, dest?: BuildNextOptions) => { From dc3502ba39c337fd802c71855775b7fa0759c2da Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Thu, 19 Dec 2024 02:32:54 +0000 Subject: [PATCH 04/14] release: v1.91.3 --- examples/react/authenticated-routes/package.json | 4 ++-- .../react/basic-default-search-params/package.json | 4 ++-- .../react/basic-file-based-codesplitting/package.json | 4 ++-- examples/react/basic-file-based/package.json | 4 ++-- .../react/basic-react-query-file-based/package.json | 4 ++-- examples/react/basic-react-query/package.json | 4 ++-- examples/react/basic-ssr-file-based/package.json | 6 +++--- .../react/basic-ssr-streaming-file-based/package.json | 6 +++--- examples/react/basic-virtual-file-based/package.json | 4 ++-- .../react/basic-virtual-inside-file-based/package.json | 4 ++-- examples/react/basic/package.json | 4 ++-- examples/react/deferred-data/package.json | 4 ++-- examples/react/kitchen-sink-file-based/package.json | 4 ++-- .../kitchen-sink-react-query-file-based/package.json | 4 ++-- examples/react/kitchen-sink-react-query/package.json | 4 ++-- examples/react/kitchen-sink/package.json | 4 ++-- examples/react/large-file-based/package.json | 4 ++-- examples/react/location-masking/package.json | 4 ++-- examples/react/navigation-blocking/package.json | 4 ++-- .../react/quickstart-esbuild-file-based/package.json | 4 ++-- examples/react/quickstart-file-based/package.json | 4 ++-- .../react/quickstart-rspack-file-based/package.json | 4 ++-- .../react/quickstart-webpack-file-based/package.json | 4 ++-- examples/react/quickstart/package.json | 4 ++-- .../react/router-monorepo-react-query/package.json | 4 ++-- .../packages/app/package.json | 2 +- .../packages/router/package.json | 2 +- examples/react/router-monorepo-simple/package.json | 4 ++-- .../router-monorepo-simple/packages/app/package.json | 2 +- .../packages/router/package.json | 2 +- examples/react/scroll-restoration/package.json | 4 ++-- examples/react/search-validator-adapters/package.json | 10 +++++----- examples/react/start-basic-auth/package.json | 6 +++--- examples/react/start-basic-react-query/package.json | 8 ++++---- examples/react/start-basic-rsc/package.json | 6 +++--- examples/react/start-basic/package.json | 6 +++--- examples/react/start-clerk-basic/package.json | 6 +++--- examples/react/start-convex-trellaux/package.json | 8 ++++---- examples/react/start-counter/package.json | 4 ++-- examples/react/start-large/package.json | 6 +++--- examples/react/start-supabase-basic/package.json | 6 +++--- examples/react/start-trellaux/package.json | 8 ++++---- examples/react/with-framer-motion/package.json | 4 ++-- examples/react/with-trpc-react-query/package.json | 4 ++-- examples/react/with-trpc/package.json | 4 ++-- packages/arktype-adapter/package.json | 2 +- packages/create-router/package.json | 2 +- packages/react-router-with-query/package.json | 2 +- packages/react-router/package.json | 2 +- packages/router-devtools/package.json | 2 +- packages/start/package.json | 2 +- packages/valibot-adapter/package.json | 2 +- packages/zod-adapter/package.json | 2 +- 53 files changed, 111 insertions(+), 111 deletions(-) diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json index 7a33a75c30..1edb5629fa 100644 --- a/examples/react/authenticated-routes/package.json +++ b/examples/react/authenticated-routes/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json index fd791fe4fe..603825423e 100644 --- a/examples/react/basic-default-search-params/package.json +++ b/examples/react/basic-default-search-params/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-file-based-codesplitting/package.json b/examples/react/basic-file-based-codesplitting/package.json index 99e2232c31..2a2c43cfc2 100644 --- a/examples/react/basic-file-based-codesplitting/package.json +++ b/examples/react/basic-file-based-codesplitting/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json index df904f8b7b..a74a8194f8 100644 --- a/examples/react/basic-file-based/package.json +++ b/examples/react/basic-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json index 7451763686..a1d75f0e89 100644 --- a/examples/react/basic-react-query-file-based/package.json +++ b/examples/react/basic-react-query-file-based/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json index 26313247e7..817956d94b 100644 --- a/examples/react/basic-react-query/package.json +++ b/examples/react/basic-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index 9802bdf98c..01a4f30898 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -11,10 +11,10 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.91.2", + "@tanstack/start": "^1.91.3", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index bc4e0db977..1259dae6b1 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -11,10 +11,10 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.91.2", + "@tanstack/start": "^1.91.3", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json index 5ea4ae18f9..469479cae7 100644 --- a/examples/react/basic-virtual-file-based/package.json +++ b/examples/react/basic-virtual-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "@tanstack/virtual-file-routes": "^1.87.6", "react": "^18.2.0", diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json index dc090057ac..d0902c695d 100644 --- a/examples/react/basic-virtual-inside-file-based/package.json +++ b/examples/react/basic-virtual-inside-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "@tanstack/virtual-file-routes": "^1.87.6", "react": "^18.2.0", diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index 59d7306eb3..a3570ae5a3 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json index 5b857166fc..bae3fa1ffd 100644 --- a/examples/react/deferred-data/package.json +++ b/examples/react/deferred-data/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json index 68658c1272..25f5312334 100644 --- a/examples/react/kitchen-sink-file-based/package.json +++ b/examples/react/kitchen-sink-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json index c34a454d63..4f20caaebd 100644 --- a/examples/react/kitchen-sink-react-query-file-based/package.json +++ b/examples/react/kitchen-sink-react-query-file-based/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json index 5befe2c954..963c9177a1 100644 --- a/examples/react/kitchen-sink-react-query/package.json +++ b/examples/react/kitchen-sink-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json index efa5310b5b..40dc7ff942 100644 --- a/examples/react/kitchen-sink/package.json +++ b/examples/react/kitchen-sink/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json index 29c86107d3..5ec9fbae23 100644 --- a/examples/react/large-file-based/package.json +++ b/examples/react/large-file-based/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json index 00ebc584e2..153bbec3f0 100644 --- a/examples/react/location-masking/package.json +++ b/examples/react/location-masking/package.json @@ -11,8 +11,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json index 45d31f64ea..85d0269d89 100644 --- a/examples/react/navigation-blocking/package.json +++ b/examples/react/navigation-blocking/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 9db4a3855c..9a75ed9e7d 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -9,8 +9,8 @@ "start": "dev" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json index eb306b7e02..8a1cd02d0c 100644 --- a/examples/react/quickstart-file-based/package.json +++ b/examples/react/quickstart-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json index 8197a09385..b365b23269 100644 --- a/examples/react/quickstart-rspack-file-based/package.json +++ b/examples/react/quickstart-rspack-file-based/package.json @@ -8,8 +8,8 @@ "preview": "rsbuild preview" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json index 5a222ae4fd..ef05207b76 100644 --- a/examples/react/quickstart-webpack-file-based/package.json +++ b/examples/react/quickstart-webpack-file-based/package.json @@ -7,8 +7,8 @@ "build": "webpack build && tsc --noEmit" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json index 49f0dd8889..948dc6e4f8 100644 --- a/examples/react/quickstart/package.json +++ b/examples/react/quickstart/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json index 8142304d58..3cddaf44b9 100644 --- a/examples/react/router-monorepo-react-query/package.json +++ b/examples/react/router-monorepo-react-query/package.json @@ -12,8 +12,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/router-monorepo-react-query/packages/app/package.json b/examples/react/router-monorepo-react-query/packages/app/package.json index a16316347a..5fa21820c1 100644 --- a/examples/react/router-monorepo-react-query/packages/app/package.json +++ b/examples/react/router-monorepo-react-query/packages/app/package.json @@ -20,7 +20,7 @@ "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/router-devtools": "^1.91.3", "vite": "^6.0.3", "vite-plugin-dts": "^4.3.0" }, diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json index 3e3e4bf450..99426797a6 100644 --- a/examples/react/router-monorepo-react-query/packages/router/package.json +++ b/examples/react/router-monorepo-react-query/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.90.0", "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", + "@tanstack/react-router": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "@router-mono-react-query/post-query": "workspace:*", "redaxios": "^0.5.1", diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json index 88b0429dca..945416e9c6 100644 --- a/examples/react/router-monorepo-simple/package.json +++ b/examples/react/router-monorepo-simple/package.json @@ -8,8 +8,8 @@ "dev": "pnpm router build && pnpm post-feature build && pnpm app dev" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/router-monorepo-simple/packages/app/package.json b/examples/react/router-monorepo-simple/packages/app/package.json index 6010293138..e36a6963a7 100644 --- a/examples/react/router-monorepo-simple/packages/app/package.json +++ b/examples/react/router-monorepo-simple/packages/app/package.json @@ -19,7 +19,7 @@ "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/router-devtools": "^1.91.3", "vite": "^6.0.3", "vite-plugin-dts": "^4.3.0" }, diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json index db316ae8f7..6a36379c7c 100644 --- a/examples/react/router-monorepo-simple/packages/router/package.json +++ b/examples/react/router-monorepo-simple/packages/router/package.json @@ -9,7 +9,7 @@ "types": "./dist/index.d.ts", "dependencies": { "@tanstack/history": "^1.90.0", - "@tanstack/react-router": "^1.91.2", + "@tanstack/react-router": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "redaxios": "^0.5.1", "zod": "^3.23.8", diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json index fdd9436580..3c162e97f2 100644 --- a/examples/react/scroll-restoration/package.json +++ b/examples/react/scroll-restoration/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", + "@tanstack/react-router": "^1.91.3", "@tanstack/react-virtual": "^3.11.1", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/router-devtools": "^1.91.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json index f470654132..e2c911e4b1 100644 --- a/examples/react/search-validator-adapters/package.json +++ b/examples/react/search-validator-adapters/package.json @@ -11,12 +11,12 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/arktype-adapter": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/arktype-adapter": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/valibot-adapter": "^1.91.2", - "@tanstack/zod-adapter": "^1.91.2", + "@tanstack/valibot-adapter": "^1.91.3", + "@tanstack/zod-adapter": "^1.91.3", "arktype": "2.0.0-rc.26", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index 19a94f9093..23c71bf022 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@prisma/client": "5.22.0", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "prisma": "^5.22.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index 6771472d25..4e96d66d9b 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/react-router-with-query": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/react-router-with-query": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-basic-rsc/package.json b/examples/react/start-basic-rsc/package.json index b532d5812f..66a417119a 100644 --- a/examples/react/start-basic-rsc/package.json +++ b/examples/react/start-basic-rsc/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@babel/plugin-syntax-typescript": "^7.25.9", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "redaxios": "^0.5.1", "tailwind-merge": "^2.5.5", "vinxi": "0.5.1" diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 32588b68da..3bef128dff 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -9,9 +9,9 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json index e0cda0c0df..060e5a816a 100644 --- a/examples/react/start-clerk-basic/package.json +++ b/examples/react/start-clerk-basic/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@clerk/tanstack-start": "0.6.5", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index db99980188..e6a54c02b2 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -13,10 +13,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/react-router-with-query": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/react-router-with-query": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "@convex-dev/react-query": "0.0.0-alpha.8", "concurrently": "^8.2.2", "convex": "^1.17.3", diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index f40595ef44..628d3c0c5d 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -9,8 +9,8 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/start": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index ad7d18bfe4..e41bca4756 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -12,9 +12,9 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index cc911f25b9..f93e6b3564 100644 --- a/examples/react/start-supabase-basic/package.json +++ b/examples/react/start-supabase-basic/package.json @@ -15,9 +15,9 @@ "dependencies": { "@supabase/ssr": "^0.5.2", "@supabase/supabase-js": "^2.47.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index f9e35cac06..fca0779f77 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/react-router-with-query": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", - "@tanstack/start": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/react-router-with-query": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", + "@tanstack/start": "^1.91.3", "ky": "^1.7.2", "msw": "^2.6.8", "react": "^18.3.1", diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json index a0c6b715ee..540a3b5946 100644 --- a/examples/react/with-framer-motion/package.json +++ b/examples/react/with-framer-motion/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "redaxios": "^0.5.1", "framer-motion": "^11.13.3", "react": "^18.2.0", diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json index 41883279d9..8ae2cab652 100644 --- a/examples/react/with-trpc-react-query/package.json +++ b/examples/react/with-trpc-react-query/package.json @@ -10,8 +10,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "@trpc/client": "11.0.0-rc.660", "@trpc/react-query": "11.0.0-rc.660", diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json index 2a2238a696..d7780a1d70 100644 --- a/examples/react/with-trpc/package.json +++ b/examples/react/with-trpc/package.json @@ -8,8 +8,8 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.91.2", - "@tanstack/router-devtools": "^1.91.2", + "@tanstack/react-router": "^1.91.3", + "@tanstack/router-devtools": "^1.91.3", "@tanstack/router-plugin": "^1.91.1", "@trpc/client": "11.0.0-rc.660", "@trpc/server": "11.0.0-rc.660", diff --git a/packages/arktype-adapter/package.json b/packages/arktype-adapter/package.json index dee43724fc..7ee5fbeabe 100644 --- a/packages/arktype-adapter/package.json +++ b/packages/arktype-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/arktype-adapter", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/create-router/package.json b/packages/create-router/package.json index 6c04da53b5..ecaa0a61d4 100644 --- a/packages/create-router/package.json +++ b/packages/create-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-router", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router-with-query/package.json b/packages/react-router-with-query/package.json index b49a05c884..dfb0eca965 100644 --- a/packages/react-router-with-query/package.json +++ b/packages/react-router-with-query/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-with-query", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index f55cdffab0..0c8b748525 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json index 9eb8710711..a08522143e 100644 --- a/packages/router-devtools/package.json +++ b/packages/router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/start/package.json b/packages/start/package.json index afde4bd213..ed1f021f52 100644 --- a/packages/start/package.json +++ b/packages/start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/start", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/valibot-adapter/package.json b/packages/valibot-adapter/package.json index 3627904f0d..a05e3db1a5 100644 --- a/packages/valibot-adapter/package.json +++ b/packages/valibot-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/valibot-adapter", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/zod-adapter/package.json b/packages/zod-adapter/package.json index d6ffa242d0..367cad576d 100644 --- a/packages/zod-adapter/package.json +++ b/packages/zod-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/zod-adapter", - "version": "1.91.2", + "version": "1.91.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", From 38702a89948f107a0d9a0970d7718557f8aa1711 Mon Sep 17 00:00:00 2001 From: AlexisPin <72150908+AlexisPin@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:03:08 +0100 Subject: [PATCH 05/14] docs: minor typo in code-based-routing.md (#3043) --- docs/framework/react/guide/code-based-routing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/framework/react/guide/code-based-routing.md b/docs/framework/react/guide/code-based-routing.md index 398a5a24fb..c1e9a628e9 100644 --- a/docs/framework/react/guide/code-based-routing.md +++ b/docs/framework/react/guide/code-based-routing.md @@ -310,7 +310,7 @@ const routeTree = rootRoute.addChildren([ ]) ``` -Now both `/layout-a` and `/layout-b` will render the their contents inside of the `LayoutComponent`: +Now both `/layout-a` and `/layout-b` will render their contents inside of the `LayoutComponent`: ```tsx // URL: /layout-a From f3de244ea7d79e9970edb6b9b463715b2323ed32 Mon Sep 17 00:00:00 2001 From: timoconnellaus Date: Fri, 20 Dec 2024 10:36:02 +1100 Subject: [PATCH 06/14] feat(start): create-start cli (#2920) * create start cli * update lockfile * ci: apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- e2e/create-start/.gitignore | 10 + e2e/create-start/package.json | 16 + e2e/create-start/playwright.config.ts | 34 ++ .../tests/templates/barebones.test.ts | 38 ++ e2e/create-start/tsconfig.json | 10 + e2e/create-start/utils/setup.ts | 72 ++++ packages/create-start/.gitignore | 3 + packages/create-start/README.md | 0 packages/create-start/build.config.ts | 41 ++ packages/create-start/copyTemplates.mjs | 24 ++ packages/create-start/eslint.config.js | 16 + packages/create-start/index.js | 3 + packages/create-start/package.json | 75 ++++ packages/create-start/src/cli-entry.ts | 6 + packages/create-start/src/cli.ts | 133 +++++++ packages/create-start/src/constants.ts | 8 + packages/create-start/src/directory.ts | 73 ++++ packages/create-start/src/index.ts | 17 + packages/create-start/src/logo.ts | 44 +++ packages/create-start/src/module.ts | 286 ++++++++++++++ .../create-start/src/modules/core/index.ts | 272 +++++++++++++ .../src/modules/core/template/app.config.ts | 5 + .../src/modules/core/template/app/client.tsx | 10 + .../src/modules/core/template/app/router.tsx | 18 + .../core/template/app/routes/__root.tsx | 50 +++ .../src/modules/core/template/app/ssr.tsx | 15 + .../src/modules/core/template/tsconfig.json | 10 + packages/create-start/src/modules/git.ts | 147 +++++++ packages/create-start/src/modules/ide.ts | 88 +++++ .../create-start/src/modules/packageJson.ts | 183 +++++++++ .../src/modules/packageManager.ts | 109 ++++++ .../create-start/src/modules/vscode/index.ts | 46 +++ .../vscode/template/_dot_vscode/settings.json | 11 + .../src/templates/barebones/index.ts | 75 ++++ .../barebones/template/app/routes/index.tsx | 11 + packages/create-start/src/templates/index.ts | 75 ++++ packages/create-start/src/types.ts | 3 + packages/create-start/src/utils/debug.ts | 95 +++++ .../src/utils/getPackageManager.ts | 16 + .../src/utils/helpers/helperFactory.ts | 20 + .../create-start/src/utils/helpers/index.ts | 284 ++++++++++++++ packages/create-start/src/utils/runCmd.ts | 10 + .../src/utils/runPackageManagerCommand.ts | 26 ++ packages/create-start/src/utils/spawnCmd.ts | 39 ++ .../src/utils/validateProjectName.ts | 25 ++ packages/create-start/tests/e2e/cli.test.ts | 135 +++++++ .../tests/e2e/templates/barebones.test.ts | 79 ++++ packages/create-start/tsconfig.json | 9 + packages/create-start/vitest.config.ts | 14 + pnpm-lock.yaml | 366 +++++++++++++++++- scripts/publish.js | 4 + 51 files changed, 3140 insertions(+), 19 deletions(-) create mode 100644 e2e/create-start/.gitignore create mode 100644 e2e/create-start/package.json create mode 100644 e2e/create-start/playwright.config.ts create mode 100644 e2e/create-start/tests/templates/barebones.test.ts create mode 100644 e2e/create-start/tsconfig.json create mode 100644 e2e/create-start/utils/setup.ts create mode 100644 packages/create-start/.gitignore create mode 100644 packages/create-start/README.md create mode 100644 packages/create-start/build.config.ts create mode 100644 packages/create-start/copyTemplates.mjs create mode 100644 packages/create-start/eslint.config.js create mode 100755 packages/create-start/index.js create mode 100644 packages/create-start/package.json create mode 100644 packages/create-start/src/cli-entry.ts create mode 100644 packages/create-start/src/cli.ts create mode 100644 packages/create-start/src/constants.ts create mode 100644 packages/create-start/src/directory.ts create mode 100644 packages/create-start/src/index.ts create mode 100644 packages/create-start/src/logo.ts create mode 100644 packages/create-start/src/module.ts create mode 100644 packages/create-start/src/modules/core/index.ts create mode 100644 packages/create-start/src/modules/core/template/app.config.ts create mode 100644 packages/create-start/src/modules/core/template/app/client.tsx create mode 100644 packages/create-start/src/modules/core/template/app/router.tsx create mode 100644 packages/create-start/src/modules/core/template/app/routes/__root.tsx create mode 100644 packages/create-start/src/modules/core/template/app/ssr.tsx create mode 100644 packages/create-start/src/modules/core/template/tsconfig.json create mode 100644 packages/create-start/src/modules/git.ts create mode 100644 packages/create-start/src/modules/ide.ts create mode 100644 packages/create-start/src/modules/packageJson.ts create mode 100644 packages/create-start/src/modules/packageManager.ts create mode 100644 packages/create-start/src/modules/vscode/index.ts create mode 100644 packages/create-start/src/modules/vscode/template/_dot_vscode/settings.json create mode 100644 packages/create-start/src/templates/barebones/index.ts create mode 100644 packages/create-start/src/templates/barebones/template/app/routes/index.tsx create mode 100644 packages/create-start/src/templates/index.ts create mode 100644 packages/create-start/src/types.ts create mode 100644 packages/create-start/src/utils/debug.ts create mode 100644 packages/create-start/src/utils/getPackageManager.ts create mode 100644 packages/create-start/src/utils/helpers/helperFactory.ts create mode 100644 packages/create-start/src/utils/helpers/index.ts create mode 100644 packages/create-start/src/utils/runCmd.ts create mode 100644 packages/create-start/src/utils/runPackageManagerCommand.ts create mode 100644 packages/create-start/src/utils/spawnCmd.ts create mode 100644 packages/create-start/src/utils/validateProjectName.ts create mode 100644 packages/create-start/tests/e2e/cli.test.ts create mode 100644 packages/create-start/tests/e2e/templates/barebones.test.ts create mode 100644 packages/create-start/tsconfig.json create mode 100644 packages/create-start/vitest.config.ts diff --git a/e2e/create-start/.gitignore b/e2e/create-start/.gitignore new file mode 100644 index 0000000000..8354e4d50d --- /dev/null +++ b/e2e/create-start/.gitignore @@ -0,0 +1,10 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local + +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ \ No newline at end of file diff --git a/e2e/create-start/package.json b/e2e/create-start/package.json new file mode 100644 index 0000000000..e37cca89c2 --- /dev/null +++ b/e2e/create-start/package.json @@ -0,0 +1,16 @@ +{ + "name": "create-start-e2e", + "private": true, + "type": "module", + "scripts": { + "test:e2e": "playwright test --project=chromium" + }, + "devDependencies": { + "@playwright/test": "^1.48.2", + "@tanstack/create-start": "workspace:^", + "get-port-please": "^3.1.2", + "tempy": "^3.1.0", + "terminate": "^2.8.0", + "wait-port": "^1.1.0" + } +} diff --git a/e2e/create-start/playwright.config.ts b/e2e/create-start/playwright.config.ts new file mode 100644 index 0000000000..b07c8b268b --- /dev/null +++ b/e2e/create-start/playwright.config.ts @@ -0,0 +1,34 @@ +import { defineConfig, devices } from '@playwright/test' + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + + reporter: [['line']], + timeout: 60000, + use: { + trace: 'on-first-retry', + }, + workers: 1, + + // use: { + // /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://localhost:3001/', + // }, + + // webServer: { + // command: 'pnpm run dev', + // url: 'http://localhost:3001', + // reuseExistingServer: !process.env.CI, + // stdout: 'pipe', + // }, + + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], +}) diff --git a/e2e/create-start/tests/templates/barebones.test.ts b/e2e/create-start/tests/templates/barebones.test.ts new file mode 100644 index 0000000000..8778ef37d7 --- /dev/null +++ b/e2e/create-start/tests/templates/barebones.test.ts @@ -0,0 +1,38 @@ +import { temporaryDirectory } from 'tempy' +import { getRandomPort } from 'get-port-please' +import { unstable_scaffoldTemplate } from '@tanstack/create-start' +import { test } from '../../utils/setup' + +// Before running any tests - create the project in the temporary directory +const projectPath = temporaryDirectory() +await unstable_scaffoldTemplate({ + cfg: { + packageManager: { + installDeps: true, + packageManager: 'pnpm', + }, + git: { + setupGit: false, + }, + packageJson: { + type: 'new', + name: 'barebones-test', + }, + ide: { + ide: 'vscode', + }, + }, + targetPath: projectPath, + templateId: 'barebones', +}) + +const PORT = await getRandomPort() +test.use({ projectPath }) +test.use({ port: PORT }) +test.use({ baseURL: `http://localhost:${PORT}` }) + +test.describe('barebones template e2e', () => { + test('Navigating to index page', async ({ page }) => { + await page.goto('/') + }) +}) diff --git a/e2e/create-start/tsconfig.json b/e2e/create-start/tsconfig.json new file mode 100644 index 0000000000..0d2a31a7d7 --- /dev/null +++ b/e2e/create-start/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "strict": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "target": "ESNext", + "moduleResolution": "Bundler", + "module": "ESNext" + } +} diff --git a/e2e/create-start/utils/setup.ts b/e2e/create-start/utils/setup.ts new file mode 100644 index 0000000000..bfb4c1fd53 --- /dev/null +++ b/e2e/create-start/utils/setup.ts @@ -0,0 +1,72 @@ +import { exec, execSync } from 'node:child_process' +import { test as baseTest } from '@playwright/test' +import terminate from 'terminate/promise' +import waitPort from 'wait-port' + +async function _setup( + projectPath: string, + port: number, +): Promise<{ + PID: number + ADDR: string + killProcess: () => Promise + deleteTempDir: () => void +}> { + const ADDR = `http://localhost:${port}` + + const childProcess = exec( + `VITE_SERVER_PORT=${port} pnpm vinxi dev --port ${port}`, + { + cwd: projectPath, + }, + ) + + childProcess.stdout?.on('data', (data) => { + const message = data.toString() + console.log('Stdout:', message) + }) + + childProcess.stderr?.on('data', (data) => { + console.error('Stderr:', data.toString()) + }) + + try { + await waitPort({ port, timeout: 30000 }) // Added timeout + } catch (err) { + console.error('Failed to start server:', err) + throw err + } + + const PID = childProcess.pid! + const killProcess = async () => { + console.log('Killing process') + try { + await terminate(PID) + } catch (err) { + console.error('Failed to kill process:', err) + } + } + const deleteTempDir = () => execSync(`rm -rf ${projectPath}`) + + return { PID, ADDR, killProcess, deleteTempDir } +} + +type SetupApp = Awaited> + +export const test = baseTest.extend<{ + setupApp: SetupApp + projectPath: string + port: number + ensureServer: void +}>({ + projectPath: ['', { option: true }], + port: [0, { option: true }], + ensureServer: [ + async ({ projectPath, port }, use) => { + const setup = await _setup(projectPath, port) + await use() + await setup.killProcess() + }, + { auto: true }, + ], +}) diff --git a/packages/create-start/.gitignore b/packages/create-start/.gitignore new file mode 100644 index 0000000000..cab449e017 --- /dev/null +++ b/packages/create-start/.gitignore @@ -0,0 +1,3 @@ +test-results +dist +node_modules \ No newline at end of file diff --git a/packages/create-start/README.md b/packages/create-start/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/create-start/build.config.ts b/packages/create-start/build.config.ts new file mode 100644 index 0000000000..1cf3d4a959 --- /dev/null +++ b/packages/create-start/build.config.ts @@ -0,0 +1,41 @@ +import { defineBuildConfig } from 'unbuild' + +// Separeate config required for dev because mkdist + cli-entry doesn't work +// with stub. It will create a .d.ts and .mjs file in the src folder +const dev = defineBuildConfig({ + entries: ['src/cli-entry'], + outDir: 'dist', + clean: true, + declaration: true, + rollup: { + inlineDependencies: true, + esbuild: { + target: 'node18', + minify: false, + }, + }, +}) + +const prod = defineBuildConfig({ + entries: [ + { + builder: 'mkdist', + cleanDist: true, + input: './src/', + pattern: ['**/*.{ts,tsx}', '!**/template/**'], + }, + ], + outDir: 'dist', + clean: true, + declaration: true, + rollup: { + inlineDependencies: true, + esbuild: { + target: 'node18', + minify: false, + }, + }, +}) + +const config = process.env.BUILD_ENV === 'production' ? prod : dev +export default config diff --git a/packages/create-start/copyTemplates.mjs b/packages/create-start/copyTemplates.mjs new file mode 100644 index 0000000000..1eb3915fad --- /dev/null +++ b/packages/create-start/copyTemplates.mjs @@ -0,0 +1,24 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import fg from 'fast-glob' +import url from 'node:url' + +const __filename = url.fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +async function copyTemplates() { + const templates = await fg('**/template/**', { + cwd: path.join(__dirname, 'src'), + onlyFiles: false, + }) + + for (const template of templates) { + const src = path.join(__dirname, 'src', template) + const dest = path.join(__dirname, 'dist', template) + + await fs.mkdir(path.dirname(dest), { recursive: true }) + await fs.cp(src, dest, { recursive: true }) + } +} + +copyTemplates().catch(console.error) diff --git a/packages/create-start/eslint.config.js b/packages/create-start/eslint.config.js new file mode 100644 index 0000000000..6e0bd1c039 --- /dev/null +++ b/packages/create-start/eslint.config.js @@ -0,0 +1,16 @@ +import rootConfig from '../../eslint.config.js' + +export default [ + ...rootConfig, + { + files: ['src/templates/**/template/**/*', 'src/modules/**/template/**/*'], + rules: { + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-nocheck': false, + }, + ], + }, + }, +] diff --git a/packages/create-start/index.js b/packages/create-start/index.js new file mode 100755 index 0000000000..9dd6223fc9 --- /dev/null +++ b/packages/create-start/index.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node + +import './dist/cli-entry.mjs' diff --git a/packages/create-start/package.json b/packages/create-start/package.json new file mode 100644 index 0000000000..0a1f6db22d --- /dev/null +++ b/packages/create-start/package.json @@ -0,0 +1,75 @@ +{ + "name": "@tanstack/create-start", + "version": "1.81.5", + "description": "Modern and scalable routing for React applications", + "author": "Tim O'Connell", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/TanStack/router.git", + "directory": "packages/create-router" + }, + "homepage": "https://tanstack.com/router", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "bin": { + "create-router": "index.js" + }, + "scripts": { + "dev": "BUILD_ENV=development unbuild --stub --watch", + "clean": "rimraf ./dist && rimraf ./coverage", + "test:eslint": "eslint ./src", + "generate-templates": "node ./dist/generate-templates/index.mjs", + "build": "BUILD_ENV=production unbuild && node ./copyTemplates.mjs", + "test": "vitest run", + "test:watch": "vitest watch", + "test:coverage": "vitest run --coverage" + }, + "type": "module", + "files": [ + "index.js", + "templates", + "dist" + ], + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "exports": { + ".": { + "import": "./src/index.ts" + } + }, + "dependencies": { + "gradient-string": "^3.0.0" + }, + "devDependencies": { + "@commander-js/extra-typings": "^12.1.0", + "@inquirer/prompts": "^5.5.0", + "@inquirer/type": "^3.0.1", + "@types/cross-spawn": "^6.0.6", + "@types/validate-npm-package-name": "^4.0.2", + "cross-spawn": "^7.0.5", + "fast-glob": "^3.3.2", + "picocolors": "^1.1.1", + "rollup-plugin-copy": "^3.5.0", + "tempy": "^3.1.0", + "tiny-invariant": "^1.3.3", + "unbuild": "^2.0.0", + "validate-npm-package-name": "^5.0.1", + "yocto-spinner": "^0.1.1", + "zod": "^3.23.8" + }, + "peerDependencies": { + "@tanstack/react-router": "workspace:^", + "@tanstack/router-devtools": "workspace:^", + "@tanstack/start": "workspace:^", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "vinxi": "0.4.3" + }, + "peerDependenciesMeta": {} +} diff --git a/packages/create-start/src/cli-entry.ts b/packages/create-start/src/cli-entry.ts new file mode 100644 index 0000000000..12e38723eb --- /dev/null +++ b/packages/create-start/src/cli-entry.ts @@ -0,0 +1,6 @@ +import { runCli } from './cli' + +runCli(process.argv).catch((error) => { + console.error(error) + process.exit(1) +}) diff --git a/packages/create-start/src/cli.ts b/packages/create-start/src/cli.ts new file mode 100644 index 0000000000..000322ef2c --- /dev/null +++ b/packages/create-start/src/cli.ts @@ -0,0 +1,133 @@ +import { Command, createOption } from '@commander-js/extra-typings' +import { packageManagerOption } from './modules/packageManager' +import { logo } from './logo' +import { + scaffoldTemplate, + templateCliOption, + templatePrompt, +} from './templates' +import { createDebugger, debugCliOption, initDebug } from './utils/debug' +import { getAbsolutePath, newProjectDirectoryPrompt } from './directory' +import { packageNameCliOption } from './modules/packageJson' +import { ideCliOption } from './modules/ide' +import type { TEMPLATE_NAME } from './templates' + +const logger = createDebugger('cli') + +const options = { + template: templateCliOption, + packageNameCliOption: packageNameCliOption, + packageManager: packageManagerOption, + directory: createOption('--directory ', 'The directory to use'), + installDeps: createOption( + '--install-deps', + 'Install dependencies after scaffolding', + ), + noInstallDeps: createOption( + '--no-install-deps', + 'Skip installing dependencies after scaffolding', + ), + initGit: createOption('--init-git', 'Initialise git'), + noInitGit: createOption('--no-init-git', 'Skip initialising git'), + hideLogo: createOption('--hide-logo', 'Hide the Tanstack Start logo'), + ide: ideCliOption, + debug: debugCliOption, +} + +const addNewProjectOptions = (command: Command) => { + return command + .addOption(options.template) + .addOption(options.packageNameCliOption) + .addOption(options.packageManager) + .addOption(options.directory) + .addOption(options.installDeps) + .addOption(options.noInstallDeps) + .addOption(options.initGit) + .addOption(options.noInitGit) + .addOption(options.hideLogo) + .addOption(options.ide) + .addOption(options.debug) +} + +// const addQueryCommand = addBaseOptions( +// new Command() +// .name('tanstack-query') +// .description('Add the Tanstack Query module'), +// ).action((options) => {}) + +// const addCommand = new Command() +// .name('add') +// .description('Add a module to your Tanstack Start project') + +const program = addNewProjectOptions( + new Command('create-start') + .name('create-start') + .description('Scaffold a Tanstack Start appliaction') + .command('default', { + isDefault: true, + hidden: true, + }), +) + // .addCommand(addCommand) + .action(async (options) => { + logger.info('Starting CLI action', { options }) + initDebug(options.debug) + + const templateId: TEMPLATE_NAME = + options.template ?? (await templatePrompt()) + logger.verbose('Template selected', { templateId }) + + const directory = options.directory ?? (await newProjectDirectoryPrompt()) + const targetPath = getAbsolutePath(directory) + logger.verbose('Target directory resolved', { directory, targetPath }) + + logger.info('Starting scaffold process', { + templateId, + targetPath, + packageManager: options.packageManager, + installDeps: options.installDeps, + packageName: options.packageName, + initGit: options.initGit, + ide: options.ide, + }) + + await scaffoldTemplate({ + cfg: { + packageManager: { + packageManager: options.packageManager, + installDeps: options.installDeps, + }, + packageJson: { + type: 'new', + name: options.packageName, + }, + git: { + setupGit: options.initGit, + }, + ide: { + ide: options.ide, + }, + }, + targetPath, + templateId, + }) + logger.info('Scaffold process complete') + }) + +export async function runCli(argv: Array) { + logger.info('CLI starting', { argv }) + if (!argv.includes('--hide-logo')) { + logo() + } + + return new Promise((resolve, reject) => { + logger.verbose('Parsing CLI arguments') + program + .parseAsync(argv) + .then(resolve) + .catch((error) => { + logger.error('CLI execution failed', error) + reject(error) + }) + }) +} diff --git a/packages/create-start/src/constants.ts b/packages/create-start/src/constants.ts new file mode 100644 index 0000000000..ef640a4203 --- /dev/null +++ b/packages/create-start/src/constants.ts @@ -0,0 +1,8 @@ +export const NAME = 'create-start' +export const SUPPORTED_PACKAGE_MANAGERS = [ + 'bun', + 'pnpm', + 'npm', + 'yarn', +] as const +export type PackageManager = (typeof SUPPORTED_PACKAGE_MANAGERS)[number] diff --git a/packages/create-start/src/directory.ts b/packages/create-start/src/directory.ts new file mode 100644 index 0000000000..297b29358e --- /dev/null +++ b/packages/create-start/src/directory.ts @@ -0,0 +1,73 @@ +import path from 'node:path' +import fs from 'node:fs/promises' +import { InvalidArgumentError, createOption } from '@commander-js/extra-typings' +import { input } from '@inquirer/prompts' + +export const getAbsolutePath = (relativePath: string) => { + return path.resolve(process.cwd(), relativePath) +} + +const doesPathExist = async (absolutePath: string) => { + try { + await fs.access(absolutePath) + return true + } catch { + return false + } +} + +const isFolderEmpty = async (absolutePath: string) => { + try { + const files = await fs.readdir(absolutePath) + return files.length === 0 + } catch { + return false + } +} + +const DEFAULT_NAME = 'my-tanstack-start-app' + +const generateDefaultName = async () => { + // Generate a unique default name e.g. my-tanstack-start-app, + // my-tanstack-start-app-1, my-tanstack-start-app-2 etc + + let folderName = DEFAULT_NAME + let absolutePath = getAbsolutePath(folderName) + let pathExists = await doesPathExist(absolutePath) + let counter = 1 + while (pathExists) { + folderName = `${DEFAULT_NAME}-${counter}` + absolutePath = getAbsolutePath(folderName) + pathExists = await doesPathExist(absolutePath) + counter++ + } + return `./${folderName}` +} + +const validateDirectory = async (directory: string) => { + const absolutePath = getAbsolutePath(directory) + const pathExists = await doesPathExist(absolutePath) + if (!pathExists) return true + const folderEmpty = await isFolderEmpty(absolutePath) + if (folderEmpty) return true + return 'The directory is not empty. New projects can only be scaffolded in empty directories' +} + +export const newProjectDirectoryCliOption = createOption( + '--directory ', + 'The directory to scaffold your app in', +).argParser(async (directory) => { + const validationResult = await validateDirectory(directory) + if (validationResult === true) return directory + throw new InvalidArgumentError(validationResult) +}) + +export const newProjectDirectoryPrompt = async () => { + return await input({ + message: 'Where should the project be created?', + default: await generateDefaultName(), + validate: async (path) => { + return await validateDirectory(path) + }, + }) +} diff --git a/packages/create-start/src/index.ts b/packages/create-start/src/index.ts new file mode 100644 index 0000000000..a73827cc6c --- /dev/null +++ b/packages/create-start/src/index.ts @@ -0,0 +1,17 @@ +import { createModule } from './module' +import { ideModule as unstable_ideModule } from './modules/ide' +import { gitModule as unstable_gitModule } from './modules/git' +import { coreModule as unstable_coreModule } from './modules/core' +import { packageJsonModule as unstable_packageJsonModule } from './modules/packageJson' +import { packageManagerModule as unstable_packageManagerModule } from './modules/packageManager' + +export { createModule as unstable_createModule } +export { scaffoldTemplate as unstable_scaffoldTemplate } from './templates' + +export const modules = { + unstable_ideModule, + unstable_gitModule, + unstable_coreModule, + unstable_packageJsonModule, + unstable_packageManagerModule, +} diff --git a/packages/create-start/src/logo.ts b/packages/create-start/src/logo.ts new file mode 100644 index 0000000000..9de211427d --- /dev/null +++ b/packages/create-start/src/logo.ts @@ -0,0 +1,44 @@ +import gradient from 'gradient-string' + +const LEFT_PADDING = 5 + +export const logo = () => { + const logoText = `|▗▄▄▄▖▗▄▖ ▗▖ ▗▖ ▗▄▄▖▗▄▄▄▖▗▄▖ ▗▄▄▖▗▖ ▗▖ + | █ ▐▌ ▐▌▐▛▚▖▐▌▐▌ █ ▐▌ ▐▌▐▌ ▐▌▗▞▘ + | █ ▐▛▀▜▌▐▌ ▝▜▌ ▝▀▚▖ █ ▐▛▀▜▌▐▌ ▐▛▚▖ + | █ ▐▌ ▐▌▐▌ ▐▌▗▄▄▞▘ █ ▐▌ ▐▌▝▚▄▄▖▐▌ ▐▌ + ` + + const startText = `| ▗▄▄▖▗▄▄▄▖▗▄▖ ▗▄▄▖▗▄▄▄▖ + | ▐▌ █ ▐▌ ▐▌▐▌ ▐▌ █ + | ▝▀▚▖ █ ▐▛▀▜▌▐▛▀▚▖ █ + | ▗▄▄▞▘ █ ▐▌ ▐▌▐▌ ▐▌ █ + ` + + const removeLeadngChars = (str: string) => { + return str + .split('\n') + .map((line) => line.replace(/^\s*\|/, '')) + .join('\n') + } + + const padLeft = (str: string) => { + return str + .split('\n') + .map((line) => ' '.repeat(LEFT_PADDING) + line) + .join('\n') + } + + // Create the gradients first + const logoGradient = gradient(['#00bba6', '#8a5eec']) + const startGradient = gradient(['#00bba6', '#00bba6']) + + // Then apply them to the processed text + const logo = logoGradient.multiline(padLeft(removeLeadngChars(logoText))) + const start = startGradient.multiline(padLeft(removeLeadngChars(startText))) + + console.log() + console.log(logo) + console.log(start) + console.log() +} diff --git a/packages/create-start/src/module.ts b/packages/create-start/src/module.ts new file mode 100644 index 0000000000..b6caf4154e --- /dev/null +++ b/packages/create-start/src/module.ts @@ -0,0 +1,286 @@ +import yoctoSpinner from 'yocto-spinner' +import { checkFolderExists, checkFolderIsEmpty } from './utils/helpers' +import { createDebugger } from './utils/debug' +import type { + ParseReturnType, + SafeParseReturnType, + ZodType, + input, + output, + z, +} from 'zod' +import type { Spinner } from 'yocto-spinner' + +const debug = createDebugger('module') + +type Schema = ZodType + +class ModuleBase { + private _baseSchema: TSchema + + constructor(baseSchema: TSchema) { + this._baseSchema = baseSchema + debug.info('Creating new module') + } + + init( + fn: (baseSchema: TSchema) => TInitSchema, + ): InitModule { + debug.verbose('Initializing module with schema transformer') + const schema = fn(this._baseSchema) + return new InitModule(this._baseSchema, schema) + } +} + +class InitModule { + private _baseSchema: TSchema + private _initSchema: TInitSchema + + constructor(baseSchema: TSchema, initSchema: TInitSchema) { + this._baseSchema = baseSchema + this._initSchema = initSchema + debug.verbose('Created init module') + } + + prompt( + fn: (initSchema: TInitSchema) => TPromptSchema, + ): PromptModule { + debug.verbose('Creating prompt module with schema transformer') + const schema = fn(this._initSchema) + return new PromptModule( + this._baseSchema, + this._initSchema, + schema, + ) + } +} + +class PromptModule< + TSchema extends Schema, + TInitSchema extends Schema, + TPromptSchema extends Schema, +> { + private _baseSchema: TSchema + private _initSchema: TInitSchema + private _promptSchema: TPromptSchema + + constructor( + baseSchema: TSchema, + initSchema: TInitSchema, + promptSchema: TPromptSchema, + ) { + this._baseSchema = baseSchema + this._initSchema = initSchema + this._promptSchema = promptSchema + debug.verbose('Created prompt module') + } + + validateAndApply< + TApplyFn extends ApplyFn, + TValidateFn extends ValidateFn, + >({ + validate, + apply, + spinnerConfigFn, + }: { + validate?: TValidateFn + apply: TApplyFn + spinnerConfigFn?: SpinnerConfigFn + }): FinalModule { + debug.verbose('Creating final module with validate and apply functions') + return new FinalModule< + TSchema, + TInitSchema, + TPromptSchema, + TValidateFn, + TApplyFn + >( + this._baseSchema, + this._initSchema, + this._promptSchema, + apply, + validate, + spinnerConfigFn, + ) + } +} + +type ApplyFn = (opts: { + targetPath: string + cfg: z.output +}) => void | Promise + +type ValidateFn = (opts: { + targetPath: string + cfg: z.output +}) => Promise> | Array + +type SpinnerOptions = { + success: string + error: string + inProgress: string +} + +type SpinnerConfigFn = ( + cfg: z.infer, +) => SpinnerOptions | undefined + +class FinalModule< + TSchema extends Schema, + TInitSchema extends Schema, + TPromptSchema extends Schema, + TValidateFn extends ValidateFn, + TApplyFn extends ApplyFn, +> { + public _baseSchema: TSchema + public _initSchema: TInitSchema + public _promptSchema: TPromptSchema + public _applyFn: TApplyFn + public _validateFn: TValidateFn | undefined + public _spinnerConfigFn: SpinnerConfigFn | undefined + + constructor( + baseSchema: TSchema, + initSchema: TInitSchema, + promptSchema: TPromptSchema, + applyFn: TApplyFn, + validateFn?: TValidateFn, + spinnerConfigFn?: SpinnerConfigFn, + ) { + this._baseSchema = baseSchema + this._initSchema = initSchema + this._promptSchema = promptSchema + this._applyFn = applyFn + this._validateFn = validateFn + if (spinnerConfigFn) this._spinnerConfigFn = spinnerConfigFn + debug.verbose('Created final module') + } + + async init(cfg: input): Promise> { + debug.verbose('Running init', { cfg }) + return await this._initSchema.parseAsync(cfg) + } + + public async initSafe( + cfg: input, + ): Promise, output>> { + debug.verbose('Running safe init', { cfg }) + return await this._initSchema.safeParseAsync(cfg) + } + + public async prompt( + cfg: input, + ): Promise, output>> { + debug.verbose('Running prompt', { cfg }) + return await this._promptSchema.parseAsync(cfg) + } + + public async validate( + cfg: input, + ): Promise, output>> { + debug.verbose('Running validate', { cfg }) + return await this._promptSchema.safeParseAsync(cfg) + } + + public async apply({ + cfg, + targetPath, + }: { + cfg: output + targetPath: string + }) { + debug.verbose('Running apply', { cfg, targetPath }) + const spinnerOptions = this._spinnerConfigFn?.(cfg) + await runWithSpinner({ + fn: async () => { + return await this._applyFn({ cfg, targetPath }) + }, + spinnerOptions, + }) + } + + public async execute({ + cfg, + targetPath, + type, + applyingMessage, + }: { + cfg: input + targetPath: string + type: 'new-project' | 'update' + applyingMessage?: string + }) { + debug.info('Executing module', { type, targetPath }) + + const targetExists = await checkFolderExists(targetPath) + const targetIsEmpty = await checkFolderIsEmpty(targetPath) + + debug.verbose('Target directory status', { targetExists, targetIsEmpty }) + + if (type === 'new-project') { + if (targetExists && !targetIsEmpty) { + debug.error('Target directory is not empty for new project') + console.error("The target folder isn't empty") + process.exit(0) + } + } + + if (type === 'update') { + if (!targetExists) { + debug.error('Target directory does not exist for update') + console.error("The target folder doesn't exist") + process.exit(0) + } + } + + debug.verbose('Parsing init state') + const initState = await this._initSchema.parseAsync(cfg) + + debug.verbose('Parsing prompt state') + const promptState = await this._promptSchema.parseAsync(initState) + + if (applyingMessage) { + console.log() + console.log(applyingMessage) + } + + debug.verbose('Applying module') + await this.apply({ cfg: promptState, targetPath }) + debug.info('Module execution complete') + } +} + +export function createModule( + baseSchema: TSchema, +): ModuleBase { + return new ModuleBase(baseSchema) +} + +export const runWithSpinner = async ({ + spinnerOptions, + fn, +}: { + spinnerOptions: SpinnerOptions | undefined + fn: () => Promise +}) => { + let spinner: Spinner + + if (spinnerOptions != undefined) { + spinner = yoctoSpinner({ + text: spinnerOptions.inProgress, + }).start() + } + + try { + await fn() + if (spinnerOptions) { + spinner!.success(spinnerOptions.success) + } + } catch (e) { + if (spinnerOptions) { + spinner!.error(spinnerOptions.error) + } + debug.error('Error in spinner operation', e) + throw e + } +} diff --git a/packages/create-start/src/modules/core/index.ts b/packages/create-start/src/modules/core/index.ts new file mode 100644 index 0000000000..97ea5a4321 --- /dev/null +++ b/packages/create-start/src/modules/core/index.ts @@ -0,0 +1,272 @@ +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { z } from 'zod' +import { packageJsonModule } from '../packageJson' +import { createModule, runWithSpinner } from '../../module' +import { ideModule } from '../ide' +import packageJson from '../../../package.json' assert { type: 'json' } +import { packageManagerModule } from '../packageManager' +import { initHelpers } from '../../utils/helpers' +import { gitModule } from '../git' +import { createDebugger } from '../../utils/debug' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const debug = createDebugger('core-module') + +export const coreModule = createModule( + z.object({ + packageJson: packageJsonModule._initSchema.optional(), + ide: ideModule._initSchema.optional(), + packageManager: packageManagerModule._initSchema.optional(), + git: gitModule._initSchema.optional(), + }), +) + .init((schema) => + schema.transform(async (vals, ctx) => { + debug.verbose('Initializing core module schema', { vals }) + + const gitignore: z.infer['gitIgnore'] = [ + { + sectionName: 'Dependencies', + lines: ['node_modules/'], + }, + { + sectionName: 'Env', + lines: [ + '.env', + '.env.local', + '.env.development', + '.env.test', + '.env.production', + '.env.staging', + ], + }, + { + sectionName: 'System Files', + lines: ['.DS_Store', 'Thumbs.db'], + }, + ] + + vals.git = { + ...vals.git, + gitIgnore: [...(vals.git?.gitIgnore ?? []), ...gitignore], + } + + const packageJson: z.infer = { + type: 'new', + dependencies: await deps([ + '@tanstack/react-router', + '@tanstack/start', + 'react', + 'react-dom', + 'vinxi', + ]), + devDependencies: await deps(['@types/react', '@types/react']), + scripts: [ + { + name: 'dev', + script: 'vinxi dev', + }, + { + name: 'build', + script: 'vinxi build', + }, + { + name: 'start', + script: 'vinxi start', + }, + ], + ...vals.packageJson, + } + + debug.verbose('Parsing package manager schema') + const packageManager = + await packageManagerModule._initSchema.safeParseAsync( + vals.packageManager, + { + path: ['packageManager'], + }, + ) + + debug.verbose('Parsing IDE schema') + const ide = await ideModule._initSchema.safeParseAsync(vals.ide, { + path: ['ide'], + }) + + debug.verbose('Parsing git schema') + const git = await gitModule._initSchema.safeParseAsync(vals.git, { + path: ['git'], + }) + + if (!ide.success || !packageManager.success || !git.success) { + debug.error('Schema validation failed', null, { + ide: ide.success, + packageManager: packageManager.success, + git: git.success, + }) + ide.error?.issues.forEach((i) => ctx.addIssue(i)) + packageManager.error?.issues.forEach((i) => ctx.addIssue(i)) + git.error?.issues.forEach((i) => ctx.addIssue(i)) + throw Error('Failed validation') + } + + debug.verbose('Schema transformation complete') + return { + ...vals, + packageManager: packageManager.data, + ide: ide.data, + git: git.data, + packageJson, + } + }), + ) + .prompt((schema) => + schema.transform(async (vals, ctx) => { + debug.verbose('Running prompt transformations', { vals }) + + debug.verbose('Parsing IDE prompt schema') + const ide = await ideModule._promptSchema.safeParseAsync(vals.ide, { + path: ['ide'], + }) + + debug.verbose('Parsing package manager prompt schema') + const packageManager = + await packageManagerModule._promptSchema.safeParseAsync( + vals.packageManager, + { path: ['packageManager'] }, + ) + + debug.verbose('Parsing git prompt schema') + const git = await gitModule._promptSchema.safeParseAsync(vals.git, { + path: ['git'], + }) + + debug.verbose('Parsing package.json prompt schema') + const packageJson = await packageJsonModule._promptSchema.safeParseAsync( + vals.packageJson, + { + path: ['packageJson'], + }, + ) + + if ( + !ide.success || + !packageManager.success || + !git.success || + !packageJson.success + ) { + debug.error('Prompt validation failed', null, { + ide: ide.success, + packageManager: packageManager.success, + git: git.success, + packageJson: packageJson.success, + }) + ide.error?.issues.forEach((i) => ctx.addIssue(i)) + packageManager.error?.issues.forEach((i) => ctx.addIssue(i)) + git.error?.issues.forEach((i) => ctx.addIssue(i)) + throw Error('Failed validation') + } + + debug.verbose('Prompt transformations complete') + return { + packageJson: packageJson.data, + ide: ide.data, + packageManager: packageManager.data, + git: git.data, + } + }), + ) + .validateAndApply({ + validate: async ({ cfg, targetPath }) => { + debug.verbose('Validating core module', { targetPath }) + const _ = initHelpers(__dirname, targetPath) + + const issues = await _.getTemplateFilesThatWouldBeOverwritten({ + file: '**/*', + templateFolder: './template', + targetFolder: targetPath, + overwrite: false, + }) + + if (ideModule._validateFn) { + debug.verbose('Running IDE validation') + const ideIssues = await ideModule._validateFn({ + cfg: cfg.ide, + targetPath, + }) + issues.push(...ideIssues) + } + + debug.info('Validation complete', { issueCount: issues.length }) + return issues + }, + apply: async ({ cfg, targetPath }) => { + debug.info('Applying core module', { targetPath }) + const _ = initHelpers(__dirname, targetPath) + + debug.verbose('Copying core template files') + await runWithSpinner({ + spinnerOptions: { + inProgress: 'Copying core template files', + error: 'Failed to copy core template files', + success: 'Copied core template files', + }, + fn: async () => + await _.copyTemplateFiles({ + file: '**/*', + templateFolder: './template', + targetFolder: '.', + overwrite: false, + }), + }) + + debug.verbose('Applying package.json module') + await packageJsonModule.apply({ cfg: cfg.packageJson, targetPath }) + + debug.verbose('Applying IDE module') + await ideModule.apply({ cfg: cfg.ide, targetPath }) + + debug.verbose('Applying git module') + await gitModule._applyFn({ cfg: cfg.git, targetPath }) + + debug.verbose('Applying package manager module') + await packageManagerModule.apply({ + cfg: cfg.packageManager, + targetPath, + }) + + debug.info('Core module application complete') + }, + }) + +type DepNames< + T extends + (typeof packageJson)['peerDependencies'] = (typeof packageJson)['peerDependencies'], +> = keyof T + +const deps = async ( + depsArray: Array, +): Promise< + Exclude< + z.infer['dependencies'], + undefined + > +> => { + debug.verbose('Resolving dependencies', { deps: depsArray }) + const result = await Promise.all( + depsArray.map((d) => { + const version = + packageJson['peerDependencies'][d] === 'workspace:^' + ? 'latest' // Use latest in development + : packageJson['peerDependencies'][d] + return { + name: d, + version: version, + } + }), + ) + + return result +} diff --git a/packages/create-start/src/modules/core/template/app.config.ts b/packages/create-start/src/modules/core/template/app.config.ts new file mode 100644 index 0000000000..bb213f88d6 --- /dev/null +++ b/packages/create-start/src/modules/core/template/app.config.ts @@ -0,0 +1,5 @@ +// @ts-nocheck + +import { defineConfig } from '@tanstack/start/config' + +export default defineConfig({}) diff --git a/packages/create-start/src/modules/core/template/app/client.tsx b/packages/create-start/src/modules/core/template/app/client.tsx new file mode 100644 index 0000000000..abff2ff760 --- /dev/null +++ b/packages/create-start/src/modules/core/template/app/client.tsx @@ -0,0 +1,10 @@ +// @ts-nocheck + +/// +import { hydrateRoot } from 'react-dom/client' +import { StartClient } from '@tanstack/start' +import { createRouter } from './router' + +const router = createRouter() + +hydrateRoot(document, ) diff --git a/packages/create-start/src/modules/core/template/app/router.tsx b/packages/create-start/src/modules/core/template/app/router.tsx new file mode 100644 index 0000000000..227f00e224 --- /dev/null +++ b/packages/create-start/src/modules/core/template/app/router.tsx @@ -0,0 +1,18 @@ +// @ts-nocheck + +import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export function createRouter() { + const router = createTanStackRouter({ + routeTree, + }) + + return router +} + +declare module '@tanstack/react-router' { + interface Register { + router: ReturnType + } +} diff --git a/packages/create-start/src/modules/core/template/app/routes/__root.tsx b/packages/create-start/src/modules/core/template/app/routes/__root.tsx new file mode 100644 index 0000000000..aa5bdec1a3 --- /dev/null +++ b/packages/create-start/src/modules/core/template/app/routes/__root.tsx @@ -0,0 +1,50 @@ +// @ts-nocheck + +import { + Outlet, + ScrollRestoration, + createRootRoute, +} from '@tanstack/react-router' +import { Meta, Scripts } from '@tanstack/start' +import type { ReactNode } from 'react' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'TanStack Start Starter', + }, + ], + }), + component: RootComponent, +}) + +function RootComponent() { + return ( + + + + ) +} + +function RootDocument({ children }: Readonly<{ children: ReactNode }>) { + return ( + + + + + + {children} + + + + + ) +} diff --git a/packages/create-start/src/modules/core/template/app/ssr.tsx b/packages/create-start/src/modules/core/template/app/ssr.tsx new file mode 100644 index 0000000000..1dc694297f --- /dev/null +++ b/packages/create-start/src/modules/core/template/app/ssr.tsx @@ -0,0 +1,15 @@ +// @ts-nocheck + +/// +import { + createStartHandler, + defaultStreamHandler, +} from '@tanstack/start/server' +import { getRouterManifest } from '@tanstack/start/router-manifest' + +import { createRouter } from './router' + +export default createStartHandler({ + createRouter, + getRouterManifest, +})(defaultStreamHandler) diff --git a/packages/create-start/src/modules/core/template/tsconfig.json b/packages/create-start/src/modules/core/template/tsconfig.json new file mode 100644 index 0000000000..edfdf0e833 --- /dev/null +++ b/packages/create-start/src/modules/core/template/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "jsx": "react-jsx", + "moduleResolution": "Bundler", + "module": "ESNext", + "target": "ES2022", + "skipLibCheck": true, + "strictNullChecks": true + } +} diff --git a/packages/create-start/src/modules/git.ts b/packages/create-start/src/modules/git.ts new file mode 100644 index 0000000000..cbf62f00a2 --- /dev/null +++ b/packages/create-start/src/modules/git.ts @@ -0,0 +1,147 @@ +import { readFile, writeFile } from 'node:fs/promises' +import { fileURLToPath } from 'node:url' +import { dirname, resolve } from 'node:path' +import { z } from 'zod' +import { select } from '@inquirer/prompts' +import { createModule } from '../module' +import { runCmd } from '../utils/runCmd' +import { createDebugger } from '../utils/debug' +import { checkFileExists, initHelpers } from '../utils/helpers' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const debug = createDebugger('git-module') + +async function appendToGitignore( + targetPath: string, + newEntries: Array, + sectionName: string, +) { + const gitignorePath = resolve(targetPath, '.gitignore') + debug.verbose('Handling gitignore', { gitignorePath }) + + let existingContent = '' + const exists = await checkFileExists(gitignorePath) + + if (exists) { + existingContent = await readFile(gitignorePath, 'utf-8') + const lines = existingContent.split('\n') + + // Find existing section + const sectionStart = lines.findIndex( + (line) => line.trim() === `# ${sectionName}`, + ) + + if (sectionStart !== -1) { + // Section exists, find end (next comment or EOF) + let sectionEnd = lines.findIndex( + (line, i) => i > sectionStart && line.trim().startsWith('#'), + ) + if (sectionEnd === -1) sectionEnd = lines.length + + // Get existing entries in section + const sectionEntries = lines + .slice(sectionStart + 1, sectionEnd) + .map((line) => line.trim()) + .filter((line) => line !== '') + + // Filter out duplicates + newEntries = newEntries.filter( + (entry) => + !sectionEntries.some( + (existing) => existing.toLowerCase() === entry.trim().toLowerCase(), + ), + ) + + if (newEntries.length > 0) { + // Insert new entries at end of section + lines.splice(sectionEnd, 0, ...newEntries) + await writeFile(gitignorePath, lines.join('\n')) + debug.info('Updated existing section in gitignore file') + } + } else { + // Add new section at end + const newContent = `${existingContent}\n\n# ${sectionName}\n${newEntries.join('\n')}` + await writeFile(gitignorePath, newContent) + debug.info('Added new section to gitignore file') + } + } else { + // Create new file with section + const content = `# ${sectionName}\n${newEntries.join('\n')}` + await writeFile(gitignorePath, content) + debug.info('Created new gitignore file') + } +} + +export const gitModule = createModule( + z.object({ + setupGit: z.boolean().optional(), + gitIgnore: z + .object({ + sectionName: z.string(), + lines: z.string().array(), + }) + .array() + .optional(), + }), +) + .init((schema) => schema) // No init required + .prompt((schema) => + schema.transform(async (vals) => { + debug.verbose('Transforming git prompt schema', { vals }) + const setupGit = + vals.setupGit != undefined + ? vals.setupGit + : await select({ + message: 'Initialize git', + choices: [ + { name: 'yes', value: true }, + { name: 'no', value: false }, + ], + default: 'yes', + }) + debug.info('Git initialization choice made', { setupGit }) + return { + setupGit, + gitIgnore: vals.gitIgnore, + } + }), + ) + .validateAndApply({ + apply: async ({ cfg, targetPath }) => { + const _ = initHelpers(__dirname, targetPath) + debug.verbose('Applying git module', { cfg, targetPath }) + + if (cfg.gitIgnore && cfg.gitIgnore.length > 0) { + for (const gitIgnore of cfg.gitIgnore) { + await appendToGitignore( + _.getFullTargetPath(''), + gitIgnore.lines, + gitIgnore.sectionName, + ) + } + debug.info('Created / updated .gitignore') + } + + if (cfg.setupGit) { + debug.info('Initializing git repository') + try { + await runCmd('git', ['init'], {}, targetPath) + debug.info('Git repository initialized successfully') + } catch (error) { + debug.error('Failed to initialize git repository', error) + throw error + } + } else { + debug.info('Skipping git initialization') + } + }, + spinnerConfigFn: () => { + return { + success: 'Git initalized', + error: 'Failed to initialize git', + inProgress: 'Initializing git', + } + }, + }) diff --git a/packages/create-start/src/modules/ide.ts b/packages/create-start/src/modules/ide.ts new file mode 100644 index 0000000000..4294e5d6ed --- /dev/null +++ b/packages/create-start/src/modules/ide.ts @@ -0,0 +1,88 @@ +import { InvalidArgumentError, createOption } from '@commander-js/extra-typings' +import { z } from 'zod' +import { select } from '@inquirer/prompts' +import { createModule } from '../module' +import { createDebugger } from '../utils/debug' +import { vsCodeModule } from './vscode' + +const debug = createDebugger('ide-module') + +const ide = z.enum(['vscode', 'cursor', 'other']) + +const schema = z.object({ + ide: ide.optional(), +}) + +const SUPPORTED_IDES = ide.options +type SupportedIDE = z.infer +const DEFAULT_IDE = 'vscode' + +export const ideCliOption = createOption( + `--ide <${SUPPORTED_IDES.join('|')}>`, + `use this IDE (${SUPPORTED_IDES.join(', ')})`, +).argParser((value) => { + debug.verbose('Parsing IDE CLI option', { value }) + if (!SUPPORTED_IDES.includes(value as SupportedIDE)) { + debug.error('Invalid IDE option provided', null, { value }) + throw new InvalidArgumentError( + `Invalid IDE: ${value}. Only the following are allowed: ${SUPPORTED_IDES.join(', ')}`, + ) + } + return value as SupportedIDE +}) + +export const ideModule = createModule(schema) + .init((schema) => schema) + .prompt((schema) => + schema.transform(async (vals) => { + debug.verbose('Prompting for IDE selection', { currentValue: vals.ide }) + const ide = vals.ide + ? vals.ide + : await select({ + message: 'Select an IDE', + choices: SUPPORTED_IDES.map((i) => ({ value: i })), + default: DEFAULT_IDE, + }) + + debug.info('IDE selected', { ide }) + return { + ide, + } + }), + ) + .validateAndApply({ + validate: async ({ cfg, targetPath }) => { + debug.verbose('Validating IDE configuration', { + ide: cfg.ide, + targetPath, + }) + const issues: Array = [] + + if (cfg.ide === 'vscode') { + debug.verbose('Validating VSCode configuration') + const issuesVsCode = + (await vsCodeModule._validateFn?.({ cfg, targetPath })) ?? [] + issues.push(...issuesVsCode) + } + + if (issues.length > 0) { + debug.warn('IDE validation issues found', { issues }) + } + return issues + }, + apply: async ({ cfg, targetPath }) => { + debug.info('Applying IDE configuration', { ide: cfg.ide, targetPath }) + await vsCodeModule._applyFn({ cfg, targetPath }) + debug.info('IDE configuration applied successfully') + }, + spinnerConfigFn: (cfg) => { + debug.verbose('Configuring spinner for IDE setup', { ide: cfg.ide }) + return ['vscode'].includes(cfg.ide) + ? { + error: `Failed to set up ${cfg.ide}`, + inProgress: `Setting up ${cfg.ide}`, + success: `${cfg.ide} set up`, + } + : undefined + }, + }) diff --git a/packages/create-start/src/modules/packageJson.ts b/packages/create-start/src/modules/packageJson.ts new file mode 100644 index 0000000000..03642b3a32 --- /dev/null +++ b/packages/create-start/src/modules/packageJson.ts @@ -0,0 +1,183 @@ +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { z } from 'zod' +import { input } from '@inquirer/prompts' +import { InvalidArgumentError, createOption } from '@commander-js/extra-typings' +import { initHelpers } from '../utils/helpers' +import { createModule } from '../module' +import { validateProjectName } from '../utils/validateProjectName' +import { createDebugger } from '../utils/debug' + +const debug = createDebugger('package-json') + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +export const packageNameCliOption = createOption( + '--package-name ', + 'The name to use in the package.json', +).argParser((name) => { + const validation = validateProjectName(name) + if (!validation.valid) { + debug.error('Invalid package name', { name, validation }) + throw new InvalidArgumentError(`The project name ${name} is invalid`) + } + debug.verbose('Validated package name', { name }) + return name +}) + +const dependencies = z.array( + z.object({ + name: z.string(), + version: z.string(), + }), +) + +const script = z.object({ + name: z.string(), + script: z.string(), +}) + +const schema = z.object({ + type: z.enum(['new', 'update']), + name: z.string().optional(), + dependencies: dependencies.optional(), + devDependencies: dependencies.optional(), + scripts: z.array(script).optional(), +}) + +export const packageJsonModule = createModule(schema) + .init((schema) => schema) + .prompt((schema) => { + return schema.transform(async (vals) => { + debug.verbose('Transforming prompt schema', vals) + if (vals.type === 'new') { + const name = vals.name + ? vals.name + : await input({ + message: 'Enter the project name', + default: 'tanstack-start', + validate: (name) => { + const validation = validateProjectName(name) + if (validation.valid) { + debug.verbose('Valid project name entered', { name }) + return true + } + debug.warn('Invalid project name entered', { + name, + problems: validation.problems, + }) + return 'Invalid project name: ' + validation.problems[0] + }, + }) + + return { + ...vals, + name, + } + } else { + return vals + } + }) + }) + .validateAndApply({ + validate: async ({ cfg, targetPath }) => { + debug.verbose('Validating package.json', { cfg, targetPath }) + const issues: Array = [] + const _ = initHelpers(__dirname, targetPath) + + const packageJsonExists = await _.targetFileExists('./package.json') + debug.verbose('Package.json exists check', { exists: packageJsonExists }) + + if (cfg.type === 'new') { + if (packageJsonExists) { + debug.warn('Package.json already exists for new project') + issues.push('Package.json already exists') + } + } else { + if (!packageJsonExists) { + debug.warn('Package.json missing for update') + issues.push("Package.json doesn't exist to update") + } + } + + return issues + }, + apply: async ({ cfg, targetPath }) => { + debug.verbose('Applying package.json changes', { cfg, targetPath }) + const _ = initHelpers(__dirname, targetPath) + if (cfg.type === 'new') { + const packageJson = { + name: cfg.name, + version: '0.0.0', + private: true, + type: 'module', + } + + debug.verbose('Creating new package.json', packageJson) + await _.writeTargetfile( + './package.json', + JSON.stringify(packageJson, null, 2), + false, + ) + } + + let packageJson = JSON.parse(await _.readTargetFile('./package.json')) + debug.verbose('Current package.json contents', packageJson) + + const dependenciesRecord = createDepsRecord(cfg.dependencies ?? []) + const devDependenciesRecord = createDepsRecord(cfg.devDependencies ?? []) + const scriptsRecord = createScriptsRecord(cfg.scripts ?? []) + + packageJson = { + ...packageJson, + scripts: { + ...packageJson.scripts, + ...scriptsRecord, + }, + dependencies: { + ...packageJson.dependencies, + ...dependenciesRecord, + }, + devDependencies: { + ...packageJson.devDependencies, + ...devDependenciesRecord, + }, + } + + debug.verbose('Updated package.json contents', packageJson) + await _.writeTargetfile( + './package.json', + JSON.stringify(packageJson, null, 2), + true, + ) + }, + spinnerConfigFn: (cfg) => ({ + success: `${cfg.type === 'new' ? 'Created' : 'Updated'} package.json`, + error: `Failed to ${cfg.type === 'new' ? 'create' : 'update'} package.json`, + inProgress: `${cfg.type === 'new' ? 'Creating' : 'Updating'} package.json`, + }), + }) + +const createDepsRecord = (deps: Array<{ name: string; version: string }>) => + deps.reduce( + (acc: Record, dep: { name: string; version: string }) => ({ + ...acc, + [dep.name]: dep.version, + }), + {}, + ) + +const createScriptsRecord = ( + scripts: Array<{ name: string; script: string }>, +) => + scripts.reduce( + ( + acc: Record, + script: { name: string; script: string }, + ) => ({ + ...acc, + [script.name]: script.script, + }), + {}, + ) diff --git a/packages/create-start/src/modules/packageManager.ts b/packages/create-start/src/modules/packageManager.ts new file mode 100644 index 0000000000..2a0c2a52e4 --- /dev/null +++ b/packages/create-start/src/modules/packageManager.ts @@ -0,0 +1,109 @@ +import { z } from 'zod' +import { InvalidArgumentError, createOption } from '@commander-js/extra-typings' +import { select } from '@inquirer/prompts' +import { createModule } from '../module' +import { SUPPORTED_PACKAGE_MANAGERS } from '../constants' +import { getPackageManager } from '../utils/getPackageManager' +import { install } from '../utils/runPackageManagerCommand' +import { createDebugger } from '../utils/debug' + +const debug = createDebugger('packageManager') + +const schema = z.object({ + packageManager: z.enum(SUPPORTED_PACKAGE_MANAGERS), + installDeps: z.boolean(), +}) + +const DEFAULT_PACKAGE_MANAGER = 'npm' +const options = schema.shape.packageManager.options +type PackageManager = z.infer['packageManager'] + +export const packageManagerOption = createOption( + `--package-manager <${options.join('|')}>`, + `use this Package Manager (${options.join(', ')})`, +).argParser((value) => { + if (!options.includes(value as PackageManager)) { + debug.error('Invalid package manager provided', { value, allowed: options }) + throw new InvalidArgumentError( + `Invalid Package Manager: ${value}. Only the following are allowed: ${options.join(', ')}`, + ) + } + return value as PackageManager +}) + +export const packageManagerModule = createModule( + z.object({ + packageManager: z.enum(SUPPORTED_PACKAGE_MANAGERS).optional(), + installDeps: z.boolean().optional(), + }), +) + .init((schema) => + schema.transform((vals) => { + debug.verbose('Initializing package manager', vals) + const detectedPM = getPackageManager() + debug.verbose('Detected package manager', { detectedPM }) + return { + packageManager: vals.packageManager ?? detectedPM, + installDeps: vals.installDeps, + } + }), + ) + .prompt((schema) => + schema.transform(async (vals) => { + debug.verbose('Prompting for package manager options', vals) + const packageManager = + vals.packageManager != undefined + ? vals.packageManager + : await select({ + message: 'Select a package manager', + choices: options.map((pm) => ({ value: pm })), + default: getPackageManager() ?? DEFAULT_PACKAGE_MANAGER, + }) + + const installDeps = + vals.installDeps != undefined + ? vals.installDeps + : await select({ + message: 'Install dependencies', + choices: [ + { name: 'yes', value: true }, + { name: 'no', value: false }, + ], + default: 'yes', + }) + + debug.verbose('Package manager options selected', { + packageManager, + installDeps, + }) + return { + installDeps, + packageManager, + } + }), + ) + .validateAndApply({ + spinnerConfigFn: (cfg) => { + debug.verbose('Configuring spinner', cfg) + return cfg.installDeps + ? { + error: `Failed to install dependencies with ${cfg.packageManager}`, + inProgress: `Installing dependencies with ${cfg.packageManager}`, + success: `Installed dependencies with ${cfg.packageManager}`, + } + : undefined + }, + apply: async ({ cfg, targetPath }) => { + if (cfg.installDeps) { + debug.info('Installing dependencies', { + packageManager: cfg.packageManager, + targetPath, + }) + + await install(cfg.packageManager, targetPath) + debug.info('Dependencies installed successfully') + } else { + debug.info('Skipping dependency installation') + } + }, + }) diff --git a/packages/create-start/src/modules/vscode/index.ts b/packages/create-start/src/modules/vscode/index.ts new file mode 100644 index 0000000000..52c448c3c2 --- /dev/null +++ b/packages/create-start/src/modules/vscode/index.ts @@ -0,0 +1,46 @@ +import { dirname } from 'node:path' +import { fileURLToPath } from 'node:url' +import { z } from 'zod' +import { initHelpers } from '../../utils/helpers' +import { createModule } from '../../module' +import { createDebugger } from '../../utils/debug' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const debug = createDebugger('vscode') + +export const vsCodeModule = createModule(z.object({})) + .init((schema) => schema) + .prompt((schema) => schema) + .validateAndApply({ + validate: async ({ targetPath }) => { + debug.verbose('Validating vscode module', { targetPath }) + const _ = initHelpers(__dirname, targetPath) + + const issues = await _.getTemplateFilesThatWouldBeOverwritten({ + file: '**/*', + templateFolder: './template', + targetFolder: targetPath, + overwrite: false, + }) + + debug.verbose('Validation complete', { issueCount: issues.length }) + return issues + }, + apply: async ({ targetPath }) => { + debug.info('Applying vscode module', { targetPath }) + // Copy the vscode template folders into the project + const _ = initHelpers(__dirname, targetPath) + + // TODO: Handle when the settings file already exists and merge settings + debug.verbose('Copying template files') + await _.copyTemplateFiles({ + file: '**/*', + templateFolder: './template', + targetFolder: '.', + overwrite: false, + }) + debug.info('VSCode module applied successfully') + }, + }) diff --git a/packages/create-start/src/modules/vscode/template/_dot_vscode/settings.json b/packages/create-start/src/modules/vscode/template/_dot_vscode/settings.json new file mode 100644 index 0000000000..00b5278e58 --- /dev/null +++ b/packages/create-start/src/modules/vscode/template/_dot_vscode/settings.json @@ -0,0 +1,11 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + } +} diff --git a/packages/create-start/src/templates/barebones/index.ts b/packages/create-start/src/templates/barebones/index.ts new file mode 100644 index 0000000000..03dcb95fa5 --- /dev/null +++ b/packages/create-start/src/templates/barebones/index.ts @@ -0,0 +1,75 @@ +import { fileURLToPath } from 'node:url' +import { dirname } from 'node:path' +import { createModule, runWithSpinner } from '../../module' +import { coreModule } from '../../modules/core' +import { initHelpers } from '../../utils/helpers' +import { createDebugger } from '../../utils/debug' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const debug = createDebugger('barebones-template') +const schema = coreModule._initSchema + +export const barebonesTemplate = createModule(schema) + .init((schema) => schema) + .prompt((schema) => + schema.transform(async (vals) => { + debug.verbose('Transforming prompt schema', { vals }) + const core = await coreModule._promptSchema.parseAsync(vals) + debug.verbose('Core module prompt complete') + + return { + ...core, + } + }), + ) + .validateAndApply({ + validate: async ({ cfg, targetPath }) => { + debug.verbose('Validating barebones template', { targetPath }) + const _ = initHelpers(__dirname, targetPath) + + const issues = await _.getTemplateFilesThatWouldBeOverwritten({ + file: '**/*', + templateFolder: './template', + targetFolder: targetPath, + overwrite: false, + }) + + debug.verbose('Template file conflicts found', { issues }) + + const coreIssues = + (await coreModule._validateFn?.({ cfg, targetPath })) ?? [] + debug.verbose('Core module validation issues', { coreIssues }) + + issues.push(...coreIssues) + + return issues + }, + apply: async ({ cfg, targetPath }) => { + debug.info('Applying barebones template', { targetPath }) + const _ = initHelpers(__dirname, targetPath) + + await runWithSpinner({ + spinnerOptions: { + inProgress: 'Copying barebones template files', + error: 'Failed to copy barebones template files', + success: 'Copied barebones template files', + }, + fn: async () => { + debug.verbose('Copying template files') + await _.copyTemplateFiles({ + file: '**/*', + templateFolder: './template', + targetFolder: '.', + overwrite: false, + }) + debug.verbose('Template files copied successfully') + }, + }) + + debug.verbose('Applying core module') + await coreModule._applyFn({ cfg, targetPath }) + debug.info('Barebones template applied successfully') + }, + }) diff --git a/packages/create-start/src/templates/barebones/template/app/routes/index.tsx b/packages/create-start/src/templates/barebones/template/app/routes/index.tsx new file mode 100644 index 0000000000..bf75abe499 --- /dev/null +++ b/packages/create-start/src/templates/barebones/template/app/routes/index.tsx @@ -0,0 +1,11 @@ +// @ts-nocheck + +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: RouteComponent, +}) + +function RouteComponent() { + return
Hello "/"!
+} diff --git a/packages/create-start/src/templates/index.ts b/packages/create-start/src/templates/index.ts new file mode 100644 index 0000000000..3fcbb7b5ec --- /dev/null +++ b/packages/create-start/src/templates/index.ts @@ -0,0 +1,75 @@ +import { select } from '@inquirer/prompts' +import { InvalidArgumentError, createOption } from '@commander-js/extra-typings' +import invariant from 'tiny-invariant' +import { createDebugger } from '../utils/debug' +import { barebonesTemplate } from './barebones' +import type { coreModule } from '../modules/core' +import type { z } from 'zod' + +const debug = createDebugger('templates') + +const templates = [ + { + id: 'barebones', + name: 'Barebones', + module: barebonesTemplate, + description: 'The bare minimum', + }, +] as const + +const templateIds = templates.map((t) => t.id) +export type TEMPLATE_NAME = (typeof templateIds)[number] +export const DEFAULT_TEMPLATE: TEMPLATE_NAME = 'barebones' + +export const templateCliOption = createOption( + '--template ', + 'Choose the template to use', +).argParser((value) => { + if (!templateIds.includes(value as TEMPLATE_NAME)) { + debug.error(`Invalid template specified: ${value}`) + throw new InvalidArgumentError( + `Invalid Template: ${value}. Only the following are allowed: ${templateIds.join(', ')}`, + ) + } + debug.verbose('Template validated from CLI', { template: value }) + return value as TEMPLATE_NAME +}) + +export const templatePrompt = async () => { + debug.info('Prompting for template selection') + const selection = await select({ + message: 'Which template would you like to use?', + choices: templates.map((t) => ({ + name: t.name, + value: t.id, + description: t.description, + })), + default: DEFAULT_TEMPLATE, + }) + debug.verbose('Template selected', { template: selection }) + return selection +} + +export const scaffoldTemplate = async ({ + templateId, + cfg, + targetPath, +}: { + templateId: TEMPLATE_NAME + cfg: z.input + targetPath: string +}) => { + debug.info('Starting template scaffolding', { templateId, targetPath }) + // const template = templates.find((f) => f.id === templateId) + const template = templates[0] // Remove this when we add more templates + invariant(template, `The template with ${templateId} is not valid`) + + debug.verbose('Executing template module', { template: template.id }) + await template.module.execute({ + cfg, + targetPath, + type: 'new-project', + applyingMessage: `Scaffolding the ${template.name} template`, + }) + debug.info('Template scaffolding complete') +} diff --git a/packages/create-start/src/types.ts b/packages/create-start/src/types.ts new file mode 100644 index 0000000000..b66c077f64 --- /dev/null +++ b/packages/create-start/src/types.ts @@ -0,0 +1,3 @@ +import type packageJson from '../package.json' + +export type PeerDependency = keyof typeof packageJson.peerDependencies diff --git a/packages/create-start/src/utils/debug.ts b/packages/create-start/src/utils/debug.ts new file mode 100644 index 0000000000..98a1548cc3 --- /dev/null +++ b/packages/create-start/src/utils/debug.ts @@ -0,0 +1,95 @@ +import { InvalidArgumentError, createOption } from '@commander-js/extra-typings' + +type Context = string +type LogLevel = 'info' | 'warn' | 'error' + +interface LogOptions { + context: Context + data?: Record +} + +let isDebugMode = false +let debugLevel = 0 // 1 = basic, 2 = verbose, 3 = trace + +const DEBUG_LEVELS = ['debug', 'trace', 'verbose'] as const +type DebugLevels = (typeof DEBUG_LEVELS)[number] + +export const debugCliOption = createOption( + `--debug <${DEBUG_LEVELS.join('|')}>`, + `Set a debug level (${DEBUG_LEVELS.join(', ')})`, +).argParser((value) => { + if (!DEBUG_LEVELS.includes(value as DebugLevels)) { + throw new InvalidArgumentError( + `Invalid IDE: ${value}. Only the following are allowed: ${DEBUG_LEVELS.join(', ')}`, + ) + } + return value as DebugLevels +}) + +export const initDebug = (level: undefined | 'debug' | 'trace' | 'verbose') => { + if (level === undefined) return + isDebugMode = true + if (level === 'debug') debugLevel = 1 + if (level === 'trace') debugLevel = 2 + if (level === 'verbose') debugLevel = 3 +} + +const formatData = (data?: Record): string => { + if (!data) return '' + return Object.entries(data) + .map(([key, value]) => `${key}=${JSON.stringify(value)}`) + .join(' ') +} + +const log = (level: LogLevel, message: string, options: LogOptions) => { + if (!isDebugMode) return + + const timestamp = new Date().toISOString() + const dataStr = formatData(options.data) + const logMessage = `[${timestamp}] [${level}] [${options.context}] ${message} ${dataStr}` + + switch (level) { + case 'error': + console.error(logMessage) + break + case 'warn': + console.warn(logMessage) + break + case 'info': + console.log(logMessage) + break + } +} + +export const createDebugger = (context: Context) => ({ + info: (message: string, data?: Record) => { + if (debugLevel < 1) return + log('info', message, { context, data }) + }, + warn: (message: string, data?: Record) => { + if (debugLevel < 2) return + log('warn', message, { context, data }) + }, + error: ( + message: string, + error?: Error | unknown, + data?: Record, + ) => { + if (debugLevel < 1) return + log('error', message, { + context, + data: { + ...data, + error: error instanceof Error ? error.message : error, + }, + }) + }, + verbose: (message: string, data?: Record) => { + if (debugLevel < 2) return + log('info', message, { context, data }) + }, + trace: (message: string, data?: Record) => { + if (debugLevel < 3) return + log('info', message, { context, data }) + }, +}) diff --git a/packages/create-start/src/utils/getPackageManager.ts b/packages/create-start/src/utils/getPackageManager.ts new file mode 100644 index 0000000000..4bcb7b8032 --- /dev/null +++ b/packages/create-start/src/utils/getPackageManager.ts @@ -0,0 +1,16 @@ +import { SUPPORTED_PACKAGE_MANAGERS } from '../constants' +import type { PackageManager } from '../constants' + +export function getPackageManager(): PackageManager | undefined { + const userAgent = process.env.npm_config_user_agent + + if (userAgent === undefined) { + return undefined + } + + const packageManager = SUPPORTED_PACKAGE_MANAGERS.find((manager) => + userAgent.startsWith(manager), + ) + + return packageManager +} diff --git a/packages/create-start/src/utils/helpers/helperFactory.ts b/packages/create-start/src/utils/helpers/helperFactory.ts new file mode 100644 index 0000000000..1f0fa6afb8 --- /dev/null +++ b/packages/create-start/src/utils/helpers/helperFactory.ts @@ -0,0 +1,20 @@ +export type Ctx = { + getFullModulePath: (relativePath: string) => string + getFullTargetPath: (relativePath: string) => string + targetFileExists: (relativePath: string) => Promise + moduleFileExists: (relativePath: string) => Promise + absoluteTargetFolder: string + absoluteModuleFolder: string +} + +type HelperFn any> = (args: { + modulePath: string + targetPath: string + ctx: Ctx +}) => T + +export const helperFactory = any>( + fn: HelperFn, +) => { + return fn +} diff --git a/packages/create-start/src/utils/helpers/index.ts b/packages/create-start/src/utils/helpers/index.ts new file mode 100644 index 0000000000..051bdf7a2c --- /dev/null +++ b/packages/create-start/src/utils/helpers/index.ts @@ -0,0 +1,284 @@ +import path, { resolve } from 'node:path' +import fs, { + access, + copyFile, + mkdir, + readFile, + readdir, + stat, + writeFile, +} from 'node:fs/promises' +import invariant from 'tiny-invariant' +import fastGlob from 'fast-glob' +import { createDebugger } from '../debug' + +import { helperFactory } from './helperFactory' +import type { Ctx } from './helperFactory' + +const debug = createDebugger('helpers') + +export const initHelpers = (modulePath: string, targetPath: string) => { + debug.info('Initializing helpers', { modulePath, targetPath }) + + const getFullModulePath = (relativePath: string) => { + const fullPath = path.join(modulePath, relativePath) + debug.trace('Getting full module path', { relativePath, fullPath }) + return fullPath + } + + const getFullTargetPath = (relativePath: string) => { + const fullPath = path.join(targetPath, relativePath) + debug.trace('Getting full target path', { relativePath, fullPath }) + return fullPath + } + + const targetFileExists = async (relativePath: string) => { + const path = resolve(targetPath, relativePath) + debug.trace('Checking if target file exists', { path }) + return await checkFileExists(path) + } + + const moduleFileExists = async (relativePath: string) => { + const path = resolve(modulePath, relativePath) + debug.trace('Checking if module file exists', { path }) + return await checkFileExists(path) + } + + const ctx: Ctx = { + targetFileExists, + moduleFileExists, + absoluteModuleFolder: getFullModulePath(modulePath), + absoluteTargetFolder: getFullTargetPath(targetPath), + getFullModulePath, + getFullTargetPath, + } + + debug.verbose('Created helper context', ctx) + + return { + ...ctx, + readTargetFile: createReadTargetFile({ + ctx, + modulePath, + targetPath, + }), + writeTargetfile: createWriteTargetFile({ + ctx, + modulePath, + targetPath, + }), + copyTemplateFiles: createCopyTemplateFiles({ + ctx, + modulePath, + targetPath, + }), + getTemplateFilesThatWouldBeOverwritten: + createGetTemplateFilesThatWouldBeOverwritten({ + ctx, + modulePath, + targetPath, + }), + } +} + +export const checkFileExists = async (path: string): Promise => { + debug.trace('Checking if file exists', { path }) + try { + await access(path, fs.constants.F_OK) + return true + } catch { + return false + } +} + +export const checkFolderExists = async (path: string): Promise => { + debug.trace('Checking if folder exists', { path }) + try { + await access(path, fs.constants.R_OK) + return true + } catch { + return false + } +} + +export const checkFolderIsEmpty = async (path: string): Promise => { + debug.trace('Checking if folder is empty', { path }) + try { + const files = await readdir(path) + return files.length === 0 + } catch { + return false + } +} + +const createReadTargetFile = helperFactory( + ({ ctx, targetPath }) => + async (relativePath: string) => { + debug.trace('Reading target file', { relativePath, targetPath }) + invariant( + await ctx.targetFileExists(relativePath), + `The file ${relativePath} doesn't exist`, + ) + const path = resolve(targetPath, relativePath) + return await readFile(path, 'utf-8') + }, +) + +export const createWriteTargetFile = helperFactory( + ({ targetPath }) => + async ( + relativePath: string, + content: string, + overwrite: boolean = false, + ) => { + debug.trace('Writing target file', { + relativePath, + targetPath, + overwrite, + }) + const path = resolve(targetPath, relativePath) + invariant( + !(!overwrite && (await checkFileExists(path))), + `File ${relativePath} already exists and overwrite is false`, + ) + await writeFile(path, content) + }, +) + +const DOT_PREFIX = '_dot_' + +const removeTsNoCheckHeader = async (filePath: string) => { + debug.trace('Removing ts-nocheck header', { filePath }) + // Template files will sometimes include // @ts-nocheck in the header of the file + // This is to avoid type checking in the template folders + // This function removes that header + + const content = await readFile(filePath, 'utf-8') + const lines = content.split('\n') + + let newContent = content + + if (lines[0]?.trim() === '// @ts-nocheck') { + newContent = lines.slice(1).join('\n').trimStart() + } + + await writeFile(filePath, newContent) +} + +async function copyDir(srcDir: string, destDir: string) { + debug.trace('Copying directory', { srcDir, destDir }) + await mkdir(destDir, { recursive: true }) + const files = await readdir(srcDir) + for (const file of files) { + const srcFile = resolve(srcDir, file) + const destFile = resolve(destDir, file) + await copy(srcFile, destFile) + } +} + +async function copy(src: string, dest: string) { + debug.trace('Copying file', { src, dest }) + const statResult = await stat(src) + const replacedDest = dest.replaceAll(DOT_PREFIX, '.') + if (statResult.isDirectory()) { + await copyDir(src, replacedDest) + } else { + await copyFile(src, replacedDest) + await removeTsNoCheckHeader(replacedDest) + } +} + +export const createGetTemplateFilesThatWouldBeOverwritten = helperFactory( + ({ ctx }) => + async ({ + file, + templateFolder, + targetFolder, + overwrite, + }: { + file: string + templateFolder: string + targetFolder: string + overwrite: boolean + }) => { + debug.verbose('Checking for files that would be overwritten', { + file, + templateFolder, + targetFolder, + overwrite, + }) + const overwrittenFiles: Array = [] + + if (overwrite) [] + + const absoluteTemplateFolder = ctx.getFullModulePath(templateFolder) + const absoluteTargetFolder = ctx.getFullTargetPath(targetFolder) + + const files = await fastGlob.glob(file, { + cwd: absoluteTemplateFolder, + onlyFiles: false, + }) + + for (const file of files) { + const exists = await checkFileExists( + resolve(absoluteTargetFolder, file), + ) + if (exists) { + debug.verbose('Found file that would be overwritten', { file }) + overwrittenFiles.push(file) + } + } + + return overwrittenFiles + }, +) + +export const createCopyTemplateFiles = helperFactory( + ({ ctx }) => + async ({ + file, + templateFolder, + targetFolder, + overwrite, + }: { + file: string + templateFolder: string + targetFolder: string + overwrite: boolean + }) => { + debug.verbose('Copying template files', { + file, + templateFolder, + targetFolder, + overwrite, + }) + const absoluteTemplateFolder = ctx.getFullModulePath(templateFolder) + const absoluteTargetFolder = ctx.getFullTargetPath(targetFolder) + + const templateFolderExists = checkFolderExists(absoluteTemplateFolder) + invariant( + templateFolderExists, + `The template folder ${templateFolder} doesn't exist`, + ) + + const files = await fastGlob.glob(file, { + cwd: absoluteTemplateFolder, + onlyFiles: false, + }) + + for (const file of files) { + if (overwrite) { + invariant( + await checkFileExists(resolve(absoluteTargetFolder, file)), + `The file ${file} couldn't be created because it would overwrite an existing file`, + ) + } + + debug.trace('Copying template file', { file }) + await copy( + resolve(absoluteTemplateFolder, file), + resolve(absoluteTargetFolder, file), + ) + } + }, +) diff --git a/packages/create-start/src/utils/runCmd.ts b/packages/create-start/src/utils/runCmd.ts new file mode 100644 index 0000000000..0b3634a294 --- /dev/null +++ b/packages/create-start/src/utils/runCmd.ts @@ -0,0 +1,10 @@ +import { spawnCommand } from './spawnCmd' + +export async function runCmd( + command: string, + args: Array, + env: NodeJS.ProcessEnv = {}, + cwd?: string, +) { + return spawnCommand(command, args, env, cwd) +} diff --git a/packages/create-start/src/utils/runPackageManagerCommand.ts b/packages/create-start/src/utils/runPackageManagerCommand.ts new file mode 100644 index 0000000000..9e7d054f20 --- /dev/null +++ b/packages/create-start/src/utils/runPackageManagerCommand.ts @@ -0,0 +1,26 @@ +import { spawnCommand } from './spawnCmd' +import type { PackageManager } from '../constants' + +export async function runPackageManagerCommand( + packageManager: PackageManager, + args: Array, + env: NodeJS.ProcessEnv = {}, + cwd?: string, +) { + return spawnCommand(packageManager, args, env, cwd) +} + +export async function install(packageManager: PackageManager, cwd?: string) { + return runPackageManagerCommand( + packageManager, + ['install'], + { + NODE_ENV: 'development', + }, + cwd, + ) +} + +export async function build(packageManager: PackageManager, cwd?: string) { + return runPackageManagerCommand(packageManager, ['run', 'build'], {}, cwd) +} diff --git a/packages/create-start/src/utils/spawnCmd.ts b/packages/create-start/src/utils/spawnCmd.ts new file mode 100644 index 0000000000..0525e2ae7b --- /dev/null +++ b/packages/create-start/src/utils/spawnCmd.ts @@ -0,0 +1,39 @@ +import spawn from 'cross-spawn' + +export async function spawnCommand( + command: string, + args: Array, + env: NodeJS.ProcessEnv = {}, + cwd?: string, +) { + return new Promise((resolve, reject) => { + const child = spawn(command, args, { + env: { + ...process.env, + ...env, + }, + stdio: ['pipe', 'pipe', 'pipe'], + cwd, + }) + let stderrBuffer = '' + let stdoutBuffer = '' + + child.stderr?.on('data', (data) => { + stderrBuffer += data + }) + + child.stdout?.on('data', (data) => { + stdoutBuffer += data + }) + + child.on('close', (code) => { + if (code !== 0) { + reject( + `"${command} ${args.join(' ')}" failed ${stdoutBuffer} ${stderrBuffer}`, + ) + return + } + resolve() + }) + }) +} diff --git a/packages/create-start/src/utils/validateProjectName.ts b/packages/create-start/src/utils/validateProjectName.ts new file mode 100644 index 0000000000..dc1366ab4a --- /dev/null +++ b/packages/create-start/src/utils/validateProjectName.ts @@ -0,0 +1,25 @@ +import validate from 'validate-npm-package-name' + +type ValidatationResult = + | { + valid: true + } + | { + valid: false + problems: Array + } + +export function validateProjectName(name: string): ValidatationResult { + const nameValidation = validate(name) + if (nameValidation.validForNewPackages) { + return { valid: true } + } + + return { + valid: false, + problems: [ + ...(nameValidation.errors || []), + ...(nameValidation.warnings || []), + ], + } +} diff --git a/packages/create-start/tests/e2e/cli.test.ts b/packages/create-start/tests/e2e/cli.test.ts new file mode 100644 index 0000000000..c03a4fc767 --- /dev/null +++ b/packages/create-start/tests/e2e/cli.test.ts @@ -0,0 +1,135 @@ +import { readFile } from 'node:fs/promises' +import { join } from 'node:path' +import { describe, expect, it } from 'vitest' +import { temporaryDirectoryTask } from 'tempy' +import { runCli } from '../../src/cli' + +const constructCliArgs = ({ + template, + directory, + packageName, + packageManager, + installDeps, + initGit, + hideLogo, + ide, +}: { + template?: string + directory?: string + packageName?: string + packageManager?: string + installDeps?: boolean + initGit?: boolean + hideLogo?: boolean + ide?: string +}) => { + return [ + 'node', + 'cli.js', + '--template', + template ?? 'barebones', + '--directory', + directory ?? '', + '--package-name', + packageName ?? '', + '--package-manager', + packageManager ?? 'npm', + ...(installDeps === false ? ['--no-install-deps'] : []), + ...(initGit === false ? ['--no-init-git'] : []), + ...(hideLogo ? ['--hide-logo'] : []), + ...(ide ? ['--ide', ide] : []), + ] +} + +describe('cli e2e', () => { + it('should create a basic project structure using CLI', async () => { + await temporaryDirectoryTask(async (tempDir) => { + const args = constructCliArgs({ + template: 'barebones', + directory: tempDir, + packageName: 'test-package', + packageManager: 'npm', + installDeps: false, + hideLogo: true, + ide: 'vscode', + initGit: false, + }) + + await runCli(args) + + // Check core files exist + const expectedFiles = [ + '.gitignore', + 'package.json', + 'tsconfig.json', + 'app.config.ts', + 'app/client.tsx', + 'app/router.tsx', + 'app/routes/__root.tsx', + 'app/routes/index.tsx', + '.vscode/settings.json', + ] + + for (const file of expectedFiles) { + const filePath = join(tempDir, file) + const exists = await readFile(filePath) + expect(exists).toBeDefined() + } + }) + }) + + it('should create project in current directory when using "." as directory', async () => { + await temporaryDirectoryTask(async (tempDir) => { + const args = constructCliArgs({ + template: 'barebones', + directory: '.', + packageName: 'test-package', + packageManager: 'npm', + installDeps: false, + hideLogo: true, + ide: 'vscode', + initGit: false, + }) + + // Run CLI from the temporary directory + process.chdir(tempDir) + await runCli(args) + + // Check core files exist in current directory + const expectedFiles = [ + 'package.json', + 'tsconfig.json', + 'app.config.ts', + 'app/client.tsx', + 'app/router.tsx', + 'app/routes/__root.tsx', + 'app/routes/index.tsx', + '.vscode/settings.json', + ] + + for (const file of expectedFiles) { + const filePath = join(tempDir, file) + const exists = await readFile(filePath) + expect(exists).toBeDefined() + } + + // Reset working directory + process.chdir('..') + }) + }) + + it('should fail when using an incorrect directory name', async () => { + const args = constructCliArgs({ + template: 'barebones', + directory: '/invalid/directory/path', + packageName: 'test-package', + packageManager: 'npm', + installDeps: false, + hideLogo: true, + ide: 'vscode', + initGit: false, + }) + + await expect(runCli(args)).rejects.toThrow() + }) +}) diff --git a/packages/create-start/tests/e2e/templates/barebones.test.ts b/packages/create-start/tests/e2e/templates/barebones.test.ts new file mode 100644 index 0000000000..e46b37c122 --- /dev/null +++ b/packages/create-start/tests/e2e/templates/barebones.test.ts @@ -0,0 +1,79 @@ +import { join } from 'node:path' +import { readFile } from 'node:fs/promises' +import { describe, expect, it } from 'vitest' +import { temporaryDirectoryTask } from 'tempy' +import { barebonesTemplate } from '../../../src/templates/barebones' + +const base = (tmpDir: string) => ({ + cfg: { + packageManager: { + installDeps: false, + packageManager: 'npm' as const, + }, + git: { + setupGit: false, + }, + packageJson: { + type: 'new' as const, + name: 'test', + }, + ide: { + ide: 'vscode' as const, + }, + }, + targetPath: tmpDir, + type: 'new-project' as const, +}) + +describe('barebones template e2e', () => { + it('should create a basic project structure', async () => { + await temporaryDirectoryTask(async (tempDir) => { + await barebonesTemplate.execute(base(tempDir)) + + // Check core files exist + const expectedFiles = [ + 'package.json', + 'tsconfig.json', + 'app.config.ts', + 'app/client.tsx', + 'app/router.tsx', + 'app/routes/__root.tsx', + 'app/routes/index.tsx', + '.vscode/settings.json', + ] + + for (const file of expectedFiles) { + const filePath = join(tempDir, file) + const exists = await readFile(filePath) + expect(exists).toBeDefined() + } + }) + }) + + it('should have valid package.json contents', async () => { + await temporaryDirectoryTask(async (tempDir) => { + await barebonesTemplate.execute(base(tempDir)) + + const pkgJsonPath = join(tempDir, 'package.json') + const pkgJson = JSON.parse(await readFile(pkgJsonPath, 'utf-8')) + + expect(pkgJson.name).toBe('test') + expect(pkgJson.private).toBe(true) + expect(pkgJson.type).toBe('module') + expect(pkgJson.dependencies).toBeDefined() + expect(pkgJson.dependencies['@tanstack/react-router']).toBeDefined() + expect(pkgJson.dependencies['@tanstack/start']).toBeDefined() + expect(pkgJson.dependencies['react']).toBeDefined() + expect(pkgJson.dependencies['react-dom']).toBeDefined() + expect(pkgJson.dependencies['vinxi']).toBeDefined() + + expect(pkgJson.devDependencies).toBeDefined() + expect(pkgJson.devDependencies['@types/react']).toBeDefined() + + expect(pkgJson.scripts).toBeDefined() + expect(pkgJson.scripts.dev).toBe('vinxi dev') + expect(pkgJson.scripts.build).toBe('vinxi build') + expect(pkgJson.scripts.start).toBe('vinxi start') + }) + }) +}) diff --git a/packages/create-start/tsconfig.json b/packages/create-start/tsconfig.json new file mode 100644 index 0000000000..747f8933df --- /dev/null +++ b/packages/create-start/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "module": "ESNext" + }, + "moduleResolution": "Bundler", + "include": ["src", "tests", "build.config.ts"] +} diff --git a/packages/create-start/vitest.config.ts b/packages/create-start/vitest.config.ts new file mode 100644 index 0000000000..f5b965c618 --- /dev/null +++ b/packages/create-start/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config' +import packageJson from './package.json' + +const config = defineConfig({ + test: { + name: packageJson.name, + dir: './tests', + watch: false, + environment: 'node', + typecheck: { enabled: true }, + }, +}) + +export default config diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af6cc1cdad..2f48031afa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2001,10 +2001,10 @@ importers: version: 18.3.1 html-webpack-plugin: specifier: ^5.6.3 - version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -3219,7 +3219,7 @@ importers: version: 4.3.4(vite@6.0.3(@types/node@22.10.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(yaml@2.6.1)) html-webpack-plugin: specifier: ^5.6.0 - version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.1) react: specifier: ^18.3.1 version: 18.3.1 @@ -3228,7 +3228,7 @@ importers: version: 18.3.1(react@18.3.1) swc-loader: specifier: ^0.2.6 - version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + version: 0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1) typescript: specifier: ^5.7.2 version: 5.7.2 @@ -3282,6 +3282,82 @@ importers: specifier: ^0.1.1 version: 0.1.1 + packages/create-start: + dependencies: + '@tanstack/react-router': + specifier: workspace:* + version: link:../react-router + '@tanstack/router-devtools': + specifier: workspace:* + version: link:../router-devtools + '@tanstack/start': + specifier: workspace:* + version: link:../start + '@types/react': + specifier: ^18.3.12 + version: 18.3.12 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.1 + gradient-string: + specifier: ^3.0.0 + version: 3.0.0 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + vinxi: + specifier: 0.4.3 + version: 0.4.3(@types/node@22.10.1)(ioredis@5.4.1)(terser@5.36.0)(typescript@5.7.2) + devDependencies: + '@commander-js/extra-typings': + specifier: ^12.1.0 + version: 12.1.0(commander@12.1.0) + '@inquirer/prompts': + specifier: ^5.5.0 + version: 5.5.0 + '@inquirer/type': + specifier: ^3.0.1 + version: 3.0.1(@types/node@22.10.1) + '@types/cross-spawn': + specifier: ^6.0.6 + version: 6.0.6 + '@types/validate-npm-package-name': + specifier: ^4.0.2 + version: 4.0.2 + cross-spawn: + specifier: ^7.0.5 + version: 7.0.6 + fast-glob: + specifier: ^3.3.2 + version: 3.3.2 + picocolors: + specifier: ^1.1.1 + version: 1.1.1 + rollup-plugin-copy: + specifier: ^3.5.0 + version: 3.5.0 + tempy: + specifier: ^3.1.0 + version: 3.1.0 + tiny-invariant: + specifier: ^1.3.3 + version: 1.3.3 + unbuild: + specifier: ^2.0.0 + version: 2.0.0(typescript@5.7.2)(vue-tsc@2.0.29(typescript@5.7.2)) + validate-npm-package-name: + specifier: ^5.0.1 + version: 5.0.1 + yocto-spinner: + specifier: ^0.1.1 + version: 0.1.1 + zod: + specifier: ^3.23.8 + version: 3.23.8 + packages/eslint-plugin-router: dependencies: '@typescript-eslint/utils': @@ -3849,6 +3925,11 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@commander-js/extra-typings@12.1.0': + resolution: {integrity: sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==} + peerDependencies: + commander: ~12.1.0 + '@commitlint/parse@19.5.0': resolution: {integrity: sha512-cZ/IxfAlfWYhAQV0TwcbdR1Oc0/r0Ik1GEessDJ3Lbuma/MRO8FRQX76eurcXtmhJC//rj52ZSZuXUg0oIX0Fw==} engines: {node: '>=v18'} @@ -5984,6 +6065,12 @@ packages: '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + '@types/fs-extra@8.1.5': + resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==} + + '@types/glob@7.2.0': + resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} + '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} @@ -6008,6 +6095,9 @@ packages: '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/minimatch@5.1.2': + resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} + '@types/mute-stream@0.0.4': resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} @@ -6056,6 +6146,9 @@ packages: '@types/statuses@2.0.5': resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/tinycolor2@1.4.6': + resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} + '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} @@ -6560,6 +6653,10 @@ packages: resolution: {integrity: sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==} engines: {node: '>=0.10.0'} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -7009,9 +7106,21 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + crossws@0.2.4: + resolution: {integrity: sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg==} + peerDependencies: + uWebSockets.js: '*' + peerDependenciesMeta: + uWebSockets.js: + optional: true + crossws@0.3.1: resolution: {integrity: sha512-HsZgeVYaG+b5zA+9PbIPGq4+J/CJynJuearykPsXx4V/eMhyQ5EDVg3Ak2FBZtVXCiOLu/U7IiwDHTr9MA+IKw==} + crypto-random-string@4.0.0: + resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} + engines: {node: '>=12'} + css-declaration-sorter@7.2.0: resolution: {integrity: sha512-h70rUM+3PNFuaBDTLe8wF/cdWu+dOZmb7pJt8Z2sedYbAcQVQV/tEchueg3GWxwqS0cxtbxmaHEdkNACqcvsow==} engines: {node: ^14 || ^16 || >=18} @@ -7796,6 +7905,10 @@ packages: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -7908,6 +8021,10 @@ packages: resolution: {integrity: sha512-49TewVEz0UxZjr1WYYsWpPrhyC/B/pA8Bq0fUmet2n+eR7yn0IvNzNaoBwnK6mdkzcN+se7Ez9zUgULTz2QH4g==} engines: {node: '>=18'} + globby@10.0.1: + resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==} + engines: {node: '>=8'} + globby@13.2.2: resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7931,6 +8048,10 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gradient-string@3.0.0: + resolution: {integrity: sha512-frdKI4Qi8Ihp4C6wZNB565de/THpIaw3DjP5ku87M+N9rNSGmPTjfkq61SdRXB7eCaL8O1hkKDvf6CDMtOzIAg==} + engines: {node: '>=14'} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -7942,6 +8063,9 @@ packages: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + h3@1.11.1: + resolution: {integrity: sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A==} + h3@1.13.0: resolution: {integrity: sha512-vFEAu/yf8UMUcB4s43OaDaigcqpQd14yanmOsn+NcRX3/guSKncyE2rOYhq8RIchgJrPSs/QiIddnTTR1ddiAg==} @@ -8234,6 +8358,10 @@ packages: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} + is-plain-object@3.0.1: + resolution: {integrity: sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==} + engines: {node: '>=0.10.0'} + is-plain-object@5.0.0: resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} @@ -9630,6 +9758,10 @@ packages: engines: {node: 20 || >=22} hasBin: true + rollup-plugin-copy@3.5.0: + resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==} + engines: {node: '>=8.3'} + rollup-plugin-dts@6.1.1: resolution: {integrity: sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==} engines: {node: '>=16'} @@ -9807,6 +9939,10 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + slash@4.0.0: resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} engines: {node: '>=12'} @@ -10022,6 +10158,14 @@ packages: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} + temp-dir@3.0.0: + resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} + engines: {node: '>=14.16'} + + tempy@3.1.0: + resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} + engines: {node: '>=14.16'} + terser-webpack-plugin@5.3.10: resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} @@ -10078,6 +10222,9 @@ packages: tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + tinycolor2@1.6.0: + resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} + tinyexec@0.3.1: resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} @@ -10085,6 +10232,9 @@ packages: resolution: {integrity: sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==} engines: {node: '>=12.0.0'} + tinygradient@1.1.5: + resolution: {integrity: sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw==} + tinypool@1.0.2: resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -10195,6 +10345,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} @@ -10320,6 +10474,10 @@ packages: unimport@3.14.3: resolution: {integrity: sha512-yEJps4GW7jBdoQlxEV0ElBCJsJmH8FdZtk4oog0y++8hgLh0dGnDpE4oaTc0Lfx4N5rRJiGFUWHrBqC8CyUBmQ==} + unique-string@3.0.0: + resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} + engines: {node: '>=12'} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} @@ -10475,6 +10633,10 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vinxi@0.4.3: + resolution: {integrity: sha512-RgJz7RWftML5h/qfPsp3QKVc2FSlvV4+HevpE0yEY2j+PS/I2ULjoSsZDXaR8Ks2WYuFFDzQr8yrox7v8aqkng==} + hasBin: true + vinxi@0.5.1: resolution: {integrity: sha512-jvl2hJ0fyWwfDVQdDDHCJiVxqU4k0A6kFAnljS0kIjrGfhdTvKEWIoj0bcJgMyrKhxNMoZZGmHZsstQgjDIL3g==} hasBin: true @@ -11094,6 +11256,10 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@commander-js/extra-typings@12.1.0(commander@12.1.0)': + dependencies: + commander: 12.1.0 + '@commitlint/parse@19.5.0': dependencies: '@commitlint/types': 19.5.0 @@ -12990,6 +13156,15 @@ snapshots: '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 + '@types/fs-extra@8.1.5': + dependencies: + '@types/node': 22.10.1 + + '@types/glob@7.2.0': + dependencies: + '@types/minimatch': 5.1.2 + '@types/node': 22.10.1 + '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 @@ -13012,6 +13187,8 @@ snapshots: '@types/mime@1.3.5': {} + '@types/minimatch@5.1.2': {} + '@types/mute-stream@0.0.4': dependencies: '@types/node': 22.10.1 @@ -13066,6 +13243,8 @@ snapshots: '@types/statuses@2.0.5': {} + '@types/tinycolor2@1.4.6': {} + '@types/tough-cookie@4.0.5': {} '@types/unist@3.0.3': {} @@ -13487,17 +13666,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)': dependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.97.1)': dependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1) @@ -13707,6 +13886,8 @@ snapshots: array-slice@1.1.0: {} + array-union@2.1.0: {} + assertion-error@2.0.1: {} ast-types@0.16.1: @@ -14171,10 +14352,16 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + crossws@0.2.4: {} + crossws@0.3.1: dependencies: uncrypto: 0.1.3 + crypto-random-string@4.0.0: + dependencies: + type-fest: 1.4.0 + css-declaration-sorter@7.2.0(postcss@8.4.49): dependencies: postcss: 8.4.49 @@ -15172,6 +15359,12 @@ snapshots: jsonfile: 4.0.0 universalify: 0.1.2 + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fs-minipass@2.1.0: dependencies: minipass: 3.3.6 @@ -15300,6 +15493,17 @@ snapshots: globals@15.13.0: {} + globby@10.0.1: + dependencies: + '@types/glob': 7.2.0 + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.2 + glob: 7.2.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + globby@13.2.2: dependencies: dir-glob: 3.0.1 @@ -15329,6 +15533,11 @@ snapshots: graceful-fs@4.2.11: {} + gradient-string@3.0.0: + dependencies: + chalk: 5.3.0 + tinygradient: 1.1.5 + graphemer@1.4.0: {} graphql@16.9.0: {} @@ -15337,6 +15546,21 @@ snapshots: dependencies: duplexer: 0.1.2 + h3@1.11.1: + dependencies: + cookie-es: 1.2.2 + crossws: 0.2.4 + defu: 6.1.4 + destr: 2.0.3 + iron-webcrypto: 1.2.1 + ohash: 1.1.4 + radix3: 1.1.2 + ufo: 1.5.4 + uncrypto: 0.1.3 + unenv: 1.10.0 + transitivePeerDependencies: + - uWebSockets.js + h3@1.13.0: dependencies: cookie-es: 1.2.2 @@ -15405,7 +15629,7 @@ snapshots: relateurl: 0.2.7 terser: 5.36.0 - html-webpack-plugin@5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + html-webpack-plugin@5.6.3(@rspack/core@1.1.5(@swc/helpers@0.5.15))(webpack@5.97.1): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -15628,6 +15852,8 @@ snapshots: dependencies: isobject: 3.0.1 + is-plain-object@3.0.1: {} + is-plain-object@5.0.0: {} is-potential-custom-element-name@1.0.1: {} @@ -17068,6 +17294,14 @@ snapshots: glob: 11.0.0 package-json-from-dist: 1.0.1 + rollup-plugin-copy@3.5.0: + dependencies: + '@types/fs-extra': 8.1.5 + colorette: 1.4.0 + fs-extra: 8.1.0 + globby: 10.0.1 + is-plain-object: 3.0.1 + rollup-plugin-dts@6.1.1(rollup@3.29.5)(typescript@5.7.2): dependencies: magic-string: 0.30.14 @@ -17286,6 +17520,8 @@ snapshots: dependencies: unicode-emoji-modifier-base: 1.0.0 + slash@3.0.0: {} + slash@4.0.0: {} slash@5.1.0: {} @@ -17466,7 +17702,7 @@ snapshots: csso: 5.0.5 picocolors: 1.1.1 - swc-loader@0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + swc-loader@0.2.6(@swc/core@1.10.1(@swc/helpers@0.5.15))(webpack@5.97.1): dependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) '@swc/counter': 0.1.3 @@ -17536,26 +17772,35 @@ snapshots: mkdirp: 1.0.4 yallist: 4.0.0 - terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + temp-dir@3.0.0: {} + + tempy@3.1.0: + dependencies: + is-stream: 3.0.0 + temp-dir: 3.0.0 + type-fest: 2.19.0 + unique-string: 3.0.0 + + terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) + webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0) optionalDependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) esbuild: 0.24.0 - terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)): + terser-webpack-plugin@5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.1): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.36.0 - webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0) + webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) optionalDependencies: '@swc/core': 1.10.1(@swc/helpers@0.5.15) esbuild: 0.24.0 @@ -17593,6 +17838,8 @@ snapshots: tinybench@2.9.0: {} + tinycolor2@1.6.0: {} + tinyexec@0.3.1: {} tinyglobby@0.2.10: @@ -17600,6 +17847,11 @@ snapshots: fdir: 6.4.2(picomatch@4.0.2) picomatch: 4.0.2 + tinygradient@1.1.5: + dependencies: + '@types/tinycolor2': 1.4.6 + tinycolor2: 1.6.0 + tinypool@1.0.2: {} tinyrainbow@1.2.0: {} @@ -17687,6 +17939,8 @@ snapshots: type-fest@0.21.3: {} + type-fest@1.4.0: {} + type-fest@2.19.0: {} type-fest@4.30.0: {} @@ -17824,6 +18078,10 @@ snapshots: transitivePeerDependencies: - rollup + unique-string@3.0.0: + dependencies: + crypto-random-string: 4.0.0 + universalify@0.1.2: {} universalify@0.2.0: {} @@ -17937,6 +18195,76 @@ snapshots: vary@1.1.2: {} + vinxi@0.4.3(@types/node@22.10.1)(ioredis@5.4.1)(terser@5.36.0)(typescript@5.7.2): + dependencies: + '@babel/core': 7.26.0 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) + '@types/micromatch': 4.0.9 + '@vinxi/listhen': 1.5.6 + boxen: 7.1.1 + chokidar: 3.6.0 + citty: 0.1.6 + consola: 3.2.3 + crossws: 0.2.4 + dax-sh: 0.39.2 + defu: 6.1.4 + es-module-lexer: 1.5.4 + esbuild: 0.20.2 + fast-glob: 3.3.2 + get-port-please: 3.1.2 + h3: 1.11.1 + hookable: 5.5.3 + http-proxy: 1.18.1 + micromatch: 4.0.8 + nitropack: 2.10.4(typescript@5.7.2) + node-fetch-native: 1.6.4 + path-to-regexp: 6.3.0 + pathe: 1.1.2 + radix3: 1.1.2 + resolve: 1.22.8 + serve-placeholder: 2.0.2 + serve-static: 1.16.2 + ufo: 1.5.4 + unctx: 2.3.1 + unenv: 1.10.0 + unstorage: 1.13.1(ioredis@5.4.1) + vite: 5.4.11(@types/node@22.10.1)(terser@5.36.0) + zod: 3.23.8 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/kv' + - better-sqlite3 + - debug + - drizzle-orm + - encoding + - idb-keyval + - ioredis + - less + - lightningcss + - mysql2 + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - typescript + - uWebSockets.js + - xml2js + vinxi@0.5.1(@types/node@22.10.1)(ioredis@5.4.1)(jiti@2.4.1)(terser@5.36.0)(tsx@4.19.2)(typescript@5.7.2)(yaml@2.6.1): dependencies: '@babel/core': 7.26.0 @@ -18175,9 +18503,9 @@ snapshots: webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack-dev-server@5.1.0)(webpack@5.97.1))(webpack-dev-server@5.1.0(webpack-cli@5.1.4)(webpack@5.97.1))(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.97.1) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.97.1) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@5.1.0)(webpack@5.97.1) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 @@ -18191,7 +18519,7 @@ snapshots: optionalDependencies: webpack-dev-server: 5.1.0(webpack-cli@5.1.4)(webpack@5.97.1) - webpack-dev-middleware@7.4.2(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)): + webpack-dev-middleware@7.4.2(webpack@5.97.1): dependencies: colorette: 2.0.20 memfs: 4.14.1 @@ -18230,7 +18558,7 @@ snapshots: serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 7.4.2(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + webpack-dev-middleware: 7.4.2(webpack@5.97.1) ws: 8.18.0 optionalDependencies: webpack: 5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4) @@ -18303,7 +18631,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.1(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.10.1(@swc/helpers@0.5.15))(esbuild@0.24.0)(webpack@5.97.1) watchpack: 2.4.2 webpack-sources: 3.2.3 optionalDependencies: diff --git a/scripts/publish.js b/scripts/publish.js index 224c6fa6be..8dcd76481b 100644 --- a/scripts/publish.js +++ b/scripts/publish.js @@ -76,6 +76,10 @@ await publish({ name: '@tanstack/eslint-plugin-router', packageDir: 'packages/eslint-plugin-router', }, + { + name: '@tanstack/create-start', + packageDir: 'packages/create-start', + }, ], branchConfigs: { main: { From 6d810d7f580a8399d1fe1abd13c971d38c1bdb54 Mon Sep 17 00:00:00 2001 From: Arnaud Kleinpeter Date: Fri, 20 Dec 2024 11:53:13 +0100 Subject: [PATCH 07/14] docs: Minor typo in middleware.md (#3042) --- docs/framework/react/start/middleware.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/framework/react/start/middleware.md b/docs/framework/react/start/middleware.md index 41d8b9e650..72b2c3b9be 100644 --- a/docs/framework/react/start/middleware.md +++ b/docs/framework/react/start/middleware.md @@ -5,7 +5,7 @@ title: Middleware ## What is Middleware? -Middleware allows you to customized the behavior of server functions created with `createServerFn` with things like shared validation, context, and much more. Middleware can even depend on other middleware to create a chain of operations that are executed hierarchically and in order. +Middleware allows you to customize the behavior of server functions created with `createServerFn` with things like shared validation, context, and much more. Middleware can even depend on other middleware to create a chain of operations that are executed hierarchically and in order. ## What kinds of things can I do with Middleware? From e44bec81b17ac5c7c5e4c78ebda311e0f267e95d Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Fri, 20 Dec 2024 10:56:38 +0000 Subject: [PATCH 08/14] release: v1.92.0 --- packages/create-start/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-start/package.json b/packages/create-start/package.json index 0a1f6db22d..aae6a88bae 100644 --- a/packages/create-start/package.json +++ b/packages/create-start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-start", - "version": "1.81.5", + "version": "1.92.0", "description": "Modern and scalable routing for React applications", "author": "Tim O'Connell", "license": "MIT", From 72b7c82ac58658a2d3558a3437e1bf65e5233022 Mon Sep 17 00:00:00 2001 From: Joshua Knauber <49953016+joshuaKnauber@users.noreply.github.com> Date: Sun, 22 Dec 2024 00:49:06 +0100 Subject: [PATCH 09/14] refactor(react-router): expose `scrollBehavior` on the `ScrollRestoration` component (#3053) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Sean Cassiere <33615041+SeanCassiere@users.noreply.github.com> --- .../framework/react/guide/scroll-restoration.md | 17 +++++++++++++++++ .../react-router/src/scroll-restoration.tsx | 7 ++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/framework/react/guide/scroll-restoration.md b/docs/framework/react/guide/scroll-restoration.md index 2f53c0519e..7b56faa245 100644 --- a/docs/framework/react/guide/scroll-restoration.md +++ b/docs/framework/react/guide/scroll-restoration.md @@ -137,3 +137,20 @@ function Component() { ) } ``` + +## Scroll Behavior + +To control the scroll behavior when navigating between pages, you can use the `scrollBehavior` option. This allows you to make the transition between pages instant instead of a smooth scroll. The global configuration of scroll restoration behavior has the same options as those supported by the browser, which are `smooth`, `instant`, and `auto` (see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#behavior) for more information). + +```tsx +import { ScrollRestoration } from '@tanstack/react-router' + +function Root() { + return ( + <> + + + + ) +} +``` diff --git a/packages/react-router/src/scroll-restoration.tsx b/packages/react-router/src/scroll-restoration.tsx index 26a3861e90..6d764bebcf 100644 --- a/packages/react-router/src/scroll-restoration.tsx +++ b/packages/react-router/src/scroll-restoration.tsx @@ -45,6 +45,7 @@ const cache: Cache = sessionsStorage export type ScrollRestorationOptions = { getKey?: (location: ParsedLocation) => string + scrollBehavior?: ScrollToOptions['behavior'] } /** @@ -154,7 +155,11 @@ export function useScrollRestoration(options?: ScrollRestorationOptions) { if (key === restoreKey) { if (elementSelector === windowKey) { windowRestored = true - window.scrollTo(entry.scrollX, entry.scrollY) + window.scrollTo({ + top: entry.scrollY, + left: entry.scrollX, + behavior: options?.scrollBehavior, + }) } else if (elementSelector) { const element = document.querySelector(elementSelector) if (element) { From 3505a1ff228f01662542e174a39dd50c77e2d22a Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sat, 21 Dec 2024 23:54:12 +0000 Subject: [PATCH 10/14] release: v1.92.1 --- examples/react/authenticated-routes/package.json | 4 ++-- .../react/basic-default-search-params/package.json | 4 ++-- .../react/basic-file-based-codesplitting/package.json | 4 ++-- examples/react/basic-file-based/package.json | 4 ++-- .../react/basic-react-query-file-based/package.json | 4 ++-- examples/react/basic-react-query/package.json | 4 ++-- examples/react/basic-ssr-file-based/package.json | 6 +++--- .../react/basic-ssr-streaming-file-based/package.json | 6 +++--- examples/react/basic-virtual-file-based/package.json | 4 ++-- .../react/basic-virtual-inside-file-based/package.json | 4 ++-- examples/react/basic/package.json | 4 ++-- examples/react/deferred-data/package.json | 4 ++-- examples/react/kitchen-sink-file-based/package.json | 4 ++-- .../kitchen-sink-react-query-file-based/package.json | 4 ++-- examples/react/kitchen-sink-react-query/package.json | 4 ++-- examples/react/kitchen-sink/package.json | 4 ++-- examples/react/large-file-based/package.json | 4 ++-- examples/react/location-masking/package.json | 4 ++-- examples/react/navigation-blocking/package.json | 4 ++-- .../react/quickstart-esbuild-file-based/package.json | 4 ++-- examples/react/quickstart-file-based/package.json | 4 ++-- .../react/quickstart-rspack-file-based/package.json | 4 ++-- .../react/quickstart-webpack-file-based/package.json | 4 ++-- examples/react/quickstart/package.json | 4 ++-- .../react/router-monorepo-react-query/package.json | 4 ++-- .../packages/app/package.json | 2 +- .../packages/router/package.json | 2 +- examples/react/router-monorepo-simple/package.json | 4 ++-- .../router-monorepo-simple/packages/app/package.json | 2 +- .../packages/router/package.json | 2 +- examples/react/scroll-restoration/package.json | 4 ++-- examples/react/search-validator-adapters/package.json | 10 +++++----- examples/react/start-basic-auth/package.json | 6 +++--- examples/react/start-basic-react-query/package.json | 8 ++++---- examples/react/start-basic-rsc/package.json | 6 +++--- examples/react/start-basic/package.json | 6 +++--- examples/react/start-clerk-basic/package.json | 6 +++--- examples/react/start-convex-trellaux/package.json | 8 ++++---- examples/react/start-counter/package.json | 4 ++-- examples/react/start-large/package.json | 6 +++--- examples/react/start-supabase-basic/package.json | 6 +++--- examples/react/start-trellaux/package.json | 8 ++++---- examples/react/with-framer-motion/package.json | 4 ++-- examples/react/with-trpc-react-query/package.json | 4 ++-- examples/react/with-trpc/package.json | 4 ++-- packages/arktype-adapter/package.json | 2 +- packages/create-router/package.json | 2 +- packages/create-start/package.json | 2 +- packages/react-router-with-query/package.json | 2 +- packages/react-router/package.json | 2 +- packages/router-devtools/package.json | 2 +- packages/start/package.json | 2 +- packages/valibot-adapter/package.json | 2 +- packages/zod-adapter/package.json | 2 +- 54 files changed, 112 insertions(+), 112 deletions(-) diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json index 1edb5629fa..d4835ade48 100644 --- a/examples/react/authenticated-routes/package.json +++ b/examples/react/authenticated-routes/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json index 603825423e..8a1e90f37f 100644 --- a/examples/react/basic-default-search-params/package.json +++ b/examples/react/basic-default-search-params/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-file-based-codesplitting/package.json b/examples/react/basic-file-based-codesplitting/package.json index 2a2c43cfc2..88499cad41 100644 --- a/examples/react/basic-file-based-codesplitting/package.json +++ b/examples/react/basic-file-based-codesplitting/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json index a74a8194f8..e82e62f253 100644 --- a/examples/react/basic-file-based/package.json +++ b/examples/react/basic-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json index a1d75f0e89..b85ef3e317 100644 --- a/examples/react/basic-react-query-file-based/package.json +++ b/examples/react/basic-react-query-file-based/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json index 817956d94b..7a5fe86251 100644 --- a/examples/react/basic-react-query/package.json +++ b/examples/react/basic-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index 01a4f30898..d00940b5d4 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -11,10 +11,10 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.91.3", + "@tanstack/start": "^1.92.1", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index 1259dae6b1..ab1ef65bae 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -11,10 +11,10 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.91.3", + "@tanstack/start": "^1.92.1", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json index 469479cae7..5dec2951fc 100644 --- a/examples/react/basic-virtual-file-based/package.json +++ b/examples/react/basic-virtual-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "@tanstack/virtual-file-routes": "^1.87.6", "react": "^18.2.0", diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json index d0902c695d..eccab24d2f 100644 --- a/examples/react/basic-virtual-inside-file-based/package.json +++ b/examples/react/basic-virtual-inside-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "@tanstack/virtual-file-routes": "^1.87.6", "react": "^18.2.0", diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index a3570ae5a3..2a64646565 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json index bae3fa1ffd..62b8d3825b 100644 --- a/examples/react/deferred-data/package.json +++ b/examples/react/deferred-data/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json index 25f5312334..83cd9c4ba3 100644 --- a/examples/react/kitchen-sink-file-based/package.json +++ b/examples/react/kitchen-sink-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json index 4f20caaebd..61fb97c1e2 100644 --- a/examples/react/kitchen-sink-react-query-file-based/package.json +++ b/examples/react/kitchen-sink-react-query-file-based/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json index 963c9177a1..8618a5a005 100644 --- a/examples/react/kitchen-sink-react-query/package.json +++ b/examples/react/kitchen-sink-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json index 40dc7ff942..08de509b7d 100644 --- a/examples/react/kitchen-sink/package.json +++ b/examples/react/kitchen-sink/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json index 5ec9fbae23..22da8997ba 100644 --- a/examples/react/large-file-based/package.json +++ b/examples/react/large-file-based/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json index 153bbec3f0..aac761ba11 100644 --- a/examples/react/location-masking/package.json +++ b/examples/react/location-masking/package.json @@ -11,8 +11,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json index 85d0269d89..112c070b34 100644 --- a/examples/react/navigation-blocking/package.json +++ b/examples/react/navigation-blocking/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 9a75ed9e7d..2cd2dc8c5c 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -9,8 +9,8 @@ "start": "dev" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json index 8a1cd02d0c..6f8c5be086 100644 --- a/examples/react/quickstart-file-based/package.json +++ b/examples/react/quickstart-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json index b365b23269..51373d8d1d 100644 --- a/examples/react/quickstart-rspack-file-based/package.json +++ b/examples/react/quickstart-rspack-file-based/package.json @@ -8,8 +8,8 @@ "preview": "rsbuild preview" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json index ef05207b76..1e23f24858 100644 --- a/examples/react/quickstart-webpack-file-based/package.json +++ b/examples/react/quickstart-webpack-file-based/package.json @@ -7,8 +7,8 @@ "build": "webpack build && tsc --noEmit" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json index 948dc6e4f8..d8f72c824b 100644 --- a/examples/react/quickstart/package.json +++ b/examples/react/quickstart/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json index 3cddaf44b9..158a91d2a7 100644 --- a/examples/react/router-monorepo-react-query/package.json +++ b/examples/react/router-monorepo-react-query/package.json @@ -12,8 +12,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/router-monorepo-react-query/packages/app/package.json b/examples/react/router-monorepo-react-query/packages/app/package.json index 5fa21820c1..ad0cf68a98 100644 --- a/examples/react/router-monorepo-react-query/packages/app/package.json +++ b/examples/react/router-monorepo-react-query/packages/app/package.json @@ -20,7 +20,7 @@ "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/router-devtools": "^1.92.1", "vite": "^6.0.3", "vite-plugin-dts": "^4.3.0" }, diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json index 99426797a6..cc761dad3a 100644 --- a/examples/react/router-monorepo-react-query/packages/router/package.json +++ b/examples/react/router-monorepo-react-query/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.90.0", "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", + "@tanstack/react-router": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "@router-mono-react-query/post-query": "workspace:*", "redaxios": "^0.5.1", diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json index 945416e9c6..994e30263e 100644 --- a/examples/react/router-monorepo-simple/package.json +++ b/examples/react/router-monorepo-simple/package.json @@ -8,8 +8,8 @@ "dev": "pnpm router build && pnpm post-feature build && pnpm app dev" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/router-monorepo-simple/packages/app/package.json b/examples/react/router-monorepo-simple/packages/app/package.json index e36a6963a7..15c400fe1a 100644 --- a/examples/react/router-monorepo-simple/packages/app/package.json +++ b/examples/react/router-monorepo-simple/packages/app/package.json @@ -19,7 +19,7 @@ "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/router-devtools": "^1.92.1", "vite": "^6.0.3", "vite-plugin-dts": "^4.3.0" }, diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json index 6a36379c7c..6e6dad2ea7 100644 --- a/examples/react/router-monorepo-simple/packages/router/package.json +++ b/examples/react/router-monorepo-simple/packages/router/package.json @@ -9,7 +9,7 @@ "types": "./dist/index.d.ts", "dependencies": { "@tanstack/history": "^1.90.0", - "@tanstack/react-router": "^1.91.3", + "@tanstack/react-router": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "redaxios": "^0.5.1", "zod": "^3.23.8", diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json index 3c162e97f2..5e5218ea8b 100644 --- a/examples/react/scroll-restoration/package.json +++ b/examples/react/scroll-restoration/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", + "@tanstack/react-router": "^1.92.1", "@tanstack/react-virtual": "^3.11.1", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/router-devtools": "^1.92.1", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json index e2c911e4b1..9fdd21c8dc 100644 --- a/examples/react/search-validator-adapters/package.json +++ b/examples/react/search-validator-adapters/package.json @@ -11,12 +11,12 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/arktype-adapter": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/arktype-adapter": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/valibot-adapter": "^1.91.3", - "@tanstack/zod-adapter": "^1.91.3", + "@tanstack/valibot-adapter": "^1.92.1", + "@tanstack/zod-adapter": "^1.92.1", "arktype": "2.0.0-rc.26", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index 23c71bf022..7ca0fe9de5 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@prisma/client": "5.22.0", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "prisma": "^5.22.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index 4e96d66d9b..56a0d87b32 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/react-router-with-query": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/react-router-with-query": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-basic-rsc/package.json b/examples/react/start-basic-rsc/package.json index 66a417119a..935cc46592 100644 --- a/examples/react/start-basic-rsc/package.json +++ b/examples/react/start-basic-rsc/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@babel/plugin-syntax-typescript": "^7.25.9", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "redaxios": "^0.5.1", "tailwind-merge": "^2.5.5", "vinxi": "0.5.1" diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 3bef128dff..b8a7b3c550 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -9,9 +9,9 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json index 060e5a816a..9a13f584a9 100644 --- a/examples/react/start-clerk-basic/package.json +++ b/examples/react/start-clerk-basic/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@clerk/tanstack-start": "0.6.5", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index e6a54c02b2..fd27572e32 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -13,10 +13,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/react-router-with-query": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/react-router-with-query": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "@convex-dev/react-query": "0.0.0-alpha.8", "concurrently": "^8.2.2", "convex": "^1.17.3", diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index 628d3c0c5d..5842688c99 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -9,8 +9,8 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/start": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index e41bca4756..0cbfa90e74 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -12,9 +12,9 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index f93e6b3564..be902ddf6a 100644 --- a/examples/react/start-supabase-basic/package.json +++ b/examples/react/start-supabase-basic/package.json @@ -15,9 +15,9 @@ "dependencies": { "@supabase/ssr": "^0.5.2", "@supabase/supabase-js": "^2.47.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index fca0779f77..9fd0ad825b 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/react-router-with-query": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", - "@tanstack/start": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/react-router-with-query": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", + "@tanstack/start": "^1.92.1", "ky": "^1.7.2", "msw": "^2.6.8", "react": "^18.3.1", diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json index 540a3b5946..5985d7fd3d 100644 --- a/examples/react/with-framer-motion/package.json +++ b/examples/react/with-framer-motion/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "redaxios": "^0.5.1", "framer-motion": "^11.13.3", "react": "^18.2.0", diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json index 8ae2cab652..f704182d45 100644 --- a/examples/react/with-trpc-react-query/package.json +++ b/examples/react/with-trpc-react-query/package.json @@ -10,8 +10,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "@trpc/client": "11.0.0-rc.660", "@trpc/react-query": "11.0.0-rc.660", diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json index d7780a1d70..9a1d3f7142 100644 --- a/examples/react/with-trpc/package.json +++ b/examples/react/with-trpc/package.json @@ -8,8 +8,8 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.91.3", - "@tanstack/router-devtools": "^1.91.3", + "@tanstack/react-router": "^1.92.1", + "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", "@trpc/client": "11.0.0-rc.660", "@trpc/server": "11.0.0-rc.660", diff --git a/packages/arktype-adapter/package.json b/packages/arktype-adapter/package.json index 7ee5fbeabe..48e04c31a0 100644 --- a/packages/arktype-adapter/package.json +++ b/packages/arktype-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/arktype-adapter", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/create-router/package.json b/packages/create-router/package.json index ecaa0a61d4..b90b35cd46 100644 --- a/packages/create-router/package.json +++ b/packages/create-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-router", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/create-start/package.json b/packages/create-start/package.json index aae6a88bae..59d81a77bd 100644 --- a/packages/create-start/package.json +++ b/packages/create-start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-start", - "version": "1.92.0", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tim O'Connell", "license": "MIT", diff --git a/packages/react-router-with-query/package.json b/packages/react-router-with-query/package.json index dfb0eca965..0d20acfa09 100644 --- a/packages/react-router-with-query/package.json +++ b/packages/react-router-with-query/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-with-query", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 0c8b748525..8424810a68 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json index a08522143e..4cd65076ef 100644 --- a/packages/router-devtools/package.json +++ b/packages/router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/start/package.json b/packages/start/package.json index ed1f021f52..55524cd98a 100644 --- a/packages/start/package.json +++ b/packages/start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/start", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/valibot-adapter/package.json b/packages/valibot-adapter/package.json index a05e3db1a5..eed9a2906a 100644 --- a/packages/valibot-adapter/package.json +++ b/packages/valibot-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/valibot-adapter", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/zod-adapter/package.json b/packages/zod-adapter/package.json index 367cad576d..eea1adbe6a 100644 --- a/packages/zod-adapter/package.json +++ b/packages/zod-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/zod-adapter", - "version": "1.91.3", + "version": "1.92.1", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", From 4a7d3ca689badcbaf0f4fa9e6fe8c875a2663815 Mon Sep 17 00:00:00 2001 From: alakhpc Date: Sun, 22 Dec 2024 15:50:11 -0500 Subject: [PATCH 11/14] fix(start): returning `null` from server functions (#3048) Co-authored-by: SeanCassiere <33615041+SeanCassiere@users.noreply.github.com> --- .../-server-fns/allow-fn-return-null.tsx | 63 +++++++++++++++++++ e2e/start/basic/app/routes/server-fns.tsx | 2 + e2e/start/basic/app/styles/app.css | 4 ++ e2e/start/basic/tests/base.spec.ts | 20 ++++++ packages/start/src/client/createServerFn.ts | 5 +- packages/start/src/server-handler/index.tsx | 13 ++++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 e2e/start/basic/app/routes/-server-fns/allow-fn-return-null.tsx diff --git a/e2e/start/basic/app/routes/-server-fns/allow-fn-return-null.tsx b/e2e/start/basic/app/routes/-server-fns/allow-fn-return-null.tsx new file mode 100644 index 0000000000..297498e45a --- /dev/null +++ b/e2e/start/basic/app/routes/-server-fns/allow-fn-return-null.tsx @@ -0,0 +1,63 @@ +/** + * This exported component checks whether the server function can + * return null without throwing an error or returning something else. + * @link https://github.com/TanStack/router/issues/2776 + */ + +import * as React from 'react' +import { createServerFn } from '@tanstack/start' + +const $allow_return_null_getFn = createServerFn().handler(async () => { + return null +}) +const $allow_return_null_postFn = createServerFn({ method: 'POST' }).handler( + async () => { + return null + }, +) + +export function AllowServerFnReturnNull() { + const [getServerResult, setGetServerResult] = React.useState('-') + const [postServerResult, setPostServerResult] = React.useState('-') + + return ( +
+

Allow ServerFn to return `null`

+

+ This component checks whether the server function can return null + without throwing an error. +

+
+ It should return{' '} + +
{JSON.stringify(null)}
+
+
+

+ {`GET: $allow_return_null_getFn returns`} +
+ + {JSON.stringify(getServerResult)} + +

+

+ {`POST: $allow_return_null_postFn returns`} +
+ + {JSON.stringify(postServerResult)} + +

+ +
+ ) +} diff --git a/e2e/start/basic/app/routes/server-fns.tsx b/e2e/start/basic/app/routes/server-fns.tsx index c829feed9d..21939570b9 100644 --- a/e2e/start/basic/app/routes/server-fns.tsx +++ b/e2e/start/basic/app/routes/server-fns.tsx @@ -3,6 +3,7 @@ import { createFileRoute } from '@tanstack/react-router' import { ConsistentServerFnCalls } from './-server-fns/consistent-fn-calls' import { MultipartServerFnCall } from './-server-fns/multipart-formdata-fn-call' +import { AllowServerFnReturnNull } from './-server-fns/allow-fn-return-null' export const Route = createFileRoute('/server-fns')({ component: RouteComponent, @@ -13,6 +14,7 @@ function RouteComponent() { <> + ) } diff --git a/e2e/start/basic/app/styles/app.css b/e2e/start/basic/app/styles/app.css index d6426ccb72..e858b17319 100644 --- a/e2e/start/basic/app/styles/app.css +++ b/e2e/start/basic/app/styles/app.css @@ -3,6 +3,10 @@ @tailwind utilities; @layer base { + html { + color-scheme: light dark; + } + html, body { @apply text-gray-900 bg-gray-50 dark:bg-gray-950 dark:text-gray-200; diff --git a/e2e/start/basic/tests/base.spec.ts b/e2e/start/basic/tests/base.spec.ts index 2f2414bccc..80c30f9d07 100644 --- a/e2e/start/basic/tests/base.spec.ts +++ b/e2e/start/basic/tests/base.spec.ts @@ -192,3 +192,23 @@ test('env-only functions can only be called on the server or client respectively 'client got: hello', ) }) + +test.only('Server function can return null for GET and POST calls', async ({ + page, +}) => { + await page.goto('/server-fns') + + await page.waitForLoadState('networkidle') + await page.getByTestId('test-allow-server-fn-return-null-btn').click() + await page.waitForLoadState('networkidle') + + // GET call + await expect( + page.getByTestId('allow_return_null_getFn-response'), + ).toContainText(JSON.stringify(null)) + + // POST call + await expect( + page.getByTestId('allow_return_null_postFn-response'), + ).toContainText(JSON.stringify(null)) +}) diff --git a/packages/start/src/client/createServerFn.ts b/packages/start/src/client/createServerFn.ts index 6408e783cd..d98522489a 100644 --- a/packages/start/src/client/createServerFn.ts +++ b/packages/start/src/client/createServerFn.ts @@ -365,7 +365,10 @@ const applyMiddleware = ( context, sendContext, headers, - result: userResult?.result ?? (mCtx as any).result, + result: + userResult?.result !== undefined + ? userResult.result + : (mCtx as any).result, } as MiddlewareResult & { method: Method }) diff --git a/packages/start/src/server-handler/index.tsx b/packages/start/src/server-handler/index.tsx index 51c9881985..6c28b2536f 100644 --- a/packages/start/src/server-handler/index.tsx +++ b/packages/start/src/server-handler/index.tsx @@ -2,6 +2,7 @@ import { defaultTransformer, isNotFound, + isPlainObject, isRedirect, } from '@tanstack/react-router' import invariant from 'tiny-invariant' @@ -82,6 +83,12 @@ export async function handleServerRequest(request: Request, _event?: H3Event) { if (result instanceof Response) { return result + } else if ( + isPlainObject(result) && + 'result' in result && + result.result instanceof Response + ) { + return result.result } // TODO: RSCs @@ -119,6 +126,12 @@ export async function handleServerRequest(request: Request, _event?: H3Event) { } catch (error: any) { if (error instanceof Response) { return error + } else if ( + isPlainObject(error) && + 'result' in error && + error.result instanceof Response + ) { + return error.result } // Currently this server-side context has no idea how to From b2ee838a8eed896e1a429d24c8bf7143e18e505d Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sun, 22 Dec 2024 20:58:48 +0000 Subject: [PATCH 12/14] release: v1.92.2 --- examples/react/basic-ssr-file-based/package.json | 2 +- examples/react/basic-ssr-streaming-file-based/package.json | 2 +- examples/react/start-basic-auth/package.json | 2 +- examples/react/start-basic-react-query/package.json | 2 +- examples/react/start-basic-rsc/package.json | 2 +- examples/react/start-basic/package.json | 2 +- examples/react/start-clerk-basic/package.json | 2 +- examples/react/start-convex-trellaux/package.json | 2 +- examples/react/start-counter/package.json | 2 +- examples/react/start-large/package.json | 2 +- examples/react/start-supabase-basic/package.json | 2 +- examples/react/start-trellaux/package.json | 2 +- packages/create-start/package.json | 2 +- packages/start/package.json | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index d00940b5d4..d3ecb76910 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -14,7 +14,7 @@ "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index ab1ef65bae..badb2005a9 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -14,7 +14,7 @@ "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index 7ca0fe9de5..717bccd91a 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -13,7 +13,7 @@ "@prisma/client": "5.22.0", "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "prisma": "^5.22.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index 56a0d87b32..938527a9f5 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -14,7 +14,7 @@ "@tanstack/react-router": "^1.92.1", "@tanstack/react-router-with-query": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-basic-rsc/package.json b/examples/react/start-basic-rsc/package.json index 935cc46592..39f979f0f0 100644 --- a/examples/react/start-basic-rsc/package.json +++ b/examples/react/start-basic-rsc/package.json @@ -12,7 +12,7 @@ "@babel/plugin-syntax-typescript": "^7.25.9", "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "redaxios": "^0.5.1", "tailwind-merge": "^2.5.5", "vinxi": "0.5.1" diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index b8a7b3c550..58ce670527 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -11,7 +11,7 @@ "dependencies": { "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json index 9a13f584a9..8ed5e6d6a7 100644 --- a/examples/react/start-clerk-basic/package.json +++ b/examples/react/start-clerk-basic/package.json @@ -12,7 +12,7 @@ "@clerk/tanstack-start": "0.6.5", "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index fd27572e32..efa76bb986 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -16,7 +16,7 @@ "@tanstack/react-router": "^1.92.1", "@tanstack/react-router-with-query": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "@convex-dev/react-query": "0.0.0-alpha.8", "concurrently": "^8.2.2", "convex": "^1.17.3", diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index 5842688c99..4276dc8289 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index 0cbfa90e74..2d6011c718 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -14,7 +14,7 @@ "@tanstack/react-query": "^5.62.3", "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index be902ddf6a..05cc827acd 100644 --- a/examples/react/start-supabase-basic/package.json +++ b/examples/react/start-supabase-basic/package.json @@ -17,7 +17,7 @@ "@supabase/supabase-js": "^2.47.3", "@tanstack/react-router": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index 9fd0ad825b..6a9d5a55bc 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -14,7 +14,7 @@ "@tanstack/react-router": "^1.92.1", "@tanstack/react-router-with-query": "^1.92.1", "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.1", + "@tanstack/start": "^1.92.2", "ky": "^1.7.2", "msw": "^2.6.8", "react": "^18.3.1", diff --git a/packages/create-start/package.json b/packages/create-start/package.json index 59d81a77bd..6dc150e430 100644 --- a/packages/create-start/package.json +++ b/packages/create-start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-start", - "version": "1.92.1", + "version": "1.92.2", "description": "Modern and scalable routing for React applications", "author": "Tim O'Connell", "license": "MIT", diff --git a/packages/start/package.json b/packages/start/package.json index 55524cd98a..c881781802 100644 --- a/packages/start/package.json +++ b/packages/start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/start", - "version": "1.92.1", + "version": "1.92.2", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", From ebf67cab353aa0d959a5e9bdb21ffad7bc9fc83f Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Sun, 22 Dec 2024 23:25:53 -0700 Subject: [PATCH 13/14] fix: allow serverFn errors to also have context (#3037) * fix: allow serverFn errors to also have context * fix: allow errors, undefined from server functions * remove logs * fix: transformer and tests * ci: apply automated fixes * no logs * fix conficts --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- packages/react-router/src/transformer.ts | 131 ++++++++++--- .../react-router/tests/transformer.test.tsx | 13 +- packages/start/src/client-runtime/fetcher.tsx | 34 +--- packages/start/src/client/createMiddleware.ts | 21 ++- packages/start/src/client/createServerFn.ts | 177 ++++++++++-------- packages/start/src/server-handler/index.tsx | 2 +- 6 files changed, 237 insertions(+), 141 deletions(-) diff --git a/packages/react-router/src/transformer.ts b/packages/react-router/src/transformer.ts index 7a915aba29..f58f03bb59 100644 --- a/packages/react-router/src/transformer.ts +++ b/packages/react-router/src/transformer.ts @@ -3,49 +3,128 @@ import { isPlainObject } from './utils' export interface RouterTransformer { stringify: (obj: unknown) => string parse: (str: string) => unknown + encode: (value: T) => T + decode: (value: T) => T } export const defaultTransformer: RouterTransformer = { stringify: (value: any) => - JSON.stringify(value, function replacer(key, value) { - const keyVal = this[key] - const transformer = transformers.find((t) => t.stringifyCondition(keyVal)) + JSON.stringify(value, function replacer(key, val) { + const ogVal = this[key] + const transformer = transformers.find((t) => t.stringifyCondition(ogVal)) if (transformer) { - return transformer.stringify(keyVal) + return transformer.stringify(ogVal) } - return value + return val }), parse: (value: string) => - JSON.parse(value, function parser(key, value) { - const keyVal = this[key] - const transformer = transformers.find((t) => t.parseCondition(keyVal)) + JSON.parse(value, function parser(key, val) { + const ogVal = this[key] + if (isPlainObject(ogVal)) { + const transformer = transformers.find((t) => t.parseCondition(ogVal)) - if (transformer) { - return transformer.parse(keyVal) + if (transformer) { + return transformer.parse(ogVal) + } } - return value + return val }), -} + encode: (value: any) => { + // When encodign, dive first + if (Array.isArray(value)) { + return value.map((v) => defaultTransformer.encode(v)) + } -const transformers = [ - { - // Dates - stringifyCondition: (value: any) => value instanceof Date, - stringify: (value: any) => ({ $date: value.toISOString() }), - parseCondition: (value: any) => isPlainObject(value) && value.$date, - parse: (value: any) => new Date(value.$date), + if (isPlainObject(value)) { + return Object.fromEntries( + Object.entries(value).map(([key, v]) => [ + key, + defaultTransformer.encode(v), + ]), + ) + } + + const transformer = transformers.find((t) => t.stringifyCondition(value)) + if (transformer) { + return transformer.stringify(value) + } + + return value }, - { - // undefined - stringifyCondition: (value: any) => value === undefined, - stringify: () => ({ $undefined: '' }), - parseCondition: (value: any) => - isPlainObject(value) && value.$undefined === '', - parse: () => undefined, + decode: (value: any) => { + // Attempt transform first + if (isPlainObject(value)) { + const transformer = transformers.find((t) => t.parseCondition(value)) + if (transformer) { + return transformer.parse(value) + } + } + + if (Array.isArray(value)) { + return value.map((v) => defaultTransformer.decode(v)) + } + + if (isPlainObject(value)) { + return Object.fromEntries( + Object.entries(value).map(([key, v]) => [ + key, + defaultTransformer.decode(v), + ]), + ) + } + + return value }, +} + +const createTransformer = ( + key: T, + check: (value: any) => boolean, + toValue: (value: any) => any = (v) => v, + fromValue: (value: any) => any = (v) => v, +) => ({ + key, + stringifyCondition: check, + stringify: (value: any) => ({ [`$${key}`]: toValue(value) }), + parseCondition: (value: any) => Object.hasOwn(value, `$${key}`), + parse: (value: any) => fromValue(value[`$${key}`]), +}) + +// Keep these ordered by predicted frequency +const transformers = [ + createTransformer( + // Key + 'undefined', + // Check + (v) => v === undefined, + // To + () => 0, + // From + () => undefined, + ), + createTransformer( + // Key + 'date', + // Check + (v) => v instanceof Date, + // To + (v) => v.toISOString(), + // From + (v) => new Date(v), + ), + createTransformer( + // Key + 'error', + // Check + (v) => v instanceof Error, + // To + (v) => ({ ...v, message: v.message, stack: v.stack, cause: v.cause }), + // From + (v) => Object.assign(new Error(v.message), v), + ), ] as const export type TransformerStringify = T extends TSerializable diff --git a/packages/react-router/tests/transformer.test.tsx b/packages/react-router/tests/transformer.test.tsx index cf7e6c67a5..f0278b6582 100644 --- a/packages/react-router/tests/transformer.test.tsx +++ b/packages/react-router/tests/transformer.test.tsx @@ -11,9 +11,9 @@ describe('transformer.stringify', () => { }) test('should stringify undefined', () => { - expect(defaultTransformer.stringify(undefined)).toMatchInlineSnapshot(` - "{"$undefined":""}" - `) + expect(defaultTransformer.stringify(undefined)).toMatchInlineSnapshot( + `"{"$undefined":0}"`, + ) }) test('should stringify object foo="bar"', () => { @@ -23,10 +23,9 @@ describe('transformer.stringify', () => { }) test('should stringify object foo=undefined', () => { - expect(defaultTransformer.stringify({ foo: undefined })) - .toMatchInlineSnapshot(` - "{"foo":{"$undefined":""}}" - `) + expect( + defaultTransformer.stringify({ foo: undefined }), + ).toMatchInlineSnapshot(`"{"foo":{"$undefined":0}}"`) }) test('should stringify object foo=Date', () => { diff --git a/packages/start/src/client-runtime/fetcher.tsx b/packages/start/src/client-runtime/fetcher.tsx index 70eea4b9ae..e47cc2dd4e 100644 --- a/packages/start/src/client-runtime/fetcher.tsx +++ b/packages/start/src/client-runtime/fetcher.tsx @@ -59,12 +59,13 @@ export async function fetcher( // Check if the response is JSON if (response.headers.get('content-type')?.includes('application/json')) { - const text = await response.text() - const json = text ? defaultTransformer.parse(text) : undefined + // Even though the response is JSON, we need to decode it + // because the server may have transformed it + const json = defaultTransformer.decode(await response.json()) // If the response is a redirect or not found, throw it // for the router to handle - if (isRedirect(json) || isNotFound(json)) { + if (isRedirect(json) || isNotFound(json) || json instanceof Error) { throw json } @@ -90,14 +91,13 @@ export async function fetcher( // If the response is JSON, return it parsed const contentType = response.headers.get('content-type') - const text = await response.text() if (contentType && contentType.includes('application/json')) { - return text ? JSON.parse(text) : undefined + return defaultTransformer.decode(await response.json()) } else { // Otherwise, return the text as a fallback // If the user wants more than this, they can pass a // request instead - return text + return response.text() } } @@ -126,27 +126,11 @@ async function handleResponseErrors(response: Response) { const contentType = response.headers.get('content-type') const isJson = contentType && contentType.includes('application/json') - const body = await (async () => { - if (isJson) { - return await response.json() - } - return await response.text() - })() - - const message = `Request failed with status ${response.status}` - if (isJson) { - throw new Error( - JSON.stringify({ - message, - body, - }), - ) - } else { - throw new Error( - [message, `${JSON.stringify(body, null, 2)}`].join('\n\n'), - ) + throw defaultTransformer.decode(await response.json()) } + + throw new Error(await response.text()) } return response diff --git a/packages/start/src/client/createMiddleware.ts b/packages/start/src/client/createMiddleware.ts index 6eefb7751f..33d7f7126a 100644 --- a/packages/start/src/client/createMiddleware.ts +++ b/packages/start/src/client/createMiddleware.ts @@ -124,6 +124,9 @@ export interface MiddlewareServerFnOptions< data: Expand> context: Expand> next: MiddlewareServerNextFn + method: Method + filename: string + functionId: string } export type MiddlewareServerFn< @@ -134,9 +137,11 @@ export type MiddlewareServerFn< TNewClientAfterContext, > = ( options: MiddlewareServerFnOptions, -) => - | Promise> - | ServerResultWithContext +) => MiddlewareServerFnResult + +export type MiddlewareServerFnResult = + | Promise> + | ServerResultWithContext export type MiddlewareClientNextFn = < TNewServerContext = undefined, @@ -156,6 +161,8 @@ export interface MiddlewareClientFnOptions< sendContext?: unknown // cc Chris Horobin method: Method next: MiddlewareClientNextFn + filename: string + functionId: string } export type MiddlewareClientFn< @@ -165,7 +172,9 @@ export type MiddlewareClientFn< TClientContext, > = ( options: MiddlewareClientFnOptions, -) => +) => MiddlewareClientFnResult + +export type MiddlewareClientFnResult = | Promise> | ClientResultWithContext @@ -208,7 +217,9 @@ export type MiddlewareClientAfterFn< TClientContext, TClientAfterContext >, -) => +) => MiddlewareClientAfterFnResult + +export type MiddlewareClientAfterFnResult = | Promise> | ClientAfterResultWithContext diff --git a/packages/start/src/client/createServerFn.ts b/packages/start/src/client/createServerFn.ts index d98522489a..5747b402ae 100644 --- a/packages/start/src/client/createServerFn.ts +++ b/packages/start/src/client/createServerFn.ts @@ -1,5 +1,9 @@ import invariant from 'tiny-invariant' -import { defaultTransformer } from '@tanstack/react-router' +import { + defaultTransformer, + isNotFound, + isRedirect, +} from '@tanstack/react-router' import { mergeHeaders } from './headers' import { globalMiddleware } from './registerGlobalMiddleware' import type { @@ -17,6 +21,8 @@ import type { MergeAllServerContext, MergeAllValidatorInputs, MergeAllValidatorOutputs, + MiddlewareClientFnResult, + MiddlewareServerFnResult, } from './createMiddleware' export interface JsonResponse extends Response { @@ -27,6 +33,7 @@ export type CompiledFetcherFnOptions = { method: Method data: unknown headers?: HeadersInit + context?: any } export type Fetcher = { @@ -35,6 +42,7 @@ export type Fetcher = { method: Method data: unknown headers?: HeadersInit + context?: any }) => Promise } & FetcherImpl @@ -86,7 +94,9 @@ export interface ServerFnCtx { } export type CompiledFetcherFn = { - (opts: CompiledFetcherFnOptions & ServerFnBaseOptions): Promise + ( + opts: CompiledFetcherFnOptions & ServerFnBaseOptions, + ): Promise url: string } @@ -240,25 +250,35 @@ export function createServerFn< data: opts?.data as any, headers: opts?.headers, context: Object.assign({}, extractedFn), - }).then((d) => d.result) + }).then((d) => { + if (d.error) throw d.error + return d.result + }) }, { // This copies over the URL, function ID and filename ...extractedFn, // The extracted function on the server-side calls // this function - __executeServer: (opts: any) => { + __executeServer: async (opts: any) => { const parsedOpts = opts instanceof FormData ? extractFormDataContext(opts) : opts - return executeMiddleware(resolvedMiddleware, 'server', { - ...extractedFn, - ...parsedOpts, - }).then((d) => ({ + const result = await executeMiddleware( + resolvedMiddleware, + 'server', + { + ...extractedFn, + ...parsedOpts, + }, + ).then((d) => ({ // Only send the result and sendContext back to the client result: d.result, + error: d.error, context: d.sendContext, })) + + return result }, }, ) as any @@ -322,55 +342,43 @@ export type MiddlewareOptions = { context?: any } -export type MiddlewareResult = { - context: any - sendContext: any - data: any - result: unknown +export type MiddlewareResult = MiddlewareOptions & { + result?: unknown + error?: unknown } -const applyMiddleware = ( - middlewareFn: NonNullable< - | AnyMiddleware['options']['client'] - | AnyMiddleware['options']['server'] - | AnyMiddleware['options']['clientAfter'] - >, - mCtx: MiddlewareOptions, - nextFn: (ctx: MiddlewareOptions) => Promise, -) => { - return middlewareFn({ - data: mCtx.data, - context: mCtx.context, - sendContext: mCtx.sendContext, - method: mCtx.method, - next: ((userResult: any) => { - // Take the user provided context - // and merge it with the current context - const context = { - ...mCtx.context, - ...userResult?.context, - } - - const sendContext = { - ...mCtx.sendContext, - ...(userResult?.sendContext ?? {}), - } +export type NextFn = (ctx: MiddlewareResult) => Promise - const headers = mergeHeaders(mCtx.headers, userResult?.headers) +export type MiddlewareFn = ( + ctx: MiddlewareOptions & { + next: NextFn + }, +) => Promise +const applyMiddleware = async ( + middlewareFn: MiddlewareFn, + ctx: MiddlewareOptions, + nextFn: NextFn, +) => { + return middlewareFn({ + ...ctx, + next: (async (userCtx: MiddlewareResult | undefined = {} as any) => { // Return the next middleware return nextFn({ - method: mCtx.method, - data: mCtx.data, - context, - sendContext, - headers, + ...ctx, + ...userCtx, + context: { + ...ctx.context, + ...userCtx.context, + }, + sendContext: { + ...ctx.sendContext, + ...(userCtx.sendContext ?? {}), + }, + headers: mergeHeaders(ctx.headers, userCtx.headers), result: - userResult?.result !== undefined - ? userResult.result - : (mCtx as any).result, - } as MiddlewareResult & { - method: Method + userCtx.result !== undefined ? userCtx.result : (ctx as any).result, + error: userCtx.error ?? (ctx as any).error, }) }) as any, }) @@ -412,13 +420,13 @@ async function executeMiddleware( ...middlewares, ]) - const next = async (ctx: MiddlewareOptions): Promise => { + const next: NextFn = async (ctx) => { // Get the next middleware const nextMiddleware = flattenedMiddlewares.shift() // If there are no more middlewares, return the context if (!nextMiddleware) { - return ctx as any + return ctx } if ( @@ -429,33 +437,47 @@ async function executeMiddleware( ctx.data = await execValidator(nextMiddleware.options.validator, ctx.data) } - const middlewareFn = + const middlewareFn = ( env === 'client' ? nextMiddleware.options.client : nextMiddleware.options.server + ) as MiddlewareFn | undefined if (middlewareFn) { // Execute the middleware - return applyMiddleware( - middlewareFn, - ctx, - async (userCtx): Promise => { - // If there is a clientAfter function and we are on the client - if (env === 'client' && nextMiddleware.options.clientAfter) { - // We need to await the next middleware and get the result - const result = await next(userCtx) - // Then we can execute the clientAfter function - return applyMiddleware( - nextMiddleware.options.clientAfter, - result as any, - // Identity, because there "next" is just returning - (d: any) => d, - ) as any + return applyMiddleware(middlewareFn, ctx, async (newCtx) => { + // If there is a clientAfter function and we are on the client + const clientAfter = nextMiddleware.options.clientAfter as + | MiddlewareFn + | undefined + + if (env === 'client' && clientAfter) { + // We need to await the next middleware and get the result + const result = await next(newCtx) + + // Then we can execute the clientAfter function + return applyMiddleware( + clientAfter, + { + ...newCtx, + ...result, + }, + // Identity, because there "next" is just returning + (d: any) => d, + ) + } + + return next(newCtx).catch((error) => { + if (isRedirect(error) || isNotFound(error)) { + return { + ...newCtx, + error, + } } - return next(userCtx) - }, - ) as any + throw error + }) + }) } return next(ctx) @@ -465,7 +487,7 @@ async function executeMiddleware( return next({ ...opts, headers: opts.headers || {}, - sendContext: (opts as any).sendContext || {}, + sendContext: opts.sendContext || {}, context: opts.context || {}, }) } @@ -481,21 +503,22 @@ function serverFnBaseToMiddleware( client: async ({ next, sendContext, ...ctx }) => { // Execute the extracted function // but not before serializing the context - const res = await options.extractedFn?.({ + const serverCtx = await options.extractedFn?.({ ...ctx, // switch the sendContext over to context context: sendContext, - } as any) + }) - return next(res) + return next(serverCtx) as unknown as MiddlewareClientFnResult }, server: async ({ next, ...ctx }) => { // Execute the server function - const result = await options.serverFn?.(ctx as any) + const result = await options.serverFn?.(ctx) return next({ + ...ctx, result, - } as any) + } as any) as unknown as MiddlewareServerFnResult }, }, } diff --git a/packages/start/src/server-handler/index.tsx b/packages/start/src/server-handler/index.tsx index 6c28b2536f..59b4b67a93 100644 --- a/packages/start/src/server-handler/index.tsx +++ b/packages/start/src/server-handler/index.tsx @@ -147,7 +147,7 @@ export async function handleServerRequest(request: Request, _event?: H3Event) { console.error(error) console.info() - return new Response(JSON.stringify(error), { + return new Response(defaultTransformer.stringify(error), { status: 500, headers: { 'Content-Type': 'application/json', From 405a60a4c57070a19882ea10a640d53e5a777e1b Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Mon, 23 Dec 2024 06:27:48 +0000 Subject: [PATCH 14/14] release: v1.92.3 --- examples/react/authenticated-routes/package.json | 4 ++-- .../react/basic-default-search-params/package.json | 4 ++-- .../react/basic-file-based-codesplitting/package.json | 4 ++-- examples/react/basic-file-based/package.json | 4 ++-- .../react/basic-react-query-file-based/package.json | 4 ++-- examples/react/basic-react-query/package.json | 4 ++-- examples/react/basic-ssr-file-based/package.json | 6 +++--- .../react/basic-ssr-streaming-file-based/package.json | 6 +++--- examples/react/basic-virtual-file-based/package.json | 4 ++-- .../react/basic-virtual-inside-file-based/package.json | 4 ++-- examples/react/basic/package.json | 4 ++-- examples/react/deferred-data/package.json | 4 ++-- examples/react/kitchen-sink-file-based/package.json | 4 ++-- .../kitchen-sink-react-query-file-based/package.json | 4 ++-- examples/react/kitchen-sink-react-query/package.json | 4 ++-- examples/react/kitchen-sink/package.json | 4 ++-- examples/react/large-file-based/package.json | 4 ++-- examples/react/location-masking/package.json | 4 ++-- examples/react/navigation-blocking/package.json | 4 ++-- .../react/quickstart-esbuild-file-based/package.json | 4 ++-- examples/react/quickstart-file-based/package.json | 4 ++-- .../react/quickstart-rspack-file-based/package.json | 4 ++-- .../react/quickstart-webpack-file-based/package.json | 4 ++-- examples/react/quickstart/package.json | 4 ++-- .../react/router-monorepo-react-query/package.json | 4 ++-- .../packages/app/package.json | 2 +- .../packages/router/package.json | 2 +- examples/react/router-monorepo-simple/package.json | 4 ++-- .../router-monorepo-simple/packages/app/package.json | 2 +- .../packages/router/package.json | 2 +- examples/react/scroll-restoration/package.json | 4 ++-- examples/react/search-validator-adapters/package.json | 10 +++++----- examples/react/start-basic-auth/package.json | 6 +++--- examples/react/start-basic-react-query/package.json | 8 ++++---- examples/react/start-basic-rsc/package.json | 6 +++--- examples/react/start-basic/package.json | 6 +++--- examples/react/start-clerk-basic/package.json | 6 +++--- examples/react/start-convex-trellaux/package.json | 8 ++++---- examples/react/start-counter/package.json | 4 ++-- examples/react/start-large/package.json | 6 +++--- examples/react/start-supabase-basic/package.json | 6 +++--- examples/react/start-trellaux/package.json | 8 ++++---- examples/react/with-framer-motion/package.json | 4 ++-- examples/react/with-trpc-react-query/package.json | 4 ++-- examples/react/with-trpc/package.json | 4 ++-- packages/arktype-adapter/package.json | 2 +- packages/create-router/package.json | 2 +- packages/create-start/package.json | 2 +- packages/react-router-with-query/package.json | 2 +- packages/react-router/package.json | 2 +- packages/router-devtools/package.json | 2 +- packages/start/package.json | 2 +- packages/valibot-adapter/package.json | 2 +- packages/zod-adapter/package.json | 2 +- 54 files changed, 112 insertions(+), 112 deletions(-) diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json index d4835ade48..3a22a3ede6 100644 --- a/examples/react/authenticated-routes/package.json +++ b/examples/react/authenticated-routes/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json index 8a1e90f37f..5e221da329 100644 --- a/examples/react/basic-default-search-params/package.json +++ b/examples/react/basic-default-search-params/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-file-based-codesplitting/package.json b/examples/react/basic-file-based-codesplitting/package.json index 88499cad41..c8f8a655bd 100644 --- a/examples/react/basic-file-based-codesplitting/package.json +++ b/examples/react/basic-file-based-codesplitting/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json index e82e62f253..3e483e581e 100644 --- a/examples/react/basic-file-based/package.json +++ b/examples/react/basic-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json index b85ef3e317..45a3906ea5 100644 --- a/examples/react/basic-react-query-file-based/package.json +++ b/examples/react/basic-react-query-file-based/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json index 7a5fe86251..e349fa2b39 100644 --- a/examples/react/basic-react-query/package.json +++ b/examples/react/basic-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index d3ecb76910..5f1d5e2496 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -11,10 +11,10 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.92.2", + "@tanstack/start": "^1.92.3", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index badb2005a9..2364221dcb 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -11,10 +11,10 @@ "debug": "node --inspect-brk server" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/start": "^1.92.2", + "@tanstack/start": "^1.92.3", "get-port": "^7.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json index 5dec2951fc..de6cc6a20f 100644 --- a/examples/react/basic-virtual-file-based/package.json +++ b/examples/react/basic-virtual-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "@tanstack/virtual-file-routes": "^1.87.6", "react": "^18.2.0", diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json index eccab24d2f..e71a68f1ed 100644 --- a/examples/react/basic-virtual-inside-file-based/package.json +++ b/examples/react/basic-virtual-inside-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "@tanstack/virtual-file-routes": "^1.87.6", "react": "^18.2.0", diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index 2a64646565..452b892363 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "react": "^18.2.0", "react-dom": "^18.2.0", "redaxios": "^0.5.1" diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json index 62b8d3825b..9fc38639c5 100644 --- a/examples/react/deferred-data/package.json +++ b/examples/react/deferred-data/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json index 83cd9c4ba3..d34d50e463 100644 --- a/examples/react/kitchen-sink-file-based/package.json +++ b/examples/react/kitchen-sink-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json index 61fb97c1e2..612a90fd08 100644 --- a/examples/react/kitchen-sink-react-query-file-based/package.json +++ b/examples/react/kitchen-sink-react-query-file-based/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json index 8618a5a005..6d14f9cc5b 100644 --- a/examples/react/kitchen-sink-react-query/package.json +++ b/examples/react/kitchen-sink-react-query/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json index 08de509b7d..777685081a 100644 --- a/examples/react/kitchen-sink/package.json +++ b/examples/react/kitchen-sink/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "immer": "^10.1.1", "react": "^18.2.0", diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json index 22da8997ba..29f8e2688c 100644 --- a/examples/react/large-file-based/package.json +++ b/examples/react/large-file-based/package.json @@ -12,8 +12,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json index aac761ba11..ee9ad09d91 100644 --- a/examples/react/location-masking/package.json +++ b/examples/react/location-masking/package.json @@ -11,8 +11,8 @@ "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json index 112c070b34..de693d0609 100644 --- a/examples/react/navigation-blocking/package.json +++ b/examples/react/navigation-blocking/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 2cd2dc8c5c..194f6f1cdc 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -9,8 +9,8 @@ "start": "dev" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json index 6f8c5be086..3a746212e7 100644 --- a/examples/react/quickstart-file-based/package.json +++ b/examples/react/quickstart-file-based/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json index 51373d8d1d..fe0218a0c4 100644 --- a/examples/react/quickstart-rspack-file-based/package.json +++ b/examples/react/quickstart-rspack-file-based/package.json @@ -8,8 +8,8 @@ "preview": "rsbuild preview" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json index 1e23f24858..3183bdbf85 100644 --- a/examples/react/quickstart-webpack-file-based/package.json +++ b/examples/react/quickstart-webpack-file-based/package.json @@ -7,8 +7,8 @@ "build": "webpack build && tsc --noEmit" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json index d8f72c824b..2278e32a46 100644 --- a/examples/react/quickstart/package.json +++ b/examples/react/quickstart/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json index 158a91d2a7..6862a25160 100644 --- a/examples/react/router-monorepo-react-query/package.json +++ b/examples/react/router-monorepo-react-query/package.json @@ -12,8 +12,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/router-monorepo-react-query/packages/app/package.json b/examples/react/router-monorepo-react-query/packages/app/package.json index ad0cf68a98..fa5c7021c4 100644 --- a/examples/react/router-monorepo-react-query/packages/app/package.json +++ b/examples/react/router-monorepo-react-query/packages/app/package.json @@ -20,7 +20,7 @@ "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/router-devtools": "^1.92.3", "vite": "^6.0.3", "vite-plugin-dts": "^4.3.0" }, diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json index cc761dad3a..8a52d53c2a 100644 --- a/examples/react/router-monorepo-react-query/packages/router/package.json +++ b/examples/react/router-monorepo-react-query/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.90.0", "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", + "@tanstack/react-router": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "@router-mono-react-query/post-query": "workspace:*", "redaxios": "^0.5.1", diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json index 994e30263e..193a42300a 100644 --- a/examples/react/router-monorepo-simple/package.json +++ b/examples/react/router-monorepo-simple/package.json @@ -8,8 +8,8 @@ "dev": "pnpm router build && pnpm post-feature build && pnpm app dev" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/router-monorepo-simple/packages/app/package.json b/examples/react/router-monorepo-simple/packages/app/package.json index 15c400fe1a..6ddd548fc7 100644 --- a/examples/react/router-monorepo-simple/packages/app/package.json +++ b/examples/react/router-monorepo-simple/packages/app/package.json @@ -19,7 +19,7 @@ "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/router-devtools": "^1.92.3", "vite": "^6.0.3", "vite-plugin-dts": "^4.3.0" }, diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json index 6e6dad2ea7..e38ebaa073 100644 --- a/examples/react/router-monorepo-simple/packages/router/package.json +++ b/examples/react/router-monorepo-simple/packages/router/package.json @@ -9,7 +9,7 @@ "types": "./dist/index.d.ts", "dependencies": { "@tanstack/history": "^1.90.0", - "@tanstack/react-router": "^1.92.1", + "@tanstack/react-router": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "redaxios": "^0.5.1", "zod": "^3.23.8", diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json index 5e5218ea8b..ea2d9437c3 100644 --- a/examples/react/scroll-restoration/package.json +++ b/examples/react/scroll-restoration/package.json @@ -9,9 +9,9 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", + "@tanstack/react-router": "^1.92.3", "@tanstack/react-virtual": "^3.11.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/router-devtools": "^1.92.3", "react": "^18.2.0", "react-dom": "^18.2.0" }, diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json index 9fdd21c8dc..253916415c 100644 --- a/examples/react/search-validator-adapters/package.json +++ b/examples/react/search-validator-adapters/package.json @@ -11,12 +11,12 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/arktype-adapter": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/arktype-adapter": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", - "@tanstack/valibot-adapter": "^1.92.1", - "@tanstack/zod-adapter": "^1.92.1", + "@tanstack/valibot-adapter": "^1.92.3", + "@tanstack/zod-adapter": "^1.92.3", "arktype": "2.0.0-rc.26", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index 717bccd91a..927957ea4a 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -11,9 +11,9 @@ }, "dependencies": { "@prisma/client": "5.22.0", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "prisma": "^5.22.0", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index 938527a9f5..b798625a80 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/react-router-with-query": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/react-router-with-query": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-basic-rsc/package.json b/examples/react/start-basic-rsc/package.json index 39f979f0f0..7230171dd2 100644 --- a/examples/react/start-basic-rsc/package.json +++ b/examples/react/start-basic-rsc/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@babel/plugin-syntax-typescript": "^7.25.9", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "redaxios": "^0.5.1", "tailwind-merge": "^2.5.5", "vinxi": "0.5.1" diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 58ce670527..b5d09b6e54 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -9,9 +9,9 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-clerk-basic/package.json b/examples/react/start-clerk-basic/package.json index 8ed5e6d6a7..91051811e2 100644 --- a/examples/react/start-clerk-basic/package.json +++ b/examples/react/start-clerk-basic/package.json @@ -10,9 +10,9 @@ }, "dependencies": { "@clerk/tanstack-start": "0.6.5", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index efa76bb986..485f1552ca 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -13,10 +13,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/react-router-with-query": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/react-router-with-query": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "@convex-dev/react-query": "0.0.0-alpha.8", "concurrently": "^8.2.2", "convex": "^1.17.3", diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index 4276dc8289..4fe177a88c 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -9,8 +9,8 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/start": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index 2d6011c718..ea21aebf88 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -12,9 +12,9 @@ }, "dependencies": { "@tanstack/react-query": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1", "redaxios": "^0.5.1", diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index 05cc827acd..be27eaade4 100644 --- a/examples/react/start-supabase-basic/package.json +++ b/examples/react/start-supabase-basic/package.json @@ -15,9 +15,9 @@ "dependencies": { "@supabase/ssr": "^0.5.2", "@supabase/supabase-js": "^2.47.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "react": "^18.3.1", "react-dom": "^18.3.1", "vinxi": "0.5.1" diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index 6a9d5a55bc..4a5d2d0435 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -11,10 +11,10 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/react-router-with-query": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", - "@tanstack/start": "^1.92.2", + "@tanstack/react-router": "^1.92.3", + "@tanstack/react-router-with-query": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", + "@tanstack/start": "^1.92.3", "ky": "^1.7.2", "msw": "^2.6.8", "react": "^18.3.1", diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json index 5985d7fd3d..e29bc41b32 100644 --- a/examples/react/with-framer-motion/package.json +++ b/examples/react/with-framer-motion/package.json @@ -9,8 +9,8 @@ "start": "vite" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "redaxios": "^0.5.1", "framer-motion": "^11.13.3", "react": "^18.2.0", diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json index f704182d45..321af31d93 100644 --- a/examples/react/with-trpc-react-query/package.json +++ b/examples/react/with-trpc-react-query/package.json @@ -10,8 +10,8 @@ "dependencies": { "@tanstack/react-query": "^5.62.3", "@tanstack/react-query-devtools": "^5.62.3", - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "@trpc/client": "11.0.0-rc.660", "@trpc/react-query": "11.0.0-rc.660", diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json index 9a1d3f7142..52038d25e4 100644 --- a/examples/react/with-trpc/package.json +++ b/examples/react/with-trpc/package.json @@ -8,8 +8,8 @@ "start": "vinxi start" }, "dependencies": { - "@tanstack/react-router": "^1.92.1", - "@tanstack/router-devtools": "^1.92.1", + "@tanstack/react-router": "^1.92.3", + "@tanstack/router-devtools": "^1.92.3", "@tanstack/router-plugin": "^1.91.1", "@trpc/client": "11.0.0-rc.660", "@trpc/server": "11.0.0-rc.660", diff --git a/packages/arktype-adapter/package.json b/packages/arktype-adapter/package.json index 48e04c31a0..65a4d5a96f 100644 --- a/packages/arktype-adapter/package.json +++ b/packages/arktype-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/arktype-adapter", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/create-router/package.json b/packages/create-router/package.json index b90b35cd46..91c6a3ab56 100644 --- a/packages/create-router/package.json +++ b/packages/create-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-router", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/create-start/package.json b/packages/create-start/package.json index 6dc150e430..882762306d 100644 --- a/packages/create-start/package.json +++ b/packages/create-start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/create-start", - "version": "1.92.2", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tim O'Connell", "license": "MIT", diff --git a/packages/react-router-with-query/package.json b/packages/react-router-with-query/package.json index 0d20acfa09..25d405a53d 100644 --- a/packages/react-router-with-query/package.json +++ b/packages/react-router-with-query/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-with-query", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 8424810a68..8cdcb21928 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json index 4cd65076ef..05f47940ea 100644 --- a/packages/router-devtools/package.json +++ b/packages/router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/start/package.json b/packages/start/package.json index c881781802..6411abee50 100644 --- a/packages/start/package.json +++ b/packages/start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/start", - "version": "1.92.2", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/valibot-adapter/package.json b/packages/valibot-adapter/package.json index eed9a2906a..f519f21db6 100644 --- a/packages/valibot-adapter/package.json +++ b/packages/valibot-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/valibot-adapter", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/zod-adapter/package.json b/packages/zod-adapter/package.json index eea1adbe6a..0ebe4b4319 100644 --- a/packages/zod-adapter/package.json +++ b/packages/zod-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/zod-adapter", - "version": "1.92.1", + "version": "1.92.3", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT",