create-better-t-stack 2.26.1 → 2.26.3
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.
- package/dist/index.js +10 -5
- package/package.json +1 -1
- package/templates/db/drizzle/postgres/src/db/index.ts.hbs +12 -2
- package/templates/frontend/react/react-router/src/components/theme-provider.tsx +5 -67
- package/templates/frontend/react/react-router/src/root.tsx.hbs +40 -28
- package/templates/frontend/react/react-router/src/routes/_index.tsx.hbs +1 -1
- package/templates/frontend/react/tanstack-router/{index.html → index.html.hbs} +2 -1
- package/templates/frontend/react/tanstack-router/src/components/theme-provider.tsx +5 -67
- package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +14 -4
package/dist/index.js
CHANGED
|
@@ -49,14 +49,16 @@ const DEFAULT_CONFIG = {
|
|
|
49
49
|
webDeploy: "none"
|
|
50
50
|
};
|
|
51
51
|
const dependencyVersionMap = {
|
|
52
|
-
"better-auth": "^1.
|
|
53
|
-
"@better-auth/expo": "^1.
|
|
52
|
+
"better-auth": "^1.3.0",
|
|
53
|
+
"@better-auth/expo": "^1.3.0",
|
|
54
54
|
"drizzle-orm": "^0.44.2",
|
|
55
55
|
"drizzle-kit": "^0.31.2",
|
|
56
56
|
"@libsql/client": "^0.15.9",
|
|
57
57
|
"@neondatabase/serverless": "^1.0.1",
|
|
58
58
|
pg: "^8.14.1",
|
|
59
59
|
"@types/pg": "^8.11.11",
|
|
60
|
+
"@types/ws": "^8.18.1",
|
|
61
|
+
ws: "^8.18.3",
|
|
60
62
|
mysql2: "^3.14.0",
|
|
61
63
|
"@prisma/client": "^6.9.0",
|
|
62
64
|
prisma: "^6.9.0",
|
|
@@ -2019,7 +2021,6 @@ async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, contex
|
|
|
2019
2021
|
async function copyBaseTemplate(projectDir, context) {
|
|
2020
2022
|
const templateDir = path.join(PKG_ROOT, "templates/base");
|
|
2021
2023
|
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
|
2022
|
-
await fs.ensureDir(path.join(projectDir, "packages"));
|
|
2023
2024
|
}
|
|
2024
2025
|
async function setupFrontendTemplates(projectDir, context) {
|
|
2025
2026
|
const hasReactWeb = context.frontend.some((f) => [
|
|
@@ -3954,8 +3955,12 @@ async function setupDatabase(config) {
|
|
|
3954
3955
|
projectDir: serverDir
|
|
3955
3956
|
});
|
|
3956
3957
|
else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
|
|
3957
|
-
dependencies: [
|
|
3958
|
-
|
|
3958
|
+
dependencies: [
|
|
3959
|
+
"drizzle-orm",
|
|
3960
|
+
"@neondatabase/serverless",
|
|
3961
|
+
"ws"
|
|
3962
|
+
],
|
|
3963
|
+
devDependencies: ["drizzle-kit", "@types/ws"],
|
|
3959
3964
|
projectDir: serverDir
|
|
3960
3965
|
});
|
|
3961
3966
|
else await addPackageDependency({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "2.26.
|
|
3
|
+
"version": "2.26.3",
|
|
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",
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
{{#if (or (eq runtime "bun") (eq runtime "node"))}}
|
|
2
2
|
{{#if (eq dbSetup "neon")}}
|
|
3
|
-
import { neon } from '@neondatabase/serverless';
|
|
3
|
+
import { neon, neonConfig } from '@neondatabase/serverless';
|
|
4
4
|
import { drizzle } from 'drizzle-orm/neon-http';
|
|
5
|
+
import ws from "ws";
|
|
6
|
+
|
|
7
|
+
neonConfig.webSocketConstructor = ws;
|
|
8
|
+
|
|
9
|
+
// To work in edge environments (Cloudflare Workers, Vercel Edge, etc.), enable querying over fetch
|
|
10
|
+
// neonConfig.poolQueryViaFetch = true
|
|
5
11
|
|
|
6
12
|
const sql = neon(process.env.DATABASE_URL || "");
|
|
7
13
|
export const db = drizzle(sql);
|
|
@@ -14,9 +20,13 @@ export const db = drizzle(process.env.DATABASE_URL || "");
|
|
|
14
20
|
|
|
15
21
|
{{#if (eq runtime "workers")}}
|
|
16
22
|
{{#if (eq dbSetup "neon")}}
|
|
17
|
-
import { neon } from '@neondatabase/serverless';
|
|
23
|
+
import { neon, neonConfig } from '@neondatabase/serverless';
|
|
18
24
|
import { drizzle } from 'drizzle-orm/neon-http';
|
|
19
25
|
import { env } from "cloudflare:workers";
|
|
26
|
+
import ws from "ws";
|
|
27
|
+
|
|
28
|
+
neonConfig.webSocketConstructor = ws;
|
|
29
|
+
neonConfig.poolQueryViaFetch = true;
|
|
20
30
|
|
|
21
31
|
const sql = neon(env.DATABASE_URL || "");
|
|
22
32
|
export const db = drizzle(sql);
|
|
@@ -1,73 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
type Theme = "dark" | "light" | "system";
|
|
4
|
-
|
|
5
|
-
type ThemeProviderProps = {
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
defaultTheme?: Theme;
|
|
8
|
-
storageKey?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type ThemeProviderState = {
|
|
12
|
-
theme: Theme;
|
|
13
|
-
setTheme: (theme: Theme) => void;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const initialState: ThemeProviderState = {
|
|
17
|
-
theme: "system",
|
|
18
|
-
setTheme: () => null,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
22
3
|
|
|
23
4
|
export function ThemeProvider({
|
|
24
5
|
children,
|
|
25
|
-
defaultTheme = "system",
|
|
26
|
-
storageKey = "vite-ui-theme",
|
|
27
6
|
...props
|
|
28
|
-
}:
|
|
29
|
-
|
|
30
|
-
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const root = window.document.documentElement;
|
|
35
|
-
|
|
36
|
-
root.classList.remove("light", "dark");
|
|
37
|
-
|
|
38
|
-
if (theme === "system") {
|
|
39
|
-
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
40
|
-
.matches
|
|
41
|
-
? "dark"
|
|
42
|
-
: "light";
|
|
43
|
-
|
|
44
|
-
root.classList.add(systemTheme);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
root.classList.add(theme);
|
|
49
|
-
}, [theme]);
|
|
50
|
-
|
|
51
|
-
const value = {
|
|
52
|
-
theme,
|
|
53
|
-
setTheme: (theme: Theme) => {
|
|
54
|
-
localStorage.setItem(storageKey, theme);
|
|
55
|
-
setTheme(theme);
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<ThemeProviderContext.Provider {...props} value={value}>
|
|
61
|
-
{children}
|
|
62
|
-
</ThemeProviderContext.Provider>
|
|
63
|
-
);
|
|
7
|
+
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
8
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
64
9
|
}
|
|
65
10
|
|
|
66
|
-
export
|
|
67
|
-
const context = useContext(ThemeProviderContext);
|
|
68
|
-
|
|
69
|
-
if (context === undefined)
|
|
70
|
-
throw new Error("useTheme must be used within a ThemeProvider");
|
|
71
|
-
|
|
72
|
-
return context;
|
|
73
|
-
};
|
|
11
|
+
export { useTheme } from "next-themes";
|
|
@@ -15,31 +15,25 @@ import { Toaster } from "./components/ui/sonner";
|
|
|
15
15
|
{{#if (eq backend "convex")}}
|
|
16
16
|
import { ConvexProvider, ConvexReactClient } from "convex/react";
|
|
17
17
|
{{else}}
|
|
18
|
-
{{#unless (eq api "none")}}
|
|
18
|
+
{{#unless (eq api "none")}}
|
|
19
19
|
import { QueryClientProvider } from "@tanstack/react-query";
|
|
20
20
|
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
|
|
21
|
-
{{#if (eq api "orpc")}}
|
|
21
|
+
{{#if (eq api "orpc")}}
|
|
22
22
|
import { queryClient } from "./utils/orpc";
|
|
23
|
-
{{/if}}
|
|
24
|
-
{{#if (eq api "trpc")}}
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#if (eq api "trpc")}}
|
|
25
25
|
import { queryClient } from "./utils/trpc";
|
|
26
|
-
{{/if}}
|
|
27
|
-
{{/unless}}
|
|
26
|
+
{{/if}}
|
|
27
|
+
{{/unless}}
|
|
28
28
|
{{/if}}
|
|
29
29
|
|
|
30
30
|
export const links: Route.LinksFunction = () => [
|
|
31
|
-
{
|
|
32
|
-
|
|
33
|
-
href: "https://fonts.googleapis.com",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
rel: "preconnect",
|
|
37
|
-
href: "https://fonts.gstatic.com",
|
|
38
|
-
crossOrigin: "anonymous",
|
|
39
|
-
},
|
|
31
|
+
{ rel: "preconnect", href: "https://fonts.googleapis.com" },
|
|
32
|
+
{ rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
|
|
40
33
|
{
|
|
41
34
|
rel: "stylesheet",
|
|
42
|
-
href:
|
|
35
|
+
href:
|
|
36
|
+
"https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap",
|
|
43
37
|
},
|
|
44
38
|
];
|
|
45
39
|
|
|
@@ -68,7 +62,12 @@ export default function App() {
|
|
|
68
62
|
);
|
|
69
63
|
return (
|
|
70
64
|
<ConvexProvider client={convex}>
|
|
71
|
-
<ThemeProvider
|
|
65
|
+
<ThemeProvider
|
|
66
|
+
attribute="class"
|
|
67
|
+
defaultTheme="dark"
|
|
68
|
+
disableTransitionOnChange
|
|
69
|
+
storageKey="vite-ui-theme"
|
|
70
|
+
>
|
|
72
71
|
<div className="grid grid-rows-[auto_1fr] h-svh">
|
|
73
72
|
<Header />
|
|
74
73
|
<Outlet />
|
|
@@ -82,13 +81,18 @@ export default function App() {
|
|
|
82
81
|
export default function App() {
|
|
83
82
|
return (
|
|
84
83
|
<QueryClientProvider client={queryClient}>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
<ThemeProvider
|
|
85
|
+
attribute="class"
|
|
86
|
+
defaultTheme="dark"
|
|
87
|
+
disableTransitionOnChange
|
|
88
|
+
storageKey="vite-ui-theme"
|
|
89
|
+
>
|
|
90
|
+
<div className="grid grid-rows-[auto_1fr] h-svh">
|
|
91
|
+
<Header />
|
|
92
|
+
<Outlet />
|
|
93
|
+
</div>
|
|
94
|
+
<Toaster richColors />
|
|
95
|
+
</ThemeProvider>
|
|
92
96
|
<ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
|
|
93
97
|
</QueryClientProvider>
|
|
94
98
|
);
|
|
@@ -97,7 +101,12 @@ export default function App() {
|
|
|
97
101
|
export default function App() {
|
|
98
102
|
return (
|
|
99
103
|
<QueryClientProvider client={queryClient}>
|
|
100
|
-
<ThemeProvider
|
|
104
|
+
<ThemeProvider
|
|
105
|
+
attribute="class"
|
|
106
|
+
defaultTheme="dark"
|
|
107
|
+
disableTransitionOnChange
|
|
108
|
+
storageKey="vite-ui-theme"
|
|
109
|
+
>
|
|
101
110
|
<div className="grid grid-rows-[auto_1fr] h-svh">
|
|
102
111
|
<Header />
|
|
103
112
|
<Outlet />
|
|
@@ -111,7 +120,12 @@ export default function App() {
|
|
|
111
120
|
{{else}}
|
|
112
121
|
export default function App() {
|
|
113
122
|
return (
|
|
114
|
-
<ThemeProvider
|
|
123
|
+
<ThemeProvider
|
|
124
|
+
attribute="class"
|
|
125
|
+
defaultTheme="dark"
|
|
126
|
+
disableTransitionOnChange
|
|
127
|
+
storageKey="vite-ui-theme"
|
|
128
|
+
>
|
|
115
129
|
<div className="grid grid-rows-[auto_1fr] h-svh">
|
|
116
130
|
<Header />
|
|
117
131
|
<Outlet />
|
|
@@ -126,7 +140,6 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|
|
126
140
|
let message = "Oops!";
|
|
127
141
|
let details = "An unexpected error occurred.";
|
|
128
142
|
let stack: string | undefined;
|
|
129
|
-
|
|
130
143
|
if (isRouteErrorResponse(error)) {
|
|
131
144
|
message = error.status === 404 ? "404" : "Error";
|
|
132
145
|
details =
|
|
@@ -137,7 +150,6 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
|
|
|
137
150
|
details = error.message;
|
|
138
151
|
stack = error.stack;
|
|
139
152
|
}
|
|
140
|
-
|
|
141
153
|
return (
|
|
142
154
|
<main className="pt-16 p-4 container mx-auto">
|
|
143
155
|
<h1>{message}</h1>
|
|
@@ -29,7 +29,7 @@ const TITLE_TEXT = `
|
|
|
29
29
|
`;
|
|
30
30
|
|
|
31
31
|
export function meta({}: Route.MetaArgs) {
|
|
32
|
-
return [{ title: "
|
|
32
|
+
return [{ title: "{{projectName}}" }, { name: "description", content: "{{projectName}} is a web application" }];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export default function Home() {
|
|
@@ -1,73 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
type Theme = "dark" | "light" | "system";
|
|
4
|
-
|
|
5
|
-
type ThemeProviderProps = {
|
|
6
|
-
children: React.ReactNode;
|
|
7
|
-
defaultTheme?: Theme;
|
|
8
|
-
storageKey?: string;
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
type ThemeProviderState = {
|
|
12
|
-
theme: Theme;
|
|
13
|
-
setTheme: (theme: Theme) => void;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const initialState: ThemeProviderState = {
|
|
17
|
-
theme: "system",
|
|
18
|
-
setTheme: () => null,
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { ThemeProvider as NextThemesProvider } from "next-themes";
|
|
22
3
|
|
|
23
4
|
export function ThemeProvider({
|
|
24
5
|
children,
|
|
25
|
-
defaultTheme = "system",
|
|
26
|
-
storageKey = "vite-ui-theme",
|
|
27
6
|
...props
|
|
28
|
-
}:
|
|
29
|
-
|
|
30
|
-
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
31
|
-
);
|
|
32
|
-
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
const root = window.document.documentElement;
|
|
35
|
-
|
|
36
|
-
root.classList.remove("light", "dark");
|
|
37
|
-
|
|
38
|
-
if (theme === "system") {
|
|
39
|
-
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
40
|
-
.matches
|
|
41
|
-
? "dark"
|
|
42
|
-
: "light";
|
|
43
|
-
|
|
44
|
-
root.classList.add(systemTheme);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
root.classList.add(theme);
|
|
49
|
-
}, [theme]);
|
|
50
|
-
|
|
51
|
-
const value = {
|
|
52
|
-
theme,
|
|
53
|
-
setTheme: (theme: Theme) => {
|
|
54
|
-
localStorage.setItem(storageKey, theme);
|
|
55
|
-
setTheme(theme);
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<ThemeProviderContext.Provider {...props} value={value}>
|
|
61
|
-
{children}
|
|
62
|
-
</ThemeProviderContext.Provider>
|
|
63
|
-
);
|
|
7
|
+
}: React.ComponentProps<typeof NextThemesProvider>) {
|
|
8
|
+
return <NextThemesProvider {...props}>{children}</NextThemesProvider>;
|
|
64
9
|
}
|
|
65
10
|
|
|
66
|
-
export
|
|
67
|
-
const context = useContext(ThemeProviderContext);
|
|
68
|
-
|
|
69
|
-
if (context === undefined)
|
|
70
|
-
throw new Error("useTheme must be used within a ThemeProvider");
|
|
71
|
-
|
|
72
|
-
return context;
|
|
73
|
-
};
|
|
11
|
+
export { useTheme } from "next-themes";
|
|
@@ -45,11 +45,11 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
|
|
|
45
45
|
head: () => ({
|
|
46
46
|
meta: [
|
|
47
47
|
{
|
|
48
|
-
title: "
|
|
48
|
+
title: "{{projectName}}",
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
51
|
name: "description",
|
|
52
|
-
content: "
|
|
52
|
+
content: "{{projectName}} is a web application",
|
|
53
53
|
},
|
|
54
54
|
],
|
|
55
55
|
links: [
|
|
@@ -75,7 +75,12 @@ function RootComponent() {
|
|
|
75
75
|
<>
|
|
76
76
|
<HeadContent />
|
|
77
77
|
{{#if (eq api "orpc")}}
|
|
78
|
-
<ThemeProvider
|
|
78
|
+
<ThemeProvider
|
|
79
|
+
attribute="class"
|
|
80
|
+
defaultTheme="dark"
|
|
81
|
+
disableTransitionOnChange
|
|
82
|
+
storageKey="vite-ui-theme"
|
|
83
|
+
>
|
|
79
84
|
<div className="grid grid-rows-[auto_1fr] h-svh">
|
|
80
85
|
<Header />
|
|
81
86
|
{isFetching ? <Loader /> : <Outlet />}
|
|
@@ -83,7 +88,12 @@ function RootComponent() {
|
|
|
83
88
|
<Toaster richColors />
|
|
84
89
|
</ThemeProvider>
|
|
85
90
|
{{else}}
|
|
86
|
-
<ThemeProvider
|
|
91
|
+
<ThemeProvider
|
|
92
|
+
attribute="class"
|
|
93
|
+
defaultTheme="dark"
|
|
94
|
+
disableTransitionOnChange
|
|
95
|
+
storageKey="vite-ui-theme"
|
|
96
|
+
>
|
|
87
97
|
<div className="grid grid-rows-[auto_1fr] h-svh">
|
|
88
98
|
<Header />
|
|
89
99
|
{isFetching ? <Loader /> : <Outlet />}
|