Merge pull request #14 from Mechse/client-router 8f8415c0
Select client router: none, reactrouter, tanstack-router
Steve Simkins · 2025-08-09 14:16 39 file(s) · +2554 −204
.github/workflows/test-cli-options.yml +384 −49
2 2
3 3
on:
4 4
  push:
5 -
    branches: [ main ]
5 +
    branches: [main]
6 6
  pull_request:
7 -
    branches: [ main ]
7 +
    branches: [main]
8 8
9 9
jobs:
10 10
  test-cli-options:
11 11
    runs-on: ubuntu-latest
12 -
    
12 +
13 13
    strategy:
14 14
      fail-fast: false
15 15
      matrix:
18 18
          - template: "default"
19 19
            rpc: true
20 20
            tanstackQuery: false
21 +
            router: "none"
21 22
            linter: "eslint"
22 -
            test_name: "Default + RPC + No TanStack Query + ESLint"
23 +
            test_name: "Default + RPC + No TanStack Query + No Router + ESLint"
24 +
          - template: "default"
25 +
            rpc: true
26 +
            tanstackQuery: false
27 +
            router: "none"
28 +
            linter: "biome"
29 +
            test_name: "Default + RPC + No TanStack Query + No Router + Biome"
30 +
          - template: "default"
31 +
            rpc: false
32 +
            tanstackQuery: false
33 +
            router: "none"
34 +
            linter: "eslint"
35 +
            test_name: "Default + No RPC + No TanStack Query + No Router + ESLint"
36 +
          - template: "default"
37 +
            rpc: false
38 +
            tanstackQuery: false
39 +
            router: "none"
40 +
            linter: "biome"
41 +
            test_name: "Default + No RPC + No TanStack Query + No Router + Biome"
42 +
          - template: "default"
43 +
            rpc: true
44 +
            tanstackQuery: true
45 +
            router: "none"
46 +
            linter: "eslint"
47 +
            test_name: "Default + RPC + TanStack Query + No Router + ESLint"
48 +
          - template: "default"
49 +
            rpc: true
50 +
            tanstackQuery: true
51 +
            router: "none"
52 +
            linter: "biome"
53 +
            test_name: "Default + RPC + TanStack Query + No Router + Biome"
54 +
          - template: "default"
55 +
            rpc: false
56 +
            tanstackQuery: true
57 +
            router: "none"
58 +
            linter: "eslint"
59 +
            test_name: "Default + No RPC + TanStack Query + No Router + ESLint"
60 +
          - template: "default"
61 +
            rpc: false
62 +
            tanstackQuery: true
63 +
            router: "none"
64 +
            linter: "biome"
65 +
            test_name: "Default + No RPC + TanStack Query + No Router + Biome"
66 +
67 +
          # Tailwind template combinations
68 +
          - template: "tailwind"
69 +
            rpc: true
70 +
            tanstackQuery: false
71 +
            router: "none"
72 +
            linter: "eslint"
73 +
            test_name: "Tailwind + RPC + No TanStack Query + No Router + ESLint"
74 +
          - template: "tailwind"
75 +
            rpc: true
76 +
            tanstackQuery: false
77 +
            router: "none"
78 +
            linter: "biome"
79 +
            test_name: "Tailwind + RPC + No TanStack Query + No Router + Biome"
80 +
          - template: "tailwind"
81 +
            rpc: false
82 +
            tanstackQuery: false
83 +
            router: "none"
84 +
            linter: "eslint"
85 +
            test_name: "Tailwind + No RPC + No TanStack Query + No Router + ESLint"
86 +
          - template: "tailwind"
87 +
            rpc: false
88 +
            tanstackQuery: false
89 +
            router: "none"
90 +
            linter: "biome"
91 +
            test_name: "Tailwind + No RPC + No TanStack Query + No Router + Biome"
92 +
          - template: "tailwind"
93 +
            rpc: true
94 +
            tanstackQuery: true
95 +
            router: "none"
96 +
            linter: "eslint"
97 +
            test_name: "Tailwind + RPC + TanStack Query + No Router + ESLint"
98 +
          - template: "tailwind"
99 +
            rpc: true
100 +
            tanstackQuery: true
101 +
            router: "none"
102 +
            linter: "biome"
103 +
            test_name: "Tailwind + RPC + TanStack Query + No Router + Biome"
104 +
          - template: "tailwind"
105 +
            rpc: false
106 +
            tanstackQuery: true
107 +
            router: "none"
108 +
            linter: "eslint"
109 +
            test_name: "Tailwind + No RPC + TanStack Query + No Router + ESLint"
110 +
          - template: "tailwind"
111 +
            rpc: false
112 +
            tanstackQuery: true
113 +
            router: "none"
114 +
            linter: "biome"
115 +
            test_name: "Tailwind + No RPC + TanStack Query + No Router + Biome"
116 +
117 +
          # Shadcn template combinations
118 +
          - template: "shadcn"
119 +
            rpc: true
120 +
            tanstackQuery: false
121 +
            router: "none"
122 +
            linter: "eslint"
123 +
            test_name: "Shadcn + RPC + No TanStack Query + No Router + ESLint"
124 +
          - template: "shadcn"
125 +
            rpc: true
126 +
            tanstackQuery: false
127 +
            router: "none"
128 +
            linter: "biome"
129 +
            test_name: "Shadcn + RPC + No TanStack Query + No Router + Biome"
130 +
          - template: "shadcn"
131 +
            rpc: false
132 +
            tanstackQuery: false
133 +
            router: "none"
134 +
            linter: "eslint"
135 +
            test_name: "Shadcn + No RPC + No TanStack Query + No Router + ESLint"
136 +
          - template: "shadcn"
137 +
            rpc: false
138 +
            tanstackQuery: false
139 +
            router: "none"
140 +
            linter: "biome"
141 +
            test_name: "Shadcn + No RPC + No TanStack Query + No Router + Biome"
142 +
          - template: "shadcn"
143 +
            rpc: true
144 +
            tanstackQuery: true
145 +
            router: "none"
146 +
            linter: "eslint"
147 +
            test_name: "Shadcn + RPC + TanStack Query + No Router + ESLint"
148 +
          - template: "shadcn"
149 +
            rpc: true
150 +
            tanstackQuery: true
151 +
            router: "none"
152 +
            linter: "biome"
153 +
            test_name: "Shadcn + RPC + TanStack Query + No Router + Biome"
154 +
          - template: "shadcn"
155 +
            rpc: false
156 +
            tanstackQuery: true
157 +
            router: "none"
158 +
            linter: "eslint"
159 +
            test_name: "Shadcn + No RPC + TanStack Query + No Router + ESLint"
160 +
          - template: "shadcn"
161 +
            rpc: false
162 +
            tanstackQuery: true
163 +
            router: "none"
164 +
            linter: "biome"
165 +
            test_name: "Shadcn + No RPC + TanStack Query + No Router + Biome"
166 +
167 +
          # React Router
168 +
          # Default template combinations
169 +
          - template: "default"
170 +
            rpc: true
171 +
            tanstackQuery: false
172 +
            router: "reactrouter"
173 +
            linter: "eslint"
174 +
            test_name: "Default + RPC + No TanStack Query + React Router + ESLint"
175 +
          - template: "default"
176 +
            rpc: true
177 +
            tanstackQuery: false
178 +
            router: "reactrouter"
179 +
            linter: "biome"
180 +
            test_name: "Default + RPC + No TanStack Query + React Router + Biome"
181 +
          - template: "default"
182 +
            rpc: false
183 +
            tanstackQuery: false
184 +
            router: "reactrouter"
185 +
            linter: "eslint"
186 +
            test_name: "Default + No RPC + No TanStack Query + React Router + ESLint"
187 +
          - template: "default"
188 +
            rpc: false
189 +
            tanstackQuery: false
190 +
            router: "reactrouter"
191 +
            linter: "biome"
192 +
            test_name: "Default + No RPC + No TanStack Query + React Router + Biome"
193 +
          - template: "default"
194 +
            rpc: true
195 +
            tanstackQuery: true
196 +
            router: "reactrouter"
197 +
            linter: "eslint"
198 +
            test_name: "Default + RPC + TanStack Query + React Router + ESLint"
199 +
          - template: "default"
200 +
            rpc: true
201 +
            tanstackQuery: true
202 +
            router: "reactrouter"
203 +
            linter: "biome"
204 +
            test_name: "Default + RPC + TanStack Query + React Router + Biome"
205 +
          - template: "default"
206 +
            rpc: false
207 +
            tanstackQuery: true
208 +
            router: "reactrouter"
209 +
            linter: "eslint"
210 +
            test_name: "Default + No RPC + TanStack Query + React Router + ESLint"
211 +
          - template: "default"
212 +
            rpc: false
213 +
            tanstackQuery: true
214 +
            router: "reactrouter"
215 +
            linter: "biome"
216 +
            test_name: "Default + No RPC + TanStack Query + React Router + Biome"
217 +
218 +
          # Tailwind template combinations
219 +
          - template: "tailwind"
220 +
            rpc: true
221 +
            tanstackQuery: false
222 +
            router: "reactrouter"
223 +
            linter: "eslint"
224 +
            test_name: "Tailwind + RPC + No TanStack Query + React Router + ESLint"
225 +
          - template: "tailwind"
226 +
            rpc: true
227 +
            tanstackQuery: false
228 +
            router: "reactrouter"
229 +
            linter: "biome"
230 +
            test_name: "Tailwind + RPC + No TanStack Query + React Router + Biome"
231 +
          - template: "tailwind"
232 +
            rpc: false
233 +
            tanstackQuery: false
234 +
            router: "reactrouter"
235 +
            linter: "eslint"
236 +
            test_name: "Tailwind + No RPC + No TanStack Query + React Router + ESLint"
237 +
          - template: "tailwind"
238 +
            rpc: false
239 +
            tanstackQuery: false
240 +
            router: "reactrouter"
241 +
            linter: "biome"
242 +
            test_name: "Tailwind + No RPC + No TanStack Query + React Router + Biome"
243 +
          - template: "tailwind"
244 +
            rpc: true
245 +
            tanstackQuery: true
246 +
            router: "reactrouter"
247 +
            linter: "eslint"
248 +
            test_name: "Tailwind + RPC + TanStack Query + React Router + ESLint"
249 +
          - template: "tailwind"
250 +
            rpc: true
251 +
            tanstackQuery: true
252 +
            router: "reactrouter"
253 +
            linter: "biome"
254 +
            test_name: "Tailwind + RPC + TanStack Query + React Router + Biome"
255 +
          - template: "tailwind"
256 +
            rpc: false
257 +
            tanstackQuery: true
258 +
            router: "reactrouter"
259 +
            linter: "eslint"
260 +
            test_name: "Tailwind + No RPC + TanStack Query + React Router + ESLint"
261 +
          - template: "tailwind"
262 +
            rpc: false
263 +
            tanstackQuery: true
264 +
            router: "reactrouter"
265 +
            linter: "biome"
266 +
            test_name: "Tailwind + No RPC + TanStack Query + React Router + Biome"
267 +
268 +
          # Shadcn template combinations
269 +
          - template: "shadcn"
270 +
            rpc: true
271 +
            tanstackQuery: false
272 +
            router: "reactrouter"
273 +
            linter: "eslint"
274 +
            test_name: "Shadcn + RPC + No TanStack Query + React Router + ESLint"
275 +
          - template: "shadcn"
276 +
            rpc: true
277 +
            tanstackQuery: false
278 +
            router: "reactrouter"
279 +
            linter: "biome"
280 +
            test_name: "Shadcn + RPC + No TanStack Query + React Router + Biome"
281 +
          - template: "shadcn"
282 +
            rpc: false
283 +
            tanstackQuery: false
284 +
            router: "reactrouter"
285 +
            linter: "eslint"
286 +
            test_name: "Shadcn + No RPC + No TanStack Query + React Router + ESLint"
287 +
          - template: "shadcn"
288 +
            rpc: false
289 +
            tanstackQuery: false
290 +
            router: "reactrouter"
291 +
            linter: "biome"
292 +
            test_name: "Shadcn + No RPC + No TanStack Query + React Router + Biome"
293 +
          - template: "shadcn"
294 +
            rpc: true
295 +
            tanstackQuery: true
296 +
            router: "reactrouter"
297 +
            linter: "eslint"
298 +
            test_name: "Shadcn + RPC + TanStack Query + React Router + ESLint"
299 +
          - template: "shadcn"
300 +
            rpc: true
301 +
            tanstackQuery: true
302 +
            router: "reactrouter"
303 +
            linter: "biome"
304 +
            test_name: "Shadcn + RPC + TanStack Query + React Router + Biome"
305 +
          - template: "shadcn"
306 +
            rpc: false
307 +
            tanstackQuery: true
308 +
            router: "reactrouter"
309 +
            linter: "eslint"
310 +
            test_name: "Shadcn + No RPC + TanStack Query + React Router + ESLint"
311 +
          - template: "shadcn"
312 +
            rpc: false
313 +
            tanstackQuery: true
314 +
            router: "reactrouter"
315 +
            linter: "biome"
316 +
            test_name: "Shadcn + No RPC + TanStack Query + React Router + Biome"
317 +
318 +
          # TanStack Router
319 +
          # Default template combinations
23 320
          - template: "default"
24 321
            rpc: true
25 322
            tanstackQuery: false
323 +
            router: "tanstackrouter"
324 +
            linter: "eslint"
325 +
            test_name: "Default + RPC + No TanStack Query + TanStack Router + ESLint"
326 +
          - template: "default"
327 +
            rpc: true
328 +
            tanstackQuery: false
329 +
            router: "tanstackrouter"
26 330
            linter: "biome"
27 -
            test_name: "Default + RPC + No TanStack Query + Biome"
331 +
            test_name: "Default + RPC + No TanStack Query + TanStack Router + Biome"
28 332
          - template: "default"
29 333
            rpc: false
30 334
            tanstackQuery: false
335 +
            router: "tanstackrouter"
31 336
            linter: "eslint"
32 -
            test_name: "Default + No RPC + No TanStack Query + ESLint"
337 +
            test_name: "Default + No RPC + No TanStack Query + TanStack Router + ESLint"
33 338
          - template: "default"
34 339
            rpc: false
35 340
            tanstackQuery: false
341 +
            router: "tanstackrouter"
36 342
            linter: "biome"
37 -
            test_name: "Default + No RPC + No TanStack Query + Biome"
343 +
            test_name: "Default + No RPC + No TanStack Query + TanStack Router + Biome"
38 344
          - template: "default"
39 345
            rpc: true
40 346
            tanstackQuery: true
347 +
            router: "tanstackrouter"
41 348
            linter: "eslint"
42 -
            test_name: "Default + RPC + TanStack Query + ESLint"
349 +
            test_name: "Default + RPC + TanStack Query + TanStack Router + ESLint"
43 350
          - template: "default"
44 351
            rpc: true
45 352
            tanstackQuery: true
353 +
            router: "tanstackrouter"
46 354
            linter: "biome"
47 -
            test_name: "Default + RPC + TanStack Query + Biome"
355 +
            test_name: "Default + RPC + TanStack Query + TanStack Router + Biome"
48 356
          - template: "default"
49 357
            rpc: false
50 358
            tanstackQuery: true
359 +
            router: "tanstackrouter"
51 360
            linter: "eslint"
52 -
            test_name: "Default + No RPC + TanStack Query + ESLint"
361 +
            test_name: "Default + No RPC + TanStack Query + TanStack Router + ESLint"
53 362
          - template: "default"
54 363
            rpc: false
55 364
            tanstackQuery: true
365 +
            router: "tanstackrouter"
56 366
            linter: "biome"
57 -
            test_name: "Default + No RPC + TanStack Query + Biome"
58 -
          
367 +
            test_name: "Default + No RPC + TanStack Query + TanStack Router + Biome"
368 +
59 369
          # Tailwind template combinations
60 370
          - template: "tailwind"
61 371
            rpc: true
62 372
            tanstackQuery: false
373 +
            router: "tanstackrouter"
63 374
            linter: "eslint"
64 -
            test_name: "Tailwind + RPC + No TanStack Query + ESLint"
375 +
            test_name: "Tailwind + RPC + No TanStack Query + TanStack Router + ESLint"
65 376
          - template: "tailwind"
66 377
            rpc: true
67 378
            tanstackQuery: false
379 +
            router: "tanstackrouter"
68 380
            linter: "biome"
69 -
            test_name: "Tailwind + RPC + No TanStack Query + Biome"
381 +
            test_name: "Tailwind + RPC + No TanStack Query + TanStack Router + Biome"
70 382
          - template: "tailwind"
71 383
            rpc: false
72 384
            tanstackQuery: false
385 +
            router: "tanstackrouter"
73 386
            linter: "eslint"
74 -
            test_name: "Tailwind + No RPC + No TanStack Query + ESLint"
387 +
            test_name: "Tailwind + No RPC + No TanStack Query + TanStack Router + ESLint"
75 388
          - template: "tailwind"
76 389
            rpc: false
77 390
            tanstackQuery: false
391 +
            router: "tanstackrouter"
78 392
            linter: "biome"
79 -
            test_name: "Tailwind + No RPC + No TanStack Query + Biome"
393 +
            test_name: "Tailwind + No RPC + No TanStack Query + TanStack Router + Biome"
80 394
          - template: "tailwind"
81 395
            rpc: true
82 396
            tanstackQuery: true
397 +
            router: "tanstackrouter"
83 398
            linter: "eslint"
84 -
            test_name: "Tailwind + RPC + TanStack Query + ESLint"
399 +
            test_name: "Tailwind + RPC + TanStack Query + TanStack Router + ESLint"
85 400
          - template: "tailwind"
86 401
            rpc: true
87 402
            tanstackQuery: true
403 +
            router: "tanstackrouter"
88 404
            linter: "biome"
89 -
            test_name: "Tailwind + RPC + TanStack Query + Biome"
405 +
            test_name: "Tailwind + RPC + TanStack Query + TanStack Router + Biome"
90 406
          - template: "tailwind"
91 407
            rpc: false
92 408
            tanstackQuery: true
409 +
            router: "tanstackrouter"
93 410
            linter: "eslint"
94 -
            test_name: "Tailwind + No RPC + TanStack Query + ESLint"
411 +
            test_name: "Tailwind + No RPC + TanStack Query + TanStack Router + ESLint"
95 412
          - template: "tailwind"
96 413
            rpc: false
97 414
            tanstackQuery: true
415 +
            router: "tanstackrouter"
98 416
            linter: "biome"
99 -
            test_name: "Tailwind + No RPC + TanStack Query + Biome"
417 +
            test_name: "Tailwind + No RPC + TanStack Query + TanStack Router + Biome"
100 418
101 419
          # Shadcn template combinations
102 420
          - template: "shadcn"
103 421
            rpc: true
104 422
            tanstackQuery: false
423 +
            router: "tanstackrouter"
105 424
            linter: "eslint"
106 -
            test_name: "Shadcn + RPC + No TanStack Query + ESLint"
425 +
            test_name: "Shadcn + RPC + No TanStack Query + TanStack Router + ESLint"
107 426
          - template: "shadcn"
108 427
            rpc: true
109 428
            tanstackQuery: false
429 +
            router: "tanstackrouter"
110 430
            linter: "biome"
111 -
            test_name: "Shadcn + RPC + No TanStack Query + Biome"
431 +
            test_name: "Shadcn + RPC + No TanStack Query + TanStack Router + Biome"
112 432
          - template: "shadcn"
113 433
            rpc: false
114 434
            tanstackQuery: false
435 +
            router: "tanstackrouter"
115 436
            linter: "eslint"
116 -
            test_name: "Shadcn + No RPC + No TanStack Query + ESLint"
437 +
            test_name: "Shadcn + No RPC + No TanStack Query + TanStack Router + ESLint"
117 438
          - template: "shadcn"
118 439
            rpc: false
119 440
            tanstackQuery: false
441 +
            router: "tanstackrouter"
120 442
            linter: "biome"
121 -
            test_name: "Shadcn + No RPC + No TanStack Query + Biome"
443 +
            test_name: "Shadcn + No RPC + No TanStack Query + TanStack Router + Biome"
122 444
          - template: "shadcn"
123 445
            rpc: true
124 446
            tanstackQuery: true
447 +
            router: "tanstackrouter"
125 448
            linter: "eslint"
126 -
            test_name: "Shadcn + RPC + TanStack Query + ESLint"
449 +
            test_name: "Shadcn + RPC + TanStack Query + TanStack Router + ESLint"
127 450
          - template: "shadcn"
128 451
            rpc: true
129 452
            tanstackQuery: true
453 +
            router: "tanstackrouter"
130 454
            linter: "biome"
131 -
            test_name: "Shadcn + RPC + TanStack Query + Biome"
455 +
            test_name: "Shadcn + RPC + TanStack Query + TanStack Router + Biome"
132 456
          - template: "shadcn"
133 457
            rpc: false
134 458
            tanstackQuery: true
459 +
            router: "tanstackrouter"
135 460
            linter: "eslint"
136 -
            test_name: "Shadcn + No RPC + TanStack Query + ESLint"
461 +
            test_name: "Shadcn + No RPC + TanStack Query + TanStack Router + ESLint"
137 462
          - template: "shadcn"
138 463
            rpc: false
139 464
            tanstackQuery: true
465 +
            router: "tanstackrouter"
140 466
            linter: "biome"
141 -
            test_name: "Shadcn + No RPC + TanStack Query + Biome"
467 +
            test_name: "Shadcn + No RPC + TanStack Query + TanStack Router + Biome"
142 468
143 469
    steps:
144 470
      - name: Checkout repository
161 487
          echo "Creating project with options:"
162 488
          echo "Template: ${{ matrix.template }}"
163 489
          echo "RPC: ${{ matrix.rpc }}"
490 +
          echo "TanStack Query: ${{ matrix.tanstackQuery }}"
491 +
          echo "Router: ${{ matrix.router }}"
164 492
          echo "Linter: ${{ matrix.linter }}"
493 +
494 +
          # Build the command with conditional flags
495 +
          cmd="./dist/index.js test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.tanstackQuery }}-${{ matrix.router }}-${{ matrix.linter }} --yes --template ${{ matrix.template }}"
165 496
          
166 497
          if [ "${{ matrix.rpc }}" = "true" ]; then
167 -
            ./dist/index.js test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }} \
168 -
              --yes \
169 -
              --template ${{ matrix.template }} \
170 -
              --rpc \
171 -
              --linter ${{ matrix.linter }}
172 -
          else
173 -
            ./dist/index.js test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }} \
174 -
              --yes \
175 -
              --template ${{ matrix.template }} \
176 -
              --linter ${{ matrix.linter }}
498 +
            cmd="$cmd --rpc"
499 +
          fi
500 +
          
501 +
          if [ "${{ matrix.tanstackQuery }}" = "true" ]; then
502 +
            cmd="$cmd --tsquery"
503 +
          fi
504 +
          
505 +
          if [ "${{ matrix.router }}" != "none" ]; then
506 +
            cmd="$cmd --router ${{ matrix.router }}"
177 507
          fi
508 +
          
509 +
          cmd="$cmd --linter ${{ matrix.linter }}"
510 +
          
511 +
          echo "Running: $cmd"
512 +
          eval $cmd
178 513
179 514
      - name: Install project dependencies
180 515
        run: |
181 -
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }}
516 +
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.tanstackQuery }}-${{ matrix.router }}-${{ matrix.linter }}
182 517
          bun install
183 518
184 519
      - name: Build test project
185 520
        run: |
186 -
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }}
521 +
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.tanstackQuery }}-${{ matrix.router }}-${{ matrix.linter }}
187 522
          bun run build
188 523
189 524
      - name: Verify build outputs
190 525
        run: |
191 -
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }}
192 -
          
526 +
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.tanstackQuery }}-${{ matrix.router }}-${{ matrix.linter }}
527 +
193 528
          # Check that dist directories exist
194 529
          if [ ! -d "client/dist" ]; then
195 530
            echo "❌ Client build failed - dist directory not found"
196 531
            exit 1
197 532
          fi
198 -
          
533 +
199 534
          if [ ! -d "server/dist" ]; then
200 535
            echo "❌ Server build failed - dist directory not found"
201 536
            exit 1
202 537
          fi
203 -
          
538 +
204 539
          # Check for expected files
205 540
          if [ ! -f "client/dist/index.html" ]; then
206 541
            echo "❌ Client build incomplete - index.html not found"
207 542
            exit 1
208 543
          fi
209 -
          
544 +
210 545
          if [ ! -f "server/dist/index.js" ]; then
211 546
            echo "❌ Server build incomplete - index.js not found"
212 547
            exit 1
213 548
          fi
214 -
          
549 +
215 550
          echo "✅ Build verification passed for ${{ matrix.test_name }}"
216 551
217 552
      - name: Run linter on generated project
218 553
        run: |
219 -
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }}
220 -
          
554 +
          cd test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.tanstackQuery }}-${{ matrix.router }}-${{ matrix.linter }}
555 +
221 556
          if [ "${{ matrix.linter }}" = "eslint" ]; then
222 557
            # Check if ESLint config exists and run it
223 558
            if [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
235 570
      - name: Cleanup test project
236 571
        if: always()
237 572
        run: |
238 -
          rm -rf test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.linter }}
573 +
          rm -rf test-project-${{ matrix.template }}-${{ matrix.rpc }}-${{ matrix.tanstackQuery }}-${{ matrix.router }}-${{ matrix.linter }}
scripts/check-template-combinations.ts +93 −154
6 6
import path from "node:path";
7 7
8 8
// Define the possible boolean options from ProjectOptions type
9 -
const BOOLEAN_OPTIONS = ["tailwind", "shadcn", "rpc", "tanstackQuery"] as const;
9 +
const BOOLEAN_OPTIONS = [
10 +
	"tailwind",
11 +
	"shadcn",
12 +
	"rpc",
13 +
	"tanstackQuery",
14 +
	"reactRouter",
15 +
	"tanstackRouter",
16 +
] as const;
17 +
18 +
// Package dependency rules
19 +
const PACKAGE_DEPENDENCIES: Record<string, string[]> = {
20 +
	shadcn: ["tailwind"], // shadcn requires tailwind
21 +
	// Add more dependencies here as needed
22 +
	// example: somePackage: ["requiredPackage1", "requiredPackage2"]
23 +
};
24 +
25 +
// Mutually exclusive groups (only one option from each group can be selected)
26 +
const MUTUALLY_EXCLUSIVE_GROUPS: string[][] = [
27 +
	// Add mutually exclusive groups here as needed
28 +
	// example: ["option1", "option2", "option3"]
29 +
	["reactRouter", "tanstackRouter"],
30 +
];
31 +
32 +
// Check if a combination is valid based on dependencies and mutual exclusivity
33 +
function isValidCombination(combination: Record<string, boolean>): boolean {
34 +
	// Skip combinations with no packages selected
35 +
	const hasAnyPackage = Object.values(combination).some((value) => value);
36 +
	if (!hasAnyPackage) {
37 +
		return false;
38 +
	}
39 +
40 +
	// Skip combinations that only contain shadcn and/or tailwind (they're cloned from repo)
41 +
	const enabledPackages = Object.keys(combination).filter(
42 +
		(key) => combination[key],
43 +
	);
44 +
	const onlyShadcnTailwind = enabledPackages.every(
45 +
		(pkg) => pkg === "shadcn" || pkg === "tailwind",
46 +
	);
47 +
	if (onlyShadcnTailwind) {
48 +
		return false;
49 +
	}
50 +
51 +
	// Check package dependencies
52 +
	for (const [packageName, dependencies] of Object.entries(
53 +
		PACKAGE_DEPENDENCIES,
54 +
	)) {
55 +
		if (combination[packageName]) {
56 +
			// If this package is enabled, all its dependencies must also be enabled
57 +
			for (const dependency of dependencies) {
58 +
				if (!combination[dependency]) {
59 +
					return false;
60 +
				}
61 +
			}
62 +
		}
63 +
	}
64 +
65 +
	// Check mutual exclusivity
66 +
	for (const group of MUTUALLY_EXCLUSIVE_GROUPS) {
67 +
		const selectedInGroup = group.filter((option) => combination[option]);
68 +
		if (selectedInGroup.length > 1) {
69 +
			return false; // More than one option selected in mutually exclusive group
70 +
		}
71 +
	}
72 +
73 +
	return true;
74 +
}
75 +
76 +
// Generate all possible combinations of boolean options with filtering
77 +
function generateAllCombinations(
78 +
	options: readonly string[],
79 +
): Array<Record<string, boolean>> {
80 +
	const combinations: Array<Record<string, boolean>> = [];
81 +
	const totalCombinations = Math.pow(2, options.length);
82 +
83 +
	for (let i = 0; i < totalCombinations; i++) {
84 +
		const combination: Record<string, boolean> = {};
85 +
		for (let j = 0; j < options.length; j++) {
86 +
			combination[options[j]] = Boolean(i & (1 << j));
87 +
		}
88 +
89 +
		// Only include valid combinations
90 +
		if (isValidCombination(combination)) {
91 +
			combinations.push(combination);
92 +
		}
93 +
	}
94 +
95 +
	return combinations;
96 +
}
10 97
11 98
// Simulate nameGenerator function locally
12 99
const nameGenerator = (
30 117
	}
31 118
	return basename;
32 119
};
33 -
34 -
// Generate all possible combinations of boolean options
35 -
function generateAllCombinations(
36 -
	options: readonly string[],
37 -
): Array<Record<string, boolean>> {
38 -
	const combinations: Array<Record<string, boolean>> = [];
39 -
	const totalCombinations = Math.pow(2, options.length);
40 -
41 -
	for (let i = 0; i < totalCombinations; i++) {
42 -
		const combination: Record<string, boolean> = {};
43 -
		for (let j = 0; j < options.length; j++) {
44 -
			combination[options[j]] = Boolean(i & (1 << j));
45 -
		}
46 -
		combinations.push(combination);
47 -
	}
48 -
49 -
	return combinations;
50 -
}
51 120
52 121
// Parse installer files to find nameGenerator calls and hardcoded template patterns
53 122
// Recursively find all .ts files in installers directory
270 339
271 340
	let result = filename + "-with";
272 341
273 -
	// Hardcoded follows specific order: tailwind, shadcn, rpc
342 +
	// Hardcoded follows specific order: tailwind, shadcn, rpc, tanstackQuery, then routers
274 343
	if (possibleOptions.tailwind) result += "-tailwind";
275 344
	if (possibleOptions.shadcn) result += "-shadcn";
276 345
	if (possibleOptions.rpc) result += "-rpc";
346 +
	if (possibleOptions.tanstackQuery) result += "-tanstackquery";
347 +
	if (possibleOptions.reactRouter) result += "-reactrouter";
277 348
278 349
	return extension ? `${result}.${extension}` : result;
279 350
};
280 351
281 352
// Check if template files exist for all combinations
282 353
async function checkTemplateFiles() {
283 -
	console.log("🔍 Analyzing template patterns in installers...\n");
284 -
285 354
	const templateCalls = await parseInstallerFiles();
286 355
287 356
	if (templateCalls.length === 0) {
288 -
		console.log("❌ No template patterns found in installer files!");
289 357
		return;
290 358
	}
291 -
292 -
	const extrasDir = path.resolve("src/templates/extras");
293 -
294 -
	for (const call of templateCalls) {
295 -
		console.log(`📁 Analyzing: ${call.file} (${call.type})`);
296 -
		console.log(`   Basename: ${call.basename}`);
297 -
		console.log(`   Used Options: [${call.usedOptions.join(", ")}]`);
298 -
		console.log(`   Template Path: ${call.templatePath}`);
299 -
		console.log("");
300 -
301 -
		// Generate all possible combinations for the used options
302 -
		const allCombinations = generateAllCombinations(call.usedOptions);
303 -
		const templateDir = path.join(
304 -
			extrasDir,
305 -
			call.templatePath.replace(/["']/g, ""),
306 -
			call.basename,
307 -
		);
308 -
309 -
		console.log(
310 -
			`   📋 All possible template files for ${call.basename} (${call.type}):`,
311 -
		);
312 -
313 -
		let foundCount = 0;
314 -
		let missingCount = 0;
315 -
316 -
		for (const combination of allCombinations) {
317 -
			const templateName =
318 -
				call.type === "nameGenerator"
319 -
					? nameGenerator(call.basename, combination)
320 -
					: hardcodedGenerator(call.basename, combination);
321 -
			const fullTemplatePath = path.join(templateDir, templateName);
322 -
323 -
			const exists = await fs.pathExists(fullTemplatePath);
324 -
			const status = exists ? "✅" : "❌";
325 -
326 -
			if (exists) {
327 -
				foundCount++;
328 -
			} else {
329 -
				missingCount++;
330 -
			}
331 -
332 -
			// Show combination details
333 -
			const enabledOptions = Object.keys(combination)
334 -
				.filter((key) => combination[key])
335 -
				.join(", ");
336 -
337 -
			console.log(
338 -
				`     ${status} ${templateName} ${enabledOptions ? `(${enabledOptions})` : "(no options)"}`,
339 -
			);
340 -
341 -
			if (!exists) {
342 -
				console.log(`        Missing: ${fullTemplatePath}`);
343 -
			}
344 -
		}
345 -
346 -
		console.log("");
347 -
		console.log(`   📊 Summary: ${foundCount} found, ${missingCount} missing`);
348 -
		console.log("   " + "=".repeat(50));
349 -
		console.log("");
350 -
	}
351 -
352 -
	// Overall statistics
353 -
	console.log("🎯 Overall Analysis Complete!");
354 -
	console.log(
355 -
		`   Found ${templateCalls.length} template patterns in installers`,
356 -
	);
357 -
	console.log(
358 -
		`   - ${templateCalls.filter((c) => c.type === "nameGenerator").length} nameGenerator calls`,
359 -
	);
360 -
	console.log(
361 -
		`   - ${templateCalls.filter((c) => c.type === "hardcoded").length} hardcoded template patterns`,
362 -
	);
363 -
364 -
	// Consistency analysis
365 -
	console.log("");
366 -
	console.log("!  CONSISTENCY ISSUES DETECTED:");
367 -
	console.log(
368 -
		"   The RPC installer uses hardcoded template names with order: tailwind-shadcn-rpc",
369 -
	);
370 -
	console.log(
371 -
		"   The TanStack Query installer uses nameGenerator with alphabetical order: rpc-shadcn-tailwind-tanstackquery",
372 -
	);
373 -
	console.log("   But the actual template files follow the hardcoded pattern!");
374 -
	console.log("");
375 -
	console.log("💡 RECOMMENDATIONS:");
376 -
	console.log(
377 -
		"   1. Standardize on nameGenerator for consistency across all installers",
378 -
	);
379 -
	console.log(
380 -
		"   2. OR rename template files to match nameGenerator's alphabetical sorting",
381 -
	);
382 -
	console.log(
383 -
		"   3. OR update nameGenerator to use the same order as hardcoded pattern",
384 -
	);
385 -
	console.log("");
386 -
	console.log("🔍 MISSING TEMPLATE FILES:");
387 -
	const totalMissing = templateCalls.reduce((acc, call) => {
388 -
		const allCombinations = generateAllCombinations(call.usedOptions);
389 -
		return (
390 -
			acc +
391 -
			allCombinations.filter((combo) => {
392 -
				const templateName =
393 -
					call.type === "nameGenerator"
394 -
						? nameGenerator(call.basename, combo)
395 -
						: hardcodedGenerator(call.basename, combo);
396 -
				const templateDir = path.join(
397 -
					path.resolve("src/templates/extras"),
398 -
					call.templatePath.replace(/["']/g, ""),
399 -
					call.basename,
400 -
				);
401 -
				const fullTemplatePath = path.join(templateDir, templateName);
402 -
				return !require("fs-extra").pathExistsSync(fullTemplatePath);
403 -
			}).length
404 -
		);
405 -
	}, 0);
406 -
	console.log(`   Total missing template files: ${totalMissing}`);
407 -
408 -
	console.log("");
409 -
	console.log("🛠  COMMANDS TO CREATE MISSING FILES:");
410 359
411 360
	for (const call of templateCalls) {
412 361
		const allCombinations = generateAllCombinations(call.usedOptions);
415 364
			call.templatePath.replace(/["']/g, ""),
416 365
			call.basename,
417 366
		);
418 -
419 -
		const missingFiles: string[] = [];
420 367
421 368
		for (const combination of allCombinations) {
422 369
			const templateName =
425 372
					: hardcodedGenerator(call.basename, combination);
426 373
			const fullTemplatePath = path.join(templateDir, templateName);
427 374
428 -
			const exists = require("fs-extra").pathExistsSync(fullTemplatePath);
375 +
			const exists = await fs.pathExists(fullTemplatePath);
429 376
			if (!exists) {
430 -
				missingFiles.push(`touch "${fullTemplatePath}"`);
377 +
				console.log(`touch "${fullTemplatePath}"`);
431 378
			}
432 -
		}
433 -
434 -
		if (missingFiles.length > 0) {
435 -
			console.log(`   # Missing files for ${call.basename} (${call.type}):`);
436 -
			for (const cmd of missingFiles) {
437 -
				console.log(`   ${cmd}`);
438 -
			}
439 -
			console.log("");
440 379
		}
441 380
	}
442 381
}
src/index.ts +4 −0
28 28
		"--tsquery",
29 29
		"use TanStack Query for data fetching and state management",
30 30
	)
31 +
	.option(
32 +
		"--router <router>",
33 +
		"specify a client router (none, reactrouter, tanstackrouter)",
34 +
	)
31 35
	.option("--linter <linter>", "specify the linter to use (eslint or biome)")
32 36
	.action(create);
33 37
src/installers/react-router.ts (added) +78 −0
1 +
import path from "node:path";
2 +
import fs from "fs-extra";
3 +
import type { ProjectOptions } from "@/types";
4 +
import yoctoSpinner from "yocto-spinner";
5 +
import pc from "picocolors";
6 +
import { consola } from "consola";
7 +
import { addPackageDependency } from "@/utils/add-package-dependency";
8 +
import { EXTRAS_DIR } from "@/utils";
9 +
import { nameGenerator } from "@/utils/name-generator";
10 +
11 +
export const reactRouterInstaller = async (
12 +
	options: Required<ProjectOptions>,
13 +
): Promise<boolean> => {
14 +
	const spinner = yoctoSpinner({
15 +
		text: "Setting up React Router...",
16 +
	}).start();
17 +
18 +
	try {
19 +
		const { projectName, rpc, shadcn, tailwind, tanstackQuery } = options;
20 +
21 +
		const projectPath = path.resolve(process.cwd(), projectName);
22 +
		spinner.text = "Installing React Router...";
23 +
		await addPackageDependency({
24 +
			dependencies: ["react-router"],
25 +
			target: "client",
26 +
			projectName,
27 +
		});
28 +
29 +
		const appTsxTemplate = nameGenerator("App.tsx", {
30 +
			reactRouter: true,
31 +
		});
32 +
33 +
		const appTsxSrc = path.join(
34 +
			EXTRAS_DIR,
35 +
			"client",
36 +
			"src",
37 +
			"App.tsx",
38 +
			appTsxTemplate,
39 +
		);
40 +
		const appTsxTarget = path.join(projectPath, "client", "src", "App.tsx");
41 +
		fs.copySync(appTsxSrc, appTsxTarget);
42 +
43 +
		const homeTsxTemplate = nameGenerator("Home.tsx", {
44 +
			rpc,
45 +
			shadcn,
46 +
			tailwind,
47 +
			tanstackQuery,
48 +
		});
49 +
50 +
		const homeTsxSrc = path.join(
51 +
			EXTRAS_DIR,
52 +
			"client",
53 +
			"src",
54 +
			"components",
55 +
			"Home.tsx",
56 +
			homeTsxTemplate,
57 +
		);
58 +
		const homeTsxTarget = path.join(
59 +
			projectPath,
60 +
			"client",
61 +
			"src",
62 +
			"components",
63 +
			"Home.tsx",
64 +
		);
65 +
		fs.copySync(homeTsxSrc, homeTsxTarget);
66 +
67 +
		spinner.success("React Router setup completed");
68 +
		return true;
69 +
	} catch (err: unknown) {
70 +
		spinner.error("Failed to set up React Router");
71 +
		if (err instanceof Error) {
72 +
			consola.error(pc.red("Error:"), err.message);
73 +
		} else {
74 +
			consola.error(pc.red("Error: Unknown error"));
75 +
		}
76 +
		return false;
77 +
	}
78 +
};
src/installers/tanstack-router.ts (added) +125 −0
1 +
import path from "node:path";
2 +
import fs from "fs-extra";
3 +
import type { ProjectOptions } from "@/types";
4 +
import yoctoSpinner from "yocto-spinner";
5 +
import pc from "picocolors";
6 +
import { consola } from "consola";
7 +
import { addPackageDependency } from "@/utils/add-package-dependency";
8 +
import { EXTRAS_DIR } from "@/utils";
9 +
import { nameGenerator } from "@/utils/name-generator";
10 +
import { execa } from "execa";
11 +
12 +
export const tanstackRouterInstaller = async (
13 +
	options: Required<ProjectOptions>,
14 +
): Promise<boolean> => {
15 +
	const spinner = yoctoSpinner({
16 +
		text: "Setting up TanStack Router...",
17 +
	}).start();
18 +
19 +
	try {
20 +
		const { projectName, rpc, shadcn, tailwind, tanstackQuery } = options;
21 +
22 +
		const projectPath = path.resolve(process.cwd(), projectName);
23 +
		spinner.text = "Installing TanStack Router...";
24 +
		await addPackageDependency({
25 +
			dependencies: [
26 +
				"@tanstack/react-router",
27 +
				"@tanstack/react-router-devtools",
28 +
			],
29 +
			target: "client",
30 +
			projectName,
31 +
		});
32 +
33 +
		await addPackageDependency({
34 +
			dependencies: ["@tanstack/router-plugin"],
35 +
			devMode: true,
36 +
			target: "client",
37 +
			projectName,
38 +
		});
39 +
40 +
		const viteConfigTemplate = nameGenerator("vite.config.ts", {
41 +
			tailwind,
42 +
			shadcn,
43 +
			tanstackRouter: true,
44 +
		});
45 +
		const viteConfigSrc = path.join(
46 +
			EXTRAS_DIR,
47 +
			"client",
48 +
			"vite.config.ts",
49 +
			viteConfigTemplate,
50 +
		);
51 +
		const viteConfigTarget = path.join(projectPath, "client", "vite.config.ts");
52 +
		fs.copySync(viteConfigSrc, viteConfigTarget);
53 +
54 +
		const rootTsxSrc = path.join(
55 +
			EXTRAS_DIR,
56 +
			"client",
57 +
			"src",
58 +
			"routes",
59 +
			"__root.tsx",
60 +
		);
61 +
		const rootTsxTarget = path.join(
62 +
			projectPath,
63 +
			"client",
64 +
			"src",
65 +
			"routes",
66 +
			"__root.tsx",
67 +
		);
68 +
		fs.copySync(rootTsxSrc, rootTsxTarget);
69 +
70 +
		const indexTsxSrc = path.join(
71 +
			EXTRAS_DIR,
72 +
			"client",
73 +
			"src",
74 +
			"routes",
75 +
			"index.tsx",
76 +
			nameGenerator("index.tsx", { tanstackQuery, tailwind, shadcn, rpc }),
77 +
		);
78 +
		const indexTsxTarget = path.join(
79 +
			projectPath,
80 +
			"client",
81 +
			"src",
82 +
			"routes",
83 +
			"index.tsx",
84 +
		);
85 +
		fs.copySync(indexTsxSrc, indexTsxTarget);
86 +
87 +
		const mainTsxSrc = path.join(
88 +
			EXTRAS_DIR,
89 +
			"client",
90 +
			"src",
91 +
			"main.tsx",
92 +
			nameGenerator("main.tsx", { tanstackQuery, tanstackRouter: true }),
93 +
		);
94 +
		const mainTsxTarget = path.join(projectPath, "client", "src", "main.tsx");
95 +
		fs.copySync(mainTsxSrc, mainTsxTarget);
96 +
97 +
		const appTsxTarget = path.join(projectPath, "client", "src", "App.tsx");
98 +
		fs.remove(appTsxTarget);
99 +
100 +
		spinner.text = "Generating TanStack Route Tree...";
101 +
102 +
		// await execa("vite", ["--config", "vite.config.ts", "--force"], {
103 +
		// 	cwd: path.join(projectPath, "client"),
104 +
		// });
105 +
		//
106 +
		await execa("bunx", ["vite", "build"], {
107 +
			cwd: path.join(projectPath, "client"),
108 +
		});
109 +
110 +
		await execa("tsc", ["-b"], {
111 +
			cwd: path.join(projectPath, "client"),
112 +
		});
113 +
114 +
		spinner.success("TanStack Router setup completed");
115 +
		return true;
116 +
	} catch (err: unknown) {
117 +
		spinner.error("Failed to set up TanStack Router");
118 +
		if (err instanceof Error) {
119 +
			consola.error(pc.red("Error:"), err.message);
120 +
		} else {
121 +
			consola.error(pc.red("Error: Unknown error"));
122 +
		}
123 +
		return false;
124 +
	}
125 +
};
src/lib/install-packages.ts +16 −1
3 3
import path from "node:path";
4 4
import { tanstackQueryInstaller } from "@/installers/tanstack-query";
5 5
import { rpcInstaller } from "@/installers/rpc";
6 +
import { reactRouterInstaller } from "@/installers/react-router";
7 +
import { tanstackRouterInstaller } from "@/installers/tanstack-router";
6 8
7 9
export async function installPackages(
8 10
	options: Required<ProjectOptions>,
9 11
): Promise<boolean> {
10 -
	const { projectName, rpc, linter, tanstackQuery } = options;
12 +
	const { projectName, rpc, router, linter, tanstackQuery } = options;
11 13
12 14
	const projectPath = path.resolve(process.cwd(), projectName);
13 15
21 23
22 24
	if (tanstackQuery) {
23 25
		await tanstackQueryInstaller(options);
26 +
	}
27 +
28 +
	if (router !== "none") {
29 +
		switch (router) {
30 +
			case "reactrouter": {
31 +
				await reactRouterInstaller(options);
32 +
				break;
33 +
			}
34 +
			case "tanstackrouter": {
35 +
				await tanstackRouterInstaller(options);
36 +
				break;
37 +
			}
38 +
		}
24 39
	}
25 40
26 41
	return false;
src/lib/prompt-for-options.ts +25 −0
93 93
		linter = linterResponse as "eslint" | "biome";
94 94
	}
95 95
96 +
	let router = options.router;
97 +
98 +
	if (!options.yes && !options.router) {
99 +
		const { data: routerResponse, error } = await tryCatch(
100 +
			consola.prompt("Select a client router:", {
101 +
				type: "select",
102 +
				options: [
103 +
					{ label: "None (default)", value: "none" },
104 +
					{ label: "React Router", value: "reactrouter" },
105 +
					{ label: "TanStack Router", value: "tanstackrouter" },
106 +
				],
107 +
				initial: "none",
108 +
				cancel: "reject",
109 +
			}),
110 +
		);
111 +
112 +
		if (error) {
113 +
			console.log(pc.yellow("Project creation cancelled."));
114 +
			process.exit(1);
115 +
		}
116 +
117 +
		router = routerResponse as "none" | "reactrouter" | "tanstackrouter";
118 +
	}
119 +
96 120
	let useTanstackQuery = options.tanstackQuery;
97 121
98 122
	if (!options.yes && !options.tanstackQuery) {
122 146
		shadcn: templateChoice === "shadcn",
123 147
		rpc: useRpc,
124 148
		linter,
149 +
		router,
125 150
		tanstackQuery: useTanstackQuery,
126 151
	};
127 152
}
src/templates/extras/client/src/App.tsx/App-with-reactrouter.tsx (added) +14 −0
1 +
import { BrowserRouter, Routes, Route } from "react-router";
2 +
import Home from "./components/Home";
3 +
4 +
function App() {
5 +
	return (
6 +
		<BrowserRouter>
7 +
			<Routes>
8 +
				<Route path="/" element={<Home />} />
9 +
			</Routes>
10 +
		</BrowserRouter>
11 +
	);
12 +
}
13 +
14 +
export default App;
src/templates/extras/client/src/components/Home.tsx/Home-with-rpc-shadcn-tailwind-tanstackquery.tsx (added) +70 −0
1 +
import { useState } from "react";
2 +
import beaver from "@/assets/beaver.svg";
3 +
import { Button } from "@/components/ui/button";
4 +
import { hcWithType } from "server/dist/client";
5 +
import { useMutation } from "@tanstack/react-query";
6 +
7 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
8 +
9 +
const client = hcWithType(SERVER_URL);
10 +
11 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
12 +
13 +
function Home() {
14 +
	const [data, setData] = useState<
15 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
16 +
	>();
17 +
18 +
	const { mutate: sendRequest } = useMutation({
19 +
		mutationFn: async () => {
20 +
			try {
21 +
				const res = await client.hello.$get();
22 +
				if (!res.ok) {
23 +
					console.log("Error fetching data");
24 +
					return;
25 +
				}
26 +
				const data = await res.json();
27 +
				setData(data);
28 +
			} catch (error) {
29 +
				console.log(error);
30 +
			}
31 +
		},
32 +
	});
33 +
34 +
	return (
35 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
36 +
			<a
37 +
				href="https://github.com/stevedylandev/bhvr"
38 +
				target="_blank"
39 +
				rel="noopener"
40 +
			>
41 +
				<img
42 +
					src={beaver}
43 +
					className="w-16 h-16 cursor-pointer"
44 +
					alt="beaver logo"
45 +
				/>
46 +
			</a>
47 +
			<h1 className="text-5xl font-black">bhvr</h1>
48 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
49 +
			<p>A typesafe fullstack monorepo</p>
50 +
			<div className="flex items-center gap-4">
51 +
				<Button onClick={() => sendRequest()}>Call API</Button>
52 +
				<Button variant="secondary" asChild>
53 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
54 +
						Docs
55 +
					</a>
56 +
				</Button>
57 +
			</div>
58 +
			{data && (
59 +
				<pre className="bg-gray-100 p-4 rounded-md">
60 +
					<code>
61 +
						Message: {data.message} <br />
62 +
						Success: {data.success.toString()}
63 +
					</code>
64 +
				</pre>
65 +
			)}
66 +
		</div>
67 +
	);
68 +
}
69 +
70 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-rpc-shadcn-tailwind.tsx (added) +67 −0
1 +
import { useState } from "react";
2 +
import beaver from "@/assets/beaver.svg";
3 +
import { Button } from "@/components/ui/button";
4 +
import { hcWithType } from "server/dist/client";
5 +
6 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
7 +
8 +
const client = hcWithType(SERVER_URL);
9 +
10 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
11 +
12 +
function Home() {
13 +
	const [data, setData] = useState<
14 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
15 +
	>();
16 +
17 +
	async function sendRequest() {
18 +
		try {
19 +
			const res = await client.hello.$get();
20 +
			if (!res.ok) {
21 +
				console.log("Error fetching data");
22 +
				return;
23 +
			}
24 +
			const data = await res.json();
25 +
			setData(data);
26 +
		} catch (error) {
27 +
			console.log(error);
28 +
		}
29 +
	}
30 +
31 +
	return (
32 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
33 +
			<a
34 +
				href="https://github.com/stevedylandev/bhvr"
35 +
				target="_blank"
36 +
				rel="noopener"
37 +
			>
38 +
				<img
39 +
					src={beaver}
40 +
					className="w-16 h-16 cursor-pointer"
41 +
					alt="beaver logo"
42 +
				/>
43 +
			</a>
44 +
			<h1 className="text-5xl font-black">bhvr</h1>
45 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
46 +
			<p>A typesafe fullstack monorepo</p>
47 +
			<div className="flex items-center gap-4">
48 +
				<Button onClick={sendRequest}>Call API</Button>
49 +
				<Button variant="secondary" asChild>
50 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
51 +
						Docs
52 +
					</a>
53 +
				</Button>
54 +
			</div>
55 +
			{data && (
56 +
				<pre className="bg-gray-100 p-4 rounded-md">
57 +
					<code>
58 +
						Message: {data.message} <br />
59 +
						Success: {data.success.toString()}
60 +
					</code>
61 +
				</pre>
62 +
			)}
63 +
		</div>
64 +
	);
65 +
}
66 +
67 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-rpc-tailwind-tanstackquery.tsx (added) +78 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import { hcWithType } from "server/dist/client";
4 +
import { useMutation } from "@tanstack/react-query";
5 +
6 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
7 +
8 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
9 +
10 +
const client = hcWithType(SERVER_URL);
11 +
12 +
function Home() {
13 +
	const [data, setData] = useState<
14 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
15 +
	>();
16 +
17 +
	const { mutate: sendRequest } = useMutation({
18 +
		mutationFn: async () => {
19 +
			try {
20 +
				const res = await client.hello.$get();
21 +
				if (!res.ok) {
22 +
					console.log("Error fetching data");
23 +
					return;
24 +
				}
25 +
				const data = await res.json();
26 +
				setData(data);
27 +
			} catch (error) {
28 +
				console.log(error);
29 +
			}
30 +
		},
31 +
	});
32 +
33 +
	return (
34 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
35 +
			<a
36 +
				href="https://github.com/stevedylandev/bhvr"
37 +
				target="_blank"
38 +
				rel="noopener"
39 +
			>
40 +
				<img
41 +
					src={beaver}
42 +
					className="w-16 h-16 cursor-pointer"
43 +
					alt="beaver logo"
44 +
				/>
45 +
			</a>
46 +
			<h1 className="text-5xl font-black">bhvr</h1>
47 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
48 +
			<p>A typesafe fullstack monorepo</p>
49 +
			<div className="flex items-center gap-4">
50 +
				<button
51 +
					type="button"
52 +
					onClick={() => sendRequest()}
53 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
54 +
				>
55 +
					Call API
56 +
				</button>
57 +
				<a
58 +
					target="_blank"
59 +
					href="https://bhvr.dev"
60 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
61 +
					rel="noopener"
62 +
				>
63 +
					Docs
64 +
				</a>
65 +
			</div>
66 +
			{data && (
67 +
				<pre className="bg-gray-100 p-4 rounded-md">
68 +
					<code>
69 +
						Message: {data.message} <br />
70 +
						Success: {data.success.toString()}
71 +
					</code>
72 +
				</pre>
73 +
			)}
74 +
		</div>
75 +
	);
76 +
}
77 +
78 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-rpc-tailwind.tsx (added) +75 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import { hcWithType } from "server/dist/client";
4 +
5 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
6 +
7 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
8 +
9 +
const client = hcWithType(SERVER_URL);
10 +
11 +
function Home() {
12 +
	const [data, setData] = useState<
13 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
14 +
	>();
15 +
16 +
	async function sendRequest() {
17 +
		try {
18 +
			const res = await client.hello.$get();
19 +
			if (!res.ok) {
20 +
				console.log("Error fetching data");
21 +
				return;
22 +
			}
23 +
			const data = await res.json();
24 +
			setData(data);
25 +
		} catch (error) {
26 +
			console.log(error);
27 +
		}
28 +
	}
29 +
30 +
	return (
31 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
32 +
			<a
33 +
				href="https://github.com/stevedylandev/bhvr"
34 +
				target="_blank"
35 +
				rel="noopener"
36 +
			>
37 +
				<img
38 +
					src={beaver}
39 +
					className="w-16 h-16 cursor-pointer"
40 +
					alt="beaver logo"
41 +
				/>
42 +
			</a>
43 +
			<h1 className="text-5xl font-black">bhvr</h1>
44 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
45 +
			<p>A typesafe fullstack monorepo</p>
46 +
			<div className="flex items-center gap-4">
47 +
				<button
48 +
					type="button"
49 +
					onClick={sendRequest}
50 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
51 +
				>
52 +
					Call API
53 +
				</button>
54 +
				<a
55 +
					target="_blank"
56 +
					href="https://bhvr.dev"
57 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
58 +
					rel="noopener"
59 +
				>
60 +
					Docs
61 +
				</a>
62 +
			</div>
63 +
			{data && (
64 +
				<pre className="bg-gray-100 p-4 rounded-md">
65 +
					<code>
66 +
						Message: {data.message} <br />
67 +
						Success: {data.success.toString()}
68 +
					</code>
69 +
				</pre>
70 +
			)}
71 +
		</div>
72 +
	);
73 +
}
74 +
75 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-rpc-tanstackquery.tsx (added) +75 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import { hcWithType } from "server/dist/client";
4 +
import { useMutation } from "@tanstack/react-query";
5 +
import "../App.css";
6 +
7 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
8 +
9 +
const client = hcWithType(SERVER_URL);
10 +
11 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
12 +
13 +
function Home() {
14 +
	const [data, setData] = useState<
15 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
16 +
	>();
17 +
18 +
	const { mutate: sendRequest } = useMutation({
19 +
		mutationFn: async () => {
20 +
			try {
21 +
				const res = await client.hello.$get();
22 +
				if (!res.ok) {
23 +
					console.log("Error fetching data");
24 +
					return;
25 +
				}
26 +
				const data = await res.json();
27 +
				setData(data);
28 +
			} catch (error) {
29 +
				console.log(error);
30 +
			}
31 +
		},
32 +
	});
33 +
34 +
	return (
35 +
		<>
36 +
			<div>
37 +
				<a
38 +
					href="https://github.com/stevedylandev/bhvr"
39 +
					target="_blank"
40 +
					rel="noopener"
41 +
				>
42 +
					<img src={beaver} className="logo" alt="beaver logo" />
43 +
				</a>
44 +
			</div>
45 +
			<h1>bhvr</h1>
46 +
			<h2>Bun + Hono + Vite + React</h2>
47 +
			<p>A typesafe fullstack monorepo</p>
48 +
			<div className="card">
49 +
				<div className="button-container">
50 +
					<button type="button" onClick={() => sendRequest()}>
51 +
						Call API
52 +
					</button>
53 +
					<a
54 +
						className="docs-link"
55 +
						target="_blank"
56 +
						href="https://bhvr.dev"
57 +
						rel="noopener"
58 +
					>
59 +
						Docs
60 +
					</a>
61 +
				</div>
62 +
				{data && (
63 +
					<pre className="response">
64 +
						<code>
65 +
							Message: {data.message} <br />
66 +
							Success: {data.success.toString()}
67 +
						</code>
68 +
					</pre>
69 +
				)}
70 +
			</div>
71 +
		</>
72 +
	);
73 +
}
74 +
75 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-rpc.tsx (added) +72 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import { hcWithType } from "server/dist/client";
4 +
import "../App.css";
5 +
6 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
7 +
8 +
const client = hcWithType(SERVER_URL);
9 +
10 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
11 +
12 +
function Home() {
13 +
	const [data, setData] = useState<
14 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
15 +
	>();
16 +
17 +
	async function sendRequest() {
18 +
		try {
19 +
			const res = await client.hello.$get();
20 +
			if (!res.ok) {
21 +
				console.log("Error fetching data");
22 +
				return;
23 +
			}
24 +
			const data = await res.json();
25 +
			setData(data);
26 +
		} catch (error) {
27 +
			console.log(error);
28 +
		}
29 +
	}
30 +
31 +
	return (
32 +
		<>
33 +
			<div>
34 +
				<a
35 +
					href="https://github.com/stevedylandev/bhvr"
36 +
					target="_blank"
37 +
					rel="noopener"
38 +
				>
39 +
					<img src={beaver} className="logo" alt="beaver logo" />
40 +
				</a>
41 +
			</div>
42 +
			<h1>bhvr</h1>
43 +
			<h2>Bun + Hono + Vite + React</h2>
44 +
			<p>A typesafe fullstack monorepo</p>
45 +
			<div className="card">
46 +
				<div className="button-container">
47 +
					<button type="button" onClick={sendRequest}>
48 +
						Call API
49 +
					</button>
50 +
					<a
51 +
						className="docs-link"
52 +
						target="_blank"
53 +
						href="https://bhvr.dev"
54 +
						rel="noopener"
55 +
					>
56 +
						Docs
57 +
					</a>
58 +
				</div>
59 +
				{data && (
60 +
					<pre className="response">
61 +
						<code>
62 +
							Message: {data.message} <br />
63 +
							Success: {data.success.toString()}
64 +
						</code>
65 +
					</pre>
66 +
				)}
67 +
			</div>
68 +
		</>
69 +
	);
70 +
}
71 +
72 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-shadcn-tailwind-tanstackquery.tsx (added) +60 −0
1 +
import { useState } from "react";
2 +
import beaver from "@/assets/beaver.svg";
3 +
import type { ApiResponse } from "shared";
4 +
import { Button } from "@/components/ui/button";
5 +
import { useMutation } from "@tanstack/react-query";
6 +
7 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
8 +
9 +
function Home() {
10 +
	const [data, setData] = useState<ApiResponse | undefined>();
11 +
12 +
	const { mutate: sendRequest } = useMutation({
13 +
		mutationFn: async () => {
14 +
			try {
15 +
				const req = await fetch(`${SERVER_URL}/hello`);
16 +
				const res: ApiResponse = await req.json();
17 +
				setData(res);
18 +
			} catch (error) {
19 +
				console.log(error);
20 +
			}
21 +
		},
22 +
	});
23 +
24 +
	return (
25 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
26 +
			<a
27 +
				href="https://github.com/stevedylandev/bhvr"
28 +
				target="_blank"
29 +
				rel="noopener"
30 +
			>
31 +
				<img
32 +
					src={beaver}
33 +
					className="w-16 h-16 cursor-pointer"
34 +
					alt="beaver logo"
35 +
				/>
36 +
			</a>
37 +
			<h1 className="text-5xl font-black">bhvr</h1>
38 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
39 +
			<p>A typesafe fullstack monorepo</p>
40 +
			<div className="flex items-center gap-4">
41 +
				<Button onClick={() => sendRequest()}>Call API</Button>
42 +
				<Button variant="secondary" asChild>
43 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
44 +
						Docs
45 +
					</a>
46 +
				</Button>
47 +
			</div>
48 +
			{data && (
49 +
				<pre className="bg-gray-100 p-4 rounded-md">
50 +
					<code>
51 +
						Message: {data.message} <br />
52 +
						Success: {data.success.toString()}
53 +
					</code>
54 +
				</pre>
55 +
			)}
56 +
		</div>
57 +
	);
58 +
}
59 +
60 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-shadcn-tailwind.tsx (added) +57 −0
1 +
import { useState } from "react";
2 +
import beaver from "@/assets/beaver.svg";
3 +
import type { ApiResponse } from "shared";
4 +
import { Button } from "@/components/ui/button";
5 +
6 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
7 +
8 +
function Home() {
9 +
	const [data, setData] = useState<ApiResponse | undefined>();
10 +
11 +
	async function sendRequest() {
12 +
		try {
13 +
			const req = await fetch(`${SERVER_URL}/hello`);
14 +
			const res: ApiResponse = await req.json();
15 +
			setData(res);
16 +
		} catch (error) {
17 +
			console.log(error);
18 +
		}
19 +
	}
20 +
21 +
	return (
22 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
23 +
			<a
24 +
				href="https://github.com/stevedylandev/bhvr"
25 +
				target="_blank"
26 +
				rel="noopener"
27 +
			>
28 +
				<img
29 +
					src={beaver}
30 +
					className="w-16 h-16 cursor-pointer"
31 +
					alt="beaver logo"
32 +
				/>
33 +
			</a>
34 +
			<h1 className="text-5xl font-black">bhvr</h1>
35 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
36 +
			<p>A typesafe fullstack monorepo</p>
37 +
			<div className="flex items-center gap-4">
38 +
				<Button onClick={sendRequest}>Call API</Button>
39 +
				<Button variant="secondary" asChild>
40 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
41 +
						Docs
42 +
					</a>
43 +
				</Button>
44 +
			</div>
45 +
			{data && (
46 +
				<pre className="bg-gray-100 p-4 rounded-md">
47 +
					<code>
48 +
						Message: {data.message} <br />
49 +
						Success: {data.success.toString()}
50 +
					</code>
51 +
				</pre>
52 +
			)}
53 +
		</div>
54 +
	);
55 +
}
56 +
57 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-tailwind-tanstackquery.tsx (added) +68 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import type { ApiResponse } from "shared";
4 +
import { useMutation } from "@tanstack/react-query";
5 +
6 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
7 +
8 +
function Home() {
9 +
	const [data, setData] = useState<ApiResponse | undefined>();
10 +
11 +
	const { mutate: sendRequest } = useMutation({
12 +
		mutationFn: async () => {
13 +
			try {
14 +
				const req = await fetch(`${SERVER_URL}/hello`);
15 +
				const res: ApiResponse = await req.json();
16 +
				setData(res);
17 +
			} catch (error) {
18 +
				console.log(error);
19 +
			}
20 +
		},
21 +
	});
22 +
23 +
	return (
24 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
25 +
			<a
26 +
				href="https://github.com/stevedylandev/bhvr"
27 +
				target="_blank"
28 +
				rel="noopener"
29 +
			>
30 +
				<img
31 +
					src={beaver}
32 +
					className="w-16 h-16 cursor-pointer"
33 +
					alt="beaver logo"
34 +
				/>
35 +
			</a>
36 +
			<h1 className="text-5xl font-black">bhvr</h1>
37 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
38 +
			<p>A typesafe fullstack monorepo</p>
39 +
			<div className="flex items-center gap-4">
40 +
				<button
41 +
					type="button"
42 +
					onClick={() => sendRequest()}
43 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
44 +
				>
45 +
					Call API
46 +
				</button>
47 +
				<a
48 +
					target="_blank"
49 +
					href="https://bhvr.dev"
50 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
51 +
					rel="noopener"
52 +
				>
53 +
					Docs
54 +
				</a>
55 +
			</div>
56 +
			{data && (
57 +
				<pre className="bg-gray-100 p-4 rounded-md">
58 +
					<code>
59 +
						Message: {data.message} <br />
60 +
						Success: {data.success.toString()}
61 +
					</code>
62 +
				</pre>
63 +
			)}
64 +
		</div>
65 +
	);
66 +
}
67 +
68 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-tailwind.tsx (added) +65 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import type { ApiResponse } from "shared";
4 +
5 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
6 +
7 +
function Home() {
8 +
	const [data, setData] = useState<ApiResponse | undefined>();
9 +
10 +
	async function sendRequest() {
11 +
		try {
12 +
			const req = await fetch(`${SERVER_URL}/hello`);
13 +
			const res: ApiResponse = await req.json();
14 +
			setData(res);
15 +
		} catch (error) {
16 +
			console.log(error);
17 +
		}
18 +
	}
19 +
20 +
	return (
21 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
22 +
			<a
23 +
				href="https://github.com/stevedylandev/bhvr"
24 +
				target="_blank"
25 +
				rel="noopener"
26 +
			>
27 +
				<img
28 +
					src={beaver}
29 +
					className="w-16 h-16 cursor-pointer"
30 +
					alt="beaver logo"
31 +
				/>
32 +
			</a>
33 +
			<h1 className="text-5xl font-black">bhvr</h1>
34 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
35 +
			<p>A typesafe fullstack monorepo</p>
36 +
			<div className="flex items-center gap-4">
37 +
				<button
38 +
					type="button"
39 +
					onClick={sendRequest}
40 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
41 +
				>
42 +
					Call API
43 +
				</button>
44 +
				<a
45 +
					target="_blank"
46 +
					href="https://bhvr.dev"
47 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
48 +
					rel="noopener"
49 +
				>
50 +
					Docs
51 +
				</a>
52 +
			</div>
53 +
			{data && (
54 +
				<pre className="bg-gray-100 p-4 rounded-md">
55 +
					<code>
56 +
						Message: {data.message} <br />
57 +
						Success: {data.success.toString()}
58 +
					</code>
59 +
				</pre>
60 +
			)}
61 +
		</div>
62 +
	);
63 +
}
64 +
65 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home-with-tanstackquery.tsx (added) +62 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import { useMutation } from "@tanstack/react-query";
4 +
import type { ApiResponse } from "shared";
5 +
import "../App.css";
6 +
7 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
8 +
9 +
function Home() {
10 +
	const [data, setData] = useState<ApiResponse | undefined>();
11 +
12 +
	const { mutate: sendRequest } = useMutation({
13 +
		mutationFn: async () => {
14 +
			const req = await fetch(`${SERVER_URL}/hello`);
15 +
			const res: ApiResponse = await req.json();
16 +
			setData(res);
17 +
		},
18 +
		onError: (err) => console.log(err),
19 +
	});
20 +
21 +
	return (
22 +
		<>
23 +
			<div>
24 +
				<a
25 +
					href="https://github.com/stevedylandev/bhvr"
26 +
					target="_blank"
27 +
					rel="noopener"
28 +
				>
29 +
					<img src={beaver} className="logo" alt="beaver logo" />
30 +
				</a>
31 +
			</div>
32 +
			<h1>bhvr</h1>
33 +
			<h2>Bun + Hono + Vite + React</h2>
34 +
			<p>A typesafe fullstack monorepo</p>
35 +
			<div className="card">
36 +
				<div className="button-container">
37 +
					<button type="button" onClick={() => sendRequest()}>
38 +
						Call API
39 +
					</button>
40 +
					<a
41 +
						className="docs-link"
42 +
						target="_blank"
43 +
						href="https://bhvr.dev"
44 +
						rel="noopener"
45 +
					>
46 +
						Docs
47 +
					</a>
48 +
				</div>
49 +
				{data && (
50 +
					<pre className="response">
51 +
						<code>
52 +
							Message: {data.message} <br />
53 +
							Success: {data.success.toString()}
54 +
						</code>
55 +
					</pre>
56 +
				)}
57 +
			</div>
58 +
		</>
59 +
	);
60 +
}
61 +
62 +
export default Home;
src/templates/extras/client/src/components/Home.tsx/Home.tsx (added) +62 −0
1 +
import { useState } from "react";
2 +
import beaver from "../assets/beaver.svg";
3 +
import type { ApiResponse } from "shared";
4 +
import "../App.css";
5 +
6 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
7 +
8 +
function Home() {
9 +
	const [data, setData] = useState<ApiResponse | undefined>();
10 +
11 +
	async function sendRequest() {
12 +
		try {
13 +
			const req = await fetch(`${SERVER_URL}/hello`);
14 +
			const res: ApiResponse = await req.json();
15 +
			setData(res);
16 +
		} catch (error) {
17 +
			console.log(error);
18 +
		}
19 +
	}
20 +
21 +
	return (
22 +
		<>
23 +
			<div>
24 +
				<a
25 +
					href="https://github.com/stevedylandev/bhvr"
26 +
					target="_blank"
27 +
					rel="noopener"
28 +
				>
29 +
					<img src={beaver} className="logo" alt="beaver logo" />
30 +
				</a>
31 +
			</div>
32 +
			<h1>bhvr</h1>
33 +
			<h2>Bun + Hono + Vite + React</h2>
34 +
			<p>A typesafe fullstack monorepo</p>
35 +
			<div className="card">
36 +
				<div className="button-container">
37 +
					<button type="button" onClick={sendRequest}>
38 +
						Call API
39 +
					</button>
40 +
					<a
41 +
						className="docs-link"
42 +
						target="_blank"
43 +
						href="https://bhvr.dev"
44 +
						rel="noopener"
45 +
					>
46 +
						Docs
47 +
					</a>
48 +
				</div>
49 +
				{data && (
50 +
					<pre className="response">
51 +
						<code>
52 +
							Message: {data.message} <br />
53 +
							Success: {data.success.toString()}
54 +
						</code>
55 +
					</pre>
56 +
				)}
57 +
			</div>
58 +
		</>
59 +
	);
60 +
}
61 +
62 +
export default Home;
src/templates/extras/client/src/main.tsx/main-with-tanstackquery-tanstackrouter.tsx (added) +40 −0
1 +
import { StrictMode } from "react";
2 +
import ReactDOM from "react-dom/client";
3 +
import { RouterProvider, createRouter } from "@tanstack/react-router";
4 +
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
5 +
import "./index.css";
6 +
7 +
const queryClient = new QueryClient();
8 +
9 +
// Import the generated route tree
10 +
import { routeTree } from "./routeTree.gen";
11 +
12 +
// Create a new router instance
13 +
const router = createRouter({ routeTree });
14 +
15 +
// Register the router instance for type safety
16 +
declare module "@tanstack/react-router" {
17 +
	interface Register {
18 +
		router: typeof router;
19 +
	}
20 +
}
21 +
22 +
const rootElement = document.getElementById("root");
23 +
24 +
if (!rootElement) {
25 +
	throw new Error(
26 +
		"Root element not found. Check if it's in your index.html or if the id is correct.",
27 +
	);
28 +
}
29 +
30 +
// Render the app
31 +
if (!rootElement.innerHTML) {
32 +
	const root = ReactDOM.createRoot(rootElement);
33 +
	root.render(
34 +
		<StrictMode>
35 +
			<QueryClientProvider client={queryClient}>
36 +
				<RouterProvider router={router} />
37 +
			</QueryClientProvider>
38 +
		</StrictMode>,
39 +
	);
40 +
}
src/templates/extras/client/src/main.tsx/main-with-tanstackrouter.tsx (added) +35 −0
1 +
import { StrictMode } from "react";
2 +
import ReactDOM from "react-dom/client";
3 +
import { RouterProvider, createRouter } from "@tanstack/react-router";
4 +
import "./index.css";
5 +
6 +
// Import the generated route tree
7 +
import { routeTree } from "./routeTree.gen";
8 +
9 +
// Create a new router instance
10 +
const router = createRouter({ routeTree });
11 +
12 +
// Register the router instance for type safety
13 +
declare module "@tanstack/react-router" {
14 +
	interface Register {
15 +
		router: typeof router;
16 +
	}
17 +
}
18 +
19 +
const rootElement = document.getElementById("root");
20 +
21 +
if (!rootElement) {
22 +
	throw new Error(
23 +
		"Root element not found. Check if it's in your index.html or if the id is correct.",
24 +
	);
25 +
}
26 +
27 +
// Render the app
28 +
if (!rootElement.innerHTML) {
29 +
	const root = ReactDOM.createRoot(rootElement);
30 +
	root.render(
31 +
		<StrictMode>
32 +
			<RouterProvider router={router} />
33 +
		</StrictMode>,
34 +
	);
35 +
}
src/templates/extras/client/src/routes/__root.tsx (added) +11 −0
1 +
import { createRootRoute, Outlet } from "@tanstack/react-router";
2 +
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
3 +
4 +
export const Route = createRootRoute({
5 +
	component: () => (
6 +
		<>
7 +
			<Outlet />
8 +
			<TanStackRouterDevtools />
9 +
		</>
10 +
	),
11 +
});
src/templates/extras/client/src/routes/index.tsx/index-with-rpc-shadcn-tailwind-tanstackquery.tsx (added) +75 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "@/assets/beaver.svg";
4 +
import { Button } from "@/components/ui/button";
5 +
import { hcWithType } from "server/dist/client";
6 +
import { useMutation } from "@tanstack/react-query";
7 +
8 +
export const Route = createFileRoute("/")({
9 +
	component: Index,
10 +
});
11 +
12 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
13 +
14 +
const client = hcWithType(SERVER_URL);
15 +
16 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
17 +
18 +
function Index() {
19 +
	const [data, setData] = useState<
20 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
21 +
	>();
22 +
23 +
	const { mutate: sendRequest } = useMutation({
24 +
		mutationFn: async () => {
25 +
			try {
26 +
				const res = await client.hello.$get();
27 +
				if (!res.ok) {
28 +
					console.log("Error fetching data");
29 +
					return;
30 +
				}
31 +
				const data = await res.json();
32 +
				setData(data);
33 +
			} catch (error) {
34 +
				console.log(error);
35 +
			}
36 +
		},
37 +
	});
38 +
39 +
	return (
40 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
41 +
			<a
42 +
				href="https://github.com/stevedylandev/bhvr"
43 +
				target="_blank"
44 +
				rel="noopener"
45 +
			>
46 +
				<img
47 +
					src={beaver}
48 +
					className="w-16 h-16 cursor-pointer"
49 +
					alt="beaver logo"
50 +
				/>
51 +
			</a>
52 +
			<h1 className="text-5xl font-black">bhvr</h1>
53 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
54 +
			<p>A typesafe fullstack monorepo</p>
55 +
			<div className="flex items-center gap-4">
56 +
				<Button onClick={() => sendRequest()}>Call API</Button>
57 +
				<Button variant="secondary" asChild>
58 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
59 +
						Docs
60 +
					</a>
61 +
				</Button>
62 +
			</div>
63 +
			{data && (
64 +
				<pre className="bg-gray-100 p-4 rounded-md">
65 +
					<code>
66 +
						Message: {data.message} <br />
67 +
						Success: {data.success.toString()}
68 +
					</code>
69 +
				</pre>
70 +
			)}
71 +
		</div>
72 +
	);
73 +
}
74 +
75 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-rpc-shadcn-tailwind.tsx (added) +72 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "@/assets/beaver.svg";
4 +
import { Button } from "@/components/ui/button";
5 +
import { hcWithType } from "server/dist/client";
6 +
7 +
export const Route = createFileRoute("/")({
8 +
	component: Index,
9 +
});
10 +
11 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
12 +
13 +
const client = hcWithType(SERVER_URL);
14 +
15 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
16 +
17 +
function Index() {
18 +
	const [data, setData] = useState<
19 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
20 +
	>();
21 +
22 +
	async function sendRequest() {
23 +
		try {
24 +
			const res = await client.hello.$get();
25 +
			if (!res.ok) {
26 +
				console.log("Error fetching data");
27 +
				return;
28 +
			}
29 +
			const data = await res.json();
30 +
			setData(data);
31 +
		} catch (error) {
32 +
			console.log(error);
33 +
		}
34 +
	}
35 +
36 +
	return (
37 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
38 +
			<a
39 +
				href="https://github.com/stevedylandev/bhvr"
40 +
				target="_blank"
41 +
				rel="noopener"
42 +
			>
43 +
				<img
44 +
					src={beaver}
45 +
					className="w-16 h-16 cursor-pointer"
46 +
					alt="beaver logo"
47 +
				/>
48 +
			</a>
49 +
			<h1 className="text-5xl font-black">bhvr</h1>
50 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
51 +
			<p>A typesafe fullstack monorepo</p>
52 +
			<div className="flex items-center gap-4">
53 +
				<Button onClick={sendRequest}>Call API</Button>
54 +
				<Button variant="secondary" asChild>
55 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
56 +
						Docs
57 +
					</a>
58 +
				</Button>
59 +
			</div>
60 +
			{data && (
61 +
				<pre className="bg-gray-100 p-4 rounded-md">
62 +
					<code>
63 +
						Message: {data.message} <br />
64 +
						Success: {data.success.toString()}
65 +
					</code>
66 +
				</pre>
67 +
			)}
68 +
		</div>
69 +
	);
70 +
}
71 +
72 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-rpc-tailwind-tanstackquery.tsx (added) +83 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import { hcWithType } from "server/dist/client";
5 +
import { useMutation } from "@tanstack/react-query";
6 +
7 +
export const Route = createFileRoute("/")({
8 +
	component: Index,
9 +
});
10 +
11 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
12 +
13 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
14 +
15 +
const client = hcWithType(SERVER_URL);
16 +
17 +
function Index() {
18 +
	const [data, setData] = useState<
19 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
20 +
	>();
21 +
22 +
	const { mutate: sendRequest } = useMutation({
23 +
		mutationFn: async () => {
24 +
			try {
25 +
				const res = await client.hello.$get();
26 +
				if (!res.ok) {
27 +
					console.log("Error fetching data");
28 +
					return;
29 +
				}
30 +
				const data = await res.json();
31 +
				setData(data);
32 +
			} catch (error) {
33 +
				console.log(error);
34 +
			}
35 +
		},
36 +
	});
37 +
38 +
	return (
39 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
40 +
			<a
41 +
				href="https://github.com/stevedylandev/bhvr"
42 +
				target="_blank"
43 +
				rel="noopener"
44 +
			>
45 +
				<img
46 +
					src={beaver}
47 +
					className="w-16 h-16 cursor-pointer"
48 +
					alt="beaver logo"
49 +
				/>
50 +
			</a>
51 +
			<h1 className="text-5xl font-black">bhvr</h1>
52 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
53 +
			<p>A typesafe fullstack monorepo</p>
54 +
			<div className="flex items-center gap-4">
55 +
				<button
56 +
					type="button"
57 +
					onClick={() => sendRequest()}
58 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
59 +
				>
60 +
					Call API
61 +
				</button>
62 +
				<a
63 +
					target="_blank"
64 +
					href="https://bhvr.dev"
65 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
66 +
					rel="noopener"
67 +
				>
68 +
					Docs
69 +
				</a>
70 +
			</div>
71 +
			{data && (
72 +
				<pre className="bg-gray-100 p-4 rounded-md">
73 +
					<code>
74 +
						Message: {data.message} <br />
75 +
						Success: {data.success.toString()}
76 +
					</code>
77 +
				</pre>
78 +
			)}
79 +
		</div>
80 +
	);
81 +
}
82 +
83 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-rpc-tailwind.tsx (added) +80 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import { hcWithType } from "server/dist/client";
5 +
6 +
export const Route = createFileRoute("/")({
7 +
	component: Index,
8 +
});
9 +
10 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
11 +
12 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
13 +
14 +
const client = hcWithType(SERVER_URL);
15 +
16 +
function Index() {
17 +
	const [data, setData] = useState<
18 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
19 +
	>();
20 +
21 +
	async function sendRequest() {
22 +
		try {
23 +
			const res = await client.hello.$get();
24 +
			if (!res.ok) {
25 +
				console.log("Error fetching data");
26 +
				return;
27 +
			}
28 +
			const data = await res.json();
29 +
			setData(data);
30 +
		} catch (error) {
31 +
			console.log(error);
32 +
		}
33 +
	}
34 +
35 +
	return (
36 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
37 +
			<a
38 +
				href="https://github.com/stevedylandev/bhvr"
39 +
				target="_blank"
40 +
				rel="noopener"
41 +
			>
42 +
				<img
43 +
					src={beaver}
44 +
					className="w-16 h-16 cursor-pointer"
45 +
					alt="beaver logo"
46 +
				/>
47 +
			</a>
48 +
			<h1 className="text-5xl font-black">bhvr</h1>
49 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
50 +
			<p>A typesafe fullstack monorepo</p>
51 +
			<div className="flex items-center gap-4">
52 +
				<button
53 +
					type="button"
54 +
					onClick={sendRequest}
55 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
56 +
				>
57 +
					Call API
58 +
				</button>
59 +
				<a
60 +
					target="_blank"
61 +
					href="https://bhvr.dev"
62 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
63 +
					rel="noopener"
64 +
				>
65 +
					Docs
66 +
				</a>
67 +
			</div>
68 +
			{data && (
69 +
				<pre className="bg-gray-100 p-4 rounded-md">
70 +
					<code>
71 +
						Message: {data.message} <br />
72 +
						Success: {data.success.toString()}
73 +
					</code>
74 +
				</pre>
75 +
			)}
76 +
		</div>
77 +
	);
78 +
}
79 +
80 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-rpc-tanstackquery.tsx (added) +80 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import { hcWithType } from "server/dist/client";
5 +
import { useMutation } from "@tanstack/react-query";
6 +
import "../App.css";
7 +
8 +
export const Route = createFileRoute("/")({
9 +
	component: Index,
10 +
});
11 +
12 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
13 +
14 +
const client = hcWithType(SERVER_URL);
15 +
16 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
17 +
18 +
function Index() {
19 +
	const [data, setData] = useState<
20 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
21 +
	>();
22 +
23 +
	const { mutate: sendRequest } = useMutation({
24 +
		mutationFn: async () => {
25 +
			try {
26 +
				const res = await client.hello.$get();
27 +
				if (!res.ok) {
28 +
					console.log("Error fetching data");
29 +
					return;
30 +
				}
31 +
				const data = await res.json();
32 +
				setData(data);
33 +
			} catch (error) {
34 +
				console.log(error);
35 +
			}
36 +
		},
37 +
	});
38 +
39 +
	return (
40 +
		<>
41 +
			<div>
42 +
				<a
43 +
					href="https://github.com/stevedylandev/bhvr"
44 +
					target="_blank"
45 +
					rel="noopener"
46 +
				>
47 +
					<img src={beaver} className="logo" alt="beaver logo" />
48 +
				</a>
49 +
			</div>
50 +
			<h1>bhvr</h1>
51 +
			<h2>Bun + Hono + Vite + React</h2>
52 +
			<p>A typesafe fullstack monorepo</p>
53 +
			<div className="card">
54 +
				<div className="button-container">
55 +
					<button type="button" onClick={() => sendRequest()}>
56 +
						Call API
57 +
					</button>
58 +
					<a
59 +
						className="docs-link"
60 +
						target="_blank"
61 +
						href="https://bhvr.dev"
62 +
						rel="noopener"
63 +
					>
64 +
						Docs
65 +
					</a>
66 +
				</div>
67 +
				{data && (
68 +
					<pre className="response">
69 +
						<code>
70 +
							Message: {data.message} <br />
71 +
							Success: {data.success.toString()}
72 +
						</code>
73 +
					</pre>
74 +
				)}
75 +
			</div>
76 +
		</>
77 +
	);
78 +
}
79 +
80 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-rpc.tsx (added) +77 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import { hcWithType } from "server/dist/client";
5 +
import "../App.css";
6 +
7 +
export const Route = createFileRoute("/")({
8 +
	component: Index,
9 +
});
10 +
11 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
12 +
13 +
const client = hcWithType(SERVER_URL);
14 +
15 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
16 +
17 +
function Index() {
18 +
	const [data, setData] = useState<
19 +
		Awaited<ReturnType<ResponseType["json"]>> | undefined
20 +
	>();
21 +
22 +
	async function sendRequest() {
23 +
		try {
24 +
			const res = await client.hello.$get();
25 +
			if (!res.ok) {
26 +
				console.log("Error fetching data");
27 +
				return;
28 +
			}
29 +
			const data = await res.json();
30 +
			setData(data);
31 +
		} catch (error) {
32 +
			console.log(error);
33 +
		}
34 +
	}
35 +
36 +
	return (
37 +
		<>
38 +
			<div>
39 +
				<a
40 +
					href="https://github.com/stevedylandev/bhvr"
41 +
					target="_blank"
42 +
					rel="noopener"
43 +
				>
44 +
					<img src={beaver} className="logo" alt="beaver logo" />
45 +
				</a>
46 +
			</div>
47 +
			<h1>bhvr</h1>
48 +
			<h2>Bun + Hono + Vite + React</h2>
49 +
			<p>A typesafe fullstack monorepo</p>
50 +
			<div className="card">
51 +
				<div className="button-container">
52 +
					<button type="button" onClick={sendRequest}>
53 +
						Call API
54 +
					</button>
55 +
					<a
56 +
						className="docs-link"
57 +
						target="_blank"
58 +
						href="https://bhvr.dev"
59 +
						rel="noopener"
60 +
					>
61 +
						Docs
62 +
					</a>
63 +
				</div>
64 +
				{data && (
65 +
					<pre className="response">
66 +
						<code>
67 +
							Message: {data.message} <br />
68 +
							Success: {data.success.toString()}
69 +
						</code>
70 +
					</pre>
71 +
				)}
72 +
			</div>
73 +
		</>
74 +
	);
75 +
}
76 +
77 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-shadcn-tailwind-tanstackquery.tsx (added) +65 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "@/assets/beaver.svg";
4 +
import type { ApiResponse } from "shared";
5 +
import { Button } from "@/components/ui/button";
6 +
import { useMutation } from "@tanstack/react-query";
7 +
8 +
export const Route = createFileRoute("/")({
9 +
	component: Index,
10 +
});
11 +
12 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
13 +
14 +
function Index() {
15 +
	const [data, setData] = useState<ApiResponse | undefined>();
16 +
17 +
	const { mutate: sendRequest } = useMutation({
18 +
		mutationFn: async () => {
19 +
			try {
20 +
				const req = await fetch(`${SERVER_URL}/hello`);
21 +
				const res: ApiResponse = await req.json();
22 +
				setData(res);
23 +
			} catch (error) {
24 +
				console.log(error);
25 +
			}
26 +
		},
27 +
	});
28 +
29 +
	return (
30 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
31 +
			<a
32 +
				href="https://github.com/stevedylandev/bhvr"
33 +
				target="_blank"
34 +
				rel="noopener"
35 +
			>
36 +
				<img
37 +
					src={beaver}
38 +
					className="w-16 h-16 cursor-pointer"
39 +
					alt="beaver logo"
40 +
				/>
41 +
			</a>
42 +
			<h1 className="text-5xl font-black">bhvr</h1>
43 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
44 +
			<p>A typesafe fullstack monorepo</p>
45 +
			<div className="flex items-center gap-4">
46 +
				<Button onClick={() => sendRequest()}>Call API</Button>
47 +
				<Button variant="secondary" asChild>
48 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
49 +
						Docs
50 +
					</a>
51 +
				</Button>
52 +
			</div>
53 +
			{data && (
54 +
				<pre className="bg-gray-100 p-4 rounded-md">
55 +
					<code>
56 +
						Message: {data.message} <br />
57 +
						Success: {data.success.toString()}
58 +
					</code>
59 +
				</pre>
60 +
			)}
61 +
		</div>
62 +
	);
63 +
}
64 +
65 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-shadcn-tailwind.tsx (added) +60 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import type { ApiResponse } from "shared";
5 +
import { Button } from "@/components/ui/button";
6 +
7 +
export const Route = createFileRoute("/")({
8 +
	component: Index,
9 +
});
10 +
11 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
12 +
13 +
function Index() {
14 +
	const [data, setData] = useState<ApiResponse | undefined>();
15 +
16 +
	async function sendRequest() {
17 +
		try {
18 +
			const req = await fetch(`${SERVER_URL}/hello`);
19 +
			const res: ApiResponse = await req.json();
20 +
			setData(res);
21 +
		} catch (error) {
22 +
			console.log(error);
23 +
		}
24 +
	}
25 +
26 +
	return (
27 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
28 +
			<a
29 +
				href="https://github.com/stevedylandev/bhvr"
30 +
				target="_blank"
31 +
				rel="noopener"
32 +
			>
33 +
				<img
34 +
					src={beaver}
35 +
					className="w-16 h-16 cursor-pointer"
36 +
					alt="beaver logo"
37 +
				/>
38 +
			</a>
39 +
			<h1 className="text-5xl font-black">bhvr</h1>
40 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
41 +
			<p>A typesafe fullstack monorepo</p>
42 +
			<div className="flex items-center gap-4">
43 +
				<Button onClick={sendRequest}>Call API</Button>
44 +
				<Button variant="secondary" asChild>
45 +
					<a target="_blank" href="https://bhvr.dev" rel="noopener">
46 +
						Docs
47 +
					</a>
48 +
				</Button>
49 +
			</div>
50 +
			{data && (
51 +
				<pre className="bg-gray-100 p-4 rounded-md">
52 +
					<code>
53 +
						Message: {data.message} <br />
54 +
						Success: {data.success.toString()}
55 +
					</code>
56 +
				</pre>
57 +
			)}
58 +
		</div>
59 +
	);
60 +
}
src/templates/extras/client/src/routes/index.tsx/index-with-tailwind-tanstackquery.tsx (added) +73 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import type { ApiResponse } from "shared";
5 +
import { useMutation } from "@tanstack/react-query";
6 +
7 +
export const Route = createFileRoute("/")({
8 +
	component: Index,
9 +
});
10 +
11 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
12 +
13 +
function Index() {
14 +
	const [data, setData] = useState<ApiResponse | undefined>();
15 +
16 +
	const { mutate: sendRequest } = useMutation({
17 +
		mutationFn: async () => {
18 +
			try {
19 +
				const req = await fetch(`${SERVER_URL}/hello`);
20 +
				const res: ApiResponse = await req.json();
21 +
				setData(res);
22 +
			} catch (error) {
23 +
				console.log(error);
24 +
			}
25 +
		},
26 +
	});
27 +
28 +
	return (
29 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
30 +
			<a
31 +
				href="https://github.com/stevedylandev/bhvr"
32 +
				target="_blank"
33 +
				rel="noopener"
34 +
			>
35 +
				<img
36 +
					src={beaver}
37 +
					className="w-16 h-16 cursor-pointer"
38 +
					alt="beaver logo"
39 +
				/>
40 +
			</a>
41 +
			<h1 className="text-5xl font-black">bhvr</h1>
42 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
43 +
			<p>A typesafe fullstack monorepo</p>
44 +
			<div className="flex items-center gap-4">
45 +
				<button
46 +
					type="button"
47 +
					onClick={() => sendRequest()}
48 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
49 +
				>
50 +
					Call API
51 +
				</button>
52 +
				<a
53 +
					target="_blank"
54 +
					href="https://bhvr.dev"
55 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
56 +
					rel="noopener"
57 +
				>
58 +
					Docs
59 +
				</a>
60 +
			</div>
61 +
			{data && (
62 +
				<pre className="bg-gray-100 p-4 rounded-md">
63 +
					<code>
64 +
						Message: {data.message} <br />
65 +
						Success: {data.success.toString()}
66 +
					</code>
67 +
				</pre>
68 +
			)}
69 +
		</div>
70 +
	);
71 +
}
72 +
73 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index-with-tailwind.tsx (added) +68 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import type { ApiResponse } from "shared";
5 +
6 +
export const Route = createFileRoute("/")({
7 +
	component: Index,
8 +
});
9 +
10 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
11 +
12 +
function Index() {
13 +
	const [data, setData] = useState<ApiResponse | undefined>();
14 +
15 +
	async function sendRequest() {
16 +
		try {
17 +
			const req = await fetch(`${SERVER_URL}/hello`);
18 +
			const res: ApiResponse = await req.json();
19 +
			setData(res);
20 +
		} catch (error) {
21 +
			console.log(error);
22 +
		}
23 +
	}
24 +
25 +
	return (
26 +
		<div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
27 +
			<a
28 +
				href="https://github.com/stevedylandev/bhvr"
29 +
				target="_blank"
30 +
				rel="noopener"
31 +
			>
32 +
				<img
33 +
					src={beaver}
34 +
					className="w-16 h-16 cursor-pointer"
35 +
					alt="beaver logo"
36 +
				/>
37 +
			</a>
38 +
			<h1 className="text-5xl font-black">bhvr</h1>
39 +
			<h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
40 +
			<p>A typesafe fullstack monorepo</p>
41 +
			<div className="flex items-center gap-4">
42 +
				<button
43 +
					type="button"
44 +
					onClick={sendRequest}
45 +
					className="bg-black text-white px-2.5 py-1.5 rounded-md"
46 +
				>
47 +
					Call API
48 +
				</button>
49 +
				<a
50 +
					target="_blank"
51 +
					href="https://bhvr.dev"
52 +
					className="border-1 border-black text-black px-2.5 py-1.5 rounded-md"
53 +
					rel="noopener"
54 +
				>
55 +
					Docs
56 +
				</a>
57 +
			</div>
58 +
			{data && (
59 +
				<pre className="bg-gray-100 p-4 rounded-md">
60 +
					<code>
61 +
						Message: {data.message} <br />
62 +
						Success: {data.success.toString()}
63 +
					</code>
64 +
				</pre>
65 +
			)}
66 +
		</div>
67 +
	);
68 +
}
src/templates/extras/client/src/routes/index.tsx/index-with-tanstackquery.tsx (added) +67 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import { useMutation } from "@tanstack/react-query";
5 +
import type { ApiResponse } from "shared";
6 +
import "../App.css";
7 +
8 +
export const Route = createFileRoute("/")({
9 +
	component: Index,
10 +
});
11 +
12 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
13 +
14 +
function Index() {
15 +
	const [data, setData] = useState<ApiResponse | undefined>();
16 +
17 +
	const { mutate: sendRequest } = useMutation({
18 +
		mutationFn: async () => {
19 +
			const req = await fetch(`${SERVER_URL}/hello`);
20 +
			const res: ApiResponse = await req.json();
21 +
			setData(res);
22 +
		},
23 +
		onError: (err) => console.log(err),
24 +
	});
25 +
26 +
	return (
27 +
		<>
28 +
			<div>
29 +
				<a
30 +
					href="https://github.com/stevedylandev/bhvr"
31 +
					target="_blank"
32 +
					rel="noopener"
33 +
				>
34 +
					<img src={beaver} className="logo" alt="beaver logo" />
35 +
				</a>
36 +
			</div>
37 +
			<h1>bhvr</h1>
38 +
			<h2>Bun + Hono + Vite + React</h2>
39 +
			<p>A typesafe fullstack monorepo</p>
40 +
			<div className="card">
41 +
				<div className="button-container">
42 +
					<button type="button" onClick={() => sendRequest()}>
43 +
						Call API
44 +
					</button>
45 +
					<a
46 +
						className="docs-link"
47 +
						target="_blank"
48 +
						href="https://bhvr.dev"
49 +
						rel="noopener"
50 +
					>
51 +
						Docs
52 +
					</a>
53 +
				</div>
54 +
				{data && (
55 +
					<pre className="response">
56 +
						<code>
57 +
							Message: {data.message} <br />
58 +
							Success: {data.success.toString()}
59 +
						</code>
60 +
					</pre>
61 +
				)}
62 +
			</div>
63 +
		</>
64 +
	);
65 +
}
66 +
67 +
export default Index;
src/templates/extras/client/src/routes/index.tsx/index.tsx (added) +65 −0
1 +
import { createFileRoute } from "@tanstack/react-router";
2 +
import { useState } from "react";
3 +
import beaver from "../assets/beaver.svg";
4 +
import type { ApiResponse } from "shared";
5 +
import "../App.css";
6 +
7 +
export const Route = createFileRoute("/")({
8 +
	component: Index,
9 +
});
10 +
11 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000";
12 +
13 +
function Index() {
14 +
	const [data, setData] = useState<ApiResponse | undefined>();
15 +
16 +
	async function sendRequest() {
17 +
		try {
18 +
			const req = await fetch(`${SERVER_URL}/hello`);
19 +
			const res: ApiResponse = await req.json();
20 +
			setData(res);
21 +
		} catch (error) {
22 +
			console.log(error);
23 +
		}
24 +
	}
25 +
26 +
	return (
27 +
		<>
28 +
			<div>
29 +
				<a
30 +
					href="https://github.com/stevedylandev/bhvr"
31 +
					target="_blank"
32 +
					rel="noopener"
33 +
				>
34 +
					<img src={beaver} className="logo" alt="beaver logo" />
35 +
				</a>
36 +
			</div>
37 +
			<h1>bhvr</h1>
38 +
			<h2>Bun + Hono + Vite + React</h2>
39 +
			<p>A typesafe fullstack monorepo</p>
40 +
			<div className="card">
41 +
				<div className="button-container">
42 +
					<button type="button" onClick={sendRequest}>
43 +
						Call API
44 +
					</button>
45 +
					<a
46 +
						className="docs-link"
47 +
						target="_blank"
48 +
						href="https://bhvr.dev"
49 +
						rel="noopener"
50 +
					>
51 +
						Docs
52 +
					</a>
53 +
				</div>
54 +
				{data && (
55 +
					<pre className="response">
56 +
						<code>
57 +
							Message: {data.message} <br />
58 +
							Success: {data.success.toString()}
59 +
						</code>
60 +
					</pre>
61 +
				)}
62 +
			</div>
63 +
		</>
64 +
	);
65 +
}
src/templates/extras/client/vite.config.ts/vite.config-with-shadcn-tailwind-tanstackrouter.ts (added) +22 −0
1 +
import { defineConfig } from "vite";
2 +
import react from "@vitejs/plugin-react";
3 +
import tailwindcss from "@tailwindcss/vite";
4 +
import path from "node:path";
5 +
import { tanstackRouter } from "@tanstack/router-plugin/vite";
6 +
7 +
export default defineConfig({
8 +
	plugins: [
9 +
		// Please make sure that '@tanstack/router-plugin' is passed before '@vitejs/plugin-react'
10 +
		tanstackRouter({
11 +
			target: "react",
12 +
			autoCodeSplitting: true,
13 +
		}),
14 +
		react(),
15 +
		tailwindcss(),
16 +
	],
17 +
	resolve: {
18 +
		alias: {
19 +
			"@": path.resolve(__dirname, "./src"),
20 +
		},
21 +
	},
22 +
});
src/templates/extras/client/vite.config.ts/vite.config-with-tailwind-tanstackrouter.ts (added) +16 −0
1 +
import { defineConfig } from "vite";
2 +
import react from "@vitejs/plugin-react";
3 +
import tailwindcss from "@tailwindcss/vite";
4 +
import { tanstackRouter } from "@tanstack/router-plugin/vite";
5 +
6 +
export default defineConfig({
7 +
	plugins: [
8 +
		// Please make sure that '@tanstack/router-plugin' is passed before '@vitejs/plugin-react'
9 +
		tanstackRouter({
10 +
			target: "react",
11 +
			autoCodeSplitting: true,
12 +
		}),
13 +
		react(),
14 +
		tailwindcss(),
15 +
	],
16 +
});
src/templates/extras/client/vite.config.ts/vite.config-with-tanstackrouter.ts (added) +14 −0
1 +
import { defineConfig } from "vite";
2 +
import react from "@vitejs/plugin-react";
3 +
import { tanstackRouter } from "@tanstack/router-plugin/vite";
4 +
5 +
export default defineConfig({
6 +
	plugins: [
7 +
		// Please make sure that '@tanstack/router-plugin' is passed before '@vitejs/plugin-react'
8 +
		tanstackRouter({
9 +
			target: "react",
10 +
			autoCodeSplitting: true,
11 +
		}),
12 +
		react(),
13 +
	],
14 +
});
src/types.ts +1 −0
14 14
	shadcn?: boolean;
15 15
	rpc?: boolean;
16 16
	linter?: "eslint" | "biome";
17 +
	router?: "none" | "reactrouter" | "tanstackrouter";
17 18
	tanstackQuery?: boolean;
18 19
};
19 20