create-better-t-stack 2.13.3 → 2.14.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.13.3",
3
+ "version": "2.14.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",
@@ -55,19 +55,20 @@
55
55
  "dependencies": {
56
56
  "@clack/prompts": "^0.11.0",
57
57
  "consola": "^3.4.2",
58
- "execa": "^9.5.3",
58
+ "execa": "^9.6.0",
59
59
  "fs-extra": "^11.3.0",
60
60
  "globby": "^14.1.0",
61
61
  "gradient-string": "^3.0.0",
62
62
  "handlebars": "^4.7.8",
63
63
  "picocolors": "^1.1.1",
64
- "yargs": "^17.7.2"
64
+ "posthog-node": "^4.18.0",
65
+ "yargs": "^18.0.0"
65
66
  },
66
67
  "devDependencies": {
67
68
  "@types/fs-extra": "^11.0.4",
68
- "@types/node": "^22.15.21",
69
+ "@types/node": "^22.15.23",
69
70
  "@types/yargs": "^17.0.33",
70
- "tsdown": "^0.12.3",
71
+ "tsdown": "^0.12.4",
71
72
  "typescript": "^5.8.3"
72
73
  }
73
74
  }
@@ -1,10 +1,6 @@
1
1
  {
2
2
  "name": "@{{projectName}}/backend",
3
3
  "version": "1.0.0",
4
- "private": true,
5
- "exports": {
6
- "./convex/*": "./convex/*"
7
- },
8
4
  "scripts": {
9
5
  "dev": "convex dev",
10
6
  "setup": "convex dev --configure --until-success"
@@ -18,6 +18,9 @@
18
18
  "name": "next"
19
19
  }
20
20
  ],
21
+ {#unless (or (eq backend "convex") (eq backend "none"))}}
22
+ "composite": true,
23
+ {{/unless}}
21
24
  "paths": {
22
25
  "@/*": ["./src/*"]
23
26
  }
@@ -20,6 +20,9 @@
20
20
  "node", "bun"
21
21
  {{/if}}
22
22
  ],
23
+ {{#unless (or (eq backend "convex") (eq backend "none"))}}
24
+ "composite": true,
25
+ {{/unless}}
23
26
  "jsx": "react-jsx"{{#if (eq backend 'hono')}},
24
27
  "jsxImportSource": "hono/jsx"{{/if}}
25
28
  },
@@ -0,0 +1,295 @@
1
+ import { useState } from "react";
2
+ import {
3
+ View,
4
+ Text,
5
+ TextInput,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ ActivityIndicator,
9
+ Alert,
10
+ } from "react-native";
11
+ import { Ionicons } from "@expo/vector-icons";
12
+ {{#if (eq backend "convex")}}
13
+ import { useMutation, useQuery } from "convex/react";
14
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
15
+ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
16
+ {{else}}
17
+ import { useMutation, useQuery } from "@tanstack/react-query";
18
+ {{/if}}
19
+
20
+ import { Container } from "@/components/container";
21
+ {{#unless (eq backend "convex")}}
22
+ {{#if (eq api "orpc")}}
23
+ import { orpc } from "@/utils/orpc";
24
+ {{/if}}
25
+ {{#if (eq api "trpc")}}
26
+ import { trpc } from "@/utils/trpc";
27
+ {{/if}}
28
+ {{/unless}}
29
+
30
+ export default function TodosScreen() {
31
+ const [newTodoText, setNewTodoText] = useState("");
32
+
33
+ {{#if (eq backend "convex")}}
34
+ const todos = useQuery(api.todos.getAll);
35
+ const createTodoMutation = useMutation(api.todos.create);
36
+ const toggleTodoMutation = useMutation(api.todos.toggle);
37
+ const deleteTodoMutation = useMutation(api.todos.deleteTodo);
38
+
39
+ const handleAddTodo = async () => {
40
+ const text = newTodoText.trim();
41
+ if (!text) return;
42
+ await createTodoMutation({ text });
43
+ setNewTodoText("");
44
+ };
45
+
46
+ const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => {
47
+ toggleTodoMutation({ id, completed: !currentCompleted });
48
+ };
49
+
50
+ const handleDeleteTodo = (id: Id<"todos">) => {
51
+ Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [
52
+ { text: "Cancel", style: "cancel" },
53
+ {
54
+ text: "Delete",
55
+ style: "destructive",
56
+ onPress: () => deleteTodoMutation({ id }),
57
+ },
58
+ ]);
59
+ };
60
+ {{else}}
61
+ {{#if (eq api "orpc")}}
62
+ const todos = useQuery(orpc.todo.getAll.queryOptions());
63
+ const createMutation = useMutation(
64
+ orpc.todo.create.mutationOptions({
65
+ onSuccess: () => {
66
+ todos.refetch();
67
+ setNewTodoText("");
68
+ },
69
+ }),
70
+ );
71
+ const toggleMutation = useMutation(
72
+ orpc.todo.toggle.mutationOptions({
73
+ onSuccess: () => todos.refetch(),
74
+ }),
75
+ );
76
+ const deleteMutation = useMutation(
77
+ orpc.todo.delete.mutationOptions({
78
+ onSuccess: () => todos.refetch(),
79
+ }),
80
+ );
81
+ {{/if}}
82
+ {{#if (eq api "trpc")}}
83
+ const todos = useQuery(trpc.todo.getAll.queryOptions());
84
+ const createMutation = useMutation(
85
+ trpc.todo.create.mutationOptions({
86
+ onSuccess: () => {
87
+ todos.refetch();
88
+ setNewTodoText("");
89
+ },
90
+ }),
91
+ );
92
+ const toggleMutation = useMutation(
93
+ trpc.todo.toggle.mutationOptions({
94
+ onSuccess: () => todos.refetch(),
95
+ }),
96
+ );
97
+ const deleteMutation = useMutation(
98
+ trpc.todo.delete.mutationOptions({
99
+ onSuccess: () => todos.refetch(),
100
+ }),
101
+ );
102
+ {{/if}}
103
+
104
+ const handleAddTodo = () => {
105
+ if (newTodoText.trim()) {
106
+ createMutation.mutate({ text: newTodoText });
107
+ }
108
+ };
109
+
110
+ const handleToggleTodo = (id: number, completed: boolean) => {
111
+ toggleMutation.mutate({ id, completed: !completed });
112
+ };
113
+
114
+ const handleDeleteTodo = (id: number) => {
115
+ Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [
116
+ { text: "Cancel", style: "cancel" },
117
+ {
118
+ text: "Delete",
119
+ style: "destructive",
120
+ onPress: () => deleteMutation.mutate({ id }),
121
+ },
122
+ ]);
123
+ };
124
+ {{/if}}
125
+
126
+ return (
127
+ <Container>
128
+ <ScrollView className="flex-1">
129
+ <View className="px-4 py-6">
130
+ <View className="mb-6 rounded-lg border border-border p-4 bg-card">
131
+ <Text className="text-foreground text-2xl font-bold mb-2">
132
+ Todo List
133
+ </Text>
134
+ <Text className="text-muted-foreground mb-4">
135
+ Manage your tasks efficiently
136
+ </Text>
137
+
138
+ <View className="mb-6">
139
+ <View className="flex-row items-center space-x-2 mb-2">
140
+ <TextInput
141
+ value={newTodoText}
142
+ onChangeText={setNewTodoText}
143
+ placeholder="Add a new task..."
144
+ placeholderTextColor="#6b7280"
145
+ {{#if (eq backend "convex")}}
146
+ {{else}}
147
+ editable={!createMutation.isPending}
148
+ {{/if}}
149
+ className="flex-1 border border-border rounded-md px-3 py-2 text-foreground bg-background"
150
+ onSubmitEditing={handleAddTodo}
151
+ returnKeyType="done"
152
+ />
153
+ <TouchableOpacity
154
+ onPress={handleAddTodo}
155
+ {{#if (eq backend "convex")}}
156
+ disabled={!newTodoText.trim()}
157
+ {{else}}
158
+ disabled={createMutation.isPending || !newTodoText.trim()}
159
+ {{/if}}
160
+ className={`px-4 py-2 rounded-md ${
161
+ {{#if (eq backend "convex")}}
162
+ !newTodoText.trim()
163
+ {{else}}
164
+ createMutation.isPending || !newTodoText.trim()
165
+ {{/if}}
166
+ ? "bg-muted"
167
+ : "bg-primary"
168
+ }`}
169
+ >
170
+ {{#if (eq backend "convex")}}
171
+ <Text className="text-white font-medium">Add</Text>
172
+ {{else}}
173
+ {createMutation.isPending ? (
174
+ <ActivityIndicator size="small" color="white" />
175
+ ) : (
176
+ <Text className="text-white font-medium">Add</Text>
177
+ )}
178
+ {{/if}}
179
+ </TouchableOpacity>
180
+ </View>
181
+ </View>
182
+
183
+ {{#if (eq backend "convex")}}
184
+ {todos === undefined ? (
185
+ <View className="flex justify-center py-8">
186
+ <ActivityIndicator size="large" color="#3b82f6" />
187
+ </View>
188
+ ) : todos.length === 0 ? (
189
+ <Text className="py-8 text-center text-muted-foreground">
190
+ No todos yet. Add one above!
191
+ </Text>
192
+ ) : (
193
+ <View className="space-y-2">
194
+ {todos.map((todo) => (
195
+ <View
196
+ key={todo._id}
197
+ className="flex-row items-center justify-between rounded-md border border-border p-3 bg-background"
198
+ >
199
+ <View className="flex-row items-center flex-1">
200
+ <TouchableOpacity
201
+ onPress={() =>
202
+ handleToggleTodo(todo._id, todo.completed)
203
+ }
204
+ className="mr-3"
205
+ >
206
+ <Ionicons
207
+ name={todo.completed ? "checkbox" : "square-outline"}
208
+ size={24}
209
+ color={todo.completed ? "#22c55e" : "#6b7280"}
210
+ />
211
+ </TouchableOpacity>
212
+ <Text
213
+ className={`flex-1 ${
214
+ todo.completed
215
+ ? "line-through text-muted-foreground"
216
+ : "text-foreground"
217
+ }`}
218
+ >
219
+ {todo.text}
220
+ </Text>
221
+ </View>
222
+ <TouchableOpacity
223
+ onPress={() => handleDeleteTodo(todo._id)}
224
+ className="ml-2 p-1"
225
+ >
226
+ <Ionicons
227
+ name="trash-outline"
228
+ size={20}
229
+ color="#ef4444"
230
+ />
231
+ </TouchableOpacity>
232
+ </View>
233
+ ))}
234
+ </View>
235
+ )}
236
+ {{else}}
237
+ {todos.isLoading ? (
238
+ <View className="flex justify-center py-8">
239
+ <ActivityIndicator size="large" color="#3b82f6" />
240
+ </View>
241
+ ) : todos.data?.length === 0 ? (
242
+ <Text className="py-8 text-center text-muted-foreground">
243
+ No todos yet. Add one above!
244
+ </Text>
245
+ ) : (
246
+ <View className="space-y-2">
247
+ {todos.data?.map((todo) => (
248
+ <View
249
+ key={todo.id}
250
+ className="flex-row items-center justify-between rounded-md border border-border p-3 bg-background"
251
+ >
252
+ <View className="flex-row items-center flex-1">
253
+ <TouchableOpacity
254
+ onPress={() =>
255
+ handleToggleTodo(todo.id, todo.completed)
256
+ }
257
+ className="mr-3"
258
+ >
259
+ <Ionicons
260
+ name={todo.completed ? "checkbox" : "square-outline"}
261
+ size={24}
262
+ color={todo.completed ? "#22c55e" : "#6b7280"}
263
+ />
264
+ </TouchableOpacity>
265
+ <Text
266
+ className={`flex-1 ${
267
+ todo.completed
268
+ ? "line-through text-muted-foreground"
269
+ : "text-foreground"
270
+ }`}
271
+ >
272
+ {todo.text}
273
+ </Text>
274
+ </View>
275
+ <TouchableOpacity
276
+ onPress={() => handleDeleteTodo(todo.id)}
277
+ className="ml-2 p-1"
278
+ >
279
+ <Ionicons
280
+ name="trash-outline"
281
+ size={20}
282
+ color="#ef4444"
283
+ />
284
+ </TouchableOpacity>
285
+ </View>
286
+ ))}
287
+ </View>
288
+ )}
289
+ {{/if}}
290
+ </View>
291
+ </View>
292
+ </ScrollView>
293
+ </Container>
294
+ );
295
+ }
@@ -15,8 +15,8 @@ import { useState } from "react";
15
15
 
16
16
  {{#if (eq backend "convex")}}
17
17
  import { useMutation, useQuery } from "convex/react";
18
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
19
- import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts";
18
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
19
+ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
20
20
  {{else}}
21
21
  import { useMutation, useQuery } from "@tanstack/react-query";
22
22
  {{#if (eq api "orpc")}}
@@ -13,8 +13,8 @@ import { useState } from "react";
13
13
 
14
14
  {{#if (eq backend "convex")}}
15
15
  import { useMutation, useQuery } from "convex/react";
16
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
17
- import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts";
16
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
17
+ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
18
18
  {{else}}
19
19
  {{#if (eq api "orpc")}}
20
20
  import { orpc } from "@/utils/orpc";
@@ -14,8 +14,8 @@ import { useState } from "react";
14
14
 
15
15
  {{#if (eq backend "convex")}}
16
16
  import { useMutation, useQuery } from "convex/react";
17
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
18
- import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts";
17
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
18
+ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
19
19
  {{else}}
20
20
  {{#if (eq api "orpc")}}
21
21
  import { orpc } from "@/utils/orpc";
@@ -16,7 +16,7 @@ import { useState } from "react";
16
16
  import { useSuspenseQuery } from "@tanstack/react-query";
17
17
  import { convexQuery } from "@convex-dev/react-query";
18
18
  import { useMutation } from "convex/react";
19
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
19
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
20
20
  import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.js";
21
21
  {{else}}
22
22
  {{#if (eq api "trpc")}}
@@ -1,7 +1,7 @@
1
1
  {{#if (eq backend "convex")}}
2
2
  <script lang="ts">
3
3
  import { useQuery, useConvexClient } from 'convex-svelte';
4
- import { api } from '@{{projectName}}/backend/convex/_generated/api.js';
4
+ import { api } from '@{{projectName}}/backend/convex/_generated/api';
5
5
  import type { Id } from '@{{projectName}}/backend/convex/_generated/dataModel.js';
6
6
 
7
7
  let newTodoText = $state('');
@@ -9,7 +9,7 @@ const DrawerLayout = () => {
9
9
  <Drawer>
10
10
  <Drawer.Screen
11
11
  name="index"
12
- options={{
12
+ options=\{{
13
13
  headerTitle: "Home",
14
14
  drawerLabel: "Home",
15
15
  drawerIcon: ({ size, color }) => (
@@ -17,9 +17,21 @@ const DrawerLayout = () => {
17
17
  ),
18
18
  }}
19
19
  />
20
+ {{#if (includes examples "todo")}}
21
+ <Drawer.Screen
22
+ name="todos"
23
+ options=\{{
24
+ headerTitle: "Todos",
25
+ drawerLabel: "Todos",
26
+ drawerIcon: ({ size, color }) => (
27
+ <Ionicons name="checkbox-outline" size={size} color={color} />
28
+ ),
29
+ }}
30
+ />
31
+ {{/if}}
20
32
  <Drawer.Screen
21
33
  name="(tabs)"
22
- options={{
34
+ options=\{{
23
35
  headerTitle: "Tabs",
24
36
  drawerLabel: "Tabs",
25
37
  drawerIcon: ({ size, color }) => (
@@ -10,7 +10,7 @@ import { trpc } from "@/utils/trpc";
10
10
  {{/if}}
11
11
  {{#if (eq backend "convex")}}
12
12
  import { useQuery } from "convex/react";
13
- import { api } from "@{{ projectName }}/backend/convex/_generated/api.js";
13
+ import { api } from "@{{ projectName }}/backend/convex/_generated/api";
14
14
  {{/if}}
15
15
 
16
16
  export default function Home() {
@@ -12,7 +12,7 @@ import { trpc } from "@/utils/trpc";
12
12
  {{/if}}
13
13
  {{#if (eq backend "convex")}}
14
14
  import { useQuery } from "convex/react";
15
- import { api } from "@{{ projectName }}/backend/convex/_generated/api.js";
15
+ import { api } from "@{{ projectName }}/backend/convex/_generated/api";
16
16
  {{/if}}
17
17
 
18
18
  export default function Home() {
@@ -1,7 +1,7 @@
1
1
  "use client"
2
2
  {{#if (eq backend "convex")}}
3
3
  import { useQuery } from "convex/react";
4
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
4
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
5
5
  {{else if (or (eq api "orpc") (eq api "trpc"))}}
6
6
  import { useQuery } from "@tanstack/react-query";
7
7
  {{#if (eq api "orpc")}}
@@ -1,7 +1,7 @@
1
1
  import type { Route } from "./+types/_index";
2
2
  {{#if (eq backend "convex")}}
3
3
  import { useQuery } from "convex/react";
4
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
4
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
5
5
  {{else if (or (eq api "orpc") (eq api "trpc"))}}
6
6
  import { useQuery } from "@tanstack/react-query";
7
7
  {{#if (eq api "orpc")}}
@@ -9,7 +9,7 @@ import { useQuery } from "@tanstack/react-query";
9
9
  {{/if}}
10
10
  {{#if (eq backend "convex")}}
11
11
  import { useQuery } from "convex/react";
12
- import { api } from "@{{ projectName }}/backend/convex/_generated/api.js";
12
+ import { api } from "@{{ projectName }}/backend/convex/_generated/api";
13
13
  {{/if}}
14
14
 
15
15
  export const Route = createFileRoute("/")({
@@ -2,7 +2,7 @@ import { createFileRoute } from "@tanstack/react-router";
2
2
  {{#if (eq backend "convex")}}
3
3
  import { convexQuery } from "@convex-dev/react-query";
4
4
  import { useSuspenseQuery } from "@tanstack/react-query";
5
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
5
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
6
6
  {{else if (or (eq api "trpc") (eq api "orpc"))}}
7
7
  import { useQuery } from "@tanstack/react-query";
8
8
  {{#if (eq api "trpc")}}
@@ -1,7 +1,7 @@
1
1
  {{#if (eq backend "convex")}}
2
2
  <script lang="ts">
3
3
  import { useQuery } from 'convex-svelte';
4
- import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
4
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
5
5
 
6
6
  const healthCheck = useQuery(api.healthCheck.get, {});
7
7