diff --git a/packages/router-generator/package.json b/packages/router-generator/package.json index 4d1704e54a..b58185a427 100644 --- a/packages/router-generator/package.json +++ b/packages/router-generator/package.json @@ -72,7 +72,7 @@ "recast": "^0.23.11", "source-map": "^0.7.4", "tsx": "^4.19.2", - "zod": "^3.24.2" + "zod": "^3.25.0 || ^4.0.0" }, "devDependencies": { "@tanstack/react-router": "workspace:*" diff --git a/packages/router-generator/src/config.ts b/packages/router-generator/src/config.ts index 0d1ae47339..31ffbc9068 100644 --- a/packages/router-generator/src/config.ts +++ b/packages/router-generator/src/config.ts @@ -4,6 +4,29 @@ import { z } from 'zod' import { virtualRootRouteSchema } from './filesystem/virtual/config' import type { GeneratorPlugin } from './plugin/types' +// Helper to create a function schema compatible with both Zod v3 and v4 +function createFunctionSchema() { + // Try Zod v4 syntax first + if (typeof (z as any).function === 'function') { + try { + // Check if this is Zod v4 by testing for the new API + const testSchema = z.string() + if ('_zod' in testSchema) { + // Zod v4: use new function API + return (z as any).function({ + input: [], + output: z.array(z.string()), + }) + } + } catch (e) { + // Fall through to v3 + } + } + + // Zod v3: use old function API + return (z as any).function().returns(z.array(z.string())) +} + export const baseConfigSchema = z.object({ target: z.enum(['react', 'solid', 'vue']).optional().default('react'), virtualRouteConfig: virtualRootRouteSchema.or(z.string()).optional(), @@ -40,7 +63,7 @@ export const configSchema = baseConfigSchema.extend({ routeTreeFileFooter: z .union([ z.array(z.string()).optional().default([]), - z.function().returns(z.array(z.string())), + createFunctionSchema(), ]) .optional(), autoCodeSplitting: z.boolean().optional(), diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index 37be7ce691..b6d05b783c 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -117,7 +117,7 @@ "babel-dead-code-elimination": "^1.0.11", "chokidar": "^3.6.0", "unplugin": "^2.1.2", - "zod": "^3.24.2" + "zod": "^3.25.0 || ^4.0.0" }, "devDependencies": { "@types/babel__core": "^7.20.5", diff --git a/packages/router-plugin/src/core/config.ts b/packages/router-plugin/src/core/config.ts index f93166d392..2dba87c01f 100644 --- a/packages/router-plugin/src/core/config.ts +++ b/packages/router-plugin/src/core/config.ts @@ -10,6 +10,29 @@ import type { } from '@tanstack/router-core' import type { CodeSplitGroupings } from './constants' +// Helper to create a function schema compatible with both Zod v3 and v4 +function createGenericFunctionSchema(): any { + // Try Zod v4 syntax first + if (typeof (z as any).function === 'function') { + try { + // Check if this is Zod v4 by testing for the new API + const testSchema = z.string() + if ('_zod' in testSchema) { + // Zod v4: use new function API with any input/output + return (z as any).function({ + input: [z.any()], + output: z.any(), + }) + } + } catch (e) { + // Fall through to v3 + } + } + + // Zod v3: use old function API + return (z as any).function() +} + export const splitGroupingsSchema = z .array( z.array( @@ -73,7 +96,7 @@ export type CodeSplittingOptions = { } const codeSplittingOptionsSchema = z.object({ - splitBehavior: z.function().optional(), + splitBehavior: createGenericFunctionSchema().optional(), defaultBehavior: splitGroupingsSchema.optional(), deleteNodes: z.array(z.string()).optional(), addHmr: z.boolean().optional().default(true), diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json index 5711e0419c..e6a6798a65 100644 --- a/packages/start-plugin-core/package.json +++ b/packages/start-plugin-core/package.json @@ -80,7 +80,7 @@ "ufo": "^1.5.4", "vitefu": "^1.1.1", "xmlbuilder2": "^4.0.3", - "zod": "^3.24.2" + "zod": "^3.25.0 || ^4.0.0" }, "devDependencies": { "@types/babel__code-frame": "^7.0.6", diff --git a/packages/start-plugin-core/src/schema.ts b/packages/start-plugin-core/src/schema.ts index 235796a129..649b62e29b 100644 --- a/packages/start-plugin-core/src/schema.ts +++ b/packages/start-plugin-core/src/schema.ts @@ -3,6 +3,33 @@ import { z } from 'zod' import { configSchema, getConfig } from '@tanstack/router-plugin' import type { TanStackStartVitePluginCoreOptions } from './types' +// Helper to create a function schema compatible with both Zod v3 and v4 +function createFunctionSchema( + args: any, + returns: any, +): any { + // Try Zod v4 syntax first + if (typeof (z as any).function === 'function') { + try { + // Check if this is Zod v4 by testing for the new API + const testSchema = z.string() + if ('_zod' in testSchema) { + // Zod v4: use new function API + return (z as any).function({ + input: Array.isArray(args) ? args : [args], + output: returns, + }) + } + } catch (e) { + // Fall through to v3 + } + } + + // Zod v3: use old function API + const argsArray = Array.isArray(args) ? args : [args] + return (z as any).function().args(...argsArray).returns(returns) +} + const tsrConfig = configSchema .omit({ autoCodeSplitting: true, target: true, verboseFileRoutes: true }) .partial() @@ -94,16 +121,13 @@ const pagePrerenderOptionsSchema = z.object({ crawlLinks: z.boolean().optional(), retryCount: z.number().optional(), retryDelay: z.number().optional(), - onSuccess: z - .function() - .args( - z.object({ - page: pageBaseSchema, - html: z.string(), - }), - ) - .returns(z.any()) - .optional(), + onSuccess: createFunctionSchema( + z.object({ + page: pageBaseSchema, + html: z.string(), + }), + z.any(), + ).optional(), headers: z.record(z.string(), z.string()).optional(), }) @@ -159,16 +183,13 @@ const tanstackStartOptionsSchema = z serverFns: z .object({ base: z.string().optional().default('/_serverFn'), - generateFunctionId: z - .function() - .args( - z.object({ - filename: z.string(), - functionName: z.string(), - }), - ) - .returns(z.string().optional()) - .optional(), + generateFunctionId: createFunctionSchema( + z.object({ + filename: z.string(), + functionName: z.string(), + }), + z.string().optional(), + ).optional(), }) .optional() .default({}), @@ -184,7 +205,7 @@ const tanstackStartOptionsSchema = z .object({ enabled: z.boolean().optional(), concurrency: z.number().optional(), - filter: z.function().args(pageSchema).returns(z.any()).optional(), + filter: createFunctionSchema(pageSchema, z.any()).optional(), failOnError: z.boolean().optional(), autoStaticPathsDiscovery: z.boolean().optional(), maxRedirects: z.number().min(0).optional(),