create-better-t-stack 3.9.0 → 3.11.0-pr749.0

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 (182) hide show
  1. package/README.md +2 -1
  2. package/bin/create-better-t-stack +98 -0
  3. package/package.json +69 -59
  4. package/src/api.ts +203 -0
  5. package/src/cli.ts +185 -0
  6. package/src/constants.ts +270 -0
  7. package/src/helpers/addons/addons-setup.ts +201 -0
  8. package/src/helpers/addons/examples-setup.ts +137 -0
  9. package/src/helpers/addons/fumadocs-setup.ts +99 -0
  10. package/src/helpers/addons/oxlint-setup.ts +36 -0
  11. package/src/helpers/addons/ruler-setup.ts +135 -0
  12. package/src/helpers/addons/starlight-setup.ts +45 -0
  13. package/src/helpers/addons/tauri-setup.ts +90 -0
  14. package/src/helpers/addons/tui-setup.ts +64 -0
  15. package/src/helpers/addons/ultracite-setup.ts +228 -0
  16. package/src/helpers/addons/vite-pwa-setup.ts +59 -0
  17. package/src/helpers/addons/wxt-setup.ts +86 -0
  18. package/src/helpers/core/add-addons.ts +85 -0
  19. package/src/helpers/core/add-deployment.ts +102 -0
  20. package/src/helpers/core/api-setup.ts +280 -0
  21. package/src/helpers/core/auth-setup.ts +203 -0
  22. package/src/helpers/core/backend-setup.ts +69 -0
  23. package/src/helpers/core/command-handlers.ts +354 -0
  24. package/src/helpers/core/convex-codegen.ts +14 -0
  25. package/src/helpers/core/create-project.ts +134 -0
  26. package/src/helpers/core/create-readme.ts +694 -0
  27. package/src/helpers/core/db-setup.ts +184 -0
  28. package/src/helpers/core/detect-project-config.ts +41 -0
  29. package/src/helpers/core/env-setup.ts +481 -0
  30. package/src/helpers/core/git.ts +23 -0
  31. package/src/helpers/core/install-dependencies.ts +29 -0
  32. package/src/helpers/core/payments-setup.ts +48 -0
  33. package/src/helpers/core/post-installation.ts +403 -0
  34. package/src/helpers/core/project-config.ts +250 -0
  35. package/src/helpers/core/runtime-setup.ts +76 -0
  36. package/src/helpers/core/template-manager.ts +917 -0
  37. package/src/helpers/core/workspace-setup.ts +184 -0
  38. package/src/helpers/database-providers/d1-setup.ts +28 -0
  39. package/src/helpers/database-providers/docker-compose-setup.ts +50 -0
  40. package/src/helpers/database-providers/mongodb-atlas-setup.ts +182 -0
  41. package/src/helpers/database-providers/neon-setup.ts +240 -0
  42. package/src/helpers/database-providers/planetscale-setup.ts +78 -0
  43. package/src/helpers/database-providers/prisma-postgres-setup.ts +193 -0
  44. package/src/helpers/database-providers/supabase-setup.ts +196 -0
  45. package/src/helpers/database-providers/turso-setup.ts +309 -0
  46. package/src/helpers/deployment/alchemy/alchemy-combined-setup.ts +80 -0
  47. package/src/helpers/deployment/alchemy/alchemy-next-setup.ts +52 -0
  48. package/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts +105 -0
  49. package/src/helpers/deployment/alchemy/alchemy-react-router-setup.ts +33 -0
  50. package/src/helpers/deployment/alchemy/alchemy-solid-setup.ts +33 -0
  51. package/src/helpers/deployment/alchemy/alchemy-svelte-setup.ts +99 -0
  52. package/src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts +34 -0
  53. package/src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts +99 -0
  54. package/src/helpers/deployment/alchemy/env-dts-setup.ts +76 -0
  55. package/src/helpers/deployment/alchemy/index.ts +7 -0
  56. package/src/helpers/deployment/server-deploy-setup.ts +55 -0
  57. package/src/helpers/deployment/web-deploy-setup.ts +58 -0
  58. package/src/index.ts +51 -0
  59. package/src/prompts/addons.ts +200 -0
  60. package/src/prompts/api.ts +49 -0
  61. package/src/prompts/auth.ts +84 -0
  62. package/src/prompts/backend.ts +83 -0
  63. package/src/prompts/config-prompts.ts +138 -0
  64. package/src/prompts/database-setup.ts +112 -0
  65. package/src/prompts/database.ts +57 -0
  66. package/src/prompts/examples.ts +60 -0
  67. package/src/prompts/frontend.ts +118 -0
  68. package/src/prompts/git.ts +16 -0
  69. package/src/prompts/install.ts +16 -0
  70. package/src/prompts/orm.ts +53 -0
  71. package/src/prompts/package-manager.ts +32 -0
  72. package/src/prompts/payments.ts +50 -0
  73. package/src/prompts/project-name.ts +86 -0
  74. package/src/prompts/runtime.ts +47 -0
  75. package/src/prompts/server-deploy.ts +91 -0
  76. package/src/prompts/web-deploy.ts +107 -0
  77. package/src/tui/app.tsx +1062 -0
  78. package/src/types.ts +70 -0
  79. package/src/utils/add-package-deps.ts +57 -0
  80. package/src/utils/analytics.ts +39 -0
  81. package/src/utils/better-auth-plugin-setup.ts +71 -0
  82. package/src/utils/bts-config.ts +122 -0
  83. package/src/utils/command-exists.ts +16 -0
  84. package/src/utils/compatibility-rules.ts +337 -0
  85. package/src/utils/compatibility.ts +11 -0
  86. package/src/utils/config-processing.ts +130 -0
  87. package/src/utils/config-validation.ts +470 -0
  88. package/src/utils/display-config.ts +96 -0
  89. package/src/utils/docker-utils.ts +70 -0
  90. package/src/utils/errors.ts +30 -0
  91. package/src/utils/file-formatter.ts +11 -0
  92. package/src/utils/generate-reproducible-command.ts +53 -0
  93. package/src/utils/get-latest-cli-version.ts +27 -0
  94. package/src/utils/get-package-manager.ts +13 -0
  95. package/src/utils/open-url.ts +18 -0
  96. package/src/utils/package-runner.ts +23 -0
  97. package/src/utils/project-directory.ts +102 -0
  98. package/src/utils/project-name-validation.ts +43 -0
  99. package/src/utils/render-title.ts +48 -0
  100. package/src/utils/setup-catalogs.ts +192 -0
  101. package/src/utils/sponsors.ts +101 -0
  102. package/src/utils/telemetry.ts +19 -0
  103. package/src/utils/template-processor.ts +64 -0
  104. package/src/utils/templates.ts +94 -0
  105. package/src/utils/ts-morph.ts +26 -0
  106. package/src/validation.ts +117 -0
  107. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +5 -7
  108. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +17 -17
  109. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +4 -4
  110. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +2 -2
  111. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +10 -10
  112. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +13 -5
  113. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +14 -12
  114. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +13 -16
  115. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +11 -5
  116. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +4 -4
  117. package/templates/auth/better-auth/fullstack/tanstack-start/src/routes/api/auth/$.ts.hbs +1 -1
  118. package/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs +17 -15
  119. package/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs +16 -15
  120. package/templates/auth/better-auth/web/react/tanstack-router/src/components/{user-menu.tsx → user-menu.tsx.hbs} +16 -15
  121. package/templates/auth/better-auth/web/react/tanstack-start/src/components/{user-menu.tsx → user-menu.tsx.hbs} +16 -15
  122. package/templates/backend/convex/packages/backend/convex/README.md +4 -4
  123. package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +17 -0
  124. package/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs +1 -1
  125. package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +9 -0
  126. package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +67 -0
  127. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +301 -3
  128. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +296 -10
  129. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +180 -1
  130. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +172 -9
  131. package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +156 -6
  132. package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +156 -4
  133. package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +159 -6
  134. package/templates/frontend/react/next/package.json.hbs +8 -7
  135. package/templates/frontend/react/next/src/app/layout.tsx.hbs +28 -1
  136. package/templates/frontend/react/next/src/components/mode-toggle.tsx.hbs +4 -6
  137. package/templates/frontend/react/next/src/components/providers.tsx.hbs +14 -4
  138. package/templates/frontend/react/react-router/package.json.hbs +2 -1
  139. package/templates/frontend/react/{tanstack-router/src/components/mode-toggle.tsx → react-router/src/components/mode-toggle.tsx.hbs} +4 -6
  140. package/templates/frontend/react/tanstack-router/package.json.hbs +2 -1
  141. package/templates/frontend/react/{react-router/src/components/mode-toggle.tsx → tanstack-router/src/components/mode-toggle.tsx.hbs} +4 -6
  142. package/templates/frontend/react/tanstack-start/package.json.hbs +2 -1
  143. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +6 -0
  144. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +13 -14
  145. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +5 -0
  146. package/templates/frontend/react/web-base/components.json +5 -2
  147. package/templates/frontend/react/web-base/src/components/ui/button.tsx.hbs +57 -0
  148. package/templates/frontend/react/web-base/src/components/ui/card.tsx.hbs +103 -0
  149. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx.hbs +26 -0
  150. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx.hbs +262 -0
  151. package/templates/frontend/react/web-base/src/components/ui/input.tsx.hbs +20 -0
  152. package/templates/frontend/react/web-base/src/components/ui/label.tsx.hbs +20 -0
  153. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +13 -0
  154. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx.hbs +44 -0
  155. package/templates/frontend/react/web-base/src/index.css.hbs +58 -64
  156. package/dist/cli.d.mts +0 -1
  157. package/dist/cli.mjs +0 -8
  158. package/dist/index.d.mts +0 -347
  159. package/dist/index.mjs +0 -4
  160. package/dist/src-DLvUK0Qf.mjs +0 -7069
  161. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +0 -7
  162. package/templates/examples/ai/web/react/base/src/components/response.tsx.hbs +0 -22
  163. package/templates/frontend/react/web-base/src/components/ui/button.tsx +0 -56
  164. package/templates/frontend/react/web-base/src/components/ui/card.tsx +0 -75
  165. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx +0 -27
  166. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx +0 -228
  167. package/templates/frontend/react/web-base/src/components/ui/input.tsx +0 -21
  168. package/templates/frontend/react/web-base/src/components/ui/label.tsx +0 -19
  169. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx +0 -13
  170. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx +0 -25
  171. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  172. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  173. /package/templates/auth/better-auth/web/react/tanstack-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  174. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  175. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  176. /package/templates/auth/better-auth/web/react/tanstack-start/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  177. /package/templates/auth/better-auth/web/solid/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  178. /package/templates/auth/better-auth/web/solid/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  179. /package/templates/auth/better-auth/web/solid/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  180. /package/templates/frontend/react/react-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  181. /package/templates/frontend/react/tanstack-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  182. /package/templates/frontend/react/web-base/src/lib/{utils.ts → utils.ts.hbs} +0 -0
@@ -0,0 +1,64 @@
1
+ import path from "node:path";
2
+ import consola from "consola";
3
+ import fs from "fs-extra";
4
+ import handlebars from "handlebars";
5
+ import type { ProjectConfig } from "../types";
6
+ import { formatFile } from "./file-formatter";
7
+
8
+ const BINARY_EXTENSIONS = new Set([".png", ".ico", ".svg"]);
9
+
10
+ function isBinaryFile(filePath: string): boolean {
11
+ const ext = path.extname(filePath).toLowerCase();
12
+ return BINARY_EXTENSIONS.has(ext);
13
+ }
14
+
15
+ export async function processTemplate(srcPath: string, destPath: string, context: ProjectConfig) {
16
+ try {
17
+ await fs.ensureDir(path.dirname(destPath));
18
+
19
+ if (isBinaryFile(srcPath) && !srcPath.endsWith(".hbs")) {
20
+ await fs.copy(srcPath, destPath);
21
+ return;
22
+ }
23
+
24
+ let content: string;
25
+
26
+ if (srcPath.endsWith(".hbs")) {
27
+ const templateContent = await fs.readFile(srcPath, "utf-8");
28
+ const template = handlebars.compile(templateContent);
29
+ content = template(context);
30
+ } else {
31
+ content = await fs.readFile(srcPath, "utf-8");
32
+ }
33
+
34
+ try {
35
+ const formattedContent = await formatFile(destPath, content);
36
+ if (formattedContent) {
37
+ content = formattedContent;
38
+ }
39
+ } catch (formatError) {
40
+ consola.debug(`Failed to format ${destPath}:`, formatError);
41
+ }
42
+
43
+ await fs.writeFile(destPath, content);
44
+ } catch (error) {
45
+ consola.error(`Error processing template ${srcPath}:`, error);
46
+ throw new Error(`Failed to process template ${srcPath}`);
47
+ }
48
+ }
49
+
50
+ handlebars.registerHelper("eq", (a, b) => a === b);
51
+ handlebars.registerHelper("ne", (a, b) => a !== b);
52
+ handlebars.registerHelper("and", (...args) => {
53
+ const values = args.slice(0, -1);
54
+ return values.every((value) => value);
55
+ });
56
+ handlebars.registerHelper("or", (...args) => {
57
+ const values = args.slice(0, -1);
58
+ return values.some((value) => value);
59
+ });
60
+
61
+ handlebars.registerHelper(
62
+ "includes",
63
+ (array, value) => Array.isArray(array) && array.includes(value),
64
+ );
@@ -0,0 +1,94 @@
1
+ import type { CreateInput, Template } from "../types";
2
+
3
+ export const TEMPLATE_PRESETS: Record<Template, CreateInput | null> = {
4
+ mern: {
5
+ database: "mongodb",
6
+ orm: "mongoose",
7
+ backend: "express",
8
+ runtime: "node",
9
+ frontend: ["react-router"],
10
+ api: "orpc",
11
+ auth: "better-auth",
12
+ payments: "none",
13
+ addons: ["turborepo"],
14
+ examples: ["todo"],
15
+ dbSetup: "mongodb-atlas",
16
+ webDeploy: "none",
17
+ serverDeploy: "none",
18
+ },
19
+ pern: {
20
+ database: "postgres",
21
+ orm: "drizzle",
22
+ backend: "express",
23
+ runtime: "node",
24
+ frontend: ["tanstack-router"],
25
+ api: "trpc",
26
+ auth: "better-auth",
27
+ payments: "none",
28
+ addons: ["turborepo"],
29
+ examples: ["todo"],
30
+ dbSetup: "none",
31
+ webDeploy: "none",
32
+ serverDeploy: "none",
33
+ },
34
+ t3: {
35
+ database: "postgres",
36
+ orm: "prisma",
37
+ backend: "self",
38
+ runtime: "none",
39
+ frontend: ["next"],
40
+ api: "trpc",
41
+ auth: "better-auth",
42
+ payments: "none",
43
+ addons: ["biome", "turborepo"],
44
+ examples: ["none"],
45
+ dbSetup: "none",
46
+ webDeploy: "none",
47
+ serverDeploy: "none",
48
+ },
49
+ uniwind: {
50
+ database: "none",
51
+ orm: "none",
52
+ backend: "none",
53
+ runtime: "none",
54
+ frontend: ["native-uniwind"],
55
+ api: "none",
56
+ auth: "none",
57
+ payments: "none",
58
+ addons: ["none"],
59
+ examples: ["none"],
60
+ dbSetup: "none",
61
+ webDeploy: "none",
62
+ serverDeploy: "none",
63
+ },
64
+ none: null,
65
+ };
66
+
67
+ export function getTemplateConfig(template: Template) {
68
+ if (template === "none" || !template) {
69
+ return null;
70
+ }
71
+
72
+ const config = TEMPLATE_PRESETS[template];
73
+ if (!config) {
74
+ throw new Error(`Unknown template: ${template}`);
75
+ }
76
+
77
+ return config;
78
+ }
79
+
80
+ export function getTemplateDescription(template: Template) {
81
+ const descriptions: Record<Template, string> = {
82
+ mern: "MongoDB + Express + React + Node.js - Classic MERN stack",
83
+ pern: "PostgreSQL + Express + React + Node.js - Popular PERN stack",
84
+ t3: "T3 Stack - Next.js + tRPC + Prisma + PostgreSQL + Better Auth",
85
+ uniwind: "Expo + Uniwind native app with no backend services",
86
+ none: "No template - Full customization",
87
+ };
88
+
89
+ return descriptions[template] || "";
90
+ }
91
+
92
+ export function listAvailableTemplates() {
93
+ return Object.keys(TEMPLATE_PRESETS).filter((t) => t !== "none") as Template[];
94
+ }
@@ -0,0 +1,26 @@
1
+ import {
2
+ type ArrayLiteralExpression,
3
+ IndentationText,
4
+ type ObjectLiteralExpression,
5
+ Project,
6
+ QuoteKind,
7
+ SyntaxKind,
8
+ } from "ts-morph";
9
+
10
+ export const tsProject = new Project({
11
+ useInMemoryFileSystem: false,
12
+ skipAddingFilesFromTsConfig: true,
13
+ manipulationSettings: {
14
+ quoteKind: QuoteKind.Single,
15
+ indentationText: IndentationText.TwoSpaces,
16
+ },
17
+ });
18
+
19
+ export function ensureArrayProperty(obj: ObjectLiteralExpression, name: string) {
20
+ return (obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ??
21
+ obj
22
+ .addPropertyAssignment({ name, initializer: "[]" })
23
+ .getFirstDescendantByKindOrThrow(
24
+ SyntaxKind.ArrayLiteralExpression,
25
+ )) as ArrayLiteralExpression;
26
+ }
@@ -0,0 +1,117 @@
1
+ import type { CLIInput, ProjectConfig } from "./types";
2
+ import { getProvidedFlags, processFlags, validateArrayOptions } from "./utils/config-processing";
3
+ import { validateConfigForProgrammaticUse, validateFullConfig } from "./utils/config-validation";
4
+ import { exitWithError } from "./utils/errors";
5
+ import { extractAndValidateProjectName } from "./utils/project-name-validation";
6
+
7
+ const CORE_STACK_FLAGS = new Set([
8
+ "database",
9
+ "orm",
10
+ "backend",
11
+ "runtime",
12
+ "frontend",
13
+ "addons",
14
+ "examples",
15
+ "auth",
16
+ "dbSetup",
17
+ "payments",
18
+ "api",
19
+ "webDeploy",
20
+ "serverDeploy",
21
+ ]);
22
+
23
+ function validateYesFlagCombination(options: CLIInput, providedFlags: Set<string>) {
24
+ if (!options.yes) return;
25
+
26
+ if (options.template && options.template !== "none") {
27
+ return;
28
+ }
29
+
30
+ const coreStackFlagsProvided = Array.from(providedFlags).filter((flag) =>
31
+ CORE_STACK_FLAGS.has(flag),
32
+ );
33
+
34
+ if (coreStackFlagsProvided.length > 0) {
35
+ exitWithError(
36
+ `Cannot combine --yes with core stack configuration flags: ${coreStackFlagsProvided.map((f) => `--${f}`).join(", ")}. ` +
37
+ "The --yes flag uses default configuration. Remove these flags or use --yes without them.",
38
+ );
39
+ }
40
+ }
41
+
42
+ export function processAndValidateFlags(
43
+ options: CLIInput,
44
+ providedFlags: Set<string>,
45
+ projectName?: string,
46
+ ) {
47
+ if (options.yolo) {
48
+ const cfg = processFlags(options, projectName);
49
+ const validatedProjectName = extractAndValidateProjectName(
50
+ projectName,
51
+ options.projectDirectory,
52
+ true,
53
+ );
54
+ if (validatedProjectName) {
55
+ cfg.projectName = validatedProjectName;
56
+ }
57
+ return cfg;
58
+ }
59
+
60
+ validateYesFlagCombination(options, providedFlags);
61
+
62
+ try {
63
+ validateArrayOptions(options);
64
+ } catch (error) {
65
+ exitWithError(error instanceof Error ? error.message : String(error));
66
+ }
67
+
68
+ const config = processFlags(options, projectName);
69
+
70
+ const validatedProjectName = extractAndValidateProjectName(
71
+ projectName,
72
+ options.projectDirectory,
73
+ false,
74
+ );
75
+ if (validatedProjectName) {
76
+ config.projectName = validatedProjectName;
77
+ }
78
+
79
+ validateFullConfig(config, providedFlags, options);
80
+
81
+ return config;
82
+ }
83
+
84
+ export function processProvidedFlagsWithoutValidation(options: CLIInput, projectName?: string) {
85
+ if (!options.yolo) {
86
+ const providedFlags = getProvidedFlags(options);
87
+ validateYesFlagCombination(options, providedFlags);
88
+ }
89
+
90
+ const config = processFlags(options, projectName);
91
+
92
+ const validatedProjectName = extractAndValidateProjectName(
93
+ projectName,
94
+ options.projectDirectory,
95
+ true,
96
+ );
97
+ if (validatedProjectName) {
98
+ config.projectName = validatedProjectName;
99
+ }
100
+
101
+ return config;
102
+ }
103
+
104
+ export function validateConfigCompatibility(
105
+ config: Partial<ProjectConfig>,
106
+ providedFlags?: Set<string>,
107
+ options?: CLIInput,
108
+ ) {
109
+ if (options?.yolo) return;
110
+ if (options && providedFlags) {
111
+ validateFullConfig(config, providedFlags, options);
112
+ } else {
113
+ validateConfigForProgrammaticUse(config);
114
+ }
115
+ }
116
+
117
+ export { getProvidedFlags };
@@ -1,8 +1,6 @@
1
+ import { getAuthConfigProvider } from "@convex-dev/better-auth/auth-config";
2
+ import type { AuthConfig } from "convex/server";
3
+
1
4
  export default {
2
- providers: [
3
- {
4
- domain: process.env.CONVEX_SITE_URL,
5
- applicationID: "convex",
6
- },
7
- ],
8
- };
5
+ providers: [getAuthConfigProvider()],
6
+ } satisfies AuthConfig;
@@ -2,19 +2,21 @@ 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
3
  import { convex } 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"))}}
@@ -23,23 +25,19 @@ const nativeAppUrl = process.env.NATIVE_APP_URL || "mybettertapp://";
23
25
 
24
26
  export const authComponent = createClient<DataModel>(components.betterAuth);
25
27
 
26
- function createAuth(
27
- ctx: GenericCtx<DataModel>,
28
- { optionsOnly }: { optionsOnly?: boolean } = { optionsOnly: false }
29
- ) {
28
+ function createAuth(ctx: GenericCtx<DataModel>) {
30
29
  return betterAuth({
31
- logger: {
32
- disabled: optionsOnly,
33
- },
34
30
  {{#if (and (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")) (or (includes frontend "tanstack-start") (includes frontend "next")))}}
35
31
  baseURL: siteUrl,
36
32
  trustedOrigins: [siteUrl, nativeAppUrl],
33
+ {{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")))}}
34
+ trustedOrigins: [siteUrl, nativeAppUrl],
37
35
  {{else if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
38
36
  trustedOrigins: [nativeAppUrl],
39
37
  {{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
40
38
  baseURL: siteUrl,
41
39
  trustedOrigins: [siteUrl],
42
- {{else}}
40
+ {{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
43
41
  trustedOrigins: [siteUrl],
44
42
  {{/if}}
45
43
  database: authComponent.adapter(ctx),
@@ -50,11 +48,13 @@ function createAuth(
50
48
  plugins: [
51
49
  {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
52
50
  expo(),
53
- {{/if}}
54
- {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
51
+ {{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
55
52
  crossDomain({ siteUrl }),
56
53
  {{/if}}
57
- convex(),
54
+ convex({
55
+ authConfig,
56
+ jwksRotateOnTokenGenerationError: true,
57
+ }),
58
58
  ],
59
59
  });
60
60
  }
@@ -64,7 +64,7 @@ export { createAuth };
64
64
  export const getCurrentUser = query({
65
65
  args: {},
66
66
  returns: v.any(),
67
- handler: async function (ctx, args) {
67
+ handler: async function (ctx) {
68
68
  return authComponent.getAuthUser(ctx);
69
69
  },
70
- });
70
+ });
@@ -3,10 +3,10 @@ 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"))}}
7
- authComponent.registerRoutes(http, createAuth);
8
- {{else}}
6
+ {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
9
7
  authComponent.registerRoutes(http, createAuth, { cors: true });
8
+ {{else}}
9
+ authComponent.registerRoutes(http, createAuth);
10
10
  {{/if}}
11
11
 
12
- export default http;
12
+ export default http;
@@ -1,3 +1,3 @@
1
- import { nextJsHandler } from "@convex-dev/better-auth/nextjs";
1
+ import { handler } from "@/lib/auth-server";
2
2
 
3
- export const { GET, POST } = nextJsHandler();
3
+ export const { GET, POST } = handler;
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  DropdownMenu,
3
3
  DropdownMenuContent,
4
+ DropdownMenuGroup,
4
5
  DropdownMenuItem,
5
6
  DropdownMenuLabel,
6
7
  DropdownMenuSeparator,
@@ -18,17 +19,16 @@ export default function UserMenu() {
18
19
 
19
20
  return (
20
21
  <DropdownMenu>
21
- <DropdownMenuTrigger asChild>
22
- <Button variant="outline">{user?.name}</Button>
22
+ <DropdownMenuTrigger render={<Button variant="outline" />}>
23
+ {user?.name}
23
24
  </DropdownMenuTrigger>
24
25
  <DropdownMenuContent className="bg-card">
25
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
26
- <DropdownMenuSeparator />
27
- <DropdownMenuItem>{user?.email}</DropdownMenuItem>
28
- <DropdownMenuItem asChild>
29
- <Button
26
+ <DropdownMenuGroup>
27
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
28
+ <DropdownMenuSeparator />
29
+ <DropdownMenuItem>{user?.email}</DropdownMenuItem>
30
+ <DropdownMenuItem
30
31
  variant="destructive"
31
- className="w-full"
32
32
  onClick={() => {
33
33
  authClient.signOut({
34
34
  fetchOptions: {
@@ -40,8 +40,8 @@ export default function UserMenu() {
40
40
  }}
41
41
  >
42
42
  Sign Out
43
- </Button>
44
- </DropdownMenuItem>
43
+ </DropdownMenuItem>
44
+ </DropdownMenuGroup>
45
45
  </DropdownMenuContent>
46
46
  </DropdownMenu>
47
47
  );
@@ -1,6 +1,14 @@
1
- import { createAuth } from "@{{projectName}}/backend/convex/auth";
2
- import { getToken as getTokenNextjs } from "@convex-dev/better-auth/nextjs";
1
+ import { convexBetterAuthNextJs } from "@convex-dev/better-auth/nextjs";
3
2
 
4
- export const getToken = () => {
5
- return getTokenNextjs(createAuth);
6
- };
3
+ export const {
4
+ handler,
5
+ preloadAuthQuery,
6
+ isAuthenticated,
7
+ getToken,
8
+ fetchAuthQuery,
9
+ fetchAuthMutation,
10
+ fetchAuthAction,
11
+ } = convexBetterAuthNextJs({
12
+ convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
13
+ convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL!,
14
+ });
@@ -1,34 +1,36 @@
1
+ import { useNavigate } from "@tanstack/react-router";
2
+
1
3
  import {
2
4
  DropdownMenu,
3
5
  DropdownMenuContent,
6
+ DropdownMenuGroup,
4
7
  DropdownMenuItem,
5
8
  DropdownMenuLabel,
6
9
  DropdownMenuSeparator,
7
10
  DropdownMenuTrigger,
8
11
  } from "@/components/ui/dropdown-menu";
9
12
  import { authClient } from "@/lib/auth-client";
10
- import { useNavigate } from "@tanstack/react-router";
11
- import { Button } from "./ui/button";
12
13
  import { useQuery } from "convex/react";
13
14
  import { api } from "@{{projectName}}/backend/convex/_generated/api";
14
15
 
16
+ import { Button } from "./ui/button";
17
+
15
18
  export default function UserMenu() {
16
19
  const navigate = useNavigate();
17
20
  const user = useQuery(api.auth.getCurrentUser)
18
21
 
19
22
  return (
20
23
  <DropdownMenu>
21
- <DropdownMenuTrigger asChild>
22
- <Button variant="outline">{user?.name}</Button>
24
+ <DropdownMenuTrigger render={<Button variant="outline" />}>
25
+ {user?.name}
23
26
  </DropdownMenuTrigger>
24
27
  <DropdownMenuContent className="bg-card">
25
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
26
- <DropdownMenuSeparator />
27
- <DropdownMenuItem>{user?.email}</DropdownMenuItem>
28
- <DropdownMenuItem asChild>
29
- <Button
28
+ <DropdownMenuGroup>
29
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
30
+ <DropdownMenuSeparator />
31
+ <DropdownMenuItem>{user?.email}</DropdownMenuItem>
32
+ <DropdownMenuItem
30
33
  variant="destructive"
31
- className="w-full"
32
34
  onClick={() => {
33
35
  authClient.signOut({
34
36
  fetchOptions: {
@@ -42,8 +44,8 @@ export default function UserMenu() {
42
44
  }}
43
45
  >
44
46
  Sign Out
45
- </Button>
46
- </DropdownMenuItem>
47
+ </DropdownMenuItem>
48
+ </DropdownMenuGroup>
47
49
  </DropdownMenuContent>
48
50
  </DropdownMenu>
49
51
  );
@@ -1,49 +1,46 @@
1
1
  import {
2
2
  DropdownMenu,
3
3
  DropdownMenuContent,
4
+ DropdownMenuGroup,
4
5
  DropdownMenuItem,
5
6
  DropdownMenuLabel,
6
7
  DropdownMenuSeparator,
7
8
  DropdownMenuTrigger,
8
9
  } from "@/components/ui/dropdown-menu";
9
10
  import { authClient } from "@/lib/auth-client";
10
- import { useNavigate } from "@tanstack/react-router";
11
- import { Button } from "./ui/button";
12
11
  import { useQuery } from "convex/react";
13
12
  import { api } from "@{{projectName}}/backend/convex/_generated/api";
14
13
 
14
+ import { Button } from "./ui/button";
15
+
15
16
  export default function UserMenu() {
16
- const navigate = useNavigate();
17
17
  const user = useQuery(api.auth.getCurrentUser)
18
18
 
19
19
  return (
20
20
  <DropdownMenu>
21
- <DropdownMenuTrigger asChild>
22
- <Button variant="outline">{user?.name}</Button>
21
+ <DropdownMenuTrigger render={<Button variant="outline" />}>
22
+ {user?.name}
23
23
  </DropdownMenuTrigger>
24
24
  <DropdownMenuContent className="bg-card">
25
- <DropdownMenuLabel>My Account</DropdownMenuLabel>
26
- <DropdownMenuSeparator />
27
- <DropdownMenuItem>{user?.email}</DropdownMenuItem>
28
- <DropdownMenuItem asChild>
29
- <Button
25
+ <DropdownMenuGroup>
26
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
27
+ <DropdownMenuSeparator />
28
+ <DropdownMenuItem>{user?.email}</DropdownMenuItem>
29
+ <DropdownMenuItem
30
30
  variant="destructive"
31
- className="w-full"
32
31
  onClick={() => {
33
32
  authClient.signOut({
34
33
  fetchOptions: {
35
34
  onSuccess: () => {
36
- navigate({
37
- to: "/dashboard",
38
- });
35
+ location.reload();
39
36
  },
40
37
  },
41
38
  });
42
39
  }}
43
40
  >
44
41
  Sign Out
45
- </Button>
46
- </DropdownMenuItem>
42
+ </DropdownMenuItem>
43
+ </DropdownMenuGroup>
47
44
  </DropdownMenuContent>
48
45
  </DropdownMenu>
49
46
  );
@@ -1,6 +1,12 @@
1
- import { createAuth } from "@{{projectName}}/backend/convex/auth";
2
- import { setupFetchClient } from "@convex-dev/better-auth/react-start";
3
- import { getCookie } from "@tanstack/react-start/server";
1
+ import { convexBetterAuthReactStart } from "@convex-dev/better-auth/react-start";
4
2
 
5
- export const { fetchQuery, fetchMutation, fetchAction } =
6
- await setupFetchClient(createAuth, getCookie);
3
+ export const {
4
+ handler,
5
+ getToken,
6
+ fetchAuthQuery,
7
+ fetchAuthMutation,
8
+ fetchAuthAction,
9
+ } = convexBetterAuthReactStart({
10
+ convexUrl: process.env.VITE_CONVEX_URL!,
11
+ convexSiteUrl: process.env.VITE_CONVEX_SITE_URL!,
12
+ });
@@ -1,11 +1,11 @@
1
- import { reactStartHandler } from "@convex-dev/better-auth/react-start";
2
1
  import { createFileRoute } from "@tanstack/react-router";
2
+ import { handler } from "@/lib/auth-server";
3
3
 
4
4
  export const Route = createFileRoute("/api/auth/$")({
5
5
  server: {
6
6
  handlers: {
7
- GET: ({ request }) => reactStartHandler(request),
8
- POST: ({ request }) => reactStartHandler(request),
7
+ GET: ({ request }) => handler(request),
8
+ POST: ({ request }) => handler(request),
9
9
  },
10
10
  },
11
- });
11
+ });
@@ -12,4 +12,4 @@ export const Route = createFileRoute('/api/auth/$')({
12
12
  },
13
13
  },
14
14
  },
15
- })
15
+ })