create-better-t-stack 2.46.2 → 2.46.3-canary.46360b92
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-NOw0j6Z9.js → src-DHpq-szu.js} +132 -35
- package/package.json +3 -2
- package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +8 -0
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +48 -0
- package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +7 -0
- package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +12 -0
- package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +16 -0
- package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +3 -0
- package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +40 -0
- package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +129 -0
- package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +154 -0
- package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +48 -0
- package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +6 -0
- package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +6 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs +133 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs +158 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +50 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +10 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +43 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +133 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +158 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +50 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +6 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +5 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +11 -0
- package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +43 -0
- package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +21 -2
- package/templates/frontend/react/next/next.config.ts.hbs +4 -1
- package/templates/frontend/react/next/src/components/providers.tsx.hbs +8 -0
- package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +8 -1
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +47 -0
- package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +2 -2
- package/templates/frontend/react/web-base/src/components/header.tsx.hbs +2 -2
- package/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx.hbs +0 -11
- /package/templates/frontend/react/web-base/src/components/{loader.tsx → loader.tsx.hbs} +0 -0
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -112,7 +112,8 @@ const dependencyVersionMap = {
|
|
|
112
112
|
"@ai-sdk/vue": "^2.0.39",
|
|
113
113
|
"@ai-sdk/svelte": "^3.0.39",
|
|
114
114
|
"@ai-sdk/react": "^2.0.39",
|
|
115
|
-
streamdown: "^1.
|
|
115
|
+
streamdown: "^1.3.0",
|
|
116
|
+
shiki: "^3.12.2",
|
|
116
117
|
"@orpc/server": "^1.8.6",
|
|
117
118
|
"@orpc/client": "^1.8.6",
|
|
118
119
|
"@orpc/openapi": "^1.8.6",
|
|
@@ -121,11 +122,12 @@ const dependencyVersionMap = {
|
|
|
121
122
|
"@trpc/tanstack-react-query": "^11.5.0",
|
|
122
123
|
"@trpc/server": "^11.5.0",
|
|
123
124
|
"@trpc/client": "^11.5.0",
|
|
124
|
-
convex: "^1.
|
|
125
|
+
convex: "^1.27.0",
|
|
125
126
|
"@convex-dev/react-query": "^0.0.0-alpha.8",
|
|
126
127
|
"convex-svelte": "^0.0.11",
|
|
127
128
|
"convex-nuxt": "0.1.5",
|
|
128
129
|
"convex-vue": "^0.1.5",
|
|
130
|
+
"@convex-dev/better-auth": "^0.8.4",
|
|
129
131
|
"@tanstack/svelte-query": "^5.85.3",
|
|
130
132
|
"@tanstack/svelte-query-devtools": "^5.85.3",
|
|
131
133
|
"@tanstack/vue-query-devtools": "^5.83.0",
|
|
@@ -141,7 +143,7 @@ const dependencyVersionMap = {
|
|
|
141
143
|
"nitro-cloudflare-dev": "^0.2.2",
|
|
142
144
|
"@sveltejs/adapter-cloudflare": "^7.2.1",
|
|
143
145
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
144
|
-
alchemy: "^0.
|
|
146
|
+
alchemy: "^0.67.0",
|
|
145
147
|
nitropack: "^2.12.4",
|
|
146
148
|
dotenv: "^17.2.1",
|
|
147
149
|
"@polar-sh/better-auth": "^1.1.3",
|
|
@@ -422,11 +424,10 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
|
|
|
422
424
|
if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
|
|
423
425
|
}
|
|
424
426
|
}
|
|
425
|
-
function validatePaymentsCompatibility(payments, auth,
|
|
427
|
+
function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
|
|
426
428
|
if (!payments || payments === "none") return;
|
|
427
429
|
if (payments === "polar") {
|
|
428
430
|
if (!auth || auth === "none" || auth !== "better-auth") exitWithError("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
|
|
429
|
-
if (backend === "convex") exitWithError("Polar payments is not compatible with Convex backend. Please use a different backend or choose a different payments provider.");
|
|
430
431
|
const { web } = splitFrontends(frontends);
|
|
431
432
|
if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
|
|
432
433
|
}
|
|
@@ -613,20 +614,36 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
|
|
|
613
614
|
const unsupportedFrontends = frontend?.filter((f) => [
|
|
614
615
|
"nuxt",
|
|
615
616
|
"svelte",
|
|
616
|
-
"solid"
|
|
617
|
+
"solid",
|
|
618
|
+
"native-nativewind",
|
|
619
|
+
"native-unistyles"
|
|
617
620
|
].includes(f));
|
|
618
621
|
if (unsupportedFrontends && unsupportedFrontends.length > 0) return "none";
|
|
622
|
+
const hasReactFrontends = frontend?.some((f) => [
|
|
623
|
+
"react-router",
|
|
624
|
+
"tanstack-router",
|
|
625
|
+
"tanstack-start",
|
|
626
|
+
"next"
|
|
627
|
+
].includes(f));
|
|
628
|
+
const options = [{
|
|
629
|
+
value: "clerk",
|
|
630
|
+
label: "Clerk",
|
|
631
|
+
hint: "More than auth, Complete User Management"
|
|
632
|
+
}];
|
|
633
|
+
if (hasReactFrontends) options.unshift({
|
|
634
|
+
value: "better-auth",
|
|
635
|
+
label: "Better-Auth",
|
|
636
|
+
hint: "comprehensive auth framework for TypeScript"
|
|
637
|
+
});
|
|
638
|
+
options.push({
|
|
639
|
+
value: "none",
|
|
640
|
+
label: "None",
|
|
641
|
+
hint: "No auth"
|
|
642
|
+
});
|
|
619
643
|
const response$1 = await select({
|
|
620
644
|
message: "Select authentication provider",
|
|
621
|
-
options
|
|
622
|
-
|
|
623
|
-
label: "Clerk",
|
|
624
|
-
hint: "More than auth, Complete User Management"
|
|
625
|
-
}, {
|
|
626
|
-
value: "none",
|
|
627
|
-
label: "None"
|
|
628
|
-
}],
|
|
629
|
-
initialValue: "clerk"
|
|
645
|
+
options,
|
|
646
|
+
initialValue: "none"
|
|
630
647
|
});
|
|
631
648
|
if (isCancel(response$1)) return exitCancelled("Operation cancelled");
|
|
632
649
|
return response$1;
|
|
@@ -1369,7 +1386,7 @@ const getLatestCLIVersion = () => {
|
|
|
1369
1386
|
*/
|
|
1370
1387
|
function isTelemetryEnabled() {
|
|
1371
1388
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
1372
|
-
const BTS_TELEMETRY = "
|
|
1389
|
+
const BTS_TELEMETRY = "0";
|
|
1373
1390
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
1374
1391
|
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
1375
1392
|
return true;
|
|
@@ -1377,8 +1394,8 @@ function isTelemetryEnabled() {
|
|
|
1377
1394
|
|
|
1378
1395
|
//#endregion
|
|
1379
1396
|
//#region src/utils/analytics.ts
|
|
1380
|
-
const POSTHOG_API_KEY = "
|
|
1381
|
-
const POSTHOG_HOST = "
|
|
1397
|
+
const POSTHOG_API_KEY = "random";
|
|
1398
|
+
const POSTHOG_HOST = "random";
|
|
1382
1399
|
function generateSessionId() {
|
|
1383
1400
|
const rand = Math.random().toString(36).slice(2);
|
|
1384
1401
|
const now = Date.now().toString(36);
|
|
@@ -1732,7 +1749,16 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
1732
1749
|
if (has("api") && config.api !== "none") exitWithError("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
|
|
1733
1750
|
if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
|
|
1734
1751
|
if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
|
|
1735
|
-
if (has("auth") && config.auth === "better-auth")
|
|
1752
|
+
if (has("auth") && config.auth === "better-auth") {
|
|
1753
|
+
const hasUnsupportedFrontends = config.frontend?.some((f) => [
|
|
1754
|
+
"nuxt",
|
|
1755
|
+
"svelte",
|
|
1756
|
+
"solid",
|
|
1757
|
+
"native-nativewind",
|
|
1758
|
+
"native-unistyles"
|
|
1759
|
+
].includes(f));
|
|
1760
|
+
if (hasUnsupportedFrontends) exitWithError("Better-Auth with Convex backend is not supported for non-React frontends (nuxt, svelte, solid) or native frontends (native-nativewind, native-unistyles). Please use '--auth clerk' or '--auth none'.");
|
|
1761
|
+
}
|
|
1736
1762
|
}
|
|
1737
1763
|
function validateBackendNoneConstraints(config, providedFlags) {
|
|
1738
1764
|
const { backend } = config;
|
|
@@ -1979,18 +2005,18 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1979
2005
|
//#endregion
|
|
1980
2006
|
//#region src/utils/add-package-deps.ts
|
|
1981
2007
|
const addPackageDependency = async (opts) => {
|
|
1982
|
-
const { dependencies = [], devDependencies = [], projectDir } = opts;
|
|
2008
|
+
const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
|
|
1983
2009
|
const pkgJsonPath = path.join(projectDir, "package.json");
|
|
1984
2010
|
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
1985
2011
|
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
1986
2012
|
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
1987
2013
|
for (const pkgName of dependencies) {
|
|
1988
|
-
const version = dependencyVersionMap[pkgName];
|
|
2014
|
+
const version = customDependencies[pkgName] || dependencyVersionMap[pkgName];
|
|
1989
2015
|
if (version) pkgJson.dependencies[pkgName] = version;
|
|
1990
2016
|
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
1991
2017
|
}
|
|
1992
2018
|
for (const pkgName of devDependencies) {
|
|
1993
|
-
const version = dependencyVersionMap[pkgName];
|
|
2019
|
+
const version = customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
|
|
1994
2020
|
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
1995
2021
|
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
1996
2022
|
}
|
|
@@ -2852,6 +2878,29 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2852
2878
|
}
|
|
2853
2879
|
return;
|
|
2854
2880
|
}
|
|
2881
|
+
if (context.backend === "convex" && authProvider === "better-auth") {
|
|
2882
|
+
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2883
|
+
const convexBetterAuthBackendSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/backend");
|
|
2884
|
+
if (await fs.pathExists(convexBetterAuthBackendSrc)) {
|
|
2885
|
+
await fs.ensureDir(convexBackendDestDir);
|
|
2886
|
+
await processAndCopyFiles("**/*", convexBetterAuthBackendSrc, convexBackendDestDir, context);
|
|
2887
|
+
}
|
|
2888
|
+
if (webAppDirExists && hasReactWeb) {
|
|
2889
|
+
const convexBetterAuthWebBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/web/react/base");
|
|
2890
|
+
if (await fs.pathExists(convexBetterAuthWebBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebBaseSrc, webAppDir, context);
|
|
2891
|
+
const reactFramework = context.frontend.find((f) => [
|
|
2892
|
+
"tanstack-router",
|
|
2893
|
+
"react-router",
|
|
2894
|
+
"tanstack-start",
|
|
2895
|
+
"next"
|
|
2896
|
+
].includes(f));
|
|
2897
|
+
if (reactFramework) {
|
|
2898
|
+
const convexBetterAuthWebSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/web/react/${reactFramework}`);
|
|
2899
|
+
if (await fs.pathExists(convexBetterAuthWebSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebSrc, webAppDir, context);
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
return;
|
|
2903
|
+
}
|
|
2855
2904
|
if (serverAppDirExists && context.backend !== "convex") {
|
|
2856
2905
|
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2857
2906
|
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
|
|
@@ -3974,12 +4023,14 @@ async function setupExamples(config) {
|
|
|
3974
4023
|
const hasNuxt = frontend.includes("nuxt");
|
|
3975
4024
|
const hasSvelte = frontend.includes("svelte");
|
|
3976
4025
|
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
4026
|
+
const hasNext = frontend.includes("next");
|
|
3977
4027
|
const hasReactNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
3978
4028
|
if (webClientDirExists) {
|
|
3979
4029
|
const dependencies = ["ai"];
|
|
3980
4030
|
if (hasNuxt) dependencies.push("@ai-sdk/vue");
|
|
3981
4031
|
else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
|
|
3982
4032
|
else if (hasReactWeb) dependencies.push("@ai-sdk/react", "streamdown");
|
|
4033
|
+
if (hasNext) dependencies.push("shiki");
|
|
3983
4034
|
await addPackageDependency({
|
|
3984
4035
|
dependencies,
|
|
3985
4036
|
projectDir: webClientDir
|
|
@@ -4243,6 +4294,35 @@ async function setupAuth(config) {
|
|
|
4243
4294
|
projectDir: clientDir
|
|
4244
4295
|
});
|
|
4245
4296
|
}
|
|
4297
|
+
if (auth === "better-auth") {
|
|
4298
|
+
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4299
|
+
const convexBackendDirExists = await fs.pathExists(convexBackendDir);
|
|
4300
|
+
if (convexBackendDirExists) await addPackageDependency({
|
|
4301
|
+
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4302
|
+
customDependencies: { "better-auth": "1.3.8" },
|
|
4303
|
+
projectDir: convexBackendDir
|
|
4304
|
+
});
|
|
4305
|
+
if (clientDirExists) {
|
|
4306
|
+
const hasNextJs = frontend.includes("next");
|
|
4307
|
+
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
4308
|
+
const hasViteReactOther = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
|
|
4309
|
+
if (hasNextJs) await addPackageDependency({
|
|
4310
|
+
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4311
|
+
customDependencies: { "better-auth": "1.3.8" },
|
|
4312
|
+
projectDir: clientDir
|
|
4313
|
+
});
|
|
4314
|
+
else if (hasTanStackStart) await addPackageDependency({
|
|
4315
|
+
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4316
|
+
customDependencies: { "better-auth": "1.3.8" },
|
|
4317
|
+
projectDir: clientDir
|
|
4318
|
+
});
|
|
4319
|
+
else if (hasViteReactOther) await addPackageDependency({
|
|
4320
|
+
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4321
|
+
customDependencies: { "better-auth": "1.3.8" },
|
|
4322
|
+
projectDir: clientDir
|
|
4323
|
+
});
|
|
4324
|
+
}
|
|
4325
|
+
}
|
|
4246
4326
|
const hasNativeWind = frontend.includes("native-nativewind");
|
|
4247
4327
|
const hasUnistyles = frontend.includes("native-unistyles");
|
|
4248
4328
|
if (auth === "clerk" && nativeDirExists && (hasNativeWind || hasUnistyles)) await addPackageDependency({
|
|
@@ -4400,6 +4480,18 @@ async function setupEnvironmentVariables(config) {
|
|
|
4400
4480
|
});
|
|
4401
4481
|
}
|
|
4402
4482
|
}
|
|
4483
|
+
if (backend === "convex" && auth === "better-auth") {
|
|
4484
|
+
if (hasNextJs) clientVars.push({
|
|
4485
|
+
key: "NEXT_PUBLIC_CONVEX_SITE_URL",
|
|
4486
|
+
value: "https://<YOUR_CONVEX_URL>",
|
|
4487
|
+
condition: true
|
|
4488
|
+
});
|
|
4489
|
+
else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) clientVars.push({
|
|
4490
|
+
key: "VITE_CONVEX_SITE_URL",
|
|
4491
|
+
value: "https://<YOUR_CONVEX_URL>",
|
|
4492
|
+
condition: true
|
|
4493
|
+
});
|
|
4494
|
+
}
|
|
4403
4495
|
await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
|
|
4404
4496
|
}
|
|
4405
4497
|
}
|
|
@@ -4425,7 +4517,24 @@ async function setupEnvironmentVariables(config) {
|
|
|
4425
4517
|
await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
|
|
4426
4518
|
}
|
|
4427
4519
|
}
|
|
4428
|
-
if (backend === "convex")
|
|
4520
|
+
if (backend === "convex") {
|
|
4521
|
+
if (auth === "better-auth") {
|
|
4522
|
+
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4523
|
+
if (await fs.pathExists(convexBackendDir)) {
|
|
4524
|
+
const convexBackendVars = [{
|
|
4525
|
+
key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
|
|
4526
|
+
value: "",
|
|
4527
|
+
condition: true
|
|
4528
|
+
}, {
|
|
4529
|
+
key: "SITE_URL",
|
|
4530
|
+
value: "http://localhost:3001",
|
|
4531
|
+
condition: true
|
|
4532
|
+
}];
|
|
4533
|
+
await addEnvVariablesToFile(path.join(convexBackendDir, ".env.local"), convexBackendVars);
|
|
4534
|
+
}
|
|
4535
|
+
}
|
|
4536
|
+
return;
|
|
4537
|
+
}
|
|
4429
4538
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4430
4539
|
if (!await fs.pathExists(serverDir)) return;
|
|
4431
4540
|
const envPath = path.join(serverDir, ".env");
|
|
@@ -5710,17 +5819,6 @@ async function setupNodeRuntime(serverDir, backend) {
|
|
|
5710
5819
|
});
|
|
5711
5820
|
}
|
|
5712
5821
|
|
|
5713
|
-
//#endregion
|
|
5714
|
-
//#region src/helpers/core/convex-codegen.ts
|
|
5715
|
-
async function runConvexCodegen(projectDir, packageManager) {
|
|
5716
|
-
const backendDir = path.join(projectDir, "packages/backend");
|
|
5717
|
-
const cmd = getPackageExecutionCommand(packageManager, "convex codegen");
|
|
5718
|
-
await execa(cmd, {
|
|
5719
|
-
cwd: backendDir,
|
|
5720
|
-
shell: true
|
|
5721
|
-
});
|
|
5722
|
-
}
|
|
5723
|
-
|
|
5724
5822
|
//#endregion
|
|
5725
5823
|
//#region src/helpers/core/create-readme.ts
|
|
5726
5824
|
async function createReadme(projectDir, options) {
|
|
@@ -6512,7 +6610,6 @@ async function createProject(options, cliInput) {
|
|
|
6512
6610
|
await setupServerDeploy(options);
|
|
6513
6611
|
await createReadme(projectDir, options);
|
|
6514
6612
|
await writeBtsConfig(options);
|
|
6515
|
-
if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
|
|
6516
6613
|
log.success("Project template successfully scaffolded!");
|
|
6517
6614
|
if (options.install) await installDependencies({
|
|
6518
6615
|
projectDir,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "2.46.
|
|
3
|
+
"version": "2.46.3-canary.46360b92",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"homepage": "https://better-t-stack.dev/",
|
|
50
50
|
"scripts": {
|
|
51
|
-
"build": "tsdown",
|
|
51
|
+
"build": "tsdown --publint",
|
|
52
52
|
"dev": "tsdown --watch",
|
|
53
53
|
"check-types": "tsc --noEmit",
|
|
54
54
|
"check": "biome check --write .",
|
|
@@ -82,6 +82,7 @@
|
|
|
82
82
|
"@types/fs-extra": "^11.0.4",
|
|
83
83
|
"@types/node": "^24.3.1",
|
|
84
84
|
"@vitest/ui": "^3.2.4",
|
|
85
|
+
"publint": "^0.3.12",
|
|
85
86
|
"tsdown": "^0.14.2",
|
|
86
87
|
"typescript": "^5.9.2",
|
|
87
88
|
"vitest": "^3.2.4"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { createClient, type GenericCtx } from "@convex-dev/better-auth";
|
|
2
|
+
{{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
3
|
+
import { convex } from "@convex-dev/better-auth/plugins";
|
|
4
|
+
{{else}}
|
|
5
|
+
import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
|
|
6
|
+
{{/if}}
|
|
7
|
+
import { components } from "./_generated/api";
|
|
8
|
+
import { DataModel } from "./_generated/dataModel";
|
|
9
|
+
import { query } from "./_generated/server";
|
|
10
|
+
import { betterAuth } from "better-auth";
|
|
11
|
+
|
|
12
|
+
const siteUrl = process.env.SITE_URL!;
|
|
13
|
+
|
|
14
|
+
export const authComponent = createClient<DataModel>(components.betterAuth);
|
|
15
|
+
|
|
16
|
+
export const createAuth = (
|
|
17
|
+
ctx: GenericCtx<DataModel>,
|
|
18
|
+
{ optionsOnly } = { optionsOnly: false },
|
|
19
|
+
) => {
|
|
20
|
+
return betterAuth({
|
|
21
|
+
logger: {
|
|
22
|
+
disabled: optionsOnly,
|
|
23
|
+
},
|
|
24
|
+
{{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
25
|
+
baseUrl: siteUrl,
|
|
26
|
+
{{else}}
|
|
27
|
+
trustedOrigins: [siteUrl],
|
|
28
|
+
{{/if}}
|
|
29
|
+
database: authComponent.adapter(ctx),
|
|
30
|
+
emailAndPassword: {
|
|
31
|
+
enabled: true,
|
|
32
|
+
requireEmailVerification: false,
|
|
33
|
+
},
|
|
34
|
+
plugins: [
|
|
35
|
+
{{#unless (or (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
36
|
+
crossDomain({ siteUrl }),
|
|
37
|
+
{{/unless}}
|
|
38
|
+
convex(),
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const getCurrentUser = query({
|
|
44
|
+
args: {},
|
|
45
|
+
handler: async (ctx) => {
|
|
46
|
+
return authComponent.getAuthUser(ctx);
|
|
47
|
+
},
|
|
48
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { httpRouter } from "convex/server";
|
|
2
|
+
import { authComponent, createAuth } from "./auth";
|
|
3
|
+
|
|
4
|
+
const http = httpRouter();
|
|
5
|
+
|
|
6
|
+
{{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
7
|
+
authComponent.registerRoutes(http, createAuth);
|
|
8
|
+
{{else}}
|
|
9
|
+
authComponent.registerRoutes(http, createAuth, { cors: true });
|
|
10
|
+
{{/if}}
|
|
11
|
+
|
|
12
|
+
export default http;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { query } from "./_generated/server";
|
|
2
|
+
|
|
3
|
+
export const get = query({
|
|
4
|
+
args: {},
|
|
5
|
+
handler: async (ctx) => {
|
|
6
|
+
const identity = await ctx.auth.getUserIdentity();
|
|
7
|
+
if (identity === null) {
|
|
8
|
+
return {
|
|
9
|
+
message: "Not authenticated",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
message: "This is private",
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import SignInForm from "@/components/sign-in-form";
|
|
4
|
+
import SignUpForm from "@/components/sign-up-form";
|
|
5
|
+
import UserMenu from "@/components/user-menu";
|
|
6
|
+
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
7
|
+
import {
|
|
8
|
+
Authenticated,
|
|
9
|
+
AuthLoading,
|
|
10
|
+
Unauthenticated,
|
|
11
|
+
useQuery,
|
|
12
|
+
} from "convex/react";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
|
|
15
|
+
export default function DashboardPage() {
|
|
16
|
+
const [showSignIn, setShowSignIn] = useState(false);
|
|
17
|
+
const privateData = useQuery(api.privateData.get);
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
<Authenticated>
|
|
22
|
+
<div>
|
|
23
|
+
<h1>Dashboard</h1>
|
|
24
|
+
<p>privateData: {privateData?.message}</p>
|
|
25
|
+
<UserMenu />
|
|
26
|
+
</div>
|
|
27
|
+
</Authenticated>
|
|
28
|
+
<Unauthenticated>
|
|
29
|
+
{showSignIn ? (
|
|
30
|
+
<SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
|
|
31
|
+
) : (
|
|
32
|
+
<SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
|
|
33
|
+
)}
|
|
34
|
+
</Unauthenticated>
|
|
35
|
+
<AuthLoading>
|
|
36
|
+
<div>Loading...</div>
|
|
37
|
+
</AuthLoading>
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { authClient } from "@/lib/auth-client";
|
|
2
|
+
import { useForm } from "@tanstack/react-form";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
import z from "zod";
|
|
5
|
+
import { Button } from "./ui/button";
|
|
6
|
+
import { Input } from "./ui/input";
|
|
7
|
+
import { Label } from "./ui/label";
|
|
8
|
+
import { useRouter } from "next/navigation";
|
|
9
|
+
|
|
10
|
+
export default function SignInForm({
|
|
11
|
+
onSwitchToSignUp,
|
|
12
|
+
}: {
|
|
13
|
+
onSwitchToSignUp: () => void;
|
|
14
|
+
}) {
|
|
15
|
+
const router = useRouter();
|
|
16
|
+
|
|
17
|
+
const form = useForm({
|
|
18
|
+
defaultValues: {
|
|
19
|
+
email: "",
|
|
20
|
+
password: "",
|
|
21
|
+
},
|
|
22
|
+
onSubmit: async ({ value }) => {
|
|
23
|
+
await authClient.signIn.email(
|
|
24
|
+
{
|
|
25
|
+
email: value.email,
|
|
26
|
+
password: value.password,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
onSuccess: () => {
|
|
30
|
+
router.push("/dashboard");
|
|
31
|
+
toast.success("Sign in successful");
|
|
32
|
+
},
|
|
33
|
+
onError: (error) => {
|
|
34
|
+
toast.error(error.error.message || error.error.statusText);
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
validators: {
|
|
40
|
+
onSubmit: z.object({
|
|
41
|
+
email: z.email("Invalid email address"),
|
|
42
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<div className="mx-auto w-full mt-10 max-w-md p-6">
|
|
49
|
+
<h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
|
50
|
+
|
|
51
|
+
<form
|
|
52
|
+
onSubmit={(e) => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
e.stopPropagation();
|
|
55
|
+
form.handleSubmit();
|
|
56
|
+
}}
|
|
57
|
+
className="space-y-4"
|
|
58
|
+
>
|
|
59
|
+
<div>
|
|
60
|
+
<form.Field name="email">
|
|
61
|
+
{(field) => (
|
|
62
|
+
<div className="space-y-2">
|
|
63
|
+
<Label htmlFor={field.name}>Email</Label>
|
|
64
|
+
<Input
|
|
65
|
+
id={field.name}
|
|
66
|
+
name={field.name}
|
|
67
|
+
type="email"
|
|
68
|
+
value={field.state.value}
|
|
69
|
+
onBlur={field.handleBlur}
|
|
70
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
71
|
+
/>
|
|
72
|
+
{field.state.meta.errors.map((error) => (
|
|
73
|
+
<p key={error?.message} className="text-red-500">
|
|
74
|
+
{error?.message}
|
|
75
|
+
</p>
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</form.Field>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div>
|
|
83
|
+
<form.Field name="password">
|
|
84
|
+
{(field) => (
|
|
85
|
+
<div className="space-y-2">
|
|
86
|
+
<Label htmlFor={field.name}>Password</Label>
|
|
87
|
+
<Input
|
|
88
|
+
id={field.name}
|
|
89
|
+
name={field.name}
|
|
90
|
+
type="password"
|
|
91
|
+
value={field.state.value}
|
|
92
|
+
onBlur={field.handleBlur}
|
|
93
|
+
onChange={(e) => field.handleChange(e.target.value)}
|
|
94
|
+
/>
|
|
95
|
+
{field.state.meta.errors.map((error) => (
|
|
96
|
+
<p key={error?.message} className="text-red-500">
|
|
97
|
+
{error?.message}
|
|
98
|
+
</p>
|
|
99
|
+
))}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</form.Field>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<form.Subscribe>
|
|
106
|
+
{(state) => (
|
|
107
|
+
<Button
|
|
108
|
+
type="submit"
|
|
109
|
+
className="w-full"
|
|
110
|
+
disabled={!state.canSubmit || state.isSubmitting}
|
|
111
|
+
>
|
|
112
|
+
{state.isSubmitting ? "Submitting..." : "Sign In"}
|
|
113
|
+
</Button>
|
|
114
|
+
)}
|
|
115
|
+
</form.Subscribe>
|
|
116
|
+
</form>
|
|
117
|
+
|
|
118
|
+
<div className="mt-4 text-center">
|
|
119
|
+
<Button
|
|
120
|
+
variant="link"
|
|
121
|
+
onClick={onSwitchToSignUp}
|
|
122
|
+
className="text-indigo-600 hover:text-indigo-800"
|
|
123
|
+
>
|
|
124
|
+
Need an account? Sign Up
|
|
125
|
+
</Button>
|
|
126
|
+
</div>
|
|
127
|
+
</div>
|
|
128
|
+
);
|
|
129
|
+
}
|