create-better-t-stack 3.8.3-pr730.1784e47 → 3.9.0-pr730.0ee9844

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 (60) hide show
  1. package/README.md +17 -16
  2. package/dist/cli.mjs +1 -1
  3. package/dist/index.mjs +1 -1
  4. package/dist/{src-GZpht_dQ.mjs → src-DW15ZRe9.mjs} +70 -89
  5. package/package.json +39 -40
  6. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +21 -19
  7. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +6 -2
  8. package/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs +11 -6
  9. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +2 -2
  10. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +23 -20
  11. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +13 -5
  12. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +25 -22
  13. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +23 -24
  14. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +4 -4
  15. package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +0 -1
  16. package/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs +24 -21
  17. package/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs +24 -21
  18. package/templates/auth/better-auth/web/react/{tanstack-start/src/components/user-menu.tsx → tanstack-router/src/components/user-menu.tsx.hbs} +26 -23
  19. package/templates/auth/better-auth/web/react/{tanstack-router/src/components/user-menu.tsx → tanstack-start/src/components/user-menu.tsx.hbs} +26 -23
  20. package/templates/frontend/react/next/package.json.hbs +8 -7
  21. package/templates/frontend/react/next/src/app/layout.tsx.hbs +28 -1
  22. package/templates/frontend/react/next/src/components/providers.tsx.hbs +14 -4
  23. package/templates/frontend/react/react-router/package.json.hbs +2 -1
  24. package/templates/frontend/react/{tanstack-router/src/components/mode-toggle.tsx → react-router/src/components/mode-toggle.tsx.hbs} +1 -1
  25. package/templates/frontend/react/tanstack-router/package.json.hbs +2 -1
  26. package/templates/frontend/react/{react-router/src/components/mode-toggle.tsx → tanstack-router/src/components/mode-toggle.tsx.hbs} +1 -1
  27. package/templates/frontend/react/tanstack-start/package.json.hbs +2 -1
  28. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +6 -0
  29. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +13 -14
  30. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +5 -0
  31. package/templates/frontend/react/web-base/components.json +5 -2
  32. package/templates/frontend/react/web-base/src/components/ui/button.tsx.hbs +57 -0
  33. package/templates/frontend/react/web-base/src/components/ui/card.tsx.hbs +103 -0
  34. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx.hbs +26 -0
  35. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx.hbs +262 -0
  36. package/templates/frontend/react/web-base/src/components/ui/input.tsx.hbs +20 -0
  37. package/templates/frontend/react/web-base/src/components/ui/label.tsx.hbs +20 -0
  38. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +13 -0
  39. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx.hbs +44 -0
  40. package/templates/frontend/react/web-base/src/index.css.hbs +57 -63
  41. package/templates/frontend/react/web-base/src/components/ui/button.tsx +0 -56
  42. package/templates/frontend/react/web-base/src/components/ui/card.tsx +0 -75
  43. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx +0 -27
  44. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx +0 -228
  45. package/templates/frontend/react/web-base/src/components/ui/input.tsx +0 -21
  46. package/templates/frontend/react/web-base/src/components/ui/label.tsx +0 -19
  47. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx +0 -13
  48. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx +0 -25
  49. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  50. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  51. /package/templates/auth/better-auth/web/react/tanstack-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  52. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  53. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  54. /package/templates/auth/better-auth/web/react/tanstack-start/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  55. /package/templates/auth/better-auth/web/solid/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  56. /package/templates/auth/better-auth/web/solid/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  57. /package/templates/auth/better-auth/web/solid/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  58. /package/templates/frontend/react/react-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  59. /package/templates/frontend/react/tanstack-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  60. /package/templates/frontend/react/web-base/src/lib/{utils.ts → utils.ts.hbs} +0 -0
package/README.md CHANGED
@@ -38,7 +38,7 @@ Follow the prompts to configure your project or use the `--yes` flag for default
38
38
  | **Runtime** | • Bun<br>• Node.js<br>• Cloudflare Workers<br>• None |
39
39
  | **Database** | • SQLite<br>• PostgreSQL<br>• MySQL<br>• MongoDB<br>• None |
40
40
  | **ORM** | • Drizzle (TypeScript-first)<br>• Prisma (feature-rich)<br>• Mongoose (for MongoDB)<br>• None |
41
- | **Database Setup** | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres<br>• MongoDB Atlas<br>• None (manual setup) |
41
+ | **Database Setup** | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres<br>• MongoDB Atlas<br>• None (manual setup) |
42
42
  | **Authentication** | Better-Auth (email/password, with more options coming soon) |
43
43
  | **Styling** | Tailwind CSS with shadcn/ui components |
44
44
  | **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Starlight (documentation site)<br>• Biome (linting and formatting)<br>• Husky (Git hooks)<br>• Turborepo (optimized builds) |
@@ -77,6 +77,7 @@ Options:
77
77
  ## Telemetry
78
78
 
79
79
  This CLI collects anonymous usage data to help improve the tool. The data collected includes:
80
+
80
81
  - Configuration options selected
81
82
  - CLI version
82
83
  - Node.js version
@@ -90,7 +91,7 @@ You can disable telemetry by setting the `BTS_TELEMETRY_DISABLED` environment va
90
91
 
91
92
  ```bash
92
93
  # Disable telemetry for a single run
93
- BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack my-app
94
+ BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack
94
95
 
95
96
  # Disable telemetry globally in your shell profile (.bashrc, .zshrc, etc.)
96
97
  export BTS_TELEMETRY_DISABLED=1
@@ -101,85 +102,85 @@ export BTS_TELEMETRY_DISABLED=1
101
102
  Create a project with default configuration:
102
103
 
103
104
  ```bash
104
- npx create-better-t-stack my-app --yes
105
+ npx create-better-t-stack --yes
105
106
  ```
106
107
 
107
108
  Create a project with specific options:
108
109
 
109
110
  ```bash
110
- npx create-better-t-stack my-app --database postgres --orm drizzle --auth --addons pwa biome
111
+ npx create-better-t-stack --database postgres --orm drizzle --auth --addons pwa biome
111
112
  ```
112
113
 
113
114
  Create a project with Elysia backend and Node.js runtime:
114
115
 
115
116
  ```bash
116
- npx create-better-t-stack my-app --backend elysia --runtime node
117
+ npx create-better-t-stack --backend elysia --runtime node
117
118
  ```
118
119
 
119
120
  Create a project with multiple frontend options (one web + one native):
120
121
 
121
122
  ```bash
122
- npx create-better-t-stack my-app --frontend tanstack-router native-bare
123
+ npx create-better-t-stack --frontend tanstack-router native-bare
123
124
  ```
124
125
 
125
126
  Create a project with examples:
126
127
 
127
128
  ```bash
128
- npx create-better-t-stack my-app --examples todo ai
129
+ npx create-better-t-stack --examples todo ai
129
130
  ```
130
131
 
131
132
  Create a project with Turso database setup:
132
133
 
133
134
  ```bash
134
- npx create-better-t-stack my-app --database sqlite --orm drizzle --db-setup turso
135
+ npx create-better-t-stack --database sqlite --orm drizzle --db-setup turso
135
136
  ```
136
137
 
137
138
  Create a project with Supabase PostgreSQL setup:
138
139
 
139
140
  ```bash
140
- npx create-better-t-stack my-app --database postgres --orm drizzle --db-setup supabase --auth
141
+ npx create-better-t-stack --database postgres --orm drizzle --db-setup supabase --auth
141
142
  ```
142
143
 
143
144
  Create a project with Convex backend:
144
145
 
145
146
  ```bash
146
- npx create-better-t-stack my-app --backend convex --frontend tanstack-router
147
+ npx create-better-t-stack --backend convex --frontend tanstack-router
147
148
  ```
148
149
 
149
150
  Create a project with documentation site:
150
151
 
151
152
  ```bash
152
- npx create-better-t-stack my-app --addons starlight
153
+ npx create-better-t-stack --addons starlight
153
154
  ```
154
155
 
155
156
  Create a minimal TypeScript project with no backend:
156
157
 
157
158
  ```bash
158
- npx create-better-t-stack my-app --backend none --frontend tanstack-router
159
+ npx create-better-t-stack --backend none --frontend tanstack-router
159
160
  ```
160
161
 
161
162
  Create a backend-only project with no frontend:
162
163
 
163
164
  ```bash
164
- npx create-better-t-stack my-app --frontend none --backend hono --database postgres --orm drizzle
165
+ npx create-better-t-stack --frontend none --backend hono --database postgres --orm drizzle
165
166
  ```
166
167
 
167
168
  Create a simple frontend-only project:
168
169
 
169
170
  ```bash
170
- npx create-better-t-stack my-app --backend none --frontend next --addons none --examples none
171
+ npx create-better-t-stack --backend none --frontend next --addons none --examples none
171
172
  ```
172
173
 
173
174
  Create a Cloudflare Workers project:
174
175
 
175
176
  ```bash
176
- npx create-better-t-stack my-app --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1
177
+ npx create-better-t-stack --backend hono --runtime workers --database sqlite --orm drizzle --db-setup d1
177
178
  ```
178
179
 
179
180
  Create a minimal API-only project:
180
181
 
181
182
  ```bash
182
- npx create-better-t-stack my-app --frontend none --backend hono --api trpc --database none --addons none
183
+ npx create-better-t-stack --frontend none --backend hono --api trpc --database none --addons none
183
184
  ```
184
185
 
185
186
  ## Compatibility Notes
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createBtsCli } from "./src-GZpht_dQ.mjs";
2
+ import { n as createBtsCli } from "./src-DW15ZRe9.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-GZpht_dQ.mjs";
2
+ import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-DW15ZRe9.mjs";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -15,7 +15,7 @@ import { $, execa } from "execa";
15
15
  import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
16
16
  import { glob } from "tinyglobby";
17
17
  import handlebars from "handlebars";
18
- import { Biome } from "@biomejs/js-api/nodejs";
18
+ import { format } from "oxfmt";
19
19
  import yaml from "yaml";
20
20
  import os$1 from "node:os";
21
21
 
@@ -95,7 +95,8 @@ const dependencyVersionMap = {
95
95
  "@vite-pwa/assets-generator": "^1.0.0",
96
96
  "@tauri-apps/cli": "^2.4.0",
97
97
  "@biomejs/biome": "^2.2.0",
98
- oxlint: "^1.32.0",
98
+ oxlint: "^1.34.0",
99
+ oxfmt: "^0.19.0",
99
100
  husky: "^9.1.7",
100
101
  "lint-staged": "^16.1.2",
101
102
  tsx: "^4.19.2",
@@ -363,7 +364,7 @@ function getAddonDisplay(addon) {
363
364
  break;
364
365
  case "oxlint":
365
366
  label = "Oxlint";
366
- hint = "Rust-powered linter";
367
+ hint = "Oxlint + Oxfmt (linting & formatting)";
367
368
  break;
368
369
  case "ultracite":
369
370
  label = "Ultracite";
@@ -2022,6 +2023,38 @@ async function setupFumadocs(config) {
2022
2023
  }
2023
2024
  }
2024
2025
 
2026
+ //#endregion
2027
+ //#region src/helpers/addons/oxlint-setup.ts
2028
+ async function setupOxlint(projectDir, packageManager) {
2029
+ await addPackageDependency({
2030
+ devDependencies: ["oxlint", "oxfmt"],
2031
+ projectDir
2032
+ });
2033
+ const packageJsonPath = path.join(projectDir, "package.json");
2034
+ if (await fs.pathExists(packageJsonPath)) {
2035
+ const packageJson = await fs.readJson(packageJsonPath);
2036
+ packageJson.scripts = {
2037
+ ...packageJson.scripts,
2038
+ check: "oxlint && oxfmt --write"
2039
+ };
2040
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2041
+ }
2042
+ const s = spinner();
2043
+ const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
2044
+ s.start("Initializing oxlint and oxfmt...");
2045
+ await execa(oxlintInitCommand, {
2046
+ cwd: projectDir,
2047
+ env: { CI: "true" },
2048
+ shell: true
2049
+ });
2050
+ await execa(getPackageExecutionCommand(packageManager, "oxfmt@latest --init"), {
2051
+ cwd: projectDir,
2052
+ env: { CI: "true" },
2053
+ shell: true
2054
+ });
2055
+ s.stop("oxlint and oxfmt initialized successfully!");
2056
+ }
2057
+
2025
2058
  //#endregion
2026
2059
  //#region src/helpers/addons/ruler-setup.ts
2027
2060
  async function setupRuler(config) {
@@ -2391,7 +2424,7 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
2391
2424
  await setupHusky(projectDir, linter);
2392
2425
  }
2393
2426
  }
2394
- if (addons.includes("oxlint")) await setupOxlint(projectDir, packageManager);
2427
+ if (hasOxlint) await setupOxlint(projectDir, packageManager);
2395
2428
  if (addons.includes("starlight")) await setupStarlight(config);
2396
2429
  if (addons.includes("ruler")) await setupRuler(config);
2397
2430
  if (addons.includes("fumadocs")) await setupFumadocs(config);
@@ -2433,7 +2466,7 @@ async function setupHusky(projectDir, linter) {
2433
2466
  ...packageJson.scripts,
2434
2467
  prepare: "husky"
2435
2468
  };
2436
- if (linter === "oxlint") packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "oxlint" };
2469
+ if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
2437
2470
  else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
2438
2471
  else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
2439
2472
  await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
@@ -2464,30 +2497,6 @@ async function setupPwa(projectDir, frontends) {
2464
2497
  const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
2465
2498
  if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
2466
2499
  }
2467
- async function setupOxlint(projectDir, packageManager) {
2468
- await addPackageDependency({
2469
- devDependencies: ["oxlint"],
2470
- projectDir
2471
- });
2472
- const packageJsonPath = path.join(projectDir, "package.json");
2473
- if (await fs.pathExists(packageJsonPath)) {
2474
- const packageJson = await fs.readJson(packageJsonPath);
2475
- packageJson.scripts = {
2476
- ...packageJson.scripts,
2477
- check: "oxlint"
2478
- };
2479
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2480
- }
2481
- const oxlintInitCommand = getPackageExecutionCommand(packageManager, "oxlint@latest --init");
2482
- const s = spinner();
2483
- s.start("Initializing oxlint...");
2484
- await execa(oxlintInitCommand, {
2485
- cwd: projectDir,
2486
- env: { CI: "true" },
2487
- shell: true
2488
- });
2489
- s.stop("oxlint initialized successfully!");
2490
- }
2491
2500
 
2492
2501
  //#endregion
2493
2502
  //#region src/helpers/core/detect-project-config.ts
@@ -2543,61 +2552,16 @@ async function installDependencies({ projectDir, packageManager }) {
2543
2552
  }
2544
2553
 
2545
2554
  //#endregion
2546
- //#region src/utils/biome-formatter.ts
2547
- function initializeBiome() {
2548
- try {
2549
- const biome = new Biome();
2550
- const projectKey = biome.openProject("./").projectKey;
2551
- biome.applyConfiguration(projectKey, {
2552
- formatter: {
2553
- enabled: true,
2554
- indentStyle: "tab",
2555
- indentWidth: 2,
2556
- lineWidth: 80
2557
- },
2558
- linter: { enabled: false },
2559
- javascript: { formatter: { enabled: true } },
2560
- json: { formatter: { enabled: true } }
2561
- });
2562
- return {
2563
- biome,
2564
- projectKey
2565
- };
2566
- } catch {
2567
- return null;
2568
- }
2569
- }
2570
- function isSupportedFile(filePath) {
2571
- const ext = path.extname(filePath).toLowerCase();
2572
- return [
2573
- ".js",
2574
- ".jsx",
2575
- ".ts",
2576
- ".tsx",
2577
- ".json",
2578
- ".jsonc"
2579
- ].includes(ext);
2580
- }
2581
- function shouldSkipFile(filePath) {
2582
- const basename = path.basename(filePath);
2583
- return [
2584
- ".hbs",
2585
- "package-lock.json",
2586
- "yarn.lock",
2587
- "pnpm-lock.yaml",
2588
- "bun.lock",
2589
- ".d.ts"
2590
- ].some((pattern) => basename.includes(pattern));
2591
- }
2592
- function formatFileWithBiome(filePath, content) {
2593
- if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
2555
+ //#region src/utils/file-formatter.ts
2556
+ const formatOptions = {
2557
+ experimentalSortPackageJson: true,
2558
+ experimentalSortImports: { order: "asc" }
2559
+ };
2560
+ async function formatFile(filePath, content) {
2594
2561
  try {
2595
- const biomeResult = initializeBiome();
2596
- if (!biomeResult) return null;
2597
- const { biome, projectKey } = biomeResult;
2598
- const result = biome.formatContent(projectKey, content, { filePath: path.basename(filePath) });
2599
- if (result.diagnostics && result.diagnostics.length > 0) consola.debug(`Biome formatting diagnostics for ${filePath}:`, result.diagnostics);
2600
- return result.content;
2562
+ const result = await format(path.basename(filePath), content, formatOptions);
2563
+ if (result.errors && result.errors.length > 0) return null;
2564
+ return result.code;
2601
2565
  } catch {
2602
2566
  return null;
2603
2567
  }
@@ -2627,7 +2591,7 @@ async function processTemplate(srcPath, destPath, context) {
2627
2591
  content = handlebars.compile(templateContent)(context);
2628
2592
  } else content = await fs.readFile(srcPath, "utf-8");
2629
2593
  try {
2630
- const formattedContent = await formatFileWithBiome(destPath, content);
2594
+ const formattedContent = await formatFile(destPath, content);
2631
2595
  if (formattedContent) content = formattedContent;
2632
2596
  } catch (formatError) {
2633
2597
  consola.debug(`Failed to format ${destPath}:`, formatError);
@@ -4513,10 +4477,12 @@ async function setupEnvironmentVariables(config) {
4513
4477
  const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
4514
4478
  const hasWeb = hasWebFrontend$1;
4515
4479
  if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4480
+ let siteUrlComments = "";
4481
+ if (hasWeb) siteUrlComments += "# npx convex env set SITE_URL http://localhost:3001\n";
4482
+ if (hasNative) siteUrlComments += "# npx convex env set NATIVE_SITE_URL http://localhost:8081 # For Expo Web\n";
4516
4483
  const convexCommands = `# Set Convex environment variables
4517
4484
  # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4518
- ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
4519
- `;
4485
+ ${siteUrlComments}`;
4520
4486
  await fs.appendFile(envLocalPath, convexCommands);
4521
4487
  }
4522
4488
  const convexBackendVars = [];
@@ -4525,6 +4491,11 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
4525
4491
  value: "",
4526
4492
  condition: true,
4527
4493
  comment: "Same as CONVEX_URL but ends in .site"
4494
+ }, {
4495
+ key: "NATIVE_SITE_URL",
4496
+ value: "http://localhost:8081",
4497
+ condition: true,
4498
+ comment: "Expo Web URL for authentication"
4528
4499
  });
4529
4500
  if (hasWeb) convexBackendVars.push({
4530
4501
  key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
@@ -4534,7 +4505,8 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
4534
4505
  }, {
4535
4506
  key: "SITE_URL",
4536
4507
  value: "http://localhost:3001",
4537
- condition: true
4508
+ condition: true,
4509
+ comment: "Web app URL for authentication"
4538
4510
  });
4539
4511
  await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4540
4512
  }
@@ -5940,6 +5912,7 @@ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, ba
5940
5912
  for (const addon of addons) if (addon === "pwa") addonsList.push("- **PWA** - Progressive Web App support");
5941
5913
  else if (addon === "tauri") addonsList.push("- **Tauri** - Build native desktop applications");
5942
5914
  else if (addon === "biome") addonsList.push("- **Biome** - Linting and formatting");
5915
+ else if (addon === "oxlint") addonsList.push("- **Oxlint** - Oxlint + Oxfmt (linting & formatting)");
5943
5916
  else if (addon === "husky") addonsList.push("- **Husky** - Git hooks for code quality");
5944
5917
  else if (addon === "starlight") addonsList.push("- **Starlight** - Documentation site with Astro");
5945
5918
  else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
@@ -6015,6 +5988,8 @@ function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNati
6015
5988
  }
6016
5989
  if (addons.includes("biome")) scripts += `
6017
5990
  - \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
5991
+ if (addons.includes("oxlint")) scripts += `
5992
+ - \`${packageManagerRunCmd} check\`: Run Oxlint and Oxfmt`;
6018
5993
  if (addons.includes("pwa")) scripts += `
6019
5994
  - \`cd apps/web && ${packageManagerRunCmd} generate-pwa-assets\`: Generate PWA assets`;
6020
5995
  if (addons.includes("tauri")) scripts += `
@@ -6166,10 +6141,12 @@ async function displayPostInstallInstructions(config) {
6166
6141
  const isBackendSelf = backend === "self";
6167
6142
  const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
6168
6143
  const cdCmd = `cd ${relativePath}`;
6169
- const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
6144
+ const hasHusky = addons?.includes("husky");
6145
+ const hasLinting = addons?.includes("biome") || addons?.includes("oxlint");
6170
6146
  const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
6171
6147
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
6172
- const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
6148
+ const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : "";
6149
+ const lintingInstructions = hasLinting ? getLintingInstructions(runCmd) : "";
6173
6150
  const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || []) : "";
6174
6151
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
6175
6152
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
@@ -6223,6 +6200,7 @@ async function displayPostInstallInstructions(config) {
6223
6200
  if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
6224
6201
  if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
6225
6202
  if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
6203
+ if (huskyInstructions) output += `\n${huskyInstructions.trim()}\n`;
6226
6204
  if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
6227
6205
  if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
6228
6206
  if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
@@ -6244,6 +6222,9 @@ function getNativeInstructions(isConvex, isBackendSelf, _frontend) {
6244
6222
  if (isConvex) instructions += `\n${pc.yellow("IMPORTANT:")} When using local development with Convex and native apps,\n ensure you use your local IP address instead of localhost or 127.0.0.1\n for proper connectivity.\n`;
6245
6223
  return instructions;
6246
6224
  }
6225
+ function getHuskyInstructions(runCmd) {
6226
+ return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
6227
+ }
6247
6228
  function getLintingInstructions(runCmd) {
6248
6229
  return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
6249
6230
  }
package/package.json CHANGED
@@ -1,52 +1,61 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.8.3-pr730.1784e47",
3
+ "version": "3.9.0-pr730.0ee9844",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "Aman Varshney",
8
- "bin": {
9
- "create-better-t-stack": "dist/cli.mjs"
10
- },
11
- "files": [
12
- "templates",
13
- "dist"
14
- ],
15
5
  "keywords": [
6
+ "better-auth",
16
7
  "better-t-stack",
17
- "typescript",
8
+ "biome",
18
9
  "boilerplate",
19
- "starter",
20
10
  "cli",
21
- "turborepo",
22
- "trpc",
23
- "better-auth",
24
- "monorepo",
25
- "fullstack",
26
- "type-safety",
27
- "react",
28
- "react-native",
11
+ "drizzle",
12
+ "elysia",
29
13
  "expo",
14
+ "fullstack",
30
15
  "hono",
31
- "elysia",
32
- "drizzle",
16
+ "monorepo",
33
17
  "prisma",
34
- "tanstack",
35
- "tailwind",
36
- "shadcn",
37
18
  "pwa",
19
+ "react",
20
+ "react-native",
21
+ "shadcn",
22
+ "starter",
23
+ "tailwind",
24
+ "tanstack",
38
25
  "tauri",
39
- "biome"
26
+ "trpc",
27
+ "turborepo",
28
+ "type-safety",
29
+ "typescript"
40
30
  ],
31
+ "homepage": "https://better-t-stack.dev/",
32
+ "license": "MIT",
33
+ "author": "Aman Varshney",
41
34
  "repository": {
42
35
  "type": "git",
43
36
  "url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
44
37
  "directory": "apps/cli"
45
38
  },
39
+ "bin": {
40
+ "create-better-t-stack": "dist/cli.mjs"
41
+ },
42
+ "files": [
43
+ "dist",
44
+ "templates"
45
+ ],
46
+ "type": "module",
47
+ "exports": {
48
+ ".": {
49
+ "types": "./dist/index.d.mts",
50
+ "import": "./dist/index.mjs"
51
+ },
52
+ "./cli": {
53
+ "import": "./dist/cli.mjs"
54
+ }
55
+ },
46
56
  "publishConfig": {
47
57
  "access": "public"
48
58
  },
49
- "homepage": "https://better-t-stack.dev/",
50
59
  "scripts": {
51
60
  "build": "tsdown --publint",
52
61
  "dev": "tsdown --watch",
@@ -57,19 +66,8 @@
57
66
  "test:ci": "bun run build && AGENT=1 bun test --bail=5",
58
67
  "prepublishOnly": "npm run build"
59
68
  },
60
- "exports": {
61
- ".": {
62
- "types": "./dist/index.d.mts",
63
- "import": "./dist/index.mjs"
64
- },
65
- "./cli": {
66
- "import": "./dist/cli.mjs"
67
- }
68
- },
69
69
  "dependencies": {
70
- "@better-t-stack/types": "3.8.3-pr730.1784e47",
71
- "@biomejs/js-api": "^4.0.0",
72
- "@biomejs/wasm-nodejs": "^2.3.8",
70
+ "@better-t-stack/types": "3.9.0-pr730.0ee9844",
73
71
  "@clack/prompts": "^1.0.0-alpha.8",
74
72
  "@orpc/server": "^1.12.2",
75
73
  "consola": "^3.4.2",
@@ -78,6 +76,7 @@
78
76
  "gradient-string": "^3.0.0",
79
77
  "handlebars": "^4.7.8",
80
78
  "jsonc-parser": "^3.3.1",
79
+ "oxfmt": "^0.19.0",
81
80
  "picocolors": "^1.1.1",
82
81
  "tinyglobby": "^0.2.15",
83
82
  "trpc-cli": "^0.12.1",
@@ -1,45 +1,44 @@
1
1
  import { createClient, type GenericCtx } from "@convex-dev/better-auth";
2
2
  {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
3
- import { convex } from "@convex-dev/better-auth/plugins";
3
+ import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
4
4
  import { expo } from "@better-auth/expo";
5
+ {{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
6
+ import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
5
7
  {{else}}
6
8
  import { convex } from "@convex-dev/better-auth/plugins";
7
9
  {{/if}}
8
- {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
9
- import { crossDomain } from "@convex-dev/better-auth/plugins";
10
- {{/if}}
11
10
  import { components } from "./_generated/api";
12
11
  import { DataModel } from "./_generated/dataModel";
13
12
  import { query } from "./_generated/server";
14
13
  import { betterAuth } from "better-auth";
15
14
  import { v } from "convex/values";
15
+ import authConfig from "./auth.config";
16
16
 
17
- {{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
17
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
18
+ const siteUrl = process.env.SITE_URL!;
19
+ {{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
18
20
  const siteUrl = process.env.SITE_URL!;
19
21
  {{/if}}
20
22
  {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
21
23
  const nativeAppUrl = process.env.NATIVE_APP_URL || "mybettertapp://";
24
+ const nativeSiteUrl = process.env.NATIVE_SITE_URL || "http://localhost:8081";
22
25
  {{/if}}
23
26
 
24
27
  export const authComponent = createClient<DataModel>(components.betterAuth);
25
28
 
26
- function createAuth(
27
- ctx: GenericCtx<DataModel>,
28
- { optionsOnly }: { optionsOnly?: boolean } = { optionsOnly: false }
29
- ) {
29
+ function createAuth(ctx: GenericCtx<DataModel>) {
30
30
  return betterAuth({
31
- logger: {
32
- disabled: optionsOnly,
33
- },
34
31
  {{#if (and (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")) (or (includes frontend "tanstack-start") (includes frontend "next")))}}
35
32
  baseURL: siteUrl,
36
- trustedOrigins: [siteUrl, nativeAppUrl],
33
+ trustedOrigins: [siteUrl, nativeSiteUrl, nativeAppUrl],
34
+ {{else if (and (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")) (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid")))}}
35
+ trustedOrigins: [siteUrl, nativeSiteUrl, nativeAppUrl],
37
36
  {{else if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
38
- trustedOrigins: [nativeAppUrl],
37
+ trustedOrigins: [nativeSiteUrl, nativeAppUrl],
39
38
  {{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
40
39
  baseURL: siteUrl,
41
40
  trustedOrigins: [siteUrl],
42
- {{else}}
41
+ {{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
43
42
  trustedOrigins: [siteUrl],
44
43
  {{/if}}
45
44
  database: authComponent.adapter(ctx),
@@ -50,11 +49,14 @@ function createAuth(
50
49
  plugins: [
51
50
  {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
52
51
  expo(),
53
- {{/if}}
54
- {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
52
+ crossDomain({ siteUrl: nativeSiteUrl }),
53
+ {{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
55
54
  crossDomain({ siteUrl }),
56
55
  {{/if}}
57
- convex(),
56
+ convex({
57
+ authConfig,
58
+ jwksRotateOnTokenGenerationError: true,
59
+ }),
58
60
  ],
59
61
  });
60
62
  }
@@ -67,4 +69,4 @@ export const getCurrentUser = query({
67
69
  handler: async function (ctx, args) {
68
70
  return authComponent.getAuthUser(ctx);
69
71
  },
70
- });
72
+ });
@@ -3,10 +3,14 @@ import { authComponent, createAuth } from "./auth";
3
3
 
4
4
  const http = httpRouter();
5
5
 
6
- {{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
6
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
7
+ {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
8
+ authComponent.registerRoutes(http, createAuth, { cors: true });
9
+ {{else}}
7
10
  authComponent.registerRoutes(http, createAuth);
11
+ {{/if}}
8
12
  {{else}}
9
13
  authComponent.registerRoutes(http, createAuth, { cors: true });
10
14
  {{/if}}
11
15
 
12
- export default http;
16
+ export default http;
@@ -1,17 +1,22 @@
1
1
  import { createAuthClient } from "better-auth/react";
2
- import { convexClient } from "@convex-dev/better-auth/client/plugins";
2
+ import { convexClient, crossDomainClient } from "@convex-dev/better-auth/client/plugins";
3
3
  import { expoClient } from "@better-auth/expo/client";
4
4
  import Constants from "expo-constants";
5
5
  import * as SecureStore from "expo-secure-store";
6
+ import { Platform } from "react-native";
6
7
 
7
8
  export const authClient = createAuthClient({
8
9
  baseURL: process.env.EXPO_PUBLIC_CONVEX_SITE_URL,
9
10
  plugins: [
10
- expoClient({
11
- scheme: Constants.expoConfig?.scheme as string,
12
- storagePrefix: Constants.expoConfig?.scheme as string,
13
- storage: SecureStore,
14
- }),
15
11
  convexClient(),
12
+ ...(Platform.OS === "web"
13
+ ? [crossDomainClient()]
14
+ : [
15
+ expoClient({
16
+ scheme: Constants.expoConfig?.scheme as string,
17
+ storagePrefix: Constants.expoConfig?.scheme as string,
18
+ storage: SecureStore,
19
+ }),
20
+ ]),
16
21
  ],
17
22
  });