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.
- package/README.md +17 -16
- package/dist/cli.mjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{src-GZpht_dQ.mjs → src-DW15ZRe9.mjs} +70 -89
- package/package.json +39 -40
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +21 -19
- package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +6 -2
- package/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs +11 -6
- package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +2 -2
- package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +23 -20
- package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +13 -5
- package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +25 -22
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +23 -24
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +4 -4
- package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +0 -1
- package/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs +24 -21
- package/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs +24 -21
- package/templates/auth/better-auth/web/react/{tanstack-start/src/components/user-menu.tsx → tanstack-router/src/components/user-menu.tsx.hbs} +26 -23
- package/templates/auth/better-auth/web/react/{tanstack-router/src/components/user-menu.tsx → tanstack-start/src/components/user-menu.tsx.hbs} +26 -23
- package/templates/frontend/react/next/package.json.hbs +8 -7
- package/templates/frontend/react/next/src/app/layout.tsx.hbs +28 -1
- package/templates/frontend/react/next/src/components/providers.tsx.hbs +14 -4
- package/templates/frontend/react/react-router/package.json.hbs +2 -1
- package/templates/frontend/react/{tanstack-router/src/components/mode-toggle.tsx → react-router/src/components/mode-toggle.tsx.hbs} +1 -1
- package/templates/frontend/react/tanstack-router/package.json.hbs +2 -1
- package/templates/frontend/react/{react-router/src/components/mode-toggle.tsx → tanstack-router/src/components/mode-toggle.tsx.hbs} +1 -1
- package/templates/frontend/react/tanstack-start/package.json.hbs +2 -1
- package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +6 -0
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +13 -14
- package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +5 -0
- package/templates/frontend/react/web-base/components.json +5 -2
- package/templates/frontend/react/web-base/src/components/ui/button.tsx.hbs +57 -0
- package/templates/frontend/react/web-base/src/components/ui/card.tsx.hbs +103 -0
- package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx.hbs +26 -0
- package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx.hbs +262 -0
- package/templates/frontend/react/web-base/src/components/ui/input.tsx.hbs +20 -0
- package/templates/frontend/react/web-base/src/components/ui/label.tsx.hbs +20 -0
- package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +13 -0
- package/templates/frontend/react/web-base/src/components/ui/sonner.tsx.hbs +44 -0
- package/templates/frontend/react/web-base/src/index.css.hbs +57 -63
- package/templates/frontend/react/web-base/src/components/ui/button.tsx +0 -56
- package/templates/frontend/react/web-base/src/components/ui/card.tsx +0 -75
- package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx +0 -27
- package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx +0 -228
- package/templates/frontend/react/web-base/src/components/ui/input.tsx +0 -21
- package/templates/frontend/react/web-base/src/components/ui/label.tsx +0 -19
- package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx +0 -13
- package/templates/frontend/react/web-base/src/components/ui/sonner.tsx +0 -25
- /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/tanstack-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/react/tanstack-start/src/routes/{login.tsx → login.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/solid/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/solid/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
- /package/templates/auth/better-auth/web/solid/src/routes/{login.tsx → login.tsx.hbs} +0 -0
- /package/templates/frontend/react/react-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
- /package/templates/frontend/react/tanstack-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
- /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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
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-
|
|
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 {
|
|
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.
|
|
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 = "
|
|
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 (
|
|
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"] = { "
|
|
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/
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
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
|
|
2596
|
-
if (
|
|
2597
|
-
|
|
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
|
|
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
|
-
${
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
"
|
|
8
|
+
"biome",
|
|
18
9
|
"boilerplate",
|
|
19
|
-
"starter",
|
|
20
10
|
"cli",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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")
|
|
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
|
-
{
|
|
54
|
-
{{
|
|
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")
|
|
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
|
});
|