mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add tanstack start
This commit is contained in:
@@ -18,9 +18,6 @@ export async function setupAddons(
|
||||
const hasWebFrontend =
|
||||
frontends.includes("react-router") || frontends.includes("tanstack-router");
|
||||
|
||||
// if (addons.includes("docker")) {
|
||||
// await setupDocker(projectDir);
|
||||
// }
|
||||
if (addons.includes("pwa") && hasWebFrontend) {
|
||||
await setupPwa(projectDir, frontends);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ export async function setupAuth(
|
||||
try {
|
||||
if (
|
||||
frontends.includes("react-router") ||
|
||||
frontends.includes("tanstack-router")
|
||||
frontends.includes("tanstack-router") ||
|
||||
frontends.includes("tanstack-start")
|
||||
) {
|
||||
addPackageDependency({
|
||||
dependencies: ["better-auth"],
|
||||
|
||||
@@ -19,12 +19,13 @@ export async function setupEnvironmentVariables(
|
||||
if (!envContent.includes("CORS_ORIGIN")) {
|
||||
const hasReactRouter = options.frontend.includes("react-router");
|
||||
const hasTanStackRouter = options.frontend.includes("tanstack-router");
|
||||
const hasTanStackStart = options.frontend.includes("tanstack-start");
|
||||
|
||||
let corsOrigin = "http://localhost:3000";
|
||||
|
||||
if (hasReactRouter) {
|
||||
corsOrigin = "http://localhost:5173";
|
||||
} else if (hasTanStackRouter) {
|
||||
} else if (hasTanStackRouter || hasTanStackStart) {
|
||||
corsOrigin = "http://localhost:3001";
|
||||
}
|
||||
|
||||
@@ -68,10 +69,12 @@ export async function setupEnvironmentVariables(
|
||||
|
||||
const hasReactRouter = options.frontend.includes("react-router");
|
||||
const hasTanStackRouter = options.frontend.includes("tanstack-router");
|
||||
const hasTanStackStart = options.frontend.includes("tanstack-start");
|
||||
const hasWebFrontend =
|
||||
hasReactRouter || hasTanStackRouter || hasTanStackStart;
|
||||
|
||||
if (hasReactRouter || hasTanStackRouter) {
|
||||
if (hasWebFrontend) {
|
||||
const clientDir = path.join(projectDir, "apps/web");
|
||||
|
||||
await setupClientEnvFile(clientDir);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,12 +13,19 @@ export async function setupExamples(
|
||||
frontend: ProjectFrontend[] = ["tanstack-router"],
|
||||
): Promise<void> {
|
||||
const hasTanstackRouter = frontend.includes("tanstack-router");
|
||||
const hasTanstackStart = frontend.includes("tanstack-start");
|
||||
const hasReactRouter = frontend.includes("react-router");
|
||||
const hasWebFrontend = hasTanstackRouter || hasReactRouter;
|
||||
const hasWebFrontend =
|
||||
hasTanstackRouter || hasReactRouter || hasTanstackStart;
|
||||
|
||||
const routerType = hasTanstackRouter
|
||||
? "web-tanstack-router"
|
||||
: "web-react-router";
|
||||
let routerType: string;
|
||||
if (hasTanstackRouter) {
|
||||
routerType = "web-tanstack-router";
|
||||
} else if (hasTanstackStart) {
|
||||
routerType = "web-tanstack-start";
|
||||
} else {
|
||||
routerType = "web-react-router";
|
||||
}
|
||||
|
||||
const webAppExists = await fs.pathExists(path.join(projectDir, "apps/web"));
|
||||
|
||||
@@ -87,27 +94,28 @@ async function updateServerIndexWithAIRoute(projectDir: string): Promise<void> {
|
||||
const importSection = `import { streamText } from "ai";\nimport { google } from "@ai-sdk/google";\nimport { stream } from "hono/streaming";`;
|
||||
|
||||
const aiRouteHandler = `
|
||||
app.post("/ai", async (c) => {
|
||||
const body = await c.req.json();
|
||||
const messages = body.messages || [];
|
||||
// AI chat endpoint
|
||||
app.post("/ai", async (c) => {
|
||||
const body = await c.req.json();
|
||||
const messages = body.messages || [];
|
||||
|
||||
const result = streamText({
|
||||
model: google("gemini-2.0-flash-exp"),
|
||||
messages,
|
||||
});
|
||||
const result = streamText({
|
||||
model: google("gemini-1.5-flash"),
|
||||
messages,
|
||||
});
|
||||
|
||||
c.header("X-Vercel-AI-Data-Stream", "v1");
|
||||
c.header("Content-Type", "text/plain; charset=utf-8");
|
||||
c.header("X-Vercel-AI-Data-Stream", "v1");
|
||||
c.header("Content-Type", "text/plain; charset=utf-8");
|
||||
|
||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||
});`;
|
||||
return stream(c, (stream) => stream.pipe(result.toDataStream()));
|
||||
});`;
|
||||
|
||||
if (indexContent.includes("import {")) {
|
||||
const lastImportIndex = indexContent.lastIndexOf("import");
|
||||
const endOfLastImport = indexContent.indexOf("\n", lastImportIndex);
|
||||
indexContent = `${indexContent.substring(0, endOfLastImport + 1)}
|
||||
${importSection}
|
||||
${indexContent.substring(endOfLastImport + 1)}`;
|
||||
${importSection}
|
||||
${indexContent.substring(endOfLastImport + 1)}`;
|
||||
} else {
|
||||
indexContent = `${importSection}
|
||||
|
||||
@@ -177,7 +185,7 @@ async function setupTodoExample(
|
||||
const todoExampleDir = path.join(PKG_ROOT, "template/examples/todo");
|
||||
|
||||
if (await fs.pathExists(todoExampleDir)) {
|
||||
const todoRouteSourceDir = path.join(
|
||||
const todoRouteSourcePath = path.join(
|
||||
todoExampleDir,
|
||||
`apps/${routerType}/src/routes/todos.tsx`,
|
||||
);
|
||||
@@ -186,8 +194,8 @@ async function setupTodoExample(
|
||||
"apps/web/src/routes/todos.tsx",
|
||||
);
|
||||
|
||||
if (await fs.pathExists(todoRouteSourceDir)) {
|
||||
await fs.copy(todoRouteSourceDir, todoRouteTargetPath, {
|
||||
if (await fs.pathExists(todoRouteSourcePath)) {
|
||||
await fs.copy(todoRouteSourcePath, todoRouteTargetPath, {
|
||||
overwrite: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,11 +43,15 @@ export function displayPostInstallInstructions(
|
||||
: "";
|
||||
|
||||
const hasTanstackRouter = frontends?.includes("tanstack-router");
|
||||
const hasTanstackStart = frontends?.includes("tanstack-start");
|
||||
const hasReactRouter = frontends?.includes("react-router");
|
||||
const hasWebFrontend = hasTanstackRouter || hasReactRouter;
|
||||
const hasWebFrontend =
|
||||
hasTanstackRouter || hasReactRouter || hasTanstackStart;
|
||||
const hasNativeFrontend = frontends?.includes("native");
|
||||
const hasFrontend = hasWebFrontend || hasNativeFrontend;
|
||||
|
||||
const webPort = hasReactRouter ? "5173" : "3001";
|
||||
|
||||
note(
|
||||
`${pc.cyan("1.")} ${cdCmd}
|
||||
${!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""}${pc.cyan(depsInstalled ? "2." : "3.")} ${runCmd} dev
|
||||
@@ -55,7 +59,7 @@ ${!depsInstalled ? `${pc.cyan("2.")} ${packageManager} install\n` : ""}${pc.cyan
|
||||
${pc.bold("Your project will be available at:")}
|
||||
${
|
||||
hasFrontend
|
||||
? `${hasWebFrontend ? `${pc.cyan("•")} Frontend: http://localhost:${hasReactRouter ? "5173" : "3001"}\n` : ""}`
|
||||
? `${hasWebFrontend ? `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n` : ""}`
|
||||
: `${pc.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)\n`
|
||||
}${pc.cyan("•")} API: http://localhost:3000
|
||||
${nativeInstructions ? `\n${nativeInstructions.trim()}` : ""}${databaseInstructions ? `\n${databaseInstructions.trim()}` : ""}${tauriInstructions ? `\n${tauriInstructions.trim()}` : ""}${lintingInstructions ? `\n${lintingInstructions.trim()}` : ""}${pwaInstructions ? `\n${pwaInstructions.trim()}` : ""}
|
||||
|
||||
@@ -49,10 +49,11 @@ export async function setupFrontendTemplates(
|
||||
frontends: ProjectFrontend[],
|
||||
): Promise<void> {
|
||||
const hasTanstackWeb = frontends.includes("tanstack-router");
|
||||
const hasTanstackStart = frontends.includes("tanstack-start");
|
||||
const hasReactRouterWeb = frontends.includes("react-router");
|
||||
const hasNative = frontends.includes("native");
|
||||
|
||||
if (hasTanstackWeb || hasReactRouterWeb) {
|
||||
if (hasTanstackWeb || hasReactRouterWeb || hasTanstackStart) {
|
||||
const webDir = path.join(projectDir, "apps/web");
|
||||
await fs.ensureDir(webDir);
|
||||
|
||||
@@ -61,9 +62,13 @@ export async function setupFrontendTemplates(
|
||||
await fs.copy(webBaseDir, webDir);
|
||||
}
|
||||
|
||||
const frameworkName = hasTanstackWeb
|
||||
? "web-tanstack-router"
|
||||
: "web-react-router";
|
||||
let frameworkName = "web-react-router";
|
||||
if (hasTanstackWeb) {
|
||||
frameworkName = "web-tanstack-router";
|
||||
} else if (hasTanstackStart) {
|
||||
frameworkName = "web-tanstack-start";
|
||||
}
|
||||
|
||||
const webFrameworkDir = path.join(
|
||||
PKG_ROOT,
|
||||
`template/base/apps/${frameworkName}`,
|
||||
@@ -155,8 +160,9 @@ export async function setupAuthTemplate(
|
||||
if (await fs.pathExists(authTemplateDir)) {
|
||||
const hasReactRouter = frontends.includes("react-router");
|
||||
const hasTanStackRouter = frontends.includes("tanstack-router");
|
||||
const hasTanStackStart = frontends.includes("tanstack-start");
|
||||
|
||||
if (hasReactRouter || hasTanStackRouter) {
|
||||
if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
|
||||
const webDir = path.join(projectDir, "apps/web");
|
||||
|
||||
const webBaseAuthDir = path.join(authTemplateDir, "apps/web-base");
|
||||
@@ -183,6 +189,16 @@ export async function setupAuthTemplate(
|
||||
await fs.copy(tanstackAuthDir, webDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTanStackStart) {
|
||||
const tanstackStartAuthDir = path.join(
|
||||
authTemplateDir,
|
||||
"apps/web-tanstack-start",
|
||||
);
|
||||
if (await fs.pathExists(tanstackStartAuthDir)) {
|
||||
await fs.copy(tanstackStartAuthDir, webDir, { overwrite: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverAuthDir = path.join(authTemplateDir, "apps/server/src");
|
||||
|
||||
@@ -43,7 +43,7 @@ async function main() {
|
||||
.option("--no-auth", "Exclude authentication")
|
||||
.option(
|
||||
"--frontend <types...>",
|
||||
"Frontend types (tanstack-router, react-router, native, none)",
|
||||
"Frontend types (tanstack-router, react-router, tanstack-start, native, none)",
|
||||
)
|
||||
.option(
|
||||
"--addons <types...>",
|
||||
@@ -271,7 +271,7 @@ function validateOptions(options: CLIOptions): void {
|
||||
if (
|
||||
options.frontend &&
|
||||
!options.frontend.some((f) =>
|
||||
["tanstack-router", "react-router"].includes(f),
|
||||
["tanstack-router", "react-router", "tanstack-start"].includes(f),
|
||||
) &&
|
||||
!options.frontend.includes("none")
|
||||
) {
|
||||
@@ -288,6 +288,7 @@ function validateOptions(options: CLIOptions): void {
|
||||
const validFrontends = [
|
||||
"tanstack-router",
|
||||
"react-router",
|
||||
"tanstack-start",
|
||||
"native",
|
||||
"none",
|
||||
];
|
||||
@@ -305,13 +306,16 @@ function validateOptions(options: CLIOptions): void {
|
||||
}
|
||||
|
||||
const webFrontends = options.frontend.filter(
|
||||
(f) => f === "tanstack-router" || f === "react-router",
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
);
|
||||
|
||||
if (webFrontends.length > 1) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, react-router",
|
||||
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -357,14 +361,13 @@ function validateOptions(options: CLIOptions): void {
|
||||
options.frontend &&
|
||||
!options.frontend.some((f) =>
|
||||
["tanstack-router", "react-router"].includes(f),
|
||||
) &&
|
||||
!options.frontend.includes("none")
|
||||
)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`PWA and Tauri addons require a web frontend. Cannot use --addons ${options.addons
|
||||
`PWA and Tauri addons require tanstack-router or react-router. Cannot use --addons ${options.addons
|
||||
.filter((a) => webSpecificAddons.includes(a))
|
||||
.join(", ")} with --frontend native only`,
|
||||
.join(", ")} with incompatible frontend options.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
@@ -384,11 +387,17 @@ function processFlags(
|
||||
} else {
|
||||
frontend = options.frontend.filter(
|
||||
(f): f is ProjectFrontend =>
|
||||
f === "tanstack-router" || f === "react-router" || f === "native",
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start" ||
|
||||
f === "native",
|
||||
);
|
||||
|
||||
const webFrontends = frontend.filter(
|
||||
(f) => f === "tanstack-router" || f === "react-router",
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
);
|
||||
|
||||
if (webFrontends.length > 1) {
|
||||
@@ -448,8 +457,9 @@ function processFlags(
|
||||
if (
|
||||
frontend &&
|
||||
frontend.length > 0 &&
|
||||
!frontend.includes("tanstack-router") &&
|
||||
!frontend.includes("react-router")
|
||||
!frontend.some((f) =>
|
||||
["tanstack-router", "react-router", "tanstack-start"].includes(f),
|
||||
)
|
||||
) {
|
||||
examples = [];
|
||||
log.warn(
|
||||
@@ -481,19 +491,23 @@ function processFlags(
|
||||
addon === "husky",
|
||||
);
|
||||
|
||||
if (
|
||||
frontend &&
|
||||
frontend.length > 0 &&
|
||||
!frontend.includes("tanstack-router") &&
|
||||
!frontend.includes("react-router")
|
||||
) {
|
||||
addons = addons.filter((addon) => !["pwa", "tauri"].includes(addon));
|
||||
if (addons.length !== options.addons.length) {
|
||||
const hasCompatibleWebFrontend = frontend?.some(
|
||||
(f) => f === "tanstack-router" || f === "react-router",
|
||||
);
|
||||
|
||||
if (!hasCompatibleWebFrontend) {
|
||||
const webSpecificAddons = ["pwa", "tauri"];
|
||||
const filteredAddons = addons.filter(
|
||||
(addon) => !webSpecificAddons.includes(addon),
|
||||
);
|
||||
|
||||
if (filteredAddons.length !== addons.length) {
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"PWA and Tauri addons require web frontend - removing these addons",
|
||||
"PWA and Tauri addons require tanstack-router or react-router - removing these addons",
|
||||
),
|
||||
);
|
||||
addons = filteredAddons;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ export async function getAddonsChoice(
|
||||
): Promise<ProjectAddons[]> {
|
||||
if (Addons !== undefined) return Addons;
|
||||
|
||||
const hasWeb =
|
||||
const hasCompatibleWebFrontend =
|
||||
frontends?.includes("react-router") ||
|
||||
frontends?.includes("tanstack-router");
|
||||
|
||||
@@ -39,10 +39,13 @@ export async function getAddonsChoice(
|
||||
},
|
||||
];
|
||||
|
||||
const options = hasWeb ? [...webAddonOptions, ...addonOptions] : addonOptions;
|
||||
const options = hasCompatibleWebFrontend
|
||||
? [...webAddonOptions, ...addonOptions]
|
||||
: addonOptions;
|
||||
|
||||
const initialValues = DEFAULT_CONFIG.addons.filter(
|
||||
(addon) => hasWeb || (addon !== "pwa" && addon !== "tauri"),
|
||||
(addon) =>
|
||||
hasCompatibleWebFrontend || (addon !== "pwa" && addon !== "tauri"),
|
||||
);
|
||||
|
||||
const response = await multiselect<ProjectAddons>({
|
||||
|
||||
@@ -20,7 +20,8 @@ export async function getExamplesChoice(
|
||||
|
||||
const hasWebFrontend =
|
||||
frontends?.includes("react-router") ||
|
||||
frontends?.includes("tanstack-router");
|
||||
frontends?.includes("tanstack-router") ||
|
||||
frontends?.includes("tanstack-start");
|
||||
|
||||
if (!hasWebFrontend) return [];
|
||||
|
||||
|
||||
@@ -23,7 +23,10 @@ export async function getFrontendChoice(
|
||||
},
|
||||
],
|
||||
initialValues: DEFAULT_CONFIG.frontend.some(
|
||||
(f) => f === "tanstack-router" || f === "react-router",
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
)
|
||||
? ["web"]
|
||||
: [],
|
||||
@@ -50,10 +53,18 @@ export async function getFrontendChoice(
|
||||
label: "React Router",
|
||||
hint: "A user‑obsessed, standards‑focused, multi‑strategy router you can deploy anywhere.",
|
||||
},
|
||||
{
|
||||
value: "tanstack-start",
|
||||
label: "TanStack Start (beta)",
|
||||
hint: "SSR, Streaming, Server Functions, API Routes, bundling and more powered by TanStack Router and Vite.",
|
||||
},
|
||||
],
|
||||
initialValue:
|
||||
DEFAULT_CONFIG.frontend.find(
|
||||
(f) => f === "tanstack-router" || f === "react-router",
|
||||
(f) =>
|
||||
f === "tanstack-router" ||
|
||||
f === "react-router" ||
|
||||
f === "tanstack-start",
|
||||
) || "tanstack-router",
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,11 @@ export type ProjectAddons = "pwa" | "biome" | "tauri" | "husky";
|
||||
export type ProjectBackend = "hono" | "elysia";
|
||||
export type ProjectRuntime = "node" | "bun";
|
||||
export type ProjectExamples = "todo" | "ai";
|
||||
export type ProjectFrontend = "react-router" | "tanstack-router" | "native";
|
||||
export type ProjectFrontend =
|
||||
| "react-router"
|
||||
| "tanstack-router"
|
||||
| "tanstack-start"
|
||||
| "native";
|
||||
|
||||
export interface ProjectConfig {
|
||||
projectName: string;
|
||||
|
||||
Reference in New Issue
Block a user