create-better-t-stack 1.12.3 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +1 -2
  2. package/dist/index.js +102 -102
  3. package/package.json +4 -3
  4. package/template/base/apps/server/_gitignore +17 -2
  5. package/template/base/apps/web-base/_gitignore +39 -13
  6. package/template/base/apps/web-base/src/index.css +1 -1
  7. package/template/base/apps/web-next/next-env.d.ts +5 -0
  8. package/template/base/apps/web-next/next.config.ts +7 -0
  9. package/template/base/apps/web-next/package.json +41 -0
  10. package/template/base/apps/web-next/postcss.config.mjs +5 -0
  11. package/template/base/apps/web-next/src/app/favicon.ico +0 -0
  12. package/template/base/apps/web-next/src/app/layout.tsx +41 -0
  13. package/template/base/apps/web-next/src/app/page.tsx +83 -0
  14. package/template/base/apps/web-next/src/components/header.tsx +30 -0
  15. package/template/base/apps/web-next/src/components/mode-toggle.tsx +39 -0
  16. package/template/base/apps/web-next/src/components/providers.tsx +25 -0
  17. package/template/base/apps/web-next/src/components/theme-provider.tsx +11 -0
  18. package/template/base/apps/web-next/src/utils/trpc.ts +33 -0
  19. package/template/base/apps/web-next/tsconfig.json +28 -0
  20. package/template/with-auth/apps/server/src/lib/with-next-context.ts +14 -0
  21. package/template/with-auth/apps/server/src/with-next-app/api/auth/[...all]/route.ts +4 -0
  22. package/template/with-auth/apps/web-next/src/app/dashboard/page.tsx +31 -0
  23. package/template/with-auth/apps/web-next/src/app/login/page.tsx +16 -0
  24. package/template/with-auth/apps/web-next/src/components/header.tsx +33 -0
  25. package/template/with-auth/apps/web-next/src/components/sign-in-form.tsx +135 -0
  26. package/template/with-auth/apps/web-next/src/components/sign-up-form.tsx +160 -0
  27. package/template/with-auth/apps/web-next/src/components/theme-provider.tsx +11 -0
  28. package/template/with-auth/apps/web-next/src/components/user-menu.tsx +60 -0
  29. package/template/with-auth/apps/web-next/src/lib/auth-client.ts +5 -0
  30. package/template/with-auth/apps/web-next/src/utils/trpc.ts +39 -0
  31. package/template/with-next/apps/server/next-env.d.ts +5 -0
  32. package/template/with-next/apps/server/next.config.ts +7 -0
  33. package/template/with-next/apps/server/package.json +20 -0
  34. package/template/with-next/apps/server/src/app/api/trpc/[trpc]/route.ts +14 -0
  35. package/template/with-next/apps/server/src/app/route.ts +5 -0
  36. package/template/with-next/apps/server/src/lib/context.ts +9 -0
  37. package/template/with-next/apps/server/src/middleware.ts +19 -0
  38. package/template/with-next/apps/server/tsconfig.json +27 -0
  39. package/template/base/apps/web-tanstack-start/src/index.css +0 -135
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "1.12.3",
3
+ "version": "1.13.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -53,15 +53,16 @@
53
53
  },
54
54
  "dependencies": {
55
55
  "@clack/prompts": "^0.10.1",
56
- "commander": "^13.1.0",
57
56
  "execa": "^8.0.1",
58
57
  "fs-extra": "^11.3.0",
59
58
  "gradient-string": "^3.0.0",
60
- "picocolors": "^1.1.1"
59
+ "picocolors": "^1.1.1",
60
+ "yargs": "^17.7.2"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/fs-extra": "^11.0.4",
64
64
  "@types/node": "^20.17.30",
65
+ "@types/yargs": "^17.0.33",
65
66
  "tsup": "^8.4.0",
66
67
  "typescript": "^5.8.3"
67
68
  }
@@ -1,22 +1,32 @@
1
1
  # prod
2
2
  dist/
3
+ /build
4
+ /out/
3
5
 
4
6
  # dev
5
7
  .yarn/
8
+ !.yarn/patches
9
+ !.yarn/plugins
6
10
  !.yarn/releases
11
+ !.yarn/versions
7
12
  .vscode/*
8
13
  !.vscode/launch.json
9
14
  !.vscode/*.code-snippets
10
15
  .idea/workspace.xml
11
16
  .idea/usage.statistics.xml
12
17
  .idea/shelf
18
+ .wrangler
19
+ /.next/
20
+ .vercel
13
21
 
14
22
  # deps
15
23
  node_modules/
16
- .wrangler
24
+ /node_modules
25
+ /.pnp
26
+ .pnp.*
17
27
 
18
28
  # env
19
- .env
29
+ .env*
20
30
  .env.production
21
31
  .dev.vars
22
32
 
@@ -31,6 +41,11 @@ lerna-debug.log*
31
41
 
32
42
  # misc
33
43
  .DS_Store
44
+ *.pem
34
45
 
35
46
  # local db
36
47
  *.db*
48
+
49
+ # typescript
50
+ *.tsbuildinfo
51
+ next-env.d.ts
@@ -1,26 +1,52 @@
1
- # Local
2
- .DS_Store
3
- *.local
4
- *.log*
1
+ # Dependencies
2
+ /node_modules
3
+ /.pnp
4
+ .pnp.*
5
+ .yarn/*
6
+ !.yarn/patches
7
+ !.yarn/plugins
8
+ !.yarn/releases
9
+ !.yarn/versions
5
10
 
6
- # Dist
7
- node_modules
8
- dist/
11
+ # Testing
12
+ /coverage
13
+
14
+ # Build outputs
15
+ /.next/
16
+ /out/
17
+ /build/
18
+ /dist/
9
19
  .vinxi
10
20
  .output
21
+ .react-router/
22
+
23
+ # Deployment
11
24
  .vercel
12
25
  .netlify
13
26
  .wrangler
14
27
 
28
+ # Environment & local files
29
+ .env*
30
+ !.env.example
31
+ .DS_Store
32
+ *.pem
33
+ *.local
34
+
35
+ # Logs
36
+ npm-debug.log*
37
+ yarn-debug.log*
38
+ yarn-error.log*
39
+ .pnpm-debug.log*
40
+ *.log*
41
+
42
+ # TypeScript
43
+ *.tsbuildinfo
44
+ next-env.d.ts
45
+
15
46
  # IDE
16
47
  .vscode/*
17
48
  !.vscode/extensions.json
18
49
  .idea
19
50
 
20
- *.env*
21
- !.env.example
22
-
51
+ # Other
23
52
  dev-dist
24
-
25
- /.react-router/
26
- /build/
@@ -4,7 +4,7 @@
4
4
  @custom-variant dark (&:where(.dark, .dark *));
5
5
 
6
6
  @theme {
7
- --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif,
7
+ --font-sans: "Inter", "Geist", ui-sans-serif, system-ui, sans-serif,
8
8
  "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
9
9
  }
10
10
 
@@ -0,0 +1,5 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {
4
+ output: "export"
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "web",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev --turbopack --port=3001",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@radix-ui/react-checkbox": "^1.1.5",
13
+ "@radix-ui/react-dropdown-menu": "^2.1.7",
14
+ "@radix-ui/react-label": "^2.1.3",
15
+ "@radix-ui/react-slot": "^1.2.0",
16
+ "@tanstack/react-form": "^1.3.2",
17
+ "@tanstack/react-query": "^5.72.2",
18
+ "@trpc/client": "^11.1.0",
19
+ "@trpc/tanstack-react-query": "^11.1.0",
20
+ "class-variance-authority": "^0.7.1",
21
+ "clsx": "^2.1.1",
22
+ "lucide-react": "^0.487.0",
23
+ "next": "15.3.0",
24
+ "next-themes": "^0.4.6",
25
+ "react": "^19.0.0",
26
+ "react-dom": "^19.0.0",
27
+ "sonner": "^2.0.3",
28
+ "tailwind-merge": "^3.2.0",
29
+ "tw-animate-css": "^1.2.5",
30
+ "zod": "^3.24.2"
31
+ },
32
+ "devDependencies": {
33
+ "@tailwindcss/postcss": "^4",
34
+ "@tanstack/react-query-devtools": "^5.72.2",
35
+ "@types/node": "^20",
36
+ "@types/react": "^19",
37
+ "@types/react-dom": "^19",
38
+ "tailwindcss": "^4",
39
+ "typescript": "^5"
40
+ }
41
+ }
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
@@ -0,0 +1,41 @@
1
+ import type { Metadata } from "next";
2
+ import { Geist, Geist_Mono } from "next/font/google";
3
+ import "../index.css";
4
+ import Providers from "@/components/providers";
5
+ import Header from "@/components/header";
6
+
7
+ const geistSans = Geist({
8
+ variable: "--font-geist-sans",
9
+ subsets: ["latin"],
10
+ });
11
+
12
+ const geistMono = Geist_Mono({
13
+ variable: "--font-geist-mono",
14
+ subsets: ["latin"],
15
+ });
16
+
17
+ export const metadata: Metadata = {
18
+ title: "Create Next App",
19
+ description: "Generated by create next app",
20
+ };
21
+
22
+ export default function RootLayout({
23
+ children,
24
+ }: Readonly<{
25
+ children: React.ReactNode;
26
+ }>) {
27
+ return (
28
+ <html lang="en" suppressHydrationWarning>
29
+ <body
30
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
31
+ >
32
+ <Providers>
33
+ <div className="grid grid-rows-[auto_1fr] h-svh">
34
+ <Header />
35
+ {children}
36
+ </div>
37
+ </Providers>
38
+ </body>
39
+ </html>
40
+ );
41
+ }
@@ -0,0 +1,83 @@
1
+ "use client"
2
+ import { trpc } from "@/utils/trpc";
3
+ import { useQuery } from "@tanstack/react-query";
4
+
5
+ const TITLE_TEXT = `
6
+ ██████╗ ███████╗████████╗████████╗███████╗██████╗
7
+ ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
8
+ ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
9
+ ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
10
+ ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
11
+ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
12
+
13
+ ████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
14
+ ╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
15
+ ██║ ███████╗ ██║ ███████║██║ █████╔╝
16
+ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
17
+ ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
18
+ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
19
+ `;
20
+
21
+ export default function Home() {
22
+ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
23
+
24
+ return (
25
+ <div className="container mx-auto max-w-3xl px-4 py-2">
26
+ <pre className="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
27
+ <div className="grid gap-6">
28
+ <section className="rounded-lg border p-4">
29
+ <h2 className="mb-2 font-medium">API Status</h2>
30
+ <div className="flex items-center gap-2">
31
+ <div
32
+ className={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
33
+ />
34
+ <span className="text-sm text-muted-foreground">
35
+ {healthCheck.isLoading
36
+ ? "Checking..."
37
+ : healthCheck.data
38
+ ? "Connected"
39
+ : "Disconnected"}
40
+ </span>
41
+ </div>
42
+ </section>
43
+
44
+ <section>
45
+ <h2 className="mb-3 font-medium">Core Features</h2>
46
+ <ul className="grid grid-cols-2 gap-3">
47
+ <FeatureItem
48
+ title="Type-Safe API"
49
+ description="End-to-end type safety with tRPC"
50
+ />
51
+ <FeatureItem
52
+ title="Modern React"
53
+ description="TanStack Router + TanStack Query"
54
+ />
55
+ <FeatureItem
56
+ title="Fast Backend"
57
+ description="Lightweight Hono server"
58
+ />
59
+ <FeatureItem
60
+ title="Beautiful UI"
61
+ description="TailwindCSS + shadcn/ui components"
62
+ />
63
+ </ul>
64
+ </section>
65
+ </div>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ function FeatureItem({
71
+ title,
72
+ description,
73
+ }: {
74
+ title: string;
75
+ description: string;
76
+ }) {
77
+ return (
78
+ <li className="border-l-2 border-primary py-1 pl-3">
79
+ <h3 className="font-medium">{title}</h3>
80
+ <p className="text-sm text-muted-foreground">{description}</p>
81
+ </li>
82
+ );
83
+ }
@@ -0,0 +1,30 @@
1
+ "use client"
2
+ import Link from "next/link";
3
+ import { ModeToggle } from "./mode-toggle";
4
+
5
+ export default function Header() {
6
+ const links = [
7
+ { to: "/", label: "Home" },
8
+ ];
9
+
10
+ return (
11
+ <div>
12
+ <div className="flex flex-row items-center justify-between px-2 py-1">
13
+ <nav className="flex gap-4 text-lg">
14
+ {links.map(({ to, label }) => (
15
+ <Link
16
+ key={to}
17
+ href={to}
18
+ >
19
+ {label}
20
+ </Link>
21
+ ))}
22
+ </nav>
23
+ <div className="flex items-center gap-2">
24
+ <ModeToggle />
25
+ </div>
26
+ </div>
27
+ <hr />
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,39 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Moon, Sun } from "lucide-react"
5
+ import { useTheme } from "next-themes"
6
+ import { Button } from "@/components/ui/button"
7
+ import {
8
+ DropdownMenu,
9
+ DropdownMenuContent,
10
+ DropdownMenuItem,
11
+ DropdownMenuTrigger,
12
+ } from "@/components/ui/dropdown-menu"
13
+
14
+ export function ModeToggle() {
15
+ const { setTheme } = useTheme()
16
+
17
+ return (
18
+ <DropdownMenu>
19
+ <DropdownMenuTrigger asChild>
20
+ <Button variant="outline" size="icon">
21
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
22
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
23
+ <span className="sr-only">Toggle theme</span>
24
+ </Button>
25
+ </DropdownMenuTrigger>
26
+ <DropdownMenuContent align="end">
27
+ <DropdownMenuItem onClick={() => setTheme("light")}>
28
+ Light
29
+ </DropdownMenuItem>
30
+ <DropdownMenuItem onClick={() => setTheme("dark")}>
31
+ Dark
32
+ </DropdownMenuItem>
33
+ <DropdownMenuItem onClick={() => setTheme("system")}>
34
+ System
35
+ </DropdownMenuItem>
36
+ </DropdownMenuContent>
37
+ </DropdownMenu>
38
+ )
39
+ }
@@ -0,0 +1,25 @@
1
+ "use client"
2
+ import { QueryClientProvider } from "@tanstack/react-query";
3
+ import { queryClient } from "@/utils/trpc";
4
+ import { ThemeProvider } from "./theme-provider";
5
+ import { Toaster } from "./ui/sonner";
6
+
7
+ export default function Providers({
8
+ children,
9
+ }: {
10
+ children: React.ReactNode
11
+ }) {
12
+ return (
13
+ <ThemeProvider
14
+ attribute="class"
15
+ defaultTheme="system"
16
+ enableSystem
17
+ disableTransitionOnChange
18
+ >
19
+ <QueryClientProvider client={queryClient}>
20
+ {children}
21
+ </QueryClientProvider>
22
+ <Toaster richColors />
23
+ </ThemeProvider>
24
+ )
25
+ }
@@ -0,0 +1,11 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { ThemeProvider as NextThemesProvider } from "next-themes"
5
+
6
+ export function ThemeProvider({
7
+ children,
8
+ ...props
9
+ }: React.ComponentProps<typeof NextThemesProvider>) {
10
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
11
+ }
@@ -0,0 +1,33 @@
1
+ import { QueryCache, QueryClient } from '@tanstack/react-query';
2
+ import { createTRPCClient, httpBatchLink } from '@trpc/client';
3
+ import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
4
+ import type { AppRouter } from '../../../server/src/routers';
5
+ import { toast } from 'sonner';
6
+
7
+ export const queryClient = new QueryClient({
8
+ queryCache: new QueryCache({
9
+ onError: (error) => {
10
+ toast.error(error.message, {
11
+ action: {
12
+ label: "retry",
13
+ onClick: () => {
14
+ queryClient.invalidateQueries();
15
+ },
16
+ },
17
+ });
18
+ },
19
+ }),
20
+ });
21
+
22
+ const trpcClient = createTRPCClient<AppRouter>({
23
+ links: [
24
+ httpBatchLink({
25
+ url: `${process.env.NEXT_PUBLIC_SERVER_URL}/api/trpc`,
26
+ }),
27
+ ],
28
+ })
29
+
30
+ export const trpc = createTRPCOptionsProxy<AppRouter>({
31
+ client: trpcClient,
32
+ queryClient,
33
+ });
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "verbatimModuleSyntax": true,
15
+ "jsx": "preserve",
16
+ "incremental": true,
17
+ "plugins": [
18
+ {
19
+ "name": "next"
20
+ }
21
+ ],
22
+ "paths": {
23
+ "@/*": ["./src/*"]
24
+ }
25
+ },
26
+ "include": ["./next-env.d.ts", "./**/*.ts", "./**/*.tsx", "./.next/types/**/*.ts"],
27
+ "exclude": ["./node_modules"]
28
+ }
@@ -0,0 +1,14 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { auth } from "./auth";
3
+
4
+ export async function createContext(req: NextRequest) {
5
+ const session = await auth.api.getSession({
6
+ headers: req.headers,
7
+ });
8
+
9
+ return {
10
+ session,
11
+ };
12
+ }
13
+
14
+ export type Context = Awaited<ReturnType<typeof createContext>>;
@@ -0,0 +1,4 @@
1
+ import { auth } from "@/lib/auth";
2
+ import { toNextJsHandler } from "better-auth/next-js";
3
+
4
+ export const { GET, POST } = toNextJsHandler(auth.handler);
@@ -0,0 +1,31 @@
1
+ "use client"
2
+ import { authClient } from "@/lib/auth-client";
3
+ import { trpc } from "@/utils/trpc";
4
+ import { useQuery } from "@tanstack/react-query";
5
+ import { useRouter } from "next/navigation";
6
+ import { useEffect } from "react";
7
+
8
+ export default function Dashboard() {
9
+ const router = useRouter()
10
+ const { data: session, isPending } = authClient.useSession();
11
+
12
+ const privateData = useQuery(trpc.privateData.queryOptions());
13
+
14
+ useEffect(() => {
15
+ if (!session && !isPending) {
16
+ router.push("/login");
17
+ }
18
+ }, [session, isPending]);
19
+
20
+ if (isPending) {
21
+ return <div>Loading...</div>;
22
+ }
23
+
24
+ return (
25
+ <div>
26
+ <h1>Dashboard</h1>
27
+ <p>Welcome {session?.user.name}</p>
28
+ <p>privateData: {privateData.data?.message}</p>
29
+ </div>
30
+ );
31
+ }
@@ -0,0 +1,16 @@
1
+ "use client"
2
+
3
+ import SignInForm from "@/components/sign-in-form";
4
+ import SignUpForm from "@/components/sign-up-form";
5
+ import { useState } from "react";
6
+
7
+
8
+ export default function LoginPage() {
9
+ const [showSignIn, setShowSignIn] = useState(false);
10
+
11
+ return showSignIn ? (
12
+ <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
13
+ ) : (
14
+ <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
15
+ );
16
+ }
@@ -0,0 +1,33 @@
1
+ "use client"
2
+ import Link from "next/link";
3
+ import { ModeToggle } from "./mode-toggle";
4
+ import UserMenu from "./user-menu";
5
+
6
+ export default function Header() {
7
+ const links = [
8
+ { to: "/", label: "Home" },
9
+ { to: "/dashboard", label: "Dashboard" },
10
+ ];
11
+
12
+ return (
13
+ <div>
14
+ <div className="flex flex-row items-center justify-between px-2 py-1">
15
+ <nav className="flex gap-4 text-lg">
16
+ {links.map(({ to, label }) => (
17
+ <Link
18
+ key={to}
19
+ href={to}
20
+ >
21
+ {label}
22
+ </Link>
23
+ ))}
24
+ </nav>
25
+ <div className="flex items-center gap-2">
26
+ <ModeToggle />
27
+ <UserMenu />
28
+ </div>
29
+ </div>
30
+ <hr />
31
+ </div>
32
+ );
33
+ }