@newt-app/templates 0.16.0 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -459,9 +459,9 @@ var page_default = {
459
459
  import { useQuery } from '@tanstack/react-query';
460
460
  import { authClient } from '@/lib/auth-client';
461
461
  import { AuthForm } from '@/app/auth-form';
462
+ import { Button } from '@<%= projectName %>/ui/button';
462
463
  import { Link } from '@<%= projectName %>/ui/link';
463
464
  import { Logo } from '@<%= projectName %>/ui/logo';
464
- import { TodoList } from '@/app/todo-list';
465
465
 
466
466
  export default function Home() {
467
467
  const { data: session, isPending } = authClient.useSession();
@@ -474,47 +474,52 @@ export default function Home() {
474
474
  return (
475
475
  <main className="max-w-lg mx-auto min-h-full px-4 py-8 space-y-4">
476
476
  <div className="pb-4 border-b">
477
- <p className="font-mono text-sm text-gray-400">apps/web/page.tsx</p>
478
- <p className="text-sm text-gray-400">Delete me to get started!</p>
477
+ <p className="font-mono text-sm text-muted-foreground">apps/web/page.tsx</p>
478
+ <p className="text-sm text-muted-foreground">Delete me to get started!</p>
479
479
  </div>
480
480
 
481
481
  <div className="flex items-center gap-3 py-2">
482
482
  <Logo className="w-10 h-auto text-foreground" />
483
483
  <div>
484
484
  <h1 className="text-4xl font-black tracking-tight"><%= projectName %></h1>
485
- <p className="text-sm text-gray-400 tracking-widest uppercase">
485
+ <p className="text-sm text-muted-foreground tracking-widest uppercase">
486
486
  Next + Nest = Newt \u{1F49C}
487
487
  </p>
488
488
  </div>
489
489
  </div>
490
490
 
491
491
  <div className="rounded-xl border p-6 space-y-1">
492
- <p className="text-xs font-mono uppercase tracking-widest text-gray-400">next.js</p>
493
- <p className="font-mono text-sm text-gray-400">apps/web/layout.tsx</p>
494
- <p className="text-sm text-gray-400">Next.js rendering</p>
492
+ <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground">next.js</p>
493
+ <p className="font-mono text-sm text-muted-foreground">apps/web/layout.tsx</p>
494
+ <p className="text-sm text-muted-foreground">Next.js rendering</p>
495
495
  </div>
496
496
 
497
497
  <div className="rounded-xl border p-6 space-y-1">
498
- <p className="text-xs font-mono uppercase tracking-widest text-gray-400">nest.js</p>
499
- <p className="font-mono text-sm text-gray-400">GET /api/hello</p>
500
- <pre className="mt-2 border rounded-md p-3 text-sm bg-neutral-800">
498
+ <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground">nest.js</p>
499
+ <p className="font-mono text-sm text-muted-foreground">GET /api/hello</p>
500
+ <pre className="mt-2 border rounded-md p-3 text-sm bg-muted/50">
501
501
  <code>{JSON.stringify(hello, null, 2)}</code>
502
502
  </pre>
503
503
  </div>
504
504
 
505
505
  <div className="rounded-xl border p-6">
506
- <p className="text-xs font-mono uppercase tracking-widest text-gray-400 mb-4">better-auth</p>
506
+ <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground mb-4">better-auth</p>
507
507
  {isPending ? (
508
- <p className="text-sm text-gray-400">Loading\u2026</p>
508
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
509
509
  ) : session ? (
510
- <TodoList session={session} />
510
+ <div className="space-y-3">
511
+ <p className="text-sm">Signed in as {session.user.name}</p>
512
+ <Button onClick={() => void authClient.signOut()}>
513
+ Sign out
514
+ </Button>
515
+ </div>
511
516
  ) : (
512
517
  <AuthForm />
513
518
  )}
514
519
  </div>
515
520
 
516
521
  <div className="px-2 py-4 text-sm">
517
- <p className="mb-2 text-gray-400">Learn more</p>
522
+ <p className="mb-2 text-muted-foreground">Learn more</p>
518
523
  <ul className="list-disc list-inside space-y-2 mt-2">
519
524
  <li>
520
525
  <Link href="https://github.com">GitHub</Link>
@@ -582,7 +587,7 @@ export function AuthForm() {
582
587
  className={
583
588
  tab === 'signup'
584
589
  ? 'font-semibold'
585
- : 'text-gray-400 hover:text-gray-200'
590
+ : 'text-muted-foreground hover:text-gray-200'
586
591
  }
587
592
  >
588
593
  Sign up
@@ -593,7 +598,7 @@ export function AuthForm() {
593
598
  className={
594
599
  tab === 'signin'
595
600
  ? 'font-semibold'
596
- : 'text-gray-400 hover:text-gray-200'
601
+ : 'text-muted-foreground hover:text-gray-200'
597
602
  }
598
603
  >
599
604
  Sign in
@@ -617,7 +622,7 @@ export function AuthForm() {
617
622
  value={field.state.value}
618
623
  onChange={(e) => field.handleChange(e.target.value)}
619
624
  required
620
- className="flex h-9 w-full rounded-md border border-neutral-700 bg-neutral-800/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
625
+ className="flex h-9 w-full rounded-md border border-neutral-700 bg-muted/50/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
621
626
  />
622
627
  </div>
623
628
  )}
@@ -633,7 +638,7 @@ export function AuthForm() {
633
638
  value={field.state.value}
634
639
  onChange={(e) => field.handleChange(e.target.value)}
635
640
  required
636
- className="flex h-9 w-full rounded-md border border-neutral-700 bg-neutral-800/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
641
+ className="flex h-9 w-full rounded-md border border-neutral-700 bg-muted/50/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
637
642
  />
638
643
  </div>
639
644
  )}
@@ -649,7 +654,7 @@ export function AuthForm() {
649
654
  onChange={(e) => field.handleChange(e.target.value)}
650
655
  required
651
656
  minLength={8}
652
- className="flex h-9 w-full rounded-md border border-neutral-700 bg-neutral-800/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
657
+ className="flex h-9 w-full rounded-md border border-neutral-700 bg-muted/50/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
653
658
  />
654
659
  </div>
655
660
  )}
@@ -674,149 +679,6 @@ export function AuthForm() {
674
679
  }`
675
680
  };
676
681
 
677
- // src/web/templates/todo-list.ts
678
- var todo_list_default = {
679
- filename: "apps/web/app/todo-list.tsx",
680
- template: `'use client';
681
-
682
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
683
- import { useForm } from '@tanstack/react-form';
684
- import { authClient } from '@/lib/auth-client';
685
- import { Button } from '@<%= projectName %>/ui/button';
686
-
687
- interface Todo {
688
- id: number;
689
- title: string;
690
- done: boolean;
691
- }
692
-
693
- const api = {
694
- getTodos: (): Promise<Todo[]> => fetch('/api/todos').then((r) => r.json()),
695
- createTodo: (title: string): Promise<Todo> =>
696
- fetch('/api/todos', {
697
- method: 'POST',
698
- headers: { 'Content-Type': 'application/json' },
699
- body: JSON.stringify({ title }),
700
- }).then((r) => r.json()),
701
- toggleTodo: (id: number): Promise<Todo> =>
702
- fetch(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }).then((r) => r.json()),
703
- deleteTodo: (id: number): Promise<void> =>
704
- fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
705
- };
706
-
707
- export function TodoList({
708
- session,
709
- }: {
710
- session: { user: { email: string } };
711
- }) {
712
- const queryClient = useQueryClient();
713
-
714
- const { data: todos = [], isPending } = useQuery({
715
- queryKey: ['todos'],
716
- queryFn: api.getTodos,
717
- });
718
-
719
- const createMutation = useMutation({
720
- mutationFn: api.createTodo,
721
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
722
- });
723
-
724
- const toggleMutation = useMutation({
725
- mutationFn: api.toggleTodo,
726
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
727
- });
728
-
729
- const deleteMutation = useMutation({
730
- mutationFn: api.deleteTodo,
731
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
732
- });
733
-
734
- const form = useForm({
735
- defaultValues: { title: '' },
736
- onSubmit: async ({ value }) => {
737
- if (!value.title.trim()) return;
738
- createMutation.mutate(value.title.trim());
739
- form.reset();
740
- },
741
- });
742
-
743
- return (
744
- <>
745
- <div className="flex items-center justify-between mb-8">
746
- <h1 className="text-2xl font-semibold">Todos</h1>
747
- <div className="flex items-center gap-3 text-sm text-gray-400">
748
- <span>{session.user.email}</span>
749
- <button
750
- className="hover:text-gray-100"
751
- onClick={() => authClient.signOut()}
752
- >
753
- Sign out
754
- </button>
755
- </div>
756
- </div>
757
-
758
- <form
759
- onSubmit={(e) => {
760
- e.preventDefault();
761
- form.handleSubmit();
762
- }}
763
- className="flex gap-2 mb-6"
764
- >
765
- <form.Field name="title">
766
- {(field) => (
767
- <input
768
- value={field.state.value}
769
- onChange={(e) => field.handleChange(e.target.value)}
770
- placeholder="New todo\u2026"
771
- className="flex h-9 flex-1 rounded-md border border-neutral-700 bg-neutral-800/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
772
- />
773
- )}
774
- </form.Field>
775
-
776
- <form.Subscribe selector={(s) => s.isSubmitting}>
777
- {(isSubmitting) => (
778
- <Button type="submit" disabled={isSubmitting || createMutation.isPending}>
779
- Add
780
- </Button>
781
- )}
782
- </form.Subscribe>
783
- </form>
784
-
785
- {isPending ? (
786
- <p className="text-sm text-gray-400">Loading\u2026</p>
787
- ) : (
788
- <ul className="divide-y divide-neutral-700">
789
- {todos.map((todo) => (
790
- <li key={todo.id} className="flex items-center gap-3 py-3">
791
- <input
792
- type="checkbox"
793
- checked={todo.done}
794
- onChange={() => toggleMutation.mutate(todo.id)}
795
- />
796
- <span
797
- className={\`flex-1 text-sm \${todo.done ? 'line-through text-gray-400' : ''}\`}
798
- >
799
- {todo.title}
800
- </span>
801
- <button
802
- onClick={() => deleteMutation.mutate(todo.id)}
803
- className="text-gray-600 hover:text-red-400 text-lg leading-none"
804
- >
805
- \xD7
806
- </button>
807
- </li>
808
- ))}
809
- </ul>
810
- )}
811
-
812
- {!isPending && todos.length === 0 && (
813
- <p className="text-sm text-gray-400">No todos yet.</p>
814
- )}
815
- </>
816
- );
817
- }`
818
- };
819
-
820
682
  // src/web/templates/auth-route.ts
821
683
  var auth_route_default = {
822
684
  filename: "apps/web/app/api/auth/[...all]/route.ts",
@@ -874,7 +736,6 @@ var web = {
874
736
  page_default,
875
737
  providers_default,
876
738
  auth_form_default,
877
- todo_list_default,
878
739
  auth_route_default,
879
740
  auth_client_default,
880
741
  manifest_default
@@ -1047,10 +908,8 @@ var app_module_default = {
1047
908
  filename: "apps/api/src/app.module.ts",
1048
909
  template: `import { Module } from '@nestjs/common';
1049
910
  import { AppService } from './app.service';
1050
- import { TodosModule } from './todos/todos.module';
1051
911
 
1052
912
  @Module({
1053
- imports: [TodosModule],
1054
913
  providers: [AppService],
1055
914
  })
1056
915
  export class AppModule {}`
@@ -1092,83 +951,6 @@ describe('AppService', () => {
1092
951
  });`
1093
952
  };
1094
953
 
1095
- // src/api/templates/todos-module.ts
1096
- var todos_module_default = {
1097
- filename: "apps/api/src/todos/todos.module.ts",
1098
- template: `import { Module } from '@nestjs/common';
1099
- import { TodosService } from './todos.service';
1100
-
1101
- @Module({
1102
- providers: [TodosService],
1103
- exports: [TodosService],
1104
- })
1105
- export class TodosModule {}`
1106
- };
1107
-
1108
- // src/api/templates/todos-service.ts
1109
- var todos_service_default = {
1110
- filename: "apps/api/src/todos/todos.service.ts",
1111
- template: `import { Injectable, NotFoundException } from '@nestjs/common';
1112
-
1113
- export interface Todo {
1114
- id: number;
1115
- title: string;
1116
- done: boolean;
1117
- }
1118
-
1119
- @Injectable()
1120
- export class TodosService {
1121
- private todos: Todo[] = [];
1122
- private nextId = 1;
1123
-
1124
- findAll(): Todo[] {
1125
- return this.todos;
1126
- }
1127
-
1128
- create(title: string): Todo {
1129
- const todo: Todo = { id: this.nextId++, title, done: false };
1130
- this.todos.push(todo);
1131
- return todo;
1132
- }
1133
-
1134
- toggle(id: number): Todo {
1135
- const todo = this.todos.find((t) => t.id === id);
1136
- if (!todo) throw new NotFoundException(\`Todo \${id} not found\`);
1137
- todo.done = !todo.done;
1138
- return todo;
1139
- }
1140
-
1141
- remove(id: number): void {
1142
- const index = this.todos.findIndex((t) => t.id === id);
1143
- if (index === -1) throw new NotFoundException(\`Todo \${id} not found\`);
1144
- this.todos.splice(index, 1);
1145
- }
1146
- }`
1147
- };
1148
-
1149
- // src/api/templates/todos-service-spec.ts
1150
- var todos_service_spec_default = {
1151
- filename: "apps/api/src/todos/todos.service.spec.ts",
1152
- template: `import { Test, TestingModule } from '@nestjs/testing';
1153
- import { TodosService } from './todos.service';
1154
-
1155
- describe('TodosService', () => {
1156
- let service: TodosService;
1157
-
1158
- beforeEach(async () => {
1159
- const module: TestingModule = await Test.createTestingModule({
1160
- providers: [TodosService],
1161
- }).compile();
1162
-
1163
- service = module.get<TodosService>(TodosService);
1164
- });
1165
-
1166
- it('should be defined', () => {
1167
- expect(service).toBeDefined();
1168
- });
1169
- });`
1170
- };
1171
-
1172
954
  // src/api/templates/readme.ts
1173
955
  var readme_default3 = {
1174
956
  filename: "apps/api/README.md",
@@ -1197,9 +979,6 @@ var api = {
1197
979
  app_module_default,
1198
980
  app_service_default,
1199
981
  app_service_spec_default,
1200
- todos_module_default,
1201
- todos_service_default,
1202
- todos_service_spec_default,
1203
982
  readme_default3
1204
983
  ]
1205
984
  };
@@ -1481,6 +1260,7 @@ var globals_css_default = {
1481
1260
  :root {
1482
1261
  --background: oklch(0.145 0 0);
1483
1262
  --foreground: oklch(0.985 0 0);
1263
+ --muted: oklch(0.269 0 0);
1484
1264
  --muted-foreground: oklch(0.708 0 0);
1485
1265
  --border: oklch(0.469 0 0);
1486
1266
  }
@@ -1488,6 +1268,7 @@ var globals_css_default = {
1488
1268
  @theme inline {
1489
1269
  --color-background: var(--background);
1490
1270
  --color-foreground: var(--foreground);
1271
+ --color-muted: var(--muted);
1491
1272
  --color-muted-foreground: var(--muted-foreground);
1492
1273
  --color-border: var(--border);
1493
1274
  }
@@ -1943,9 +1724,9 @@ var page_default2 = {
1943
1724
  import { useQuery } from '@tanstack/react-query';
1944
1725
  import { authClient } from '@/lib/auth-client';
1945
1726
  import { AuthForm } from '@/app/auth-form';
1727
+ import { Button } from '@<%= projectName %>/ui/button';
1946
1728
  import { Link } from '@<%= projectName %>/ui/link';
1947
1729
  import { Logo } from '@<%= projectName %>/ui/logo';
1948
- import { TodoList } from '@/app/todo-list';
1949
1730
  import { ModeToggle } from '@<%= projectName %>/ui/mode-toggle';
1950
1731
  import { Card, CardContent, CardHeader, CardTitle } from '@<%= projectName %>/ui/card';
1951
1732
 
@@ -2013,7 +1794,12 @@ export default function Home() {
2013
1794
  {isPending ? (
2014
1795
  <p className="text-sm text-muted-foreground">Loading\u2026</p>
2015
1796
  ) : session ? (
2016
- <TodoList session={session} />
1797
+ <div className="space-y-3">
1798
+ <p className="text-sm">Signed in as {session.user.name}</p>
1799
+ <Button variant="outline" onClick={() => authClient.signOut()}>
1800
+ Sign out
1801
+ </Button>
1802
+ </div>
2017
1803
  ) : (
2018
1804
  <AuthForm />
2019
1805
  )}
@@ -2185,169 +1971,22 @@ export function AuthForm() {
2185
1971
  }`
2186
1972
  };
2187
1973
 
2188
- // src/shadcn-ui/templates/todo-list.ts
2189
- var todo_list_default2 = {
2190
- filename: "apps/web/app/todo-list.tsx",
2191
- template: `'use client';
1974
+ // src/shadcn-ui/templates/accordion.ts
1975
+ var accordion_default = {
1976
+ filename: "packages/ui/src/components/accordion.tsx",
1977
+ template: `import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
2192
1978
 
2193
- import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
2194
- import { useForm } from '@tanstack/react-form';
2195
- import { authClient } from '@/lib/auth-client';
2196
- import { Button } from '@<%= projectName %>/ui/button';
2197
- import { Input } from '@<%= projectName %>/ui/input';
2198
- import { Checkbox } from '@<%= projectName %>/ui/checkbox';
1979
+ import { cn } from "@<%= projectName %>/ui/lib/utils"
1980
+ import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
2199
1981
 
2200
- interface Todo {
2201
- id: number;
2202
- title: string;
2203
- done: boolean;
2204
- }
2205
-
2206
- const api = {
2207
- getTodos: (): Promise<Todo[]> => fetch('/api/todos').then((r) => r.json()),
2208
- createTodo: (title: string): Promise<Todo> =>
2209
- fetch('/api/todos', {
2210
- method: 'POST',
2211
- headers: { 'Content-Type': 'application/json' },
2212
- body: JSON.stringify({ title }),
2213
- }).then((r) => r.json()),
2214
- toggleTodo: (id: number): Promise<Todo> =>
2215
- fetch(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }).then((r) => r.json()),
2216
- deleteTodo: (id: number): Promise<void> =>
2217
- fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
2218
- };
2219
-
2220
- export function TodoList({
2221
- session,
2222
- }: {
2223
- session: { user: { email: string } };
2224
- }) {
2225
- const queryClient = useQueryClient();
2226
-
2227
- const { data: todos = [], isPending } = useQuery({
2228
- queryKey: ['todos'],
2229
- queryFn: api.getTodos,
2230
- });
2231
-
2232
- const createMutation = useMutation({
2233
- mutationFn: api.createTodo,
2234
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
2235
- });
2236
-
2237
- const toggleMutation = useMutation({
2238
- mutationFn: api.toggleTodo,
2239
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
2240
- });
2241
-
2242
- const deleteMutation = useMutation({
2243
- mutationFn: api.deleteTodo,
2244
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
2245
- });
2246
-
2247
- const form = useForm({
2248
- defaultValues: { title: '' },
2249
- onSubmit: async ({ value }) => {
2250
- if (!value.title.trim()) return;
2251
- createMutation.mutate(value.title.trim());
2252
- form.reset();
2253
- },
2254
- });
2255
-
2256
- return (
2257
- <>
2258
- <div className="flex items-center justify-between mb-6">
2259
- <h1 className="text-2xl font-semibold">Todos</h1>
2260
- <div className="flex items-center gap-3 text-sm text-muted-foreground">
2261
- <span>{session.user.email}</span>
2262
- <Button
2263
- variant="ghost"
2264
- size="sm"
2265
- onClick={() => authClient.signOut()}
2266
- >
2267
- Sign out
2268
- </Button>
2269
- </div>
2270
- </div>
2271
-
2272
- <form
2273
- onSubmit={(e) => {
2274
- e.preventDefault();
2275
- form.handleSubmit();
2276
- }}
2277
- className="flex gap-2 mb-6"
2278
- >
2279
- <form.Field name="title">
2280
- {(field) => (
2281
- <Input
2282
- value={field.state.value}
2283
- onChange={(e) => field.handleChange(e.target.value)}
2284
- placeholder="New todo\u2026"
2285
- className="flex-1"
2286
- />
2287
- )}
2288
- </form.Field>
2289
-
2290
- <form.Subscribe selector={(s) => s.isSubmitting}>
2291
- {(isSubmitting) => (
2292
- <Button type="submit" disabled={isSubmitting || createMutation.isPending}>
2293
- Add
2294
- </Button>
2295
- )}
2296
- </form.Subscribe>
2297
- </form>
2298
-
2299
- {isPending ? (
2300
- <p className="text-sm text-muted-foreground">Loading\u2026</p>
2301
- ) : (
2302
- <ul className="divide-y divide-border">
2303
- {todos.map((todo) => (
2304
- <li key={todo.id} className="flex items-center gap-3 py-3">
2305
- <Checkbox
2306
- checked={todo.done}
2307
- onCheckedChange={() => toggleMutation.mutate(todo.id)}
2308
- />
2309
- <span
2310
- className={\`flex-1 text-sm \${todo.done ? 'line-through text-muted-foreground' : ''}\`}
2311
- >
2312
- {todo.title}
2313
- </span>
2314
- <Button
2315
- variant="ghost"
2316
- size="sm"
2317
- onClick={() => deleteMutation.mutate(todo.id)}
2318
- className="text-muted-foreground hover:text-destructive h-7 w-7 p-0"
2319
- >
2320
- \xD7
2321
- </Button>
2322
- </li>
2323
- ))}
2324
- </ul>
2325
- )}
2326
-
2327
- {!isPending && todos.length === 0 && (
2328
- <p className="text-sm text-muted-foreground">No todos yet.</p>
2329
- )}
2330
- </>
2331
- );
2332
- }`
2333
- };
2334
-
2335
- // src/shadcn-ui/templates/accordion.ts
2336
- var accordion_default = {
2337
- filename: "packages/ui/src/components/accordion.tsx",
2338
- template: `import { Accordion as AccordionPrimitive } from "@base-ui/react/accordion"
2339
-
2340
- import { cn } from "@<%= projectName %>/ui/lib/utils"
2341
- import { ChevronDownIcon, ChevronUpIcon } from "lucide-react"
2342
-
2343
- function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
2344
- return (
2345
- <AccordionPrimitive.Root
2346
- data-slot="accordion"
2347
- className={cn("flex w-full flex-col", className)}
2348
- {...props}
2349
- />
2350
- )
1982
+ function Accordion({ className, ...props }: AccordionPrimitive.Root.Props) {
1983
+ return (
1984
+ <AccordionPrimitive.Root
1985
+ data-slot="accordion"
1986
+ className={cn("flex w-full flex-col", className)}
1987
+ {...props}
1988
+ />
1989
+ )
2351
1990
  }
2352
1991
 
2353
1992
  function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
@@ -9333,7 +8972,6 @@ var shadcnUi = {
9333
8972
  page_default2,
9334
8973
  providers_default2,
9335
8974
  auth_form_default2,
9336
- todo_list_default2,
9337
8975
  accordion_default,
9338
8976
  alert_dialog_default,
9339
8977
  alert_default,
@@ -10444,54 +10082,6 @@ export async function inject<T>(token: Type<T> | Abstract<T> | string | symbol):
10444
10082
  }`
10445
10083
  };
10446
10084
 
10447
- // src/nest-di-only/templates/web-todos-route.ts
10448
- var web_todos_route_default = {
10449
- filename: "apps/web/app/api/todos/route.ts",
10450
- template: `import { NextResponse } from 'next/server';
10451
- import { inject } from '@/lib/nest';
10452
- import { TodosService } from '@<%= projectName %>/api';
10453
-
10454
- export async function GET() {
10455
- const todos = await inject(TodosService);
10456
- return NextResponse.json(todos.findAll());
10457
- }
10458
-
10459
- export async function POST(req: Request) {
10460
- const { title } = await req.json();
10461
- const todos = await inject(TodosService);
10462
- return NextResponse.json(todos.create(title), { status: 201 });
10463
- }`
10464
- };
10465
-
10466
- // src/nest-di-only/templates/web-todos-id-route.ts
10467
- var web_todos_id_route_default = {
10468
- filename: "apps/web/app/api/todos/[id]/route.ts",
10469
- template: `import { NextResponse } from 'next/server';
10470
- import { inject } from '@/lib/nest';
10471
- import { TodosService } from '@<%= projectName %>/api';
10472
-
10473
- export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
10474
- const { id } = await params;
10475
- const todos = await inject(TodosService);
10476
- todos.remove(Number(id));
10477
- return new NextResponse(null, { status: 204 });
10478
- }`
10479
- };
10480
-
10481
- // src/nest-di-only/templates/web-todos-toggle-route.ts
10482
- var web_todos_toggle_route_default = {
10483
- filename: "apps/web/app/api/todos/[id]/toggle/route.ts",
10484
- template: `import { NextResponse } from 'next/server';
10485
- import { inject } from '@/lib/nest';
10486
- import { TodosService } from '@<%= projectName %>/api';
10487
-
10488
- export async function PATCH(_req: Request, { params }: { params: Promise<{ id: string }> }) {
10489
- const { id } = await params;
10490
- const todos = await inject(TodosService);
10491
- return NextResponse.json(todos.toggle(Number(id)));
10492
- }`
10493
- };
10494
-
10495
10085
  // src/nest-di-only/templates/web-package-json.ts
10496
10086
  var web_package_json_default2 = {
10497
10087
  filename: "apps/web/package.json",
@@ -10613,9 +10203,6 @@ var nestDiOnly = {
10613
10203
  api_index_default,
10614
10204
  web_nest_context_default,
10615
10205
  web_hello_route_default,
10616
- web_todos_route_default,
10617
- web_todos_id_route_default,
10618
- web_todos_toggle_route_default,
10619
10206
  web_package_json_default2,
10620
10207
  web_next_config_default3,
10621
10208
  web_tsconfig_default
@@ -10682,10 +10269,9 @@ import { AuthGuard, AuthModule } from '@thallesp/nestjs-better-auth';
10682
10269
  import { auth } from '@<%= projectName %>/auth';
10683
10270
  import { AppController } from './app.controller';
10684
10271
  import { AppService } from './app.service';
10685
- import { TodosModule } from './todos/todos.module';
10686
10272
 
10687
10273
  @Module({
10688
- imports: [AuthModule.forRoot({ auth }), TodosModule],
10274
+ imports: [AuthModule.forRoot({ auth })],
10689
10275
  controllers: [AppController],
10690
10276
  providers: [AppService, { provide: APP_GUARD, useClass: AuthGuard }],
10691
10277
  })
@@ -10717,72 +10303,720 @@ async function bootstrap() {
10717
10303
  void bootstrap();`
10718
10304
  };
10719
10305
 
10720
- // src/api-controllers/templates/todos-controller.ts
10721
- var todos_controller_default = {
10722
- filename: "apps/api/src/todos/todos.controller.ts",
10723
- template: `import {
10724
- Body,
10725
- Controller,
10726
- Delete,
10727
- Get,
10728
- Param,
10729
- Patch,
10730
- Post,
10731
- } from '@nestjs/common';
10732
- import { TodosService } from './todos.service';
10733
-
10734
- @Controller('todos')
10735
- export class TodosController {
10736
- constructor(private readonly todosService: TodosService) {}
10737
-
10738
- @Get()
10739
- findAll() {
10740
- return this.todosService.findAll();
10741
- }
10742
-
10743
- @Post()
10744
- create(@Body('title') title: string) {
10745
- return this.todosService.create(title);
10746
- }
10747
-
10748
- @Patch(':id/toggle')
10749
- toggle(@Param('id') id: string) {
10750
- return this.todosService.toggle(Number(id));
10751
- }
10752
-
10753
- @Delete(':id')
10754
- remove(@Param('id') id: string) {
10755
- return this.todosService.remove(Number(id));
10756
- }
10757
- }`
10306
+ // src/api-controllers/index.ts
10307
+ var apiControllers = {
10308
+ templates: [
10309
+ app_controller_default,
10310
+ app_controller_spec_default,
10311
+ app_module_default2,
10312
+ main_default
10313
+ ]
10758
10314
  };
10315
+ var api_controllers_default = apiControllers;
10759
10316
 
10760
- // src/api-controllers/templates/todos-module.ts
10761
- var todos_module_default2 = {
10317
+ // src/todo-example/templates/todos-module.ts
10318
+ var todos_module_default = {
10762
10319
  filename: "apps/api/src/todos/todos.module.ts",
10763
10320
  template: `import { Module } from '@nestjs/common';
10764
- import { TodosController } from './todos.controller';
10765
10321
  import { TodosService } from './todos.service';
10766
10322
 
10767
10323
  @Module({
10768
- controllers: [TodosController],
10769
10324
  providers: [TodosService],
10325
+ exports: [TodosService],
10770
10326
  })
10771
10327
  export class TodosModule {}`
10772
10328
  };
10773
10329
 
10774
- // src/api-controllers/index.ts
10775
- var apiControllers = {
10776
- templates: [
10777
- app_controller_default,
10778
- app_controller_spec_default,
10779
- app_module_default2,
10780
- main_default,
10781
- todos_controller_default,
10782
- todos_module_default2
10783
- ]
10784
- };
10785
- var api_controllers_default = apiControllers;
10330
+ // src/todo-example/templates/todos-service.ts
10331
+ var todos_service_default = {
10332
+ filename: "apps/api/src/todos/todos.service.ts",
10333
+ template: `import { Injectable, NotFoundException } from '@nestjs/common';
10334
+
10335
+ export interface Todo {
10336
+ id: number;
10337
+ title: string;
10338
+ done: boolean;
10339
+ }
10340
+
10341
+ @Injectable()
10342
+ export class TodosService {
10343
+ private todos: Todo[] = [];
10344
+ private nextId = 1;
10345
+
10346
+ findAll(): Todo[] {
10347
+ return this.todos;
10348
+ }
10349
+
10350
+ create(title: string): Todo {
10351
+ const todo: Todo = { id: this.nextId++, title, done: false };
10352
+ this.todos.push(todo);
10353
+ return todo;
10354
+ }
10355
+
10356
+ toggle(id: number): Todo {
10357
+ const todo = this.todos.find((t) => t.id === id);
10358
+ if (!todo) throw new NotFoundException(\`Todo \${id} not found\`);
10359
+ todo.done = !todo.done;
10360
+ return todo;
10361
+ }
10362
+
10363
+ remove(id: number): void {
10364
+ const index = this.todos.findIndex((t) => t.id === id);
10365
+ if (index === -1) throw new NotFoundException(\`Todo \${id} not found\`);
10366
+ this.todos.splice(index, 1);
10367
+ }
10368
+ }`
10369
+ };
10370
+
10371
+ // src/todo-example/templates/todos-service-spec.ts
10372
+ var todos_service_spec_default = {
10373
+ filename: "apps/api/src/todos/todos.service.spec.ts",
10374
+ template: `import { Test, TestingModule } from '@nestjs/testing';
10375
+ import { TodosService } from './todos.service';
10376
+
10377
+ describe('TodosService', () => {
10378
+ let service: TodosService;
10379
+
10380
+ beforeEach(async () => {
10381
+ const module: TestingModule = await Test.createTestingModule({
10382
+ providers: [TodosService],
10383
+ }).compile();
10384
+
10385
+ service = module.get<TodosService>(TodosService);
10386
+ });
10387
+
10388
+ it('should be defined', () => {
10389
+ expect(service).toBeDefined();
10390
+ });
10391
+ });`
10392
+ };
10393
+
10394
+ // src/todo-example/templates/todos-controller.ts
10395
+ var todos_controller_default = {
10396
+ filename: "apps/api/src/todos/todos.controller.ts",
10397
+ template: `import {
10398
+ Body,
10399
+ Controller,
10400
+ Delete,
10401
+ Get,
10402
+ Param,
10403
+ Patch,
10404
+ Post,
10405
+ } from '@nestjs/common';
10406
+ import { TodosService } from './todos.service';
10407
+
10408
+ @Controller('todos')
10409
+ export class TodosController {
10410
+ constructor(private readonly todosService: TodosService) {}
10411
+
10412
+ @Get()
10413
+ findAll() {
10414
+ return this.todosService.findAll();
10415
+ }
10416
+
10417
+ @Post()
10418
+ create(@Body('title') title: string) {
10419
+ return this.todosService.create(title);
10420
+ }
10421
+
10422
+ @Patch(':id/toggle')
10423
+ toggle(@Param('id') id: string) {
10424
+ return this.todosService.toggle(Number(id));
10425
+ }
10426
+
10427
+ @Delete(':id')
10428
+ remove(@Param('id') id: string) {
10429
+ return this.todosService.remove(Number(id));
10430
+ }
10431
+ }`
10432
+ };
10433
+
10434
+ // src/todo-example/templates/todos-module-controllers.ts
10435
+ var todos_module_controllers_default = {
10436
+ filename: "apps/api/src/todos/todos.module.ts",
10437
+ template: `import { Module } from '@nestjs/common';
10438
+ import { TodosController } from './todos.controller';
10439
+ import { TodosService } from './todos.service';
10440
+
10441
+ @Module({
10442
+ controllers: [TodosController],
10443
+ providers: [TodosService],
10444
+ })
10445
+ export class TodosModule {}`
10446
+ };
10447
+
10448
+ // src/todo-example/templates/app-module-controllers.ts
10449
+ var app_module_controllers_default = {
10450
+ filename: "apps/api/src/app.module.ts",
10451
+ template: `import { Module } from '@nestjs/common';
10452
+ import { APP_GUARD } from '@nestjs/core';
10453
+ import { AuthGuard, AuthModule } from '@thallesp/nestjs-better-auth';
10454
+ import { auth } from '@<%= projectName %>/auth';
10455
+ import { AppController } from './app.controller';
10456
+ import { AppService } from './app.service';
10457
+ import { TodosModule } from './todos/todos.module';
10458
+
10459
+ @Module({
10460
+ imports: [AuthModule.forRoot({ auth }), TodosModule],
10461
+ controllers: [AppController],
10462
+ providers: [AppService, { provide: APP_GUARD, useClass: AuthGuard }],
10463
+ })
10464
+ export class AppModule {}`
10465
+ };
10466
+
10467
+ // src/todo-example/templates/app-module-di.ts
10468
+ var app_module_di_default = {
10469
+ filename: "apps/api/src/app.module.ts",
10470
+ template: `import { Module } from '@nestjs/common';
10471
+ import { AppService } from './app.service';
10472
+ import { TodosModule } from './todos/todos.module';
10473
+
10474
+ @Module({
10475
+ imports: [TodosModule],
10476
+ providers: [AppService],
10477
+ })
10478
+ export class AppModule {}`
10479
+ };
10480
+
10481
+ // src/todo-example/templates/web-todos-route.ts
10482
+ var web_todos_route_default = {
10483
+ filename: "apps/web/app/api/todos/route.ts",
10484
+ template: `import { NextResponse } from 'next/server';
10485
+ import { inject } from '@/lib/nest';
10486
+ import { TodosService } from '@<%= projectName %>/api';
10487
+
10488
+ export async function GET() {
10489
+ const todos = await inject(TodosService);
10490
+ return NextResponse.json(todos.findAll());
10491
+ }
10492
+
10493
+ export async function POST(req: Request) {
10494
+ const { title } = await req.json();
10495
+ const todos = await inject(TodosService);
10496
+ return NextResponse.json(todos.create(title), { status: 201 });
10497
+ }`
10498
+ };
10499
+
10500
+ // src/todo-example/templates/web-todos-id-route.ts
10501
+ var web_todos_id_route_default = {
10502
+ filename: "apps/web/app/api/todos/[id]/route.ts",
10503
+ template: `import { NextResponse } from 'next/server';
10504
+ import { inject } from '@/lib/nest';
10505
+ import { TodosService } from '@<%= projectName %>/api';
10506
+
10507
+ export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
10508
+ const { id } = await params;
10509
+ const todos = await inject(TodosService);
10510
+ todos.remove(Number(id));
10511
+ return new NextResponse(null, { status: 204 });
10512
+ }`
10513
+ };
10514
+
10515
+ // src/todo-example/templates/web-todos-toggle-route.ts
10516
+ var web_todos_toggle_route_default = {
10517
+ filename: "apps/web/app/api/todos/[id]/toggle/route.ts",
10518
+ template: `import { NextResponse } from 'next/server';
10519
+ import { inject } from '@/lib/nest';
10520
+ import { TodosService } from '@<%= projectName %>/api';
10521
+
10522
+ export async function PATCH(_req: Request, { params }: { params: Promise<{ id: string }> }) {
10523
+ const { id } = await params;
10524
+ const todos = await inject(TodosService);
10525
+ return NextResponse.json(todos.toggle(Number(id)));
10526
+ }`
10527
+ };
10528
+
10529
+ // src/todo-example/templates/web-todo-list.ts
10530
+ var web_todo_list_default = {
10531
+ filename: "apps/web/app/todo-list.tsx",
10532
+ template: `'use client';
10533
+
10534
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
10535
+ import { useForm } from '@tanstack/react-form';
10536
+ import { authClient } from '@/lib/auth-client';
10537
+ import { Button } from '@<%= projectName %>/ui/button';
10538
+
10539
+ interface Todo {
10540
+ id: number;
10541
+ title: string;
10542
+ done: boolean;
10543
+ }
10544
+
10545
+ const api = {
10546
+ getTodos: (): Promise<Todo[]> => fetch('/api/todos').then((r) => r.json()),
10547
+ createTodo: (title: string): Promise<Todo> =>
10548
+ fetch('/api/todos', {
10549
+ method: 'POST',
10550
+ headers: { 'Content-Type': 'application/json' },
10551
+ body: JSON.stringify({ title }),
10552
+ }).then((r) => r.json()),
10553
+ toggleTodo: (id: number): Promise<Todo> =>
10554
+ fetch(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }).then((r) => r.json()),
10555
+ deleteTodo: (id: number): Promise<void> =>
10556
+ fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
10557
+ };
10558
+
10559
+ export function TodoList({
10560
+ session,
10561
+ }: {
10562
+ session: { user: { email: string } };
10563
+ }) {
10564
+ const queryClient = useQueryClient();
10565
+
10566
+ const { data: todos = [], isPending } = useQuery({
10567
+ queryKey: ['todos'],
10568
+ queryFn: api.getTodos,
10569
+ });
10570
+
10571
+ const createMutation = useMutation({
10572
+ mutationFn: api.createTodo,
10573
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
10574
+ });
10575
+
10576
+ const toggleMutation = useMutation({
10577
+ mutationFn: api.toggleTodo,
10578
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
10579
+ });
10580
+
10581
+ const deleteMutation = useMutation({
10582
+ mutationFn: api.deleteTodo,
10583
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
10584
+ });
10585
+
10586
+ const form = useForm({
10587
+ defaultValues: { title: '' },
10588
+ onSubmit: async ({ value }) => {
10589
+ if (!value.title.trim()) return;
10590
+ createMutation.mutate(value.title.trim());
10591
+ form.reset();
10592
+ },
10593
+ });
10594
+
10595
+ return (
10596
+ <>
10597
+ <div className="flex items-center justify-between mb-8">
10598
+ <h1 className="text-2xl font-semibold">Todos</h1>
10599
+ <div className="flex items-center gap-3 text-sm text-muted-foreground">
10600
+ <span>{session.user.email}</span>
10601
+ <button
10602
+ className="hover:text-gray-100"
10603
+ onClick={() => authClient.signOut()}
10604
+ >
10605
+ Sign out
10606
+ </button>
10607
+ </div>
10608
+ </div>
10609
+
10610
+ <form
10611
+ onSubmit={(e) => {
10612
+ e.preventDefault();
10613
+ form.handleSubmit();
10614
+ }}
10615
+ className="flex gap-2 mb-6"
10616
+ >
10617
+ <form.Field name="title">
10618
+ {(field) => (
10619
+ <input
10620
+ value={field.state.value}
10621
+ onChange={(e) => field.handleChange(e.target.value)}
10622
+ placeholder="New todo\u2026"
10623
+ className="flex h-9 flex-1 rounded-md border border-neutral-700 bg-muted/50/50 px-3 py-1 text-sm shadow-xs transition-[color,box-shadow] outline-none placeholder:text-neutral-500 focus-visible:border-neutral-500 focus-visible:ring-2 focus-visible:ring-neutral-500/20"
10624
+ />
10625
+ )}
10626
+ </form.Field>
10627
+
10628
+ <form.Subscribe selector={(s) => s.isSubmitting}>
10629
+ {(isSubmitting) => (
10630
+ <Button type="submit" disabled={isSubmitting || createMutation.isPending}>
10631
+ Add
10632
+ </Button>
10633
+ )}
10634
+ </form.Subscribe>
10635
+ </form>
10636
+
10637
+ {isPending ? (
10638
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
10639
+ ) : (
10640
+ <ul className="divide-y divide-neutral-700">
10641
+ {todos.map((todo) => (
10642
+ <li key={todo.id} className="flex items-center gap-3 py-3">
10643
+ <input
10644
+ type="checkbox"
10645
+ checked={todo.done}
10646
+ onChange={() => toggleMutation.mutate(todo.id)}
10647
+ />
10648
+ <span
10649
+ className={\`flex-1 text-sm \${todo.done ? 'line-through text-muted-foreground' : ''}\`}
10650
+ >
10651
+ {todo.title}
10652
+ </span>
10653
+ <button
10654
+ onClick={() => deleteMutation.mutate(todo.id)}
10655
+ className="text-gray-600 hover:text-red-400 text-lg leading-none"
10656
+ >
10657
+ \xD7
10658
+ </button>
10659
+ </li>
10660
+ ))}
10661
+ </ul>
10662
+ )}
10663
+
10664
+ {!isPending && todos.length === 0 && (
10665
+ <p className="text-sm text-muted-foreground">No todos yet.</p>
10666
+ )}
10667
+ </>
10668
+ );
10669
+ }`
10670
+ };
10671
+
10672
+ // src/todo-example/templates/web-page.ts
10673
+ var web_page_default = {
10674
+ filename: "apps/web/app/page.tsx",
10675
+ template: `'use client';
10676
+
10677
+ import { useQuery } from '@tanstack/react-query';
10678
+ import { authClient } from '@/lib/auth-client';
10679
+ import { AuthForm } from '@/app/auth-form';
10680
+ import { Link } from '@<%= projectName %>/ui/link';
10681
+ import { Logo } from '@<%= projectName %>/ui/logo';
10682
+ import { TodoList } from '@/app/todo-list';
10683
+
10684
+ export default function Home() {
10685
+ const { data: session, isPending } = authClient.useSession();
10686
+
10687
+ const { data: hello } = useQuery({
10688
+ queryKey: ['hello'],
10689
+ queryFn: () => fetch('/api/hello').then((r) => r.json()),
10690
+ });
10691
+
10692
+ return (
10693
+ <main className="max-w-lg mx-auto min-h-full px-4 py-8 space-y-4">
10694
+ <div className="pb-4 border-b">
10695
+ <p className="font-mono text-sm text-muted-foreground">apps/web/page.tsx</p>
10696
+ <p className="text-sm text-muted-foreground">Delete me to get started!</p>
10697
+ </div>
10698
+
10699
+ <div className="flex items-center gap-3 py-2">
10700
+ <Logo className="w-10 h-auto text-foreground" />
10701
+ <div>
10702
+ <h1 className="text-4xl font-black tracking-tight"><%= projectName %></h1>
10703
+ <p className="text-sm text-muted-foreground tracking-widest uppercase">
10704
+ Next + Nest = Newt \u{1F49C}
10705
+ </p>
10706
+ </div>
10707
+ </div>
10708
+
10709
+ <div className="rounded-xl border p-6 space-y-1">
10710
+ <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground">next.js</p>
10711
+ <p className="font-mono text-sm text-muted-foreground">apps/web/layout.tsx</p>
10712
+ <p className="text-sm text-muted-foreground">Next.js rendering</p>
10713
+ </div>
10714
+
10715
+ <div className="rounded-xl border p-6 space-y-1">
10716
+ <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground">nest.js</p>
10717
+ <p className="font-mono text-sm text-muted-foreground">GET /api/hello</p>
10718
+ <pre className="mt-2 border rounded-md p-3 text-sm bg-muted/50">
10719
+ <code>{JSON.stringify(hello, null, 2)}</code>
10720
+ </pre>
10721
+ </div>
10722
+
10723
+ <div className="rounded-xl border p-6">
10724
+ <p className="text-xs font-mono uppercase tracking-widest text-muted-foreground mb-4">better-auth</p>
10725
+ {isPending ? (
10726
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
10727
+ ) : session ? (
10728
+ <TodoList session={session} />
10729
+ ) : (
10730
+ <AuthForm />
10731
+ )}
10732
+ </div>
10733
+
10734
+ <div className="px-2 py-4 text-sm">
10735
+ <p className="mb-2 text-muted-foreground">Learn more</p>
10736
+ <ul className="list-disc list-inside space-y-2 mt-2">
10737
+ <li>
10738
+ <Link href="https://github.com">GitHub</Link>
10739
+ </li>
10740
+ <li>
10741
+ <Link href="https://nextjs.org">Next.js</Link>
10742
+ </li>
10743
+ <li>
10744
+ <Link href="https://nestjs.com">NestJS</Link>
10745
+ </li>
10746
+ </ul>
10747
+ </div>
10748
+ </main>
10749
+ );
10750
+ }`
10751
+ };
10752
+
10753
+ // src/todo-example/templates/shadcn-todo-list.ts
10754
+ var shadcn_todo_list_default = {
10755
+ filename: "apps/web/app/todo-list.tsx",
10756
+ template: `'use client';
10757
+
10758
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
10759
+ import { useForm } from '@tanstack/react-form';
10760
+ import { authClient } from '@/lib/auth-client';
10761
+ import { Button } from '@<%= projectName %>/ui/button';
10762
+ import { Input } from '@<%= projectName %>/ui/input';
10763
+ import { Checkbox } from '@<%= projectName %>/ui/checkbox';
10764
+
10765
+ interface Todo {
10766
+ id: number;
10767
+ title: string;
10768
+ done: boolean;
10769
+ }
10770
+
10771
+ const api = {
10772
+ getTodos: (): Promise<Todo[]> => fetch('/api/todos').then((r) => r.json()),
10773
+ createTodo: (title: string): Promise<Todo> =>
10774
+ fetch('/api/todos', {
10775
+ method: 'POST',
10776
+ headers: { 'Content-Type': 'application/json' },
10777
+ body: JSON.stringify({ title }),
10778
+ }).then((r) => r.json()),
10779
+ toggleTodo: (id: number): Promise<Todo> =>
10780
+ fetch(\`/api/todos/\${id}/toggle\`, { method: 'PATCH' }).then((r) => r.json()),
10781
+ deleteTodo: (id: number): Promise<void> =>
10782
+ fetch(\`/api/todos/\${id}\`, { method: 'DELETE' }).then(() => undefined),
10783
+ };
10784
+
10785
+ export function TodoList({
10786
+ session,
10787
+ }: {
10788
+ session: { user: { email: string } };
10789
+ }) {
10790
+ const queryClient = useQueryClient();
10791
+
10792
+ const { data: todos = [], isPending } = useQuery({
10793
+ queryKey: ['todos'],
10794
+ queryFn: api.getTodos,
10795
+ });
10796
+
10797
+ const createMutation = useMutation({
10798
+ mutationFn: api.createTodo,
10799
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
10800
+ });
10801
+
10802
+ const toggleMutation = useMutation({
10803
+ mutationFn: api.toggleTodo,
10804
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
10805
+ });
10806
+
10807
+ const deleteMutation = useMutation({
10808
+ mutationFn: api.deleteTodo,
10809
+ onSuccess: () => queryClient.invalidateQueries({ queryKey: ['todos'] }),
10810
+ });
10811
+
10812
+ const form = useForm({
10813
+ defaultValues: { title: '' },
10814
+ onSubmit: async ({ value }) => {
10815
+ if (!value.title.trim()) return;
10816
+ createMutation.mutate(value.title.trim());
10817
+ form.reset();
10818
+ },
10819
+ });
10820
+
10821
+ return (
10822
+ <>
10823
+ <div className="flex items-center justify-between mb-6">
10824
+ <h1 className="text-2xl font-semibold">Todos</h1>
10825
+ <div className="flex items-center gap-3 text-sm text-muted-foreground">
10826
+ <span>{session.user.email}</span>
10827
+ <Button
10828
+ variant="ghost"
10829
+ size="sm"
10830
+ onClick={() => authClient.signOut()}
10831
+ >
10832
+ Sign out
10833
+ </Button>
10834
+ </div>
10835
+ </div>
10836
+
10837
+ <form
10838
+ onSubmit={(e) => {
10839
+ e.preventDefault();
10840
+ form.handleSubmit();
10841
+ }}
10842
+ className="flex gap-2 mb-6"
10843
+ >
10844
+ <form.Field name="title">
10845
+ {(field) => (
10846
+ <Input
10847
+ value={field.state.value}
10848
+ onChange={(e) => field.handleChange(e.target.value)}
10849
+ placeholder="New todo\u2026"
10850
+ className="flex-1"
10851
+ />
10852
+ )}
10853
+ </form.Field>
10854
+
10855
+ <form.Subscribe selector={(s) => s.isSubmitting}>
10856
+ {(isSubmitting) => (
10857
+ <Button type="submit" disabled={isSubmitting || createMutation.isPending}>
10858
+ Add
10859
+ </Button>
10860
+ )}
10861
+ </form.Subscribe>
10862
+ </form>
10863
+
10864
+ {isPending ? (
10865
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
10866
+ ) : (
10867
+ <ul className="divide-y divide-border">
10868
+ {todos.map((todo) => (
10869
+ <li key={todo.id} className="flex items-center gap-3 py-3">
10870
+ <Checkbox
10871
+ checked={todo.done}
10872
+ onCheckedChange={() => toggleMutation.mutate(todo.id)}
10873
+ />
10874
+ <span
10875
+ className={\`flex-1 text-sm \${todo.done ? 'line-through text-muted-foreground' : ''}\`}
10876
+ >
10877
+ {todo.title}
10878
+ </span>
10879
+ <Button
10880
+ variant="ghost"
10881
+ size="sm"
10882
+ onClick={() => deleteMutation.mutate(todo.id)}
10883
+ className="text-muted-foreground hover:text-destructive h-7 w-7 p-0"
10884
+ >
10885
+ \xD7
10886
+ </Button>
10887
+ </li>
10888
+ ))}
10889
+ </ul>
10890
+ )}
10891
+
10892
+ {!isPending && todos.length === 0 && (
10893
+ <p className="text-sm text-muted-foreground">No todos yet.</p>
10894
+ )}
10895
+ </>
10896
+ );
10897
+ }`
10898
+ };
10899
+
10900
+ // src/todo-example/templates/shadcn-page.ts
10901
+ var shadcn_page_default = {
10902
+ filename: "apps/web/app/page.tsx",
10903
+ template: `'use client';
10904
+
10905
+ import { useQuery } from '@tanstack/react-query';
10906
+ import { authClient } from '@/lib/auth-client';
10907
+ import { AuthForm } from '@/app/auth-form';
10908
+ import { Link } from '@<%= projectName %>/ui/link';
10909
+ import { Logo } from '@<%= projectName %>/ui/logo';
10910
+ import { TodoList } from '@/app/todo-list';
10911
+ import { ModeToggle } from '@<%= projectName %>/ui/mode-toggle';
10912
+ import { Card, CardContent, CardHeader, CardTitle } from '@<%= projectName %>/ui/card';
10913
+
10914
+ export default function Home() {
10915
+ const { data: session, isPending } = authClient.useSession();
10916
+
10917
+ const { data: hello } = useQuery({
10918
+ queryKey: ['hello'],
10919
+ queryFn: () => fetch('/api/hello').then((r) => r.json()),
10920
+ });
10921
+
10922
+ return (
10923
+ <main className="max-w-lg mx-auto min-h-full px-4 py-8 space-y-4">
10924
+ <div className="pb-4 border-b flex items-start justify-between">
10925
+ <div>
10926
+ <p className="font-mono text-sm text-muted-foreground">apps/web/page.tsx</p>
10927
+ <p className="text-sm text-muted-foreground">Delete me to get started!</p>
10928
+ </div>
10929
+ <ModeToggle />
10930
+ </div>
10931
+
10932
+ <div className="flex items-center gap-3 py-2">
10933
+ <Logo className="w-10 h-auto text-foreground" />
10934
+ <div>
10935
+ <h1 className="text-4xl font-black tracking-tight"><%= projectName %></h1>
10936
+ <p className="text-sm text-muted-foreground tracking-widest uppercase">
10937
+ Next + Nest = Newt \u{1F49C}
10938
+ </p>
10939
+ </div>
10940
+ </div>
10941
+
10942
+ <Card>
10943
+ <CardHeader>
10944
+ <CardTitle className="text-xs font-mono uppercase tracking-widest text-muted-foreground">
10945
+ next.js
10946
+ </CardTitle>
10947
+ </CardHeader>
10948
+ <CardContent>
10949
+ <p className="font-mono text-sm text-muted-foreground">apps/web/layout.tsx</p>
10950
+ <p className="text-sm text-muted-foreground">Next.js rendering</p>
10951
+ </CardContent>
10952
+ </Card>
10953
+
10954
+ <Card>
10955
+ <CardHeader>
10956
+ <CardTitle className="text-xs font-mono uppercase tracking-widest text-muted-foreground">
10957
+ nest.js
10958
+ </CardTitle>
10959
+ </CardHeader>
10960
+ <CardContent>
10961
+ <p className="font-mono text-sm text-muted-foreground">GET /api/hello</p>
10962
+ <pre className="mt-2 rounded-md border p-3 text-sm bg-muted/50">
10963
+ <code>{JSON.stringify(hello, null, 2)}</code>
10964
+ </pre>
10965
+ </CardContent>
10966
+ </Card>
10967
+
10968
+ <Card>
10969
+ <CardHeader>
10970
+ <CardTitle className="text-xs font-mono uppercase tracking-widest text-muted-foreground">
10971
+ better-auth
10972
+ </CardTitle>
10973
+ </CardHeader>
10974
+ <CardContent>
10975
+ {isPending ? (
10976
+ <p className="text-sm text-muted-foreground">Loading\u2026</p>
10977
+ ) : session ? (
10978
+ <TodoList session={session} />
10979
+ ) : (
10980
+ <AuthForm />
10981
+ )}
10982
+ </CardContent>
10983
+ </Card>
10984
+
10985
+ <div className="px-2 py-4 text-sm">
10986
+ <p className="mb-2 text-muted-foreground">Learn more</p>
10987
+ <ul className="list-disc list-inside space-y-2 mt-2">
10988
+ <li>
10989
+ <Link href="https://github.com">GitHub</Link>
10990
+ </li>
10991
+ <li>
10992
+ <Link href="https://nextjs.org">Next.js</Link>
10993
+ </li>
10994
+ <li>
10995
+ <Link href="https://nestjs.com">NestJS</Link>
10996
+ </li>
10997
+ </ul>
10998
+ </div>
10999
+ </main>
11000
+ );
11001
+ }`
11002
+ };
11003
+
11004
+ // src/todo-example/index.ts
11005
+ var todoExampleApi = {
11006
+ templates: [todos_module_default, todos_service_default, todos_service_spec_default]
11007
+ };
11008
+ var todoExampleControllers = {
11009
+ templates: [todos_controller_default, todos_module_controllers_default, app_module_controllers_default]
11010
+ };
11011
+ var todoExampleDi = {
11012
+ templates: [web_todos_route_default, web_todos_id_route_default, web_todos_toggle_route_default, app_module_di_default]
11013
+ };
11014
+ var todoExampleWeb = {
11015
+ templates: [web_todo_list_default, web_page_default]
11016
+ };
11017
+ var todoExampleShadcn = {
11018
+ templates: [shadcn_todo_list_default, shadcn_page_default]
11019
+ };
10786
11020
 
10787
11021
  // src/index.ts
10788
11022
  var staticDir = new URL("./static/", import.meta.url);
@@ -10805,7 +11039,12 @@ var templates = {
10805
11039
  deploymentCustomServer: single_process_custom_server_default,
10806
11040
  deploymentSpa: single_process_static_export_default,
10807
11041
  nestDiOnly: nest_di_only_default,
10808
- apiControllers: api_controllers_default
11042
+ apiControllers: api_controllers_default,
11043
+ todoExampleApi,
11044
+ todoExampleControllers,
11045
+ todoExampleDi,
11046
+ todoExampleWeb,
11047
+ todoExampleShadcn
10809
11048
  };
10810
11049
  export {
10811
11050
  getStaticFilePath,