create-better-t-stack 3.7.1 → 3.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/{src-BmyCNYPk.js → src-BNPik1LZ.js} +26 -33
  4. package/package.json +1 -2
  5. package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +6 -2
  6. package/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-in-form.tsx +117 -121
  7. package/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-up-form.tsx +141 -145
  8. package/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-in-form.tsx +117 -121
  9. package/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-up-form.tsx +141 -145
  10. package/templates/auth/better-auth/web/solid/src/components/sign-in-form.tsx +3 -11
  11. package/templates/auth/better-auth/web/solid/src/components/sign-up-form.tsx +4 -14
  12. package/templates/backend/convex/packages/backend/convex/healthCheck.ts +2 -2
  13. package/templates/examples/ai/native/bare/polyfills.js +3 -7
  14. package/templates/examples/ai/native/unistyles/polyfills.js +3 -6
  15. package/templates/examples/ai/native/uniwind/polyfills.js +3 -7
  16. package/templates/examples/todo/server/drizzle/postgres/src/schema/todo.ts +1 -1
  17. package/templates/frontend/react/react-router/src/components/mode-toggle.tsx +3 -9
  18. package/templates/frontend/react/tanstack-router/src/components/mode-toggle.tsx +3 -9
  19. package/templates/frontend/react/web-base/src/components/ui/button.tsx +13 -16
  20. package/templates/frontend/react/web-base/src/components/ui/card.tsx +13 -30
  21. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx +8 -11
  22. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx +37 -66
  23. package/templates/frontend/react/web-base/src/components/ui/input.tsx +5 -5
  24. package/templates/frontend/react/web-base/src/components/ui/label.tsx +7 -10
  25. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx +3 -3
  26. package/templates/frontend/react/web-base/src/lib/utils.ts +3 -3
  27. package/templates/frontend/svelte/src/app.d.ts +7 -7
  28. package/templates/frontend/svelte/src/lib/index.ts +1 -0
  29. package/templates/frontend/svelte/vite.config.ts +4 -4
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createBtsCli } from "./src-BmyCNYPk.js";
2
+ import { n as createBtsCli } from "./src-BNPik1LZ.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.js 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-BmyCNYPk.js";
2
+ import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-BNPik1LZ.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -68,14 +68,14 @@ const dependencyVersionMap = {
68
68
  "@clerk/clerk-react": "^5.45.0",
69
69
  "@clerk/tanstack-react-start": "^0.26.3",
70
70
  "@clerk/clerk-expo": "^2.14.25",
71
- "drizzle-orm": "^0.44.2",
72
- "drizzle-kit": "^0.31.2",
71
+ "drizzle-orm": "^0.45.0",
72
+ "drizzle-kit": "^0.31.8",
73
73
  "@planetscale/database": "^1.19.0",
74
74
  "@libsql/client": "^0.14.0",
75
75
  libsql: "^0.5.22",
76
76
  "@neondatabase/serverless": "^1.0.2",
77
- pg: "^8.14.1",
78
- "@types/pg": "^8.11.11",
77
+ pg: "^8.16.3",
78
+ "@types/pg": "^8.15.6",
79
79
  "@types/ws": "^8.18.1",
80
80
  ws: "^8.18.3",
81
81
  mysql2: "^3.14.0",
@@ -1045,7 +1045,7 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
1045
1045
  if (orm !== void 0) return orm;
1046
1046
  const response = await select({
1047
1047
  message: "Select ORM",
1048
- options: [...database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma]],
1048
+ options: database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma],
1049
1049
  initialValue: database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
1050
1050
  });
1051
1051
  if (isCancel(response)) return exitCancelled("Operation cancelled");
@@ -1165,7 +1165,6 @@ async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
1165
1165
  }
1166
1166
  }
1167
1167
  if (existingDeployment && existingDeployment !== "none") return "none";
1168
- if (options.length > 0) {}
1169
1168
  if (options.length === 0) return "none";
1170
1169
  const response = await select({
1171
1170
  message: "Select server deployment",
@@ -1363,20 +1362,19 @@ async function sendConvexEvent(payload) {
1363
1362
  headers: { "Content-Type": "application/json" },
1364
1363
  body: JSON.stringify(payload)
1365
1364
  });
1366
- } catch (_error) {}
1365
+ } catch {}
1367
1366
  }
1368
1367
  async function trackProjectCreation(config, disableAnalytics = false) {
1369
1368
  if (!isTelemetryEnabled() || disableAnalytics) return;
1370
- const { projectName, projectDir, relativePath,...safeConfig } = config;
1369
+ const { projectName: _projectName, projectDir: _projectDir, relativePath: _relativePath,...safeConfig } = config;
1371
1370
  try {
1372
1371
  await sendConvexEvent({
1373
- event: "project_created",
1374
1372
  ...safeConfig,
1375
1373
  cli_version: getLatestCLIVersion(),
1376
1374
  node_version: typeof process !== "undefined" ? process.version : "",
1377
1375
  platform: typeof process !== "undefined" ? process.platform : ""
1378
1376
  });
1379
- } catch (_error) {}
1377
+ } catch {}
1380
1378
  }
1381
1379
 
1382
1380
  //#endregion
@@ -1980,6 +1978,7 @@ async function writeBtsConfig(projectConfig) {
1980
1978
  addons: btsConfig.addons,
1981
1979
  examples: btsConfig.examples,
1982
1980
  auth: btsConfig.auth,
1981
+ payments: btsConfig.payments,
1983
1982
  packageManager: btsConfig.packageManager,
1984
1983
  dbSetup: btsConfig.dbSetup,
1985
1984
  api: btsConfig.api,
@@ -2015,7 +2014,7 @@ async function readBtsConfig(projectDir) {
2015
2014
  return null;
2016
2015
  }
2017
2016
  return config;
2018
- } catch (_error) {
2017
+ } catch {
2019
2018
  return null;
2020
2019
  }
2021
2020
  }
@@ -2033,7 +2032,7 @@ async function updateBtsConfig(projectDir, updates) {
2033
2032
  modifiedContent = JSONC.applyEdits(modifiedContent, editResult);
2034
2033
  }
2035
2034
  await fs.writeFile(configPath, modifiedContent, "utf-8");
2036
- } catch (_error) {}
2035
+ } catch {}
2037
2036
  }
2038
2037
 
2039
2038
  //#endregion
@@ -2212,7 +2211,7 @@ async function setupRuler(config) {
2212
2211
  shell: true
2213
2212
  });
2214
2213
  s.stop("Applied rules with Ruler");
2215
- } catch (_error) {
2214
+ } catch {
2216
2215
  s.stop(pc.red("Failed to apply rules"));
2217
2216
  }
2218
2217
  } catch (error) {
@@ -2636,14 +2635,14 @@ async function detectProjectConfig(projectDir) {
2636
2635
  serverDeploy: btsConfig.serverDeploy
2637
2636
  };
2638
2637
  return null;
2639
- } catch (_error) {
2638
+ } catch {
2640
2639
  return null;
2641
2640
  }
2642
2641
  }
2643
2642
  async function isBetterTStackProject(projectDir) {
2644
2643
  try {
2645
2644
  return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
2646
- } catch (_error) {
2645
+ } catch {
2647
2646
  return false;
2648
2647
  }
2649
2648
  }
@@ -2686,7 +2685,7 @@ function initializeBiome() {
2686
2685
  biome,
2687
2686
  projectKey
2688
2687
  };
2689
- } catch (_error) {
2688
+ } catch {
2690
2689
  return null;
2691
2690
  }
2692
2691
  }
@@ -2721,7 +2720,7 @@ function formatFileWithBiome(filePath, content) {
2721
2720
  const result = biome.formatContent(projectKey, content, { filePath: path.basename(filePath) });
2722
2721
  if (result.diagnostics && result.diagnostics.length > 0) consola.debug(`Biome formatting diagnostics for ${filePath}:`, result.diagnostics);
2723
2722
  return result.content;
2724
- } catch (_error) {
2723
+ } catch {
2725
2724
  return null;
2726
2725
  }
2727
2726
  }
@@ -4035,7 +4034,7 @@ async function addBackendWorkspaceDependency(projectDir, backendPackageName, wor
4035
4034
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
4036
4035
  pkgJson.dependencies[backendPackageName] = workspaceVersion;
4037
4036
  await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4038
- } catch (_error) {}
4037
+ } catch {}
4039
4038
  }
4040
4039
  function getFrontendType(frontend) {
4041
4040
  const reactBasedFrontends = [
@@ -4754,15 +4753,11 @@ async function setupCloudflareD1(config) {
4754
4753
  const { projectDir, serverDeploy, orm, backend } = config;
4755
4754
  if (serverDeploy === "alchemy" && orm === "prisma") {
4756
4755
  const targetApp2 = backend === "self" ? "apps/web" : "apps/server";
4757
- const envPath = path.join(projectDir, targetApp2, ".env");
4758
- const variables = [{
4756
+ await addEnvVariablesToFile(path.join(projectDir, targetApp2, ".env"), [{
4759
4757
  key: "DATABASE_URL",
4760
4758
  value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
4761
4759
  condition: true
4762
- }];
4763
- try {
4764
- await addEnvVariablesToFile(envPath, variables);
4765
- } catch (_err) {}
4760
+ }]);
4766
4761
  await addPackageDependency({
4767
4762
  dependencies: ["@prisma/adapter-d1"],
4768
4763
  projectDir: path.join(projectDir, backend === "self" ? "apps/web" : "apps/server")
@@ -4817,7 +4812,7 @@ async function checkAtlasCLI() {
4817
4812
  if (exists) log.info("MongoDB Atlas CLI found");
4818
4813
  else log.warn(pc.yellow("MongoDB Atlas CLI not found"));
4819
4814
  return exists;
4820
- } catch (_error) {
4815
+ } catch {
4821
4816
  log.error(pc.red("Error checking MongoDB Atlas CLI"));
4822
4817
  return false;
4823
4818
  }
@@ -4862,7 +4857,7 @@ async function writeEnvFile$3(projectDir, backend, config) {
4862
4857
  value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
4863
4858
  condition: true
4864
4859
  }]);
4865
- } catch (_error) {
4860
+ } catch {
4866
4861
  consola.error("Failed to update environment configuration");
4867
4862
  }
4868
4863
  }
@@ -4999,7 +4994,7 @@ async function createNeonProject(projectName, regionId, packageManager) {
4999
4994
  }
5000
4995
  consola$1.error(pc.red("Failed to extract connection information from response"));
5001
4996
  return null;
5002
- } catch (_error) {
4997
+ } catch {
5003
4998
  consola$1.error(pc.red("Failed to create Neon project"));
5004
4999
  }
5005
5000
  }
@@ -5247,7 +5242,7 @@ async function writeEnvFile$1(projectDir, backend, config) {
5247
5242
  condition: true
5248
5243
  });
5249
5244
  await addEnvVariablesToFile(envPath, variables);
5250
- } catch (_error) {
5245
+ } catch {
5251
5246
  consola$1.error("Failed to update environment configuration");
5252
5247
  }
5253
5248
  }
@@ -5474,7 +5469,7 @@ async function loginToTurso() {
5474
5469
  await $`turso auth login`;
5475
5470
  s.stop("Logged into Turso");
5476
5471
  return true;
5477
- } catch (_error) {
5472
+ } catch {
5478
5473
  s.stop(pc.red("Failed to log in to Turso"));
5479
5474
  }
5480
5475
  }
@@ -5562,7 +5557,7 @@ async function createTursoDatabase(dbName, groupName) {
5562
5557
  dbUrl: dbUrl.trim(),
5563
5558
  authToken: authToken.trim()
5564
5559
  };
5565
- } catch (_error) {
5560
+ } catch {
5566
5561
  s.stop(pc.red("Failed to retrieve database connection details"));
5567
5562
  }
5568
5563
  }
@@ -5688,7 +5683,6 @@ async function setupDatabase(config, cliInput) {
5688
5683
  }
5689
5684
  return;
5690
5685
  }
5691
- const s = spinner();
5692
5686
  const dbPackageDir = path.join(projectDir, "packages/db");
5693
5687
  const webDir = path.join(projectDir, "apps/web");
5694
5688
  const webDirExists = await fs.pathExists(webDir);
@@ -5791,7 +5785,6 @@ async function setupDatabase(config, cliInput) {
5791
5785
  if (dbSetup === "planetscale") await setupPlanetScale(config);
5792
5786
  } else if (database === "mongodb" && dbSetup === "mongodb-atlas") await setupMongoDBAtlas(config, cliInput);
5793
5787
  } catch (error) {
5794
- s.stop(pc.red("Failed to set up database"));
5795
5788
  if (error instanceof Error) consola.error(pc.red(error.message));
5796
5789
  }
5797
5790
  }
@@ -6668,7 +6661,7 @@ async function updateRootPackageJson(projectDir, options) {
6668
6661
  try {
6669
6662
  const { stdout } = await execa(options.packageManager, ["-v"], { cwd: projectDir });
6670
6663
  packageJson.packageManager = `${options.packageManager}@${stdout.trim()}`;
6671
- } catch (_e) {
6664
+ } catch {
6672
6665
  log.warn(`Could not determine ${options.packageManager} version.`);
6673
6666
  }
6674
6667
  if (!packageJson.workspaces) packageJson.workspaces = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.7.1",
3
+ "version": "3.7.2",
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",
@@ -51,7 +51,6 @@
51
51
  "build": "tsdown --publint",
52
52
  "dev": "tsdown --watch",
53
53
  "check-types": "tsc --noEmit",
54
- "check": "biome check --write .",
55
54
  "test": "bun run build && vitest run; rm -rf .smoke || true",
56
55
  "test:ui": "bun run build && vitest --ui",
57
56
  "prepublishOnly": "npm run build"
@@ -1,6 +1,8 @@
1
1
  import type { auth } from "@{{projectName}}/auth";
2
2
  import { createAuthClient } from "better-auth/react";
3
- import { inferAdditionalFields } from "better-auth/client/plugins";
3
+ {{#if (eq payments "polar")}}
4
+ import { polarClient } from "@polar-sh/better-auth";
5
+ {{/if}}
4
6
 
5
7
  export const authClient = createAuthClient({
6
8
  {{#unless (eq backend "self")}}
@@ -11,5 +13,7 @@ export const authClient = createAuthClient({
11
13
  import.meta.env.VITE_SERVER_URL,
12
14
  {{/if}}
13
15
  {{/unless}}
14
- plugins: [inferAdditionalFields<typeof auth>()],
16
+ {{#if (eq payments "polar")}}
17
+ plugins: [polarClient()]
18
+ {{/if}}
15
19
  });
@@ -8,132 +8,128 @@ import { Button } from "./ui/button";
8
8
  import { Input } from "./ui/input";
9
9
  import { Label } from "./ui/label";
10
10
 
11
- export default function SignInForm({
12
- onSwitchToSignUp,
13
- }: {
14
- onSwitchToSignUp: () => void;
15
- }) {
16
- const navigate = useNavigate({
17
- from: "/",
18
- });
19
- const { isPending } = authClient.useSession();
11
+ export default function SignInForm({ onSwitchToSignUp }: { onSwitchToSignUp: () => void }) {
12
+ const navigate = useNavigate({
13
+ from: "/",
14
+ });
15
+ const { isPending } = authClient.useSession();
20
16
 
21
- const form = useForm({
22
- defaultValues: {
23
- email: "",
24
- password: "",
25
- },
26
- onSubmit: async ({ value }) => {
27
- await authClient.signIn.email(
28
- {
29
- email: value.email,
30
- password: value.password,
31
- },
32
- {
33
- onSuccess: () => {
34
- navigate({
35
- to: "/dashboard",
36
- });
37
- toast.success("Sign in successful");
38
- },
39
- onError: (error) => {
40
- toast.error(error.error.message || error.error.statusText);
41
- },
42
- },
43
- );
44
- },
45
- validators: {
46
- onSubmit: z.object({
47
- email: z.email("Invalid email address"),
48
- password: z.string().min(8, "Password must be at least 8 characters"),
49
- }),
50
- },
51
- });
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
+ navigate({
31
+ to: "/dashboard",
32
+ });
33
+ toast.success("Sign in successful");
34
+ },
35
+ onError: (error) => {
36
+ toast.error(error.error.message || error.error.statusText);
37
+ },
38
+ },
39
+ );
40
+ },
41
+ validators: {
42
+ onSubmit: z.object({
43
+ email: z.email("Invalid email address"),
44
+ password: z.string().min(8, "Password must be at least 8 characters"),
45
+ }),
46
+ },
47
+ });
52
48
 
53
- if (isPending) {
54
- return <Loader />;
55
- }
49
+ if (isPending) {
50
+ return <Loader />;
51
+ }
56
52
 
57
- return (
58
- <div className="mx-auto w-full mt-10 max-w-md p-6">
59
- <h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
53
+ return (
54
+ <div className="mx-auto w-full mt-10 max-w-md p-6">
55
+ <h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
60
56
 
61
- <form
62
- onSubmit={(e) => {
63
- e.preventDefault();
64
- e.stopPropagation();
65
- form.handleSubmit();
66
- }}
67
- className="space-y-4"
68
- >
69
- <div>
70
- <form.Field name="email">
71
- {(field) => (
72
- <div className="space-y-2">
73
- <Label htmlFor={field.name}>Email</Label>
74
- <Input
75
- id={field.name}
76
- name={field.name}
77
- type="email"
78
- value={field.state.value}
79
- onBlur={field.handleBlur}
80
- onChange={(e) => field.handleChange(e.target.value)}
81
- />
82
- {field.state.meta.errors.map((error) => (
83
- <p key={error?.message} className="text-red-500">
84
- {error?.message}
85
- </p>
86
- ))}
87
- </div>
88
- )}
89
- </form.Field>
90
- </div>
57
+ <form
58
+ onSubmit={(e) => {
59
+ e.preventDefault();
60
+ e.stopPropagation();
61
+ form.handleSubmit();
62
+ }}
63
+ className="space-y-4"
64
+ >
65
+ <div>
66
+ <form.Field name="email">
67
+ {(field) => (
68
+ <div className="space-y-2">
69
+ <Label htmlFor={field.name}>Email</Label>
70
+ <Input
71
+ id={field.name}
72
+ name={field.name}
73
+ type="email"
74
+ value={field.state.value}
75
+ onBlur={field.handleBlur}
76
+ onChange={(e) => field.handleChange(e.target.value)}
77
+ />
78
+ {field.state.meta.errors.map((error) => (
79
+ <p key={error?.message} className="text-red-500">
80
+ {error?.message}
81
+ </p>
82
+ ))}
83
+ </div>
84
+ )}
85
+ </form.Field>
86
+ </div>
91
87
 
92
- <div>
93
- <form.Field name="password">
94
- {(field) => (
95
- <div className="space-y-2">
96
- <Label htmlFor={field.name}>Password</Label>
97
- <Input
98
- id={field.name}
99
- name={field.name}
100
- type="password"
101
- value={field.state.value}
102
- onBlur={field.handleBlur}
103
- onChange={(e) => field.handleChange(e.target.value)}
104
- />
105
- {field.state.meta.errors.map((error) => (
106
- <p key={error?.message} className="text-red-500">
107
- {error?.message}
108
- </p>
109
- ))}
110
- </div>
111
- )}
112
- </form.Field>
113
- </div>
88
+ <div>
89
+ <form.Field name="password">
90
+ {(field) => (
91
+ <div className="space-y-2">
92
+ <Label htmlFor={field.name}>Password</Label>
93
+ <Input
94
+ id={field.name}
95
+ name={field.name}
96
+ type="password"
97
+ value={field.state.value}
98
+ onBlur={field.handleBlur}
99
+ onChange={(e) => field.handleChange(e.target.value)}
100
+ />
101
+ {field.state.meta.errors.map((error) => (
102
+ <p key={error?.message} className="text-red-500">
103
+ {error?.message}
104
+ </p>
105
+ ))}
106
+ </div>
107
+ )}
108
+ </form.Field>
109
+ </div>
114
110
 
115
- <form.Subscribe>
116
- {(state) => (
117
- <Button
118
- type="submit"
119
- className="w-full"
120
- disabled={!state.canSubmit || state.isSubmitting}
121
- >
122
- {state.isSubmitting ? "Submitting..." : "Sign In"}
123
- </Button>
124
- )}
125
- </form.Subscribe>
126
- </form>
111
+ <form.Subscribe>
112
+ {(state) => (
113
+ <Button
114
+ type="submit"
115
+ className="w-full"
116
+ disabled={!state.canSubmit || state.isSubmitting}
117
+ >
118
+ {state.isSubmitting ? "Submitting..." : "Sign In"}
119
+ </Button>
120
+ )}
121
+ </form.Subscribe>
122
+ </form>
127
123
 
128
- <div className="mt-4 text-center">
129
- <Button
130
- variant="link"
131
- onClick={onSwitchToSignUp}
132
- className="text-indigo-600 hover:text-indigo-800"
133
- >
134
- Need an account? Sign Up
135
- </Button>
136
- </div>
137
- </div>
138
- );
124
+ <div className="mt-4 text-center">
125
+ <Button
126
+ variant="link"
127
+ onClick={onSwitchToSignUp}
128
+ className="text-indigo-600 hover:text-indigo-800"
129
+ >
130
+ Need an account? Sign Up
131
+ </Button>
132
+ </div>
133
+ </div>
134
+ );
139
135
  }