better-ts-stack 2.1.0 → 2.1.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.
Files changed (50) hide show
  1. package/dist/args.js +3 -0
  2. package/dist/builder/configGenerator.js +3 -1
  3. package/dist/builder/moduleSelector.js +3 -1
  4. package/dist/builder/readmeGenerator.js +6 -0
  5. package/dist/builder/templateContext.js +23 -0
  6. package/dist/output/nextSteps.js +4 -0
  7. package/dist/prompts/index.js +3 -1
  8. package/dist/prompts/stack.js +2 -0
  9. package/dist/types/index.js +1 -0
  10. package/dist/ui/messages.js +1 -0
  11. package/package.json +3 -3
  12. package/templates/backend/express/config.json +8 -8
  13. package/templates/backend/express/gitignore +3 -0
  14. package/templates/backend/nestjs/config.json +10 -10
  15. package/templates/backend/nestjs/gitignore +3 -0
  16. package/templates/backend/nestjs/src/app.module.ts.hbs +1 -1
  17. package/templates/backend/nestjs/src/health/{health.controller.ts → health.controller.ts.hbs} +8 -0
  18. package/templates/frontend/nextjs/config.json +12 -14
  19. package/templates/frontend/nextjs/gitignore +3 -0
  20. package/templates/frontend/tanstack/config.json +15 -15
  21. package/templates/frontend/tanstack/gitignore +3 -0
  22. package/templates/frontend/tanstack/vite.config.ts +5 -4
  23. package/templates/modules/auth/express/config.json +1 -1
  24. package/templates/modules/auth/express/src/middleware/requireAuth.ts.hbs +1 -2
  25. package/templates/modules/auth/nestjs/config.json +1 -1
  26. package/templates/modules/auth/nextjs/config.json +4 -4
  27. package/templates/{frontend/nextjs/proxy.ts.hbs → modules/auth/nextjs/proxy.ts} +1 -8
  28. package/templates/modules/auth/tanstack/config.json +4 -4
  29. package/templates/modules/ci/.github/workflows/ci.yml.hbs +43 -0
  30. package/templates/modules/ci/config.json +10 -0
  31. package/templates/modules/docker/.dockerignore +1 -0
  32. package/templates/modules/docker/docker-compose.yml.hbs +1 -1
  33. package/templates/modules/drizzle/express/config.json +3 -3
  34. package/templates/modules/drizzle/nestjs/config.json +3 -3
  35. package/templates/modules/drizzle/nextjs/config.json +3 -3
  36. package/templates/modules/drizzle/tanstack/config.json +3 -3
  37. package/templates/modules/mongoose/express/config.json +2 -2
  38. package/templates/modules/mongoose/nestjs/config.json +1 -1
  39. package/templates/modules/mongoose/nextjs/config.json +2 -2
  40. package/templates/modules/mongoose/tanstack/config.json +2 -2
  41. package/templates/modules/prisma/express/config.json +4 -4
  42. package/templates/modules/prisma/express/prisma.config.ts +4 -2
  43. package/templates/modules/prisma/nestjs/config.json +4 -4
  44. package/templates/modules/prisma/nestjs/prisma.config.ts +4 -2
  45. package/templates/modules/prisma/nextjs/config.json +4 -4
  46. package/templates/modules/prisma/nextjs/prisma.config.ts +4 -2
  47. package/templates/modules/prisma/tanstack/config.json +4 -4
  48. package/templates/modules/prisma/tanstack/prisma.config.ts +4 -2
  49. package/templates/modules/rate-limit/express/config.json +1 -1
  50. package/templates/modules/rate-limit/express/src/middleware/rateLimit.ts +3 -1
package/dist/args.js CHANGED
@@ -22,6 +22,7 @@ Options left out are asked interactively, unless --yes is given.
22
22
  --docker, --no-docker Add Docker files
23
23
  --rate-limit, --no-rate-limit
24
24
  Add rate limiting (Express and NestJS)
25
+ --ci, --no-ci Add a GitHub Actions workflow
25
26
  --git, --no-git Initialize a git repository
26
27
  --install, --no-install Install dependencies
27
28
  -y, --yes Use defaults for missing options and skip confirmation
@@ -56,6 +57,7 @@ function parseCliArgs(argv) {
56
57
  auth: { type: "boolean" },
57
58
  docker: { type: "boolean" },
58
59
  "rate-limit": { type: "boolean" },
60
+ ci: { type: "boolean" },
59
61
  git: { type: "boolean" },
60
62
  install: { type: "boolean" },
61
63
  yes: { type: "boolean", short: "y" },
@@ -79,6 +81,7 @@ function parseCliArgs(argv) {
79
81
  useAuth: values.auth,
80
82
  useDocker: values.docker,
81
83
  useRateLimit: values["rate-limit"],
84
+ useCi: values.ci,
82
85
  initGit: values.git,
83
86
  installDeps: values.install,
84
87
  };
@@ -136,7 +136,9 @@ async function generateEnvFile(targetDir, envVars) {
136
136
  let envContent = "# Environment Variables\n";
137
137
  envContent +=
138
138
  "# Copy this file to .env and update with your actual values\n\n";
139
- let localEnvContent = envContent;
139
+ let localEnvContent = "# Environment Variables\n";
140
+ localEnvContent +=
141
+ "# Local values for this project. Do not commit them\n\n";
140
142
  // Add each environment variable
141
143
  for (const [key, value] of Object.entries(envVars)) {
142
144
  const resolvedValue = exports.GENERATED_SECRETS.includes(key)
@@ -10,7 +10,7 @@ function selectModules(config) {
10
10
  : `frontend/${framework}`;
11
11
  const modules = [];
12
12
  // Database, auth, and rate-limit modules have one variant per framework
13
- // (prisma/express, auth/nextjs, ...); the Docker module is shared
13
+ // (prisma/express, auth/nextjs, ...); the Docker and CI modules are shared
14
14
  if (config.database !== "none")
15
15
  modules.push(`${config.database}/${framework}`);
16
16
  if (config.useDocker)
@@ -19,5 +19,7 @@ function selectModules(config) {
19
19
  modules.push(`auth/${framework}`);
20
20
  if (config.useRateLimit)
21
21
  modules.push(`rate-limit/${framework}`);
22
+ if (config.useCi)
23
+ modules.push("ci");
22
24
  return { base, modules };
23
25
  }
@@ -52,8 +52,14 @@ function renderReadme(config, mergedConfig, targetDir) {
52
52
  ? "- **Auth:** Better Auth with email and password"
53
53
  : "- **Auth:** JWT with `/auth/register`, `/auth/login`, and `/auth/me`");
54
54
  }
55
+ if (config.useRateLimit) {
56
+ lines.push(`- **Rate limiting:** 100 requests per client IP in 15 minutes${config.useAuth ? ", and 10 sign-up or login attempts" : ""}`);
57
+ }
55
58
  if (config.useDocker)
56
59
  lines.push("- **Docker:** Dockerfile and Compose file");
60
+ if (config.useCi) {
61
+ lines.push("- **CI:** GitHub Actions builds, type-checks, and lints every push to `main` and every pull request");
62
+ }
57
63
  lines.push(`- **Package manager:** ${config.packageManager}`, "");
58
64
  const steps = (0, nextSteps_1.generateNextSteps)(config, targetDir, false, true);
59
65
  lines.push("## Getting started", "", ...steps.map((step, index) => {
@@ -31,6 +31,28 @@ const DOCKER_COMMANDS = {
31
31
  run: "bun run",
32
32
  },
33
33
  };
34
+ // Placeholders for the CI build, the same ones the Dockerfile uses: a build
35
+ // needs these to be set, but never connects to a database
36
+ function getCiBuildEnv(config) {
37
+ const env = [];
38
+ if (config.database === "prisma" || config.database === "drizzle") {
39
+ env.push({
40
+ name: "DATABASE_URL",
41
+ value: "postgresql://postgres:postgres@localhost:5432/postgres",
42
+ });
43
+ }
44
+ if (config.database === "mongoose") {
45
+ env.push({ name: "MONGODB_URI", value: "mongodb://localhost:27017/build" });
46
+ }
47
+ // next build loads the auth route, which initializes Better Auth
48
+ if (config.framework === "nextjs" && config.useAuth) {
49
+ env.push({
50
+ name: "BETTER_AUTH_SECRET",
51
+ value: "build-only-placeholder-secret-not-used-at-runtime",
52
+ });
53
+ }
54
+ return env;
55
+ }
34
56
  function buildTemplateContext(config) {
35
57
  return {
36
58
  projectName: config.projectName,
@@ -42,6 +64,7 @@ function buildTemplateContext(config) {
42
64
  useAuth: config.useAuth,
43
65
  useRateLimit: config.useRateLimit,
44
66
  docker: DOCKER_COMMANDS[config.packageManager],
67
+ ciBuildEnv: getCiBuildEnv(config),
45
68
  helpers: {
46
69
  eq: (a, b) => a === b,
47
70
  runner: () => {
@@ -37,6 +37,10 @@ function generateNextSteps(config, targetDir, depsInstalled, isForReadme = false
37
37
  if (hasDockerDatabase) {
38
38
  steps.push("docker compose up -d db");
39
39
  }
40
+ if ((config.database === "prisma" || config.database === "drizzle") &&
41
+ !hasDockerDatabase) {
42
+ steps.push("Ensure PostgreSQL is running locally or update DATABASE_URL in .env");
43
+ }
40
44
  if (config.database === "prisma") {
41
45
  steps.push(`${config.packageManager} run prisma:generate`);
42
46
  steps.push(`${config.packageManager} run prisma:migrate`);
@@ -23,13 +23,15 @@ async function collectProjectPath(preset, isNonInteractive) {
23
23
  return (0, stack_1.ask)(preset, () => (0, ui_1.text)({
24
24
  message: "Project name",
25
25
  placeholder: "my-awesome-project",
26
- validate: (value) => (0, validators_1.validateProjectPath)(value ?? ""),
26
+ // An empty path would resolve to the current directory and pass
27
+ validate: (value) => value ? (0, validators_1.validateProjectPath)(value) : "Project name is required",
27
28
  }));
28
29
  }
29
30
  // Asks for final confirmation before building; declining ends the session
30
31
  async function confirmBuild() {
31
32
  const shouldBuild = await (0, stack_1.ask)(undefined, () => (0, ui_1.confirm)({ message: "Create this project?", initialValue: true }));
32
33
  if (!shouldBuild) {
34
+ (0, ui_1.info)("Nothing was created.");
33
35
  process.exit(0);
34
36
  }
35
37
  }
@@ -95,6 +95,7 @@ async function collectStackChoices(preset) {
95
95
  const useRateLimit = isFullstack
96
96
  ? false
97
97
  : await ask(preset.useRateLimit, () => (0, ui_1.confirm)({ message: "Add rate limiting?", initialValue: false }));
98
+ const useCi = await ask(preset.useCi, () => (0, ui_1.confirm)({ message: "Add GitHub Actions CI?", initialValue: false }));
98
99
  const initGit = await ask(preset.initGit, () => (0, ui_1.confirm)({ message: "Initialize a git repository?", initialValue: true }));
99
100
  const installDeps = await ask(preset.installDeps, () => (0, ui_1.confirm)({ message: "Install dependencies now?", initialValue: false }));
100
101
  return {
@@ -104,6 +105,7 @@ async function collectStackChoices(preset) {
104
105
  useDocker,
105
106
  useAuth,
106
107
  useRateLimit,
108
+ useCi,
107
109
  initGit,
108
110
  installDeps,
109
111
  };
@@ -12,6 +12,7 @@ exports.DEFAULT_CHOICES = {
12
12
  useDocker: false,
13
13
  useAuth: false,
14
14
  useRateLimit: false,
15
+ useCi: false,
15
16
  packageManager: "npm",
16
17
  initGit: true,
17
18
  installDeps: false,
@@ -75,6 +75,7 @@ function showSummary(config, targetDir) {
75
75
  config.useAuth && "auth",
76
76
  config.useRateLimit && "rate limit",
77
77
  config.useDocker && "docker",
78
+ config.useCi && "ci",
78
79
  ];
79
80
  const setup = [
80
81
  config.packageManager,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-ts-stack",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "A CLI that scaffolds Express, NestJS, Next.js, and TanStack Start projects with a database, authentication, and Docker already set up.",
5
5
  "keywords": [
6
6
  "cli",
@@ -51,8 +51,8 @@
51
51
  "dependencies": {
52
52
  "@clack/core": "^1.4.3",
53
53
  "fs-extra": "^11.4.0",
54
- "handlebars": "^4.7.9",
55
- "zod": "^4.5.4"
54
+ "handlebars": "^4.7.10",
55
+ "zod": "^4.6.7"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@better-ts-stack/eslint-config": "*",
@@ -13,13 +13,13 @@
13
13
  "type:check": "tsc --noEmit"
14
14
  },
15
15
  "dependencies": {
16
- "express": "^5.2.1",
16
+ "express": "^5.3.0",
17
17
  "dotenv": "^17.4.2",
18
18
  "cors": "^2.8.6",
19
19
  "cross-env": "^10.1.0",
20
20
  "helmet": "^8.3.0",
21
- "morgan": "^1.12.0",
22
- "zod": "^4.6.5"
21
+ "morgan": "^1.12.1",
22
+ "zod": "^4.6.7"
23
23
  },
24
24
  "devDependencies": {
25
25
  "@eslint/js": "^10.0.1",
@@ -27,13 +27,13 @@
27
27
  "@types/cors": "^2.8.19",
28
28
  "@types/express": "^5.0.6",
29
29
  "@types/morgan": "^1.9.10",
30
- "@types/node": "^24.13.3",
31
- "eslint": "^10.10.0",
30
+ "@types/node": "^24.19.2",
31
+ "eslint": "^10.14.0",
32
32
  "eslint-config-prettier": "^10.1.8",
33
- "prettier": "^3.9.6",
34
- "tsx": "^4.23.13",
33
+ "prettier": "^3.9.10",
34
+ "tsx": "^4.23.15",
35
35
  "typescript": "^6.0.3",
36
- "typescript-eslint": "^8.69.0"
36
+ "typescript-eslint": "^8.72.1"
37
37
  },
38
38
  "envVars": {
39
39
  "PORT": "3000",
@@ -34,3 +34,6 @@ coverage/
34
34
 
35
35
  # Misc
36
36
  *.tsbuildinfo
37
+
38
+ # Prisma client, created by prisma generate
39
+ src/generated/
@@ -13,28 +13,28 @@
13
13
  "type:check": "tsc --noEmit"
14
14
  },
15
15
  "dependencies": {
16
- "@nestjs/common": "^12.1.2",
17
- "@nestjs/core": "^12.1.2",
18
- "@nestjs/platform-express": "^12.1.2",
16
+ "@nestjs/common": "^12.3.2",
17
+ "@nestjs/core": "^12.3.2",
18
+ "@nestjs/platform-express": "^12.3.2",
19
19
  "cross-env": "^10.1.0",
20
20
  "dotenv": "^17.4.2",
21
21
  "helmet": "^8.3.0",
22
22
  "reflect-metadata": "^0.2.2",
23
23
  "rxjs": "^7.8.2",
24
- "zod": "^4.6.5"
24
+ "zod": "^4.6.7"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@eslint/js": "^10.0.1",
28
- "@nestjs/cli": "^12.0.8",
29
- "@nestjs/schematics": "^12.0.6",
28
+ "@nestjs/cli": "^12.0.11",
29
+ "@nestjs/schematics": "^12.0.12",
30
30
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
31
31
  "@types/express": "^5.0.6",
32
- "@types/node": "^24.13.3",
33
- "eslint": "^10.10.0",
32
+ "@types/node": "^24.19.2",
33
+ "eslint": "^10.14.0",
34
34
  "eslint-config-prettier": "^10.1.8",
35
- "prettier": "^3.9.6",
35
+ "prettier": "^3.9.10",
36
36
  "typescript": "^6.0.3",
37
- "typescript-eslint": "^8.69.0"
37
+ "typescript-eslint": "^8.72.1"
38
38
  },
39
39
  "envVars": {
40
40
  "PORT": "3000",
@@ -34,3 +34,6 @@ coverage/
34
34
 
35
35
  # Misc
36
36
  *.tsbuildinfo
37
+
38
+ # Prisma client, created by prisma generate
39
+ src/generated/
@@ -35,7 +35,7 @@ import { PrismaModule } from "./prisma/prisma.module.js";
35
35
  AuthModule,
36
36
  {{/if}}
37
37
  {{#if useRateLimit}}
38
- // Every route: 100 requests per client IP in 15 minutes
38
+ // Every route except /health: 100 requests per client IP in 15 minutes
39
39
  ThrottlerModule.forRoot([{ ttl: 15 * 60 * 1000, limit: 100 }]),
40
40
  {{/if}}
41
41
  ],
@@ -1,5 +1,13 @@
1
1
  import { Controller, Get } from "@nestjs/common";
2
+ {{#if useRateLimit}}
3
+ import { SkipThrottle } from "@nestjs/throttler";
4
+ {{/if}}
2
5
 
6
+ {{#if useRateLimit}}
7
+ // Docker and load balancers call the health check on a schedule, so it is not
8
+ // counted
9
+ @SkipThrottle()
10
+ {{/if}}
3
11
  @Controller("health")
4
12
  export class HealthController {
5
13
  @Get()
@@ -13,25 +13,23 @@
13
13
  "dependencies": {
14
14
  "class-variance-authority": "^0.7.1",
15
15
  "clsx": "^2.1.1",
16
- "lucide-react": "^1.41.0",
17
- "next": "^16.3.4",
18
- "react": "^19.2.8",
19
- "react-dom": "^19.2.8",
20
- "tailwind-merge": "^3.6.0",
21
- "@radix-ui/react-slot": "^1.3.3"
16
+ "lucide-react": "^1.66.0",
17
+ "next": "^16.4.3",
18
+ "react": "^19.3.1",
19
+ "react-dom": "^19.3.1",
20
+ "tailwind-merge": "^3.7.0",
21
+ "@radix-ui/react-slot": "^1.5.0"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@tailwindcss/postcss": "^4.3.3",
25
- "@types/node": "^24.13.3",
26
- "@types/react": "^19.2.18",
27
- "@types/react-dom": "^19.2.7",
28
- "eslint": "^10.10.0",
29
- "eslint-config-next": "^16.3.4",
25
+ "@types/node": "^24.19.2",
26
+ "@types/react": "^19.3.0",
27
+ "@types/react-dom": "^19.3.0",
28
+ "eslint": "^10.14.0",
29
+ "eslint-config-next": "^16.4.3",
30
30
  "tailwindcss": "^4.3.3",
31
31
  "typescript": "^6.0.3",
32
32
  "@eslint/compat": "^2.1.1"
33
33
  },
34
- "envVars": {
35
- "PORT": "3000"
36
- }
34
+ "envVars": {}
37
35
  }
@@ -40,3 +40,6 @@ yarn-error.log*
40
40
  # typescript
41
41
  *.tsbuildinfo
42
42
  next-env.d.ts
43
+
44
+ # Prisma client, created by prisma generate
45
+ /generated/
@@ -12,32 +12,32 @@
12
12
  "lint": "eslint src"
13
13
  },
14
14
  "dependencies": {
15
- "@tanstack/react-query": "^5.104.3",
16
- "@tanstack/react-query-devtools": "^5.104.3",
17
- "@tanstack/react-router": "^1.170.45",
15
+ "@tanstack/react-query": "^5.105.0",
16
+ "@tanstack/react-query-devtools": "^5.105.0",
17
+ "@tanstack/react-router": "^1.170.46",
18
18
  "@tanstack/react-router-devtools": "^1.167.2",
19
19
  "@tanstack/react-router-ssr-query": "^1.167.3",
20
- "@tanstack/react-start": "^1.169.3",
20
+ "@tanstack/react-start": "^1.169.4",
21
21
  "class-variance-authority": "^0.7.1",
22
22
  "clsx": "^2.1.1",
23
- "lucide-react": "^1.41.0",
24
- "react": "^19.2.8",
25
- "react-dom": "^19.2.8",
26
- "tailwind-merge": "^3.6.0"
23
+ "lucide-react": "^1.66.0",
24
+ "react": "^19.3.1",
25
+ "react-dom": "^19.3.1",
26
+ "tailwind-merge": "^3.7.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@eslint/js": "^10.0.1",
30
30
  "@tailwindcss/vite": "^4.3.3",
31
- "@types/node": "^24.13.3",
32
- "@types/react": "^19.2.18",
33
- "@types/react-dom": "^19.2.7",
31
+ "@types/node": "^24.19.2",
32
+ "@types/react": "^19.3.0",
33
+ "@types/react-dom": "^19.3.0",
34
34
  "@vitejs/plugin-react": "^6.1.2",
35
- "eslint": "^10.10.0",
36
- "nitro": "^3.0.260903-beta",
35
+ "eslint": "^10.14.0",
36
+ "nitro": "^3.0.261029-rc",
37
37
  "tailwindcss": "^4.3.3",
38
38
  "typescript": "^6.0.3",
39
- "typescript-eslint": "^8.69.0",
40
- "vite": "^8.3.4"
39
+ "typescript-eslint": "^8.72.1",
40
+ "vite": "^8.3.5"
41
41
  },
42
42
  "envVars": {
43
43
  "PORT": "3000"
@@ -37,3 +37,6 @@ yarn-error.log*
37
37
 
38
38
  # typescript
39
39
  *.tsbuildinfo
40
+
41
+ # Prisma client, created by prisma generate
42
+ /generated/
@@ -4,7 +4,7 @@ import viteReact from "@vitejs/plugin-react";
4
4
  import { nitro } from "nitro/vite";
5
5
  import { defineConfig } from "vite";
6
6
 
7
- export default defineConfig(({ command }) => ({
7
+ export default defineConfig(({ command, isPreview }) => ({
8
8
  server: {
9
9
  port: 3000,
10
10
  },
@@ -15,8 +15,9 @@ export default defineConfig(({ command }) => ({
15
15
  tanstackStart({ srcDirectory: "src" }),
16
16
  tailwindcss(),
17
17
  viteReact(),
18
- // Nitro builds the production server in .output/ (run with node). It is
19
- // left out of `vite dev`, where TanStack Start's own dev server is steadier
20
- command === "build" && nitro(),
18
+ // Nitro builds the production server in .output/ (run with node) and serves
19
+ // it for `vite preview`. It is left out of `vite dev`, where TanStack Start's
20
+ // own dev server is steadier
21
+ (command === "build" || isPreview) && nitro(),
21
22
  ],
22
23
  }));
@@ -6,7 +6,7 @@
6
6
  "dependencies": {
7
7
  "bcrypt": "^6.0.0",
8
8
  "jsonwebtoken": "^9.0.3",
9
- "zod": "^4.6.5"
9
+ "zod": "^4.6.7"
10
10
  },
11
11
  "devDependencies": {
12
12
  "@types/bcrypt": "^6.0.0",
@@ -19,8 +19,7 @@ export function requireAuth(req: AuthenticatedRequest, res: Response, next: Next
19
19
  const payload = verifyToken(token);
20
20
  req.user = payload;
21
21
  next();
22
- } catch (error) {
23
- console.error('JWT verification failed:', error);
22
+ } catch {
24
23
  res.status(401).json({ error: 'Invalid or expired token' });
25
24
  }
26
25
  }
@@ -6,7 +6,7 @@
6
6
  "dependencies": {
7
7
  "bcrypt": "^6.0.0",
8
8
  "jsonwebtoken": "^9.0.3",
9
- "zod": "^4.6.5"
9
+ "zod": "^4.6.7"
10
10
  },
11
11
  "devDependencies": {
12
12
  "@types/bcrypt": "^6.0.0",
@@ -4,10 +4,10 @@
4
4
  "description": "Better Auth for Next.js with optional Prisma or Drizzle adapter",
5
5
  "type": "feature",
6
6
  "dependencies": {
7
- "@hookform/resolvers": "^5.9.1",
8
- "better-auth": "^1.7.3",
9
- "react-hook-form": "^7.87.0",
10
- "zod": "^4.6.5"
7
+ "@hookform/resolvers": "^5.9.2",
8
+ "better-auth": "^1.7.14",
9
+ "react-hook-form": "^7.91.0",
10
+ "zod": "^4.6.7"
11
11
  },
12
12
  "devDependencies": {},
13
13
  "scripts": {},
@@ -1,8 +1,6 @@
1
+ import { getSessionCookie } from "better-auth/cookies";
1
2
  import { NextResponse } from "next/server";
2
-
3
- {{#if useAuth}}
4
3
  import type { NextRequest } from "next/server";
5
- import { getSessionCookie } from "better-auth/cookies";
6
4
 
7
5
  export function proxy(request: NextRequest) {
8
6
  const sessionCookie = getSessionCookie(request);
@@ -17,8 +15,3 @@ export function proxy(request: NextRequest) {
17
15
  export const config = {
18
16
  matcher: ["/dashboard/:path*"],
19
17
  };
20
- {{else}}
21
- export function proxy() {
22
- return NextResponse.next();
23
- }
24
- {{/if}}
@@ -4,11 +4,11 @@
4
4
  "description": "Better Auth for TanStack Start with optional Prisma, Drizzle, or MongoDB adapter",
5
5
  "type": "feature",
6
6
  "dependencies": {
7
- "@hookform/resolvers": "^5.9.1",
8
- "better-auth": "^1.7.3",
7
+ "@hookform/resolvers": "^5.9.2",
8
+ "better-auth": "^1.7.14",
9
9
  "dotenv": "^17.4.2",
10
- "react-hook-form": "^7.87.0",
11
- "zod": "^4.6.5"
10
+ "react-hook-form": "^7.91.0",
11
+ "zod": "^4.6.7"
12
12
  },
13
13
  "devDependencies": {},
14
14
  "scripts": {},
@@ -0,0 +1,43 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ check:
13
+ name: Build, type-check, lint
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v7
17
+ {{#if (eq packageManager 'pnpm')}}
18
+ - uses: pnpm/action-setup@v6
19
+ with:
20
+ version: 11
21
+ {{/if}}
22
+ - uses: actions/setup-node@v7
23
+ with:
24
+ node-version: 24
25
+ {{#if (eq packageManager 'bun')}}
26
+ - uses: oven-sh/setup-bun@v2
27
+ {{/if}}
28
+ - name: Install
29
+ run: {{docker.install}}
30
+ - name: Build
31
+ {{#if ciBuildEnv.length}}
32
+ # Placeholders: the build never connects to a database. Add real values
33
+ # as repository secrets if your build needs them
34
+ env:
35
+ {{#each ciBuildEnv}}
36
+ {{name}}: "{{value}}"
37
+ {{/each}}
38
+ {{/if}}
39
+ run: {{docker.run}} build
40
+ - name: Type-check
41
+ run: {{docker.run}} type:check
42
+ - name: Lint
43
+ run: {{docker.run}} lint
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "ci",
3
+ "name": "GitHub Actions CI",
4
+ "description": "A GitHub Actions workflow that builds, type-checks, and lints every push and pull request",
5
+ "type": "feature",
6
+ "dependencies": {},
7
+ "devDependencies": {},
8
+ "scripts": {},
9
+ "envVars": {}
10
+ }
@@ -2,6 +2,7 @@ node_modules
2
2
  npm-debug.log
3
3
  .env*
4
4
  .git
5
+ .github
5
6
  .gitignore
6
7
  README.md
7
8
  .vscode
@@ -1,5 +1,5 @@
1
1
  services:
2
- {{projectName}}:
2
+ app:
3
3
  build: .
4
4
  ports:
5
5
  - "{{port}}:{{port}}"
@@ -4,12 +4,12 @@
4
4
  "description": "Drizzle ORM for Express with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "drizzle-orm": "^0.45.2",
8
- "pg": "^8.23.0",
7
+ "drizzle-orm": "^0.45.4",
8
+ "pg": "^8.23.1",
9
9
  "dotenv": "^17.4.2"
10
10
  },
11
11
  "devDependencies": {
12
- "drizzle-kit": "^0.31.10",
12
+ "drizzle-kit": "^0.31.11",
13
13
  "@types/pg": "^8.23.1"
14
14
  },
15
15
  "scripts": {
@@ -4,12 +4,12 @@
4
4
  "description": "Drizzle ORM for NestJS with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "drizzle-orm": "^0.45.2",
8
- "pg": "^8.23.0",
7
+ "drizzle-orm": "^0.45.4",
8
+ "pg": "^8.23.1",
9
9
  "dotenv": "^17.4.2"
10
10
  },
11
11
  "devDependencies": {
12
- "drizzle-kit": "^0.31.10",
12
+ "drizzle-kit": "^0.31.11",
13
13
  "@types/pg": "^8.23.1"
14
14
  },
15
15
  "scripts": {
@@ -4,12 +4,12 @@
4
4
  "description": "Drizzle ORM for Next.js with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "drizzle-orm": "^0.45.2",
8
- "pg": "^8.23.0",
7
+ "drizzle-orm": "^0.45.4",
8
+ "pg": "^8.23.1",
9
9
  "dotenv": "^17.4.2"
10
10
  },
11
11
  "devDependencies": {
12
- "drizzle-kit": "^0.31.10",
12
+ "drizzle-kit": "^0.31.11",
13
13
  "@types/pg": "^8.23.1"
14
14
  },
15
15
  "scripts": {
@@ -4,12 +4,12 @@
4
4
  "description": "Drizzle ORM for TanStack Start with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "drizzle-orm": "^0.45.2",
8
- "pg": "^8.23.0",
7
+ "drizzle-orm": "^0.45.4",
8
+ "pg": "^8.23.1",
9
9
  "dotenv": "^17.4.2"
10
10
  },
11
11
  "devDependencies": {
12
- "drizzle-kit": "^0.31.10",
12
+ "drizzle-kit": "^0.31.11",
13
13
  "@types/pg": "^8.23.1"
14
14
  },
15
15
  "scripts": {
@@ -4,8 +4,8 @@
4
4
  "description": "MongoDB database integration for Express using Mongoose ODM",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "mongoose": "^9.9.5",
8
- "mongodb": "^7.6.0"
7
+ "mongoose": "^9.11.4",
8
+ "mongodb": "^7.8.0"
9
9
  },
10
10
  "devDependencies": {},
11
11
  "scripts": {},
@@ -5,7 +5,7 @@
5
5
  "type": "database",
6
6
  "dependencies": {
7
7
  "@nestjs/mongoose": "^12.0.0",
8
- "mongoose": "^9.9.5"
8
+ "mongoose": "^9.11.4"
9
9
  },
10
10
  "devDependencies": {},
11
11
  "scripts": {},
@@ -4,8 +4,8 @@
4
4
  "description": "MongoDB database integration for Next.js using Mongoose ODM",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "mongoose": "^9.9.5",
8
- "mongodb": "^7.6.0"
7
+ "mongoose": "^9.11.4",
8
+ "mongodb": "^7.8.0"
9
9
  },
10
10
  "devDependencies": {},
11
11
  "scripts": {},
@@ -4,8 +4,8 @@
4
4
  "description": "MongoDB database integration for TanStack Start using Mongoose ODM",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "mongoose": "^9.9.5",
8
- "mongodb": "^7.6.0",
7
+ "mongoose": "^9.11.4",
8
+ "mongodb": "^7.8.0",
9
9
  "dotenv": "^17.4.2"
10
10
  },
11
11
  "devDependencies": {},
@@ -4,11 +4,11 @@
4
4
  "description": "Prisma ORM with PostgreSQL database support",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "@prisma/adapter-pg": "^7.10.0",
8
- "@prisma/client": "^7.10.0"
7
+ "@prisma/adapter-pg": "^7.10.1",
8
+ "@prisma/client": "^7.10.1"
9
9
  },
10
10
  "devDependencies": {
11
- "prisma": "^7.10.0"
11
+ "prisma": "^7.10.1"
12
12
  },
13
13
  "scripts": {
14
14
  "prisma:generate": "prisma generate",
@@ -22,6 +22,6 @@
22
22
  },
23
23
  "overrides": {
24
24
  "deepmerge-ts": "^8.0.2",
25
- "mysql2": "^3.24.3"
25
+ "mysql2": "^3.24.6"
26
26
  }
27
27
  }
@@ -1,5 +1,5 @@
1
1
  import "dotenv/config";
2
- import { defineConfig, env } from "prisma/config";
2
+ import { defineConfig } from "prisma/config";
3
3
 
4
4
  export default defineConfig({
5
5
  schema: "prisma/schema.prisma",
@@ -7,6 +7,8 @@ export default defineConfig({
7
7
  path: "prisma/migrations",
8
8
  },
9
9
  datasource: {
10
- url: env("DATABASE_URL"),
10
+ // prisma generate needs no URL, so a build without .env (CI, a host) works.
11
+ // Commands that connect report the empty URL
12
+ url: process.env.DATABASE_URL ?? "",
11
13
  },
12
14
  });
@@ -4,11 +4,11 @@
4
4
  "description": "Prisma ORM for NestJS with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "@prisma/adapter-pg": "^7.10.0",
8
- "@prisma/client": "^7.10.0"
7
+ "@prisma/adapter-pg": "^7.10.1",
8
+ "@prisma/client": "^7.10.1"
9
9
  },
10
10
  "devDependencies": {
11
- "prisma": "^7.10.0"
11
+ "prisma": "^7.10.1"
12
12
  },
13
13
  "scripts": {
14
14
  "prisma:generate": "prisma generate",
@@ -22,6 +22,6 @@
22
22
  },
23
23
  "overrides": {
24
24
  "deepmerge-ts": "^8.0.2",
25
- "mysql2": "^3.24.3"
25
+ "mysql2": "^3.24.6"
26
26
  }
27
27
  }
@@ -1,5 +1,5 @@
1
1
  import "dotenv/config";
2
- import { defineConfig, env } from "prisma/config";
2
+ import { defineConfig } from "prisma/config";
3
3
 
4
4
  export default defineConfig({
5
5
  schema: "prisma/schema.prisma",
@@ -7,6 +7,8 @@ export default defineConfig({
7
7
  path: "prisma/migrations",
8
8
  },
9
9
  datasource: {
10
- url: env("DATABASE_URL"),
10
+ // prisma generate needs no URL, so a build without .env (CI, a host) works.
11
+ // Commands that connect report the empty URL
12
+ url: process.env.DATABASE_URL ?? "",
11
13
  },
12
14
  });
@@ -4,11 +4,11 @@
4
4
  "description": "Prisma ORM for Next.js with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "@prisma/client": "^7.10.0",
8
- "@prisma/adapter-pg": "^7.10.0"
7
+ "@prisma/client": "^7.10.1",
8
+ "@prisma/adapter-pg": "^7.10.1"
9
9
  },
10
10
  "devDependencies": {
11
- "prisma": "^7.10.0",
11
+ "prisma": "^7.10.1",
12
12
  "dotenv": "^17.4.2"
13
13
  },
14
14
  "scripts": {
@@ -23,6 +23,6 @@
23
23
  },
24
24
  "overrides": {
25
25
  "deepmerge-ts": "^8.0.2",
26
- "mysql2": "^3.24.3"
26
+ "mysql2": "^3.24.6"
27
27
  }
28
28
  }
@@ -1,5 +1,5 @@
1
1
  import "dotenv/config";
2
- import { defineConfig, env } from "prisma/config";
2
+ import { defineConfig } from "prisma/config";
3
3
 
4
4
  export default defineConfig({
5
5
  schema: "prisma/schema.prisma",
@@ -7,6 +7,8 @@ export default defineConfig({
7
7
  path: "prisma/migrations",
8
8
  },
9
9
  datasource: {
10
- url: env("DATABASE_URL"),
10
+ // prisma generate needs no URL, so a build without .env (CI, a host) works.
11
+ // Commands that connect report the empty URL
12
+ url: process.env.DATABASE_URL ?? "",
11
13
  },
12
14
  });
@@ -4,11 +4,11 @@
4
4
  "description": "Prisma ORM for TanStack Start with PostgreSQL",
5
5
  "type": "database",
6
6
  "dependencies": {
7
- "@prisma/client": "^7.10.0",
8
- "@prisma/adapter-pg": "^7.10.0"
7
+ "@prisma/client": "^7.10.1",
8
+ "@prisma/adapter-pg": "^7.10.1"
9
9
  },
10
10
  "devDependencies": {
11
- "prisma": "^7.10.0",
11
+ "prisma": "^7.10.1",
12
12
  "dotenv": "^17.4.2"
13
13
  },
14
14
  "scripts": {
@@ -23,6 +23,6 @@
23
23
  },
24
24
  "overrides": {
25
25
  "deepmerge-ts": "^8.0.2",
26
- "mysql2": "^3.24.3"
26
+ "mysql2": "^3.24.6"
27
27
  }
28
28
  }
@@ -1,5 +1,5 @@
1
1
  import "dotenv/config";
2
- import { defineConfig, env } from "prisma/config";
2
+ import { defineConfig } from "prisma/config";
3
3
 
4
4
  export default defineConfig({
5
5
  schema: "prisma/schema.prisma",
@@ -7,6 +7,8 @@ export default defineConfig({
7
7
  path: "prisma/migrations",
8
8
  },
9
9
  datasource: {
10
- url: env("DATABASE_URL"),
10
+ // prisma generate needs no URL, so a build without .env (CI, a host) works.
11
+ // Commands that connect report the empty URL
12
+ url: process.env.DATABASE_URL ?? "",
11
13
  },
12
14
  });
@@ -4,7 +4,7 @@
4
4
  "description": "Per-IP request limits for Express with express-rate-limit",
5
5
  "type": "feature",
6
6
  "dependencies": {
7
- "express-rate-limit": "^8.7.1"
7
+ "express-rate-limit": "^8.7.2"
8
8
  },
9
9
  "devDependencies": {},
10
10
  "scripts": {},
@@ -4,12 +4,14 @@ import { rateLimit } from "express-rate-limit";
4
4
  // balancer, set app.set("trust proxy", 1) so the real client IP is used.
5
5
  const FIFTEEN_MINUTES = 15 * 60 * 1000;
6
6
 
7
- // Every route: 100 requests per IP in 15 minutes
7
+ // Every route: 100 requests per IP in 15 minutes. The health check is left out
8
+ // because Docker and load balancers call it on a schedule
8
9
  export const apiLimiter = rateLimit({
9
10
  windowMs: FIFTEEN_MINUTES,
10
11
  limit: 100,
11
12
  standardHeaders: "draft-8",
12
13
  legacyHeaders: false,
14
+ skip: (req) => req.path === "/health",
13
15
  });
14
16
 
15
17
  // Sign-up and login: 10 attempts per IP in 15 minutes, against password guessing