better-ts-stack 2.1.1 → 2.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/LICENSE +21 -21
  2. package/dist/args.js +6 -0
  3. package/dist/builder/moduleSelector.js +6 -1
  4. package/dist/builder/readmeGenerator.js +6 -0
  5. package/dist/builder/templateContext.js +1 -0
  6. package/dist/prompts/stack.js +7 -0
  7. package/dist/types/index.js +2 -0
  8. package/dist/ui/messages.js +2 -0
  9. package/package.json +71 -71
  10. package/templates/backend/express/.prettierignore +52 -52
  11. package/templates/backend/express/.prettierrc +16 -16
  12. package/templates/backend/express/gitignore +3 -1
  13. package/templates/backend/express/tsconfig.eslint.json +9 -9
  14. package/templates/backend/nestjs/.prettierignore +52 -52
  15. package/templates/backend/nestjs/gitignore +3 -1
  16. package/templates/backend/nestjs/tsconfig.eslint.json +9 -9
  17. package/templates/frontend/nextjs/app/globals.css +89 -89
  18. package/templates/frontend/nextjs/app/layout.tsx +34 -34
  19. package/templates/frontend/nextjs/components/ui/button.tsx +51 -51
  20. package/templates/frontend/nextjs/components/ui/card.tsx +60 -60
  21. package/templates/frontend/nextjs/components/ui/field.tsx +67 -67
  22. package/templates/frontend/nextjs/components/ui/input.tsx +18 -18
  23. package/templates/frontend/nextjs/config.json +35 -35
  24. package/templates/frontend/nextjs/eslint.config.mjs +11 -11
  25. package/templates/frontend/nextjs/next.config.ts.hbs +12 -12
  26. package/templates/frontend/nextjs/postcss.config.mjs +7 -7
  27. package/templates/frontend/nextjs/public/file.svg +1 -1
  28. package/templates/frontend/nextjs/public/globe.svg +1 -1
  29. package/templates/frontend/nextjs/public/window.svg +1 -1
  30. package/templates/frontend/tanstack/components.json +18 -18
  31. package/templates/frontend/tanstack/eslint.config.mjs +23 -23
  32. package/templates/frontend/tanstack/gitignore +39 -39
  33. package/templates/frontend/tanstack/public/favicon.svg +7 -7
  34. package/templates/frontend/tanstack/src/components/ui/button.tsx +51 -51
  35. package/templates/frontend/tanstack/src/components/ui/card.tsx +60 -60
  36. package/templates/frontend/tanstack/src/components/ui/field.tsx +67 -67
  37. package/templates/frontend/tanstack/src/components/ui/input.tsx +18 -18
  38. package/templates/frontend/tanstack/src/lib/utils.ts +6 -6
  39. package/templates/frontend/tanstack/src/router.tsx +31 -31
  40. package/templates/frontend/tanstack/src/routes/__root.tsx +69 -69
  41. package/templates/frontend/tanstack/tsconfig.json +22 -22
  42. package/templates/modules/agents/AGENTS.md.hbs +237 -0
  43. package/templates/modules/agents/CLAUDE.md +1 -0
  44. package/templates/modules/agents/config.json +10 -0
  45. package/templates/modules/auth/express/src/lib/jwt.ts.hbs +27 -27
  46. package/templates/modules/auth/nextjs/app/api/auth/[...all]/route.ts +4 -4
  47. package/templates/modules/auth/nextjs/config.json +18 -18
  48. package/templates/modules/auth/nextjs/lib/auth-client.ts +3 -3
  49. package/templates/modules/auth/tanstack/config.json +18 -18
  50. package/templates/modules/auth/tanstack/src/lib/auth-client.ts +2 -2
  51. package/templates/modules/auth/tanstack/src/lib/auth-functions.ts +8 -8
  52. package/templates/modules/auth/tanstack/src/lib/auth.ts.hbs +37 -37
  53. package/templates/modules/auth/tanstack/src/routes/api/auth/$.ts +15 -15
  54. package/templates/modules/auth/tanstack/src/routes/dashboard.tsx +110 -110
  55. package/templates/modules/auth/tanstack/src/routes/sign-in.tsx +36 -36
  56. package/templates/modules/auth/tanstack/src/routes/sign-up.tsx +36 -36
  57. package/templates/modules/docker/.dockerignore +2 -0
  58. package/templates/modules/docker/config.json +15 -15
  59. package/templates/modules/drizzle/express/config.json +26 -26
  60. package/templates/modules/drizzle/nestjs/config.json +26 -26
  61. package/templates/modules/drizzle/nextjs/config.json +26 -26
  62. package/templates/modules/drizzle/nextjs/drizzle.config.ts +13 -13
  63. package/templates/modules/drizzle/tanstack/config.json +26 -26
  64. package/templates/modules/drizzle/tanstack/drizzle.config.ts +12 -12
  65. package/templates/modules/drizzle/tanstack/src/lib/db.ts +13 -13
  66. package/templates/modules/mongoose/express/config.json +15 -15
  67. package/templates/modules/mongoose/express/src/lib/db.ts +43 -43
  68. package/templates/modules/mongoose/express/src/models/User.ts +48 -48
  69. package/templates/modules/mongoose/nestjs/config.json +15 -15
  70. package/templates/modules/mongoose/nextjs/config.json +15 -15
  71. package/templates/modules/mongoose/nextjs/models/User.ts +48 -48
  72. package/templates/modules/mongoose/tanstack/config.json +16 -16
  73. package/templates/modules/mongoose/tanstack/src/models/User.ts +48 -48
  74. package/templates/modules/prisma/express/config.json +27 -27
  75. package/templates/modules/prisma/express/prisma/schema.prisma +23 -23
  76. package/templates/modules/prisma/nestjs/config.json +27 -27
  77. package/templates/modules/prisma/nextjs/config.json +28 -28
  78. package/templates/modules/prisma/nextjs/lib/prisma.ts +18 -18
  79. package/templates/modules/prisma/nextjs/prisma.config.ts +14 -14
  80. package/templates/modules/prisma/tanstack/config.json +28 -28
  81. package/templates/modules/prisma/tanstack/prisma/schema.prisma.hbs +91 -91
  82. package/templates/modules/prisma/tanstack/prisma.config.ts +14 -14
  83. package/templates/modules/prisma/tanstack/src/lib/prisma.ts +17 -17
  84. package/templates/modules/vscode/.vscode/extensions.json.hbs +29 -0
  85. package/templates/modules/vscode/.vscode/settings.json.hbs +22 -0
  86. package/templates/modules/vscode/config.json +10 -0
@@ -1,67 +1,67 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "~/lib/utils";
4
-
5
- function FieldGroup({
6
- className,
7
- ...props
8
- }: React.HTMLAttributes<HTMLDivElement>) {
9
- return <div className={cn("space-y-5", className)} {...props} />;
10
- }
11
-
12
- type FieldProps = React.HTMLAttributes<HTMLDivElement> & {
13
- "data-invalid"?: boolean;
14
- };
15
-
16
- function Field({
17
- className,
18
- "data-invalid": dataInvalid,
19
- ...props
20
- }: FieldProps) {
21
- return (
22
- <div
23
- data-invalid={dataInvalid}
24
- className={cn("space-y-2", className)}
25
- {...props}
26
- />
27
- );
28
- }
29
-
30
- function FieldLabel({
31
- className,
32
- ...props
33
- }: React.LabelHTMLAttributes<HTMLLabelElement>) {
34
- return (
35
- <label
36
- className={cn("text-sm font-medium text-slate-900", className)}
37
- {...props}
38
- />
39
- );
40
- }
41
-
42
- function FieldDescription({
43
- className,
44
- ...props
45
- }: React.HTMLAttributes<HTMLParagraphElement>) {
46
- return <p className={cn("text-sm text-slate-500", className)} {...props} />;
47
- }
48
-
49
- type FieldErrorProps = React.HTMLAttributes<HTMLParagraphElement> & {
50
- errors?: Array<{ message?: string } | undefined>;
51
- };
52
-
53
- function FieldError({ className, errors, ...props }: FieldErrorProps) {
54
- const message = errors?.find((error) => error?.message)?.message;
55
-
56
- if (!message) {
57
- return null;
58
- }
59
-
60
- return (
61
- <p className={cn("text-sm font-medium text-rose-600", className)} {...props}>
62
- {message}
63
- </p>
64
- );
65
- }
66
-
67
- export { Field, FieldDescription, FieldError, FieldGroup, FieldLabel };
1
+ import * as React from "react";
2
+
3
+ import { cn } from "~/lib/utils";
4
+
5
+ function FieldGroup({
6
+ className,
7
+ ...props
8
+ }: React.HTMLAttributes<HTMLDivElement>) {
9
+ return <div className={cn("space-y-5", className)} {...props} />;
10
+ }
11
+
12
+ type FieldProps = React.HTMLAttributes<HTMLDivElement> & {
13
+ "data-invalid"?: boolean;
14
+ };
15
+
16
+ function Field({
17
+ className,
18
+ "data-invalid": dataInvalid,
19
+ ...props
20
+ }: FieldProps) {
21
+ return (
22
+ <div
23
+ data-invalid={dataInvalid}
24
+ className={cn("space-y-2", className)}
25
+ {...props}
26
+ />
27
+ );
28
+ }
29
+
30
+ function FieldLabel({
31
+ className,
32
+ ...props
33
+ }: React.LabelHTMLAttributes<HTMLLabelElement>) {
34
+ return (
35
+ <label
36
+ className={cn("text-sm font-medium text-slate-900", className)}
37
+ {...props}
38
+ />
39
+ );
40
+ }
41
+
42
+ function FieldDescription({
43
+ className,
44
+ ...props
45
+ }: React.HTMLAttributes<HTMLParagraphElement>) {
46
+ return <p className={cn("text-sm text-slate-500", className)} {...props} />;
47
+ }
48
+
49
+ type FieldErrorProps = React.HTMLAttributes<HTMLParagraphElement> & {
50
+ errors?: Array<{ message?: string } | undefined>;
51
+ };
52
+
53
+ function FieldError({ className, errors, ...props }: FieldErrorProps) {
54
+ const message = errors?.find((error) => error?.message)?.message;
55
+
56
+ if (!message) {
57
+ return null;
58
+ }
59
+
60
+ return (
61
+ <p className={cn("text-sm font-medium text-rose-600", className)} {...props}>
62
+ {message}
63
+ </p>
64
+ );
65
+ }
66
+
67
+ export { Field, FieldDescription, FieldError, FieldGroup, FieldLabel };
@@ -1,18 +1,18 @@
1
- import * as React from "react";
2
-
3
- import { cn } from "~/lib/utils";
4
-
5
- function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
- return (
7
- <input
8
- type={type}
9
- className={cn(
10
- "flex h-11 w-full rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 shadow-sm transition-[color,box-shadow] outline-none placeholder:text-slate-400 focus-visible:border-sky-500 focus-visible:ring-[3px] focus-visible:ring-sky-500/20 disabled:cursor-not-allowed disabled:opacity-50",
11
- className
12
- )}
13
- {...props}
14
- />
15
- );
16
- }
17
-
18
- export { Input };
1
+ import * as React from "react";
2
+
3
+ import { cn } from "~/lib/utils";
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ className={cn(
10
+ "flex h-11 w-full rounded-2xl border border-slate-200 bg-white px-3 py-2 text-sm text-slate-950 shadow-sm transition-[color,box-shadow] outline-none placeholder:text-slate-400 focus-visible:border-sky-500 focus-visible:ring-[3px] focus-visible:ring-sky-500/20 disabled:cursor-not-allowed disabled:opacity-50",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ export { Input };
@@ -1,6 +1,6 @@
1
- import { clsx, type ClassValue } from "clsx";
2
- import { twMerge } from "tailwind-merge";
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs));
6
- }
1
+ import { clsx, type ClassValue } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -1,32 +1,32 @@
1
- import { QueryClient } from "@tanstack/react-query";
2
- import { createRouter } from "@tanstack/react-router";
3
- import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
4
-
5
- import { routeTree } from "~/routeTree.gen";
6
-
7
- export function getRouter() {
8
- const queryClient = new QueryClient({
9
- defaultOptions: {
10
- queries: {
11
- staleTime: 30_000,
12
- },
13
- },
14
- });
15
-
16
- const router = createRouter({
17
- routeTree,
18
- context: {
19
- queryClient,
20
- },
21
- defaultPreload: "intent",
22
- defaultPreloadStaleTime: 0,
23
- scrollRestoration: true,
24
- });
25
-
26
- setupRouterSsrQueryIntegration({
27
- router,
28
- queryClient,
29
- });
30
-
31
- return router;
1
+ import { QueryClient } from "@tanstack/react-query";
2
+ import { createRouter } from "@tanstack/react-router";
3
+ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query";
4
+
5
+ import { routeTree } from "~/routeTree.gen";
6
+
7
+ export function getRouter() {
8
+ const queryClient = new QueryClient({
9
+ defaultOptions: {
10
+ queries: {
11
+ staleTime: 30_000,
12
+ },
13
+ },
14
+ });
15
+
16
+ const router = createRouter({
17
+ routeTree,
18
+ context: {
19
+ queryClient,
20
+ },
21
+ defaultPreload: "intent",
22
+ defaultPreloadStaleTime: 0,
23
+ scrollRestoration: true,
24
+ });
25
+
26
+ setupRouterSsrQueryIntegration({
27
+ router,
28
+ queryClient,
29
+ });
30
+
31
+ return router;
32
32
  }
@@ -1,70 +1,70 @@
1
- /// <reference types="vite/client" />
2
- import {
3
- HeadContent,
4
- Outlet,
5
- Scripts,
6
- createRootRouteWithContext,
7
- } from "@tanstack/react-router";
8
- import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
9
- import type { QueryClient } from "@tanstack/react-query";
10
- import * as React from "react";
11
-
12
- import appCss from "~/styles/app.css?url";
13
-
14
- export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
15
- head: () => ({
16
- meta: [
17
- {
18
- charSet: "utf-8",
19
- },
20
- {
21
- name: "viewport",
22
- content: "width=device-width, initial-scale=1",
23
- },
24
- ],
25
- links: [
26
- {
27
- rel: "icon",
28
- type: "image/svg+xml",
29
- href: "/favicon.svg",
30
- },
31
- ],
32
- }),
33
- component: RootComponent,
34
- notFoundComponent: () => <NotFound />,
35
- })
36
-
37
- function RootComponent() {
38
- return (
39
- <RootDocument>
40
- <Outlet />
41
- </RootDocument>
42
- )
43
- }
44
-
45
- function RootDocument({ children }: { children: React.ReactNode }) {
46
- return (
47
- <html lang="en">
48
- <head>
49
- <HeadContent />
50
- <link rel="stylesheet" href={appCss} />
51
- </head>
52
- <body>
53
- {children}
54
- <TanStackRouterDevtools />
55
- <Scripts />
56
- </body>
57
- </html>
58
- )
59
- }
60
-
61
- function NotFound() {
62
- return (
63
- <div className="flex min-h-[50vh] items-center justify-center">
64
- <div className="text-center">
65
- <h1 className="text-4xl font-bold">404</h1>
66
- <p className="mt-2 text-muted-foreground">Page not found</p>
67
- </div>
68
- </div>
69
- )
1
+ /// <reference types="vite/client" />
2
+ import {
3
+ HeadContent,
4
+ Outlet,
5
+ Scripts,
6
+ createRootRouteWithContext,
7
+ } from "@tanstack/react-router";
8
+ import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
9
+ import type { QueryClient } from "@tanstack/react-query";
10
+ import * as React from "react";
11
+
12
+ import appCss from "~/styles/app.css?url";
13
+
14
+ export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
15
+ head: () => ({
16
+ meta: [
17
+ {
18
+ charSet: "utf-8",
19
+ },
20
+ {
21
+ name: "viewport",
22
+ content: "width=device-width, initial-scale=1",
23
+ },
24
+ ],
25
+ links: [
26
+ {
27
+ rel: "icon",
28
+ type: "image/svg+xml",
29
+ href: "/favicon.svg",
30
+ },
31
+ ],
32
+ }),
33
+ component: RootComponent,
34
+ notFoundComponent: () => <NotFound />,
35
+ })
36
+
37
+ function RootComponent() {
38
+ return (
39
+ <RootDocument>
40
+ <Outlet />
41
+ </RootDocument>
42
+ )
43
+ }
44
+
45
+ function RootDocument({ children }: { children: React.ReactNode }) {
46
+ return (
47
+ <html lang="en">
48
+ <head>
49
+ <HeadContent />
50
+ <link rel="stylesheet" href={appCss} />
51
+ </head>
52
+ <body>
53
+ {children}
54
+ <TanStackRouterDevtools />
55
+ <Scripts />
56
+ </body>
57
+ </html>
58
+ )
59
+ }
60
+
61
+ function NotFound() {
62
+ return (
63
+ <div className="flex min-h-[50vh] items-center justify-center">
64
+ <div className="text-center">
65
+ <h1 className="text-4xl font-bold">404</h1>
66
+ <p className="mt-2 text-muted-foreground">Page not found</p>
67
+ </div>
68
+ </div>
69
+ )
70
70
  }
@@ -1,22 +1,22 @@
1
- {
2
- "include": ["**/*.ts", "**/*.tsx"],
3
- "compilerOptions": {
4
- "strict": true,
5
- "esModuleInterop": true,
6
- "jsx": "react-jsx",
7
- "module": "ESNext",
8
- "moduleResolution": "Bundler",
9
- "lib": ["DOM", "DOM.Iterable", "ES2022"],
10
- "isolatedModules": true,
11
- "resolveJsonModule": true,
12
- "skipLibCheck": true,
13
- "target": "ES2022",
14
- "allowJs": true,
15
- "noEmit": true,
16
- "forceConsistentCasingInFileNames": true,
17
- "paths": {
18
- "~/*": ["./src/*"]
19
- }
20
- },
21
- "exclude": ["node_modules"]
22
- }
1
+ {
2
+ "include": ["**/*.ts", "**/*.tsx"],
3
+ "compilerOptions": {
4
+ "strict": true,
5
+ "esModuleInterop": true,
6
+ "jsx": "react-jsx",
7
+ "module": "ESNext",
8
+ "moduleResolution": "Bundler",
9
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
10
+ "isolatedModules": true,
11
+ "resolveJsonModule": true,
12
+ "skipLibCheck": true,
13
+ "target": "ES2022",
14
+ "allowJs": true,
15
+ "noEmit": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "paths": {
18
+ "~/*": ["./src/*"]
19
+ }
20
+ },
21
+ "exclude": ["node_modules"]
22
+ }
@@ -0,0 +1,237 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for AI coding agents working in {{projectName}}. Read this before making changes.
4
+
5
+ ## Stack
6
+
7
+ {{#if (eq framework 'express')}}
8
+ - Express 5 REST API in TypeScript, compiled to CommonJS with `tsc`
9
+ {{/if}}
10
+ {{#if (eq framework 'nestjs')}}
11
+ - NestJS 12 REST API in TypeScript, using ES modules
12
+ {{/if}}
13
+ {{#if (eq framework 'nextjs')}}
14
+ - Next.js 16 with the App Router, React 19, Tailwind CSS 4, and shadcn/ui
15
+ {{/if}}
16
+ {{#if (eq framework 'tanstack')}}
17
+ - TanStack Start with Vite, Nitro, React 19, Tailwind CSS 4, and shadcn/ui
18
+ {{/if}}
19
+ {{#if (eq database 'prisma')}}
20
+ - PostgreSQL through Prisma 7
21
+ {{/if}}
22
+ {{#if (eq database 'drizzle')}}
23
+ - PostgreSQL through Drizzle ORM
24
+ {{/if}}
25
+ {{#if (eq database 'mongoose')}}
26
+ - MongoDB through Mongoose
27
+ {{/if}}
28
+ {{#if useAuth}}
29
+ {{#if (eq framework 'express')}}
30
+ - JWT auth: `POST /auth/register`, `POST /auth/login`, and `GET /auth/me`
31
+ {{/if}}
32
+ {{#if (eq framework 'nestjs')}}
33
+ - JWT auth: `POST /auth/register`, `POST /auth/login`, and `GET /auth/me`
34
+ {{/if}}
35
+ {{#if (eq framework 'nextjs')}}
36
+ - Better Auth with email and password
37
+ {{/if}}
38
+ {{#if (eq framework 'tanstack')}}
39
+ - Better Auth with email and password
40
+ {{/if}}
41
+ {{/if}}
42
+ {{#if useRateLimit}}
43
+ - Rate limiting on every route except `/health`
44
+ {{/if}}
45
+ {{#if useDocker}}
46
+ - Docker: `Dockerfile` and `docker-compose.yml`
47
+ {{/if}}
48
+ {{#if useCi}}
49
+ - GitHub Actions CI in `.github/workflows/ci.yml`
50
+ {{/if}}
51
+ - Package manager: {{packageManager}}. Do not use another one or add its lockfile
52
+ - Node.js 24 or later
53
+
54
+ ## Commands
55
+
56
+ - Install: `{{packageManager}} install`
57
+ - Dev server: `{{docker.run}} dev`
58
+ - Build: `{{docker.run}} build`
59
+ - Type-check: `{{docker.run}} type:check`
60
+ - Lint: `{{docker.run}} lint`
61
+ {{#if (eq database 'prisma')}}
62
+ - After changing `prisma/schema.prisma`: `{{docker.run}} prisma:migrate` creates and applies a migration and regenerates the client
63
+ {{/if}}
64
+ {{#if (eq database 'drizzle')}}
65
+ - After changing the schema: `{{docker.run}} db:generate` writes a migration, then `{{docker.run}} db:migrate` applies it
66
+ {{/if}}
67
+
68
+ ## Before you finish
69
+
70
+ Run type-check and lint, and fix every error they report. Run the build too when you changed config, dependencies, or anything the build reads. Do not report a task as done while any of them fails.
71
+
72
+ ## Layout
73
+
74
+ {{#if (eq framework 'express')}}
75
+ - `src/index.ts`: creates the app, adds middleware, and mounts the routers
76
+ - `src/env.ts`: checks the environment variables with Zod at startup, so a missing one stops the server with a clear message
77
+ - `src/routes/`: one router per resource, mounted in `src/index.ts`
78
+ {{#if useAuth}}
79
+ - `src/controllers/`, `src/services/`: request handlers and data access for auth
80
+ - `src/middleware/requireAuth.ts`: add it to a route to require a valid JWT
81
+ {{/if}}
82
+ {{#if useRateLimit}}
83
+ - `src/middleware/rateLimit.ts`: the rate limiters
84
+ {{/if}}
85
+ {{#if (eq database 'prisma')}}
86
+ - `src/lib/prisma.ts`: the shared Prisma client. Do not create another one
87
+ {{/if}}
88
+ {{#if (eq database 'drizzle')}}
89
+ - `src/lib/db.ts`, `src/lib/schema.ts`: the database client and the tables
90
+ {{/if}}
91
+ {{#if (eq database 'mongoose')}}
92
+ - `src/lib/db.ts`, `src/models/`: the connection and the Mongoose models
93
+ {{/if}}
94
+ {{/if}}
95
+ {{#if (eq framework 'nestjs')}}
96
+ - `src/main.ts`: starts the app
97
+ - `src/app.module.ts`: the root module. Register new modules and controllers here
98
+ - `src/env.ts`: checks the environment variables with Zod at startup, so a missing one stops the server with a clear message
99
+ - `src/health/`: `GET /health`
100
+ {{#if useAuth}}
101
+ - `src/auth/`: the auth module. Put `@UseGuards(AuthGuard)` on a route to require a valid JWT
102
+ {{/if}}
103
+ {{#if (eq database 'prisma')}}
104
+ - `src/prisma/`: `PrismaService`, injected wherever the database is needed
105
+ {{/if}}
106
+ {{#if (eq database 'drizzle')}}
107
+ - `src/database/`: the database module and the tables in `schema.ts`
108
+ {{/if}}
109
+ {{#if (eq database 'mongoose')}}
110
+ - `src/users/user.schema.ts`: the Mongoose schema for users
111
+ {{/if}}
112
+ {{/if}}
113
+ {{#if (eq framework 'nextjs')}}
114
+ - `app/`: routes, layouts, and route handlers. Pages are Server Components unless they need state, effects, or event handlers
115
+ - `components/ui/`: shadcn/ui components. Add new ones with the shadcn CLI instead of writing them by hand
116
+ - `lib/utils.ts`: the `cn()` class name helper
117
+ {{#if useAuth}}
118
+ - `lib/auth.ts`: the Better Auth server. `lib/auth-client.ts`: the client for components
119
+ - `app/api/auth/[...all]/route.ts`: the Better Auth endpoints. Do not rename it
120
+ - `proxy.ts`: sends signed-out visitors from `/dashboard` to `/sign-in`. Add other protected paths to its `matcher`
121
+ {{/if}}
122
+ {{#if (eq database 'prisma')}}
123
+ - `lib/prisma.ts`: the shared Prisma client. Do not create another one
124
+ {{/if}}
125
+ {{#if (eq database 'drizzle')}}
126
+ - `lib/db.ts`, `lib/schema.ts`: the database client and the tables
127
+ {{/if}}
128
+ {{#if (eq database 'mongoose')}}
129
+ - `lib/db.ts`, `models/`: the connection and the Mongoose models
130
+ {{/if}}
131
+ {{/if}}
132
+ {{#if (eq framework 'tanstack')}}
133
+ - `src/routes/`: file-based routes. A new file becomes a route
134
+ - `src/routeTree.gen.ts`: generated by the dev server and the build. Never edit it
135
+ - `src/router.tsx`, `src/routes/__root.tsx`: the router and the root layout
136
+ - `src/components/ui/`: shadcn/ui components. Add new ones with the shadcn CLI instead of writing them by hand
137
+ - `src/lib/utils.ts`: the `cn()` class name helper
138
+ {{#if useAuth}}
139
+ - `src/lib/auth.ts`: the Better Auth server. `src/lib/auth-client.ts`: the client for components
140
+ - `src/routes/api/auth/$.ts`: the Better Auth endpoints. Do not rename it
141
+ - `src/routes/dashboard.tsx`: an example of a protected route that redirects in `beforeLoad`
142
+ {{/if}}
143
+ {{#if (eq database 'prisma')}}
144
+ - `src/lib/prisma.ts`: the shared Prisma client. Do not create another one
145
+ {{/if}}
146
+ {{#if (eq database 'drizzle')}}
147
+ - `src/lib/db.ts`, `src/lib/schema.ts`: the database client and the tables
148
+ {{/if}}
149
+ {{#if (eq database 'mongoose')}}
150
+ - `src/lib/db.ts`, `src/models/`: the connection and the Mongoose models
151
+ {{/if}}
152
+ {{/if}}
153
+
154
+ ## Rules
155
+
156
+ - TypeScript is strict. Do not use `any` or `@ts-ignore` to silence an error; fix the type
157
+ {{#if (eq framework 'nestjs')}}
158
+ - Relative imports end in `.js`, as ES modules require: `import { AuthModule } from "./auth/auth.module.js"`
159
+ {{/if}}
160
+ - Keep `.env` out of git. When you add an environment variable, add it to `.env.example` too{{#if (eq framework 'express')}}, and to `src/env.ts`{{/if}}{{#if (eq framework 'nestjs')}}, and to `src/env.ts`{{/if}}
161
+ {{#if (eq database 'prisma')}}
162
+ - The Prisma client is generated into {{#if (eq framework 'express')}}`src/generated/`{{/if}}{{#if (eq framework 'nestjs')}}`src/generated/`{{/if}}{{#if (eq framework 'nextjs')}}`generated/`{{/if}}{{#if (eq framework 'tanstack')}}`generated/`{{/if}}. Never edit or commit it
163
+ {{/if}}
164
+ - Match the existing code style. ESLint settings are in `eslint.config.mjs`{{#if (eq framework 'express')}} and Prettier settings in `.prettierrc`{{/if}}{{#if (eq framework 'nestjs')}} and Prettier settings in `.prettierrc`{{/if}}
165
+ - Add a dependency only when the task needs it, and install it with {{packageManager}}
166
+
167
+ ## Production rules
168
+
169
+ - Validate every input from outside the app (request bodies, query strings, form data) with Zod before you use it{{#unless useAuth}}{{#if (eq framework 'nextjs')}} (add `zod` with {{packageManager}} first){{/if}}{{#if (eq framework 'tanstack')}} (add `zod` with {{packageManager}} first){{/if}}{{/unless}}
170
+ - Never log or return secrets, tokens, or passwords. Error responses get a short message and the right status code, never a stack trace
171
+ {{#unless (eq database 'none')}}
172
+ - Query the database through {{#if (eq database 'prisma')}}Prisma{{/if}}{{#if (eq database 'drizzle')}}Drizzle{{/if}}{{#if (eq database 'mongoose')}}Mongoose{{/if}}, never with SQL or queries built from strings
173
+ {{/unless}}
174
+ {{#if (eq framework 'express')}}
175
+ - Keep the security middleware in `src/index.ts` (`helmet`, `cors`{{#if useRateLimit}}, the rate limiter{{/if}}) in place. Set `CORS_ORIGIN` to your real origins in production
176
+ {{/if}}
177
+ {{#if (eq framework 'nestjs')}}
178
+ - Keep the security setup in `src/main.ts` and `src/app.module.ts` in place. Set `CORS_ORIGIN` to your real origins in production
179
+ {{/if}}
180
+ {{#if (eq framework 'nextjs')}}
181
+ - Code that reads secrets or the database stays on the server. Only variables prefixed with `NEXT_PUBLIC_` reach the browser, so never put a secret in one
182
+ {{/if}}
183
+ {{#if (eq framework 'tanstack')}}
184
+ - Code that reads secrets or the database runs in server functions or server routes. Only variables prefixed with `VITE_` reach the browser, so never put a secret in one
185
+ {{/if}}
186
+ {{#if useAuth}}
187
+ {{#if (eq framework 'nextjs')}}
188
+ - Check the session on the server in every protected page, route handler, and server action. Hiding a button in the UI does not protect anything
189
+ {{/if}}
190
+ {{#if (eq framework 'tanstack')}}
191
+ - Check the session on the server in every protected route and server function. Hiding a button in the UI does not protect anything
192
+ {{/if}}
193
+ {{/if}}
194
+ {{#if (eq framework 'express')}}
195
+ - Keep `GET /health` fast and free of auth{{#if useRateLimit}} and rate limiting{{/if}}, so hosts can check that the app is up
196
+ {{/if}}
197
+ {{#if (eq framework 'nestjs')}}
198
+ - Keep `GET /health` fast and free of auth{{#if useRateLimit}} and rate limiting{{/if}}, so hosts can check that the app is up
199
+ {{/if}}
200
+ - Keep changes small and focused on the task. Do not add layers, abstractions, or files the task does not need
201
+
202
+ ## When you are unsure
203
+
204
+ The libraries here may be newer than what you know. When you are not sure how an API works, read the official docs for the version in `package.json` instead of guessing:
205
+
206
+ {{#if (eq framework 'express')}}
207
+ - Express 5: https://expressjs.com/en/5x/api.html
208
+ {{/if}}
209
+ {{#if (eq framework 'nestjs')}}
210
+ - NestJS: https://docs.nestjs.com
211
+ {{/if}}
212
+ {{#if (eq framework 'nextjs')}}
213
+ - Next.js: https://nextjs.org/docs
214
+ {{/if}}
215
+ {{#if (eq framework 'tanstack')}}
216
+ - TanStack Start: https://tanstack.com/start/latest/docs
217
+ {{/if}}
218
+ {{#if (eq database 'prisma')}}
219
+ - Prisma: https://www.prisma.io/docs
220
+ {{/if}}
221
+ {{#if (eq database 'drizzle')}}
222
+ - Drizzle: https://orm.drizzle.team/docs/overview
223
+ {{/if}}
224
+ {{#if (eq database 'mongoose')}}
225
+ - Mongoose: https://mongoosejs.com/docs
226
+ {{/if}}
227
+ {{#if useAuth}}
228
+ {{#if (eq framework 'nextjs')}}
229
+ - Better Auth: https://www.better-auth.com/docs
230
+ {{/if}}
231
+ {{#if (eq framework 'tanstack')}}
232
+ - Better Auth: https://www.better-auth.com/docs
233
+ {{/if}}
234
+ {{/if}}
235
+ - Zod: https://zod.dev
236
+
237
+ If the docs and this file disagree, follow the docs and say so.
@@ -0,0 +1 @@
1
+ @AGENTS.md