create-better-t-stack 3.10.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 (124) hide show
  1. package/bin/create-better-t-stack +98 -0
  2. package/package.json +40 -30
  3. package/src/api.ts +203 -0
  4. package/src/cli.ts +185 -0
  5. package/src/constants.ts +270 -0
  6. package/src/helpers/addons/addons-setup.ts +201 -0
  7. package/src/helpers/addons/examples-setup.ts +137 -0
  8. package/src/helpers/addons/fumadocs-setup.ts +99 -0
  9. package/src/helpers/addons/oxlint-setup.ts +36 -0
  10. package/src/helpers/addons/ruler-setup.ts +135 -0
  11. package/src/helpers/addons/starlight-setup.ts +45 -0
  12. package/src/helpers/addons/tauri-setup.ts +90 -0
  13. package/src/helpers/addons/tui-setup.ts +64 -0
  14. package/src/helpers/addons/ultracite-setup.ts +228 -0
  15. package/src/helpers/addons/vite-pwa-setup.ts +59 -0
  16. package/src/helpers/addons/wxt-setup.ts +86 -0
  17. package/src/helpers/core/add-addons.ts +85 -0
  18. package/src/helpers/core/add-deployment.ts +102 -0
  19. package/src/helpers/core/api-setup.ts +280 -0
  20. package/src/helpers/core/auth-setup.ts +203 -0
  21. package/src/helpers/core/backend-setup.ts +69 -0
  22. package/src/helpers/core/command-handlers.ts +354 -0
  23. package/src/helpers/core/convex-codegen.ts +14 -0
  24. package/src/helpers/core/create-project.ts +134 -0
  25. package/src/helpers/core/create-readme.ts +694 -0
  26. package/src/helpers/core/db-setup.ts +184 -0
  27. package/src/helpers/core/detect-project-config.ts +41 -0
  28. package/src/helpers/core/env-setup.ts +481 -0
  29. package/src/helpers/core/git.ts +23 -0
  30. package/src/helpers/core/install-dependencies.ts +29 -0
  31. package/src/helpers/core/payments-setup.ts +48 -0
  32. package/src/helpers/core/post-installation.ts +403 -0
  33. package/src/helpers/core/project-config.ts +250 -0
  34. package/src/helpers/core/runtime-setup.ts +76 -0
  35. package/src/helpers/core/template-manager.ts +917 -0
  36. package/src/helpers/core/workspace-setup.ts +184 -0
  37. package/src/helpers/database-providers/d1-setup.ts +28 -0
  38. package/src/helpers/database-providers/docker-compose-setup.ts +50 -0
  39. package/src/helpers/database-providers/mongodb-atlas-setup.ts +182 -0
  40. package/src/helpers/database-providers/neon-setup.ts +240 -0
  41. package/src/helpers/database-providers/planetscale-setup.ts +78 -0
  42. package/src/helpers/database-providers/prisma-postgres-setup.ts +193 -0
  43. package/src/helpers/database-providers/supabase-setup.ts +196 -0
  44. package/src/helpers/database-providers/turso-setup.ts +309 -0
  45. package/src/helpers/deployment/alchemy/alchemy-combined-setup.ts +80 -0
  46. package/src/helpers/deployment/alchemy/alchemy-next-setup.ts +52 -0
  47. package/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts +105 -0
  48. package/src/helpers/deployment/alchemy/alchemy-react-router-setup.ts +33 -0
  49. package/src/helpers/deployment/alchemy/alchemy-solid-setup.ts +33 -0
  50. package/src/helpers/deployment/alchemy/alchemy-svelte-setup.ts +99 -0
  51. package/src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts +34 -0
  52. package/src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts +99 -0
  53. package/src/helpers/deployment/alchemy/env-dts-setup.ts +76 -0
  54. package/src/helpers/deployment/alchemy/index.ts +7 -0
  55. package/src/helpers/deployment/server-deploy-setup.ts +55 -0
  56. package/src/helpers/deployment/web-deploy-setup.ts +58 -0
  57. package/src/index.ts +51 -0
  58. package/src/prompts/addons.ts +200 -0
  59. package/src/prompts/api.ts +49 -0
  60. package/src/prompts/auth.ts +84 -0
  61. package/src/prompts/backend.ts +83 -0
  62. package/src/prompts/config-prompts.ts +138 -0
  63. package/src/prompts/database-setup.ts +112 -0
  64. package/src/prompts/database.ts +57 -0
  65. package/src/prompts/examples.ts +60 -0
  66. package/src/prompts/frontend.ts +118 -0
  67. package/src/prompts/git.ts +16 -0
  68. package/src/prompts/install.ts +16 -0
  69. package/src/prompts/orm.ts +53 -0
  70. package/src/prompts/package-manager.ts +32 -0
  71. package/src/prompts/payments.ts +50 -0
  72. package/src/prompts/project-name.ts +86 -0
  73. package/src/prompts/runtime.ts +47 -0
  74. package/src/prompts/server-deploy.ts +91 -0
  75. package/src/prompts/web-deploy.ts +107 -0
  76. package/src/tui/app.tsx +1062 -0
  77. package/src/types.ts +70 -0
  78. package/src/utils/add-package-deps.ts +57 -0
  79. package/src/utils/analytics.ts +39 -0
  80. package/src/utils/better-auth-plugin-setup.ts +71 -0
  81. package/src/utils/bts-config.ts +122 -0
  82. package/src/utils/command-exists.ts +16 -0
  83. package/src/utils/compatibility-rules.ts +337 -0
  84. package/src/utils/compatibility.ts +11 -0
  85. package/src/utils/config-processing.ts +130 -0
  86. package/src/utils/config-validation.ts +470 -0
  87. package/src/utils/display-config.ts +96 -0
  88. package/src/utils/docker-utils.ts +70 -0
  89. package/src/utils/errors.ts +30 -0
  90. package/src/utils/file-formatter.ts +11 -0
  91. package/src/utils/generate-reproducible-command.ts +53 -0
  92. package/src/utils/get-latest-cli-version.ts +27 -0
  93. package/src/utils/get-package-manager.ts +13 -0
  94. package/src/utils/open-url.ts +18 -0
  95. package/src/utils/package-runner.ts +23 -0
  96. package/src/utils/project-directory.ts +102 -0
  97. package/src/utils/project-name-validation.ts +43 -0
  98. package/src/utils/render-title.ts +48 -0
  99. package/src/utils/setup-catalogs.ts +192 -0
  100. package/src/utils/sponsors.ts +101 -0
  101. package/src/utils/telemetry.ts +19 -0
  102. package/src/utils/template-processor.ts +64 -0
  103. package/src/utils/templates.ts +94 -0
  104. package/src/utils/ts-morph.ts +26 -0
  105. package/src/validation.ts +117 -0
  106. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +1 -1
  107. package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +17 -0
  108. package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +9 -0
  109. package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +67 -0
  110. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +301 -3
  111. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +296 -10
  112. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +180 -1
  113. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +172 -9
  114. package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +156 -6
  115. package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +156 -4
  116. package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +159 -6
  117. package/templates/frontend/react/web-base/src/index.css.hbs +1 -1
  118. package/dist/cli.d.mts +0 -1
  119. package/dist/cli.mjs +0 -8
  120. package/dist/index.d.mts +0 -347
  121. package/dist/index.mjs +0 -4
  122. package/dist/src-QkFdHtZE.mjs +0 -7072
  123. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +0 -7
  124. package/templates/examples/ai/web/react/base/src/components/response.tsx.hbs +0 -22
@@ -0,0 +1,49 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import type { API, Backend, Frontend } from "../types";
3
+ import { allowedApisForFrontends } from "../utils/compatibility-rules";
4
+ import { exitCancelled } from "../utils/errors";
5
+
6
+ export async function getApiChoice(
7
+ Api?: API | undefined,
8
+ frontend?: Frontend[],
9
+ backend?: Backend,
10
+ ) {
11
+ if (backend === "convex" || backend === "none") {
12
+ return "none";
13
+ }
14
+
15
+ const allowed = allowedApisForFrontends(frontend ?? []);
16
+
17
+ if (Api) {
18
+ return allowed.includes(Api) ? Api : allowed[0];
19
+ }
20
+ const apiOptions = allowed.map((a) =>
21
+ a === "trpc"
22
+ ? {
23
+ value: "trpc" as const,
24
+ label: "tRPC",
25
+ hint: "End-to-end typesafe APIs made easy",
26
+ }
27
+ : a === "orpc"
28
+ ? {
29
+ value: "orpc" as const,
30
+ label: "oRPC",
31
+ hint: "End-to-end type-safe APIs that adhere to OpenAPI standards",
32
+ }
33
+ : {
34
+ value: "none" as const,
35
+ label: "None",
36
+ hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)",
37
+ },
38
+ );
39
+
40
+ const apiType = await select<API>({
41
+ message: "Select API type",
42
+ options: apiOptions,
43
+ initialValue: apiOptions[0].value,
44
+ });
45
+
46
+ if (isCancel(apiType)) return exitCancelled("Operation cancelled");
47
+
48
+ return apiType;
49
+ }
@@ -0,0 +1,84 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import { DEFAULT_CONFIG } from "../constants";
3
+ import type { Auth, Backend } from "../types";
4
+ import { exitCancelled } from "../utils/errors";
5
+
6
+ export async function getAuthChoice(
7
+ auth: Auth | undefined,
8
+ backend?: Backend,
9
+ frontend?: string[],
10
+ ) {
11
+ if (auth !== undefined) return auth;
12
+ if (backend === "none") {
13
+ return "none" as Auth;
14
+ }
15
+ if (backend === "convex") {
16
+ const supportedBetterAuthFrontends = frontend?.some((f) =>
17
+ [
18
+ "tanstack-router",
19
+ "tanstack-start",
20
+ "next",
21
+ "native-bare",
22
+ "native-uniwind",
23
+ "native-unistyles",
24
+ ].includes(f),
25
+ );
26
+
27
+ const hasClerkCompatibleFrontends = frontend?.some((f) =>
28
+ [
29
+ "react-router",
30
+ "tanstack-router",
31
+ "tanstack-start",
32
+ "next",
33
+ "native-bare",
34
+ "native-uniwind",
35
+ "native-unistyles",
36
+ ].includes(f),
37
+ );
38
+
39
+ const options = [];
40
+
41
+ if (supportedBetterAuthFrontends) {
42
+ options.push({
43
+ value: "better-auth",
44
+ label: "Better-Auth",
45
+ hint: "comprehensive auth framework for TypeScript",
46
+ });
47
+ }
48
+
49
+ if (hasClerkCompatibleFrontends) {
50
+ options.push({
51
+ value: "clerk",
52
+ label: "Clerk",
53
+ hint: "More than auth, Complete User Management",
54
+ });
55
+ }
56
+
57
+ options.push({ value: "none", label: "None", hint: "No auth" });
58
+
59
+ const response = await select({
60
+ message: "Select authentication provider",
61
+ options,
62
+ initialValue: "none",
63
+ });
64
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
65
+ return response as Auth;
66
+ }
67
+
68
+ const response = await select({
69
+ message: "Select authentication provider",
70
+ options: [
71
+ {
72
+ value: "better-auth",
73
+ label: "Better-Auth",
74
+ hint: "comprehensive auth framework for TypeScript",
75
+ },
76
+ { value: "none", label: "None" },
77
+ ],
78
+ initialValue: DEFAULT_CONFIG.auth,
79
+ });
80
+
81
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
82
+
83
+ return response as Auth;
84
+ }
@@ -0,0 +1,83 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import { DEFAULT_CONFIG } from "../constants";
3
+ import type { Backend, Frontend } from "../types";
4
+ import { exitCancelled } from "../utils/errors";
5
+
6
+ // Temporarily restrict to Next.js and TanStack Start only for backend="self"
7
+ const FULLSTACK_FRONTENDS: readonly Frontend[] = [
8
+ "next",
9
+ "tanstack-start",
10
+ // "nuxt", // TODO: Add support in future update
11
+ // "svelte", // TODO: Add support in future update
12
+ ] as const;
13
+
14
+ export async function getBackendFrameworkChoice(
15
+ backendFramework?: Backend,
16
+ frontends?: Frontend[],
17
+ ) {
18
+ if (backendFramework !== undefined) return backendFramework;
19
+
20
+ const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
21
+ const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
22
+
23
+ const backendOptions: Array<{
24
+ value: Backend;
25
+ label: string;
26
+ hint: string;
27
+ }> = [];
28
+
29
+ if (hasFullstackFrontend) {
30
+ backendOptions.push({
31
+ value: "self" as const,
32
+ label: "Self (Fullstack)",
33
+ hint: "Use frontend's built-in api routes",
34
+ });
35
+ }
36
+
37
+ backendOptions.push(
38
+ {
39
+ value: "hono" as const,
40
+ label: "Hono",
41
+ hint: "Lightweight, ultrafast web framework",
42
+ },
43
+ {
44
+ value: "express" as const,
45
+ label: "Express",
46
+ hint: "Fast, unopinionated, minimalist web framework for Node.js",
47
+ },
48
+ {
49
+ value: "fastify" as const,
50
+ label: "Fastify",
51
+ hint: "Fast, low-overhead web framework for Node.js",
52
+ },
53
+ {
54
+ value: "elysia" as const,
55
+ label: "Elysia",
56
+ hint: "Ergonomic web framework for building backend servers",
57
+ },
58
+ );
59
+
60
+ if (!hasIncompatibleFrontend) {
61
+ backendOptions.push({
62
+ value: "convex" as const,
63
+ label: "Convex",
64
+ hint: "Reactive backend-as-a-service platform",
65
+ });
66
+ }
67
+
68
+ backendOptions.push({
69
+ value: "none" as const,
70
+ label: "None",
71
+ hint: "No backend server",
72
+ });
73
+
74
+ const response = await select<Backend>({
75
+ message: "Select backend",
76
+ options: backendOptions,
77
+ initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend,
78
+ });
79
+
80
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
81
+
82
+ return response;
83
+ }
@@ -0,0 +1,138 @@
1
+ import { group } from "@clack/prompts";
2
+ import type {
3
+ Addons,
4
+ API,
5
+ Auth,
6
+ Backend,
7
+ Database,
8
+ DatabaseSetup,
9
+ Examples,
10
+ Frontend,
11
+ ORM,
12
+ PackageManager,
13
+ Payments,
14
+ ProjectConfig,
15
+ Runtime,
16
+ ServerDeploy,
17
+ WebDeploy,
18
+ } from "../types";
19
+ import { exitCancelled } from "../utils/errors";
20
+ import { getAddonsChoice } from "./addons";
21
+ import { getApiChoice } from "./api";
22
+ import { getAuthChoice } from "./auth";
23
+ import { getBackendFrameworkChoice } from "./backend";
24
+ import { getDatabaseChoice } from "./database";
25
+ import { getDBSetupChoice } from "./database-setup";
26
+ import { getExamplesChoice } from "./examples";
27
+ import { getFrontendChoice } from "./frontend";
28
+ import { getGitChoice } from "./git";
29
+ import { getinstallChoice } from "./install";
30
+ import { getORMChoice } from "./orm";
31
+ import { getPackageManagerChoice } from "./package-manager";
32
+ import { getPaymentsChoice } from "./payments";
33
+ import { getRuntimeChoice } from "./runtime";
34
+ import { getServerDeploymentChoice } from "./server-deploy";
35
+ import { getDeploymentChoice } from "./web-deploy";
36
+
37
+ type PromptGroupResults = {
38
+ frontend: Frontend[];
39
+ backend: Backend;
40
+ runtime: Runtime;
41
+ database: Database;
42
+ orm: ORM;
43
+ api: API;
44
+ auth: Auth;
45
+ payments: Payments;
46
+ addons: Addons[];
47
+ examples: Examples[];
48
+ dbSetup: DatabaseSetup;
49
+ git: boolean;
50
+ packageManager: PackageManager;
51
+ install: boolean;
52
+ webDeploy: WebDeploy;
53
+ serverDeploy: ServerDeploy;
54
+ };
55
+
56
+ export async function gatherConfig(
57
+ flags: Partial<ProjectConfig>,
58
+ projectName: string,
59
+ projectDir: string,
60
+ relativePath: string,
61
+ ) {
62
+ const result = await group<PromptGroupResults>(
63
+ {
64
+ frontend: () => getFrontendChoice(flags.frontend, flags.backend, flags.auth),
65
+ backend: ({ results }) => getBackendFrameworkChoice(flags.backend, results.frontend),
66
+ runtime: ({ results }) => getRuntimeChoice(flags.runtime, results.backend),
67
+ database: ({ results }) =>
68
+ getDatabaseChoice(flags.database, results.backend, results.runtime),
69
+ orm: ({ results }) =>
70
+ getORMChoice(
71
+ flags.orm,
72
+ results.database !== "none",
73
+ results.database,
74
+ results.backend,
75
+ results.runtime,
76
+ ),
77
+ api: ({ results }) =>
78
+ getApiChoice(flags.api, results.frontend, results.backend) as Promise<API>,
79
+ auth: ({ results }) => getAuthChoice(flags.auth, results.backend, results.frontend),
80
+ payments: ({ results }) =>
81
+ getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend),
82
+ addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend, results.auth),
83
+ examples: ({ results }) =>
84
+ getExamplesChoice(
85
+ flags.examples,
86
+ results.database,
87
+ results.frontend,
88
+ results.backend,
89
+ results.api,
90
+ ) as Promise<Examples[]>,
91
+ dbSetup: ({ results }) =>
92
+ getDBSetupChoice(
93
+ results.database ?? "none",
94
+ flags.dbSetup,
95
+ results.orm,
96
+ results.backend,
97
+ results.runtime,
98
+ ),
99
+ webDeploy: ({ results }) =>
100
+ getDeploymentChoice(flags.webDeploy, results.runtime, results.backend, results.frontend),
101
+ serverDeploy: ({ results }) =>
102
+ getServerDeploymentChoice(
103
+ flags.serverDeploy,
104
+ results.runtime,
105
+ results.backend,
106
+ results.webDeploy,
107
+ ),
108
+ git: () => getGitChoice(flags.git),
109
+ packageManager: () => getPackageManagerChoice(flags.packageManager),
110
+ install: () => getinstallChoice(flags.install),
111
+ },
112
+ {
113
+ onCancel: () => exitCancelled("Operation cancelled"),
114
+ },
115
+ );
116
+
117
+ return {
118
+ projectName: projectName,
119
+ projectDir: projectDir,
120
+ relativePath: relativePath,
121
+ frontend: result.frontend,
122
+ backend: result.backend,
123
+ runtime: result.runtime,
124
+ database: result.database,
125
+ orm: result.orm,
126
+ auth: result.auth,
127
+ payments: result.payments,
128
+ addons: result.addons,
129
+ examples: result.examples,
130
+ git: result.git,
131
+ packageManager: result.packageManager,
132
+ install: result.install,
133
+ dbSetup: result.dbSetup,
134
+ api: result.api,
135
+ webDeploy: result.webDeploy,
136
+ serverDeploy: result.serverDeploy,
137
+ };
138
+ }
@@ -0,0 +1,112 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import type { Backend, DatabaseSetup, ORM, Runtime } from "../types";
3
+ import { exitCancelled } from "../utils/errors";
4
+
5
+ export async function getDBSetupChoice(
6
+ databaseType: string,
7
+ dbSetup: DatabaseSetup | undefined,
8
+ _orm?: ORM,
9
+ backend?: Backend,
10
+ runtime?: Runtime,
11
+ ) {
12
+ if (backend === "convex") {
13
+ return "none";
14
+ }
15
+
16
+ if (dbSetup !== undefined) return dbSetup as DatabaseSetup;
17
+
18
+ if (databaseType === "none") {
19
+ return "none";
20
+ }
21
+
22
+ let options: Array<{ value: DatabaseSetup; label: string; hint: string }> = [];
23
+
24
+ if (databaseType === "sqlite") {
25
+ options = [
26
+ {
27
+ value: "turso" as const,
28
+ label: "Turso",
29
+ hint: "SQLite for Production. Powered by libSQL",
30
+ },
31
+ ...(runtime === "workers"
32
+ ? [
33
+ {
34
+ value: "d1" as const,
35
+ label: "Cloudflare D1",
36
+ hint: "Cloudflare's managed, serverless database with SQLite's SQL semantics",
37
+ },
38
+ ]
39
+ : []),
40
+ { value: "none" as const, label: "None", hint: "Manual setup" },
41
+ ];
42
+ } else if (databaseType === "postgres") {
43
+ options = [
44
+ {
45
+ value: "neon" as const,
46
+ label: "Neon Postgres",
47
+ hint: "Serverless Postgres with branching capability",
48
+ },
49
+ {
50
+ value: "planetscale" as const,
51
+ label: "PlanetScale",
52
+ hint: "Postgres & Vitess (MySQL) on NVMe",
53
+ },
54
+ {
55
+ value: "supabase" as const,
56
+ label: "Supabase",
57
+ hint: "Local Supabase stack (requires Docker)",
58
+ },
59
+ {
60
+ value: "prisma-postgres" as const,
61
+ label: "Prisma Postgres",
62
+ hint: "Instant Postgres for Global Applications",
63
+ },
64
+ {
65
+ value: "docker" as const,
66
+ label: "Docker",
67
+ hint: "Run locally with docker compose",
68
+ },
69
+ { value: "none" as const, label: "None", hint: "Manual setup" },
70
+ ];
71
+ } else if (databaseType === "mysql") {
72
+ options = [
73
+ {
74
+ value: "planetscale" as const,
75
+ label: "PlanetScale",
76
+ hint: "MySQL on Vitess (NVMe, HA)",
77
+ },
78
+ {
79
+ value: "docker" as const,
80
+ label: "Docker",
81
+ hint: "Run locally with docker compose",
82
+ },
83
+ { value: "none" as const, label: "None", hint: "Manual setup" },
84
+ ];
85
+ } else if (databaseType === "mongodb") {
86
+ options = [
87
+ {
88
+ value: "mongodb-atlas" as const,
89
+ label: "MongoDB Atlas",
90
+ hint: "The most effective way to deploy MongoDB",
91
+ },
92
+ {
93
+ value: "docker" as const,
94
+ label: "Docker",
95
+ hint: "Run locally with docker compose",
96
+ },
97
+ { value: "none" as const, label: "None", hint: "Manual setup" },
98
+ ];
99
+ } else {
100
+ return "none";
101
+ }
102
+
103
+ const response = await select<DatabaseSetup>({
104
+ message: `Select ${databaseType} setup option`,
105
+ options,
106
+ initialValue: "none",
107
+ });
108
+
109
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
110
+
111
+ return response;
112
+ }
@@ -0,0 +1,57 @@
1
+ import { isCancel, select } from "@clack/prompts";
2
+ import { DEFAULT_CONFIG } from "../constants";
3
+ import type { Backend, Database, Runtime } from "../types";
4
+ import { exitCancelled } from "../utils/errors";
5
+
6
+ export async function getDatabaseChoice(database?: Database, backend?: Backend, runtime?: Runtime) {
7
+ if (backend === "convex" || backend === "none") {
8
+ return "none";
9
+ }
10
+
11
+ if (database !== undefined) return database;
12
+
13
+ const databaseOptions: Array<{
14
+ value: Database;
15
+ label: string;
16
+ hint: string;
17
+ }> = [
18
+ {
19
+ value: "none",
20
+ label: "None",
21
+ hint: "No database setup",
22
+ },
23
+ {
24
+ value: "sqlite",
25
+ label: "SQLite",
26
+ hint: "lightweight, server-less, embedded relational database",
27
+ },
28
+ {
29
+ value: "postgres",
30
+ label: "PostgreSQL",
31
+ hint: "powerful, open source object-relational database system",
32
+ },
33
+ {
34
+ value: "mysql",
35
+ label: "MySQL",
36
+ hint: "popular open-source relational database system",
37
+ },
38
+ ];
39
+
40
+ if (runtime !== "workers") {
41
+ databaseOptions.push({
42
+ value: "mongodb",
43
+ label: "MongoDB",
44
+ hint: "open-source NoSQL database that stores data in JSON-like documents called BSON",
45
+ });
46
+ }
47
+
48
+ const response = await select<Database>({
49
+ message: "Select database",
50
+ options: databaseOptions,
51
+ initialValue: DEFAULT_CONFIG.database,
52
+ });
53
+
54
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
55
+
56
+ return response;
57
+ }
@@ -0,0 +1,60 @@
1
+ import { isCancel, multiselect } from "@clack/prompts";
2
+ import { DEFAULT_CONFIG } from "../constants";
3
+ import type { API, Backend, Database, Examples, Frontend } from "../types";
4
+ import { isExampleAIAllowed, isExampleTodoAllowed } from "../utils/compatibility-rules";
5
+ import { exitCancelled } from "../utils/errors";
6
+
7
+ export async function getExamplesChoice(
8
+ examples?: Examples[],
9
+ database?: Database,
10
+ frontends?: Frontend[],
11
+ backend?: Backend,
12
+ api?: API,
13
+ ) {
14
+ if (examples !== undefined) return examples;
15
+
16
+ if (backend === "none") {
17
+ return [];
18
+ }
19
+
20
+ if (backend !== "convex") {
21
+ if (api === "none") {
22
+ return [];
23
+ }
24
+ if (database === "none") {
25
+ return [];
26
+ }
27
+ }
28
+
29
+ let response: Examples[] | symbol = [];
30
+ const options: { value: Examples; label: string; hint: string }[] = [];
31
+
32
+ if (isExampleTodoAllowed(backend, database)) {
33
+ options.push({
34
+ value: "todo" as const,
35
+ label: "Todo App",
36
+ hint: "A simple CRUD example app",
37
+ });
38
+ }
39
+
40
+ if (isExampleAIAllowed(backend, frontends ?? [])) {
41
+ options.push({
42
+ value: "ai" as const,
43
+ label: "AI Chat",
44
+ hint: "A simple AI chat interface using AI SDK",
45
+ });
46
+ }
47
+
48
+ if (options.length === 0) return [];
49
+
50
+ response = await multiselect<Examples>({
51
+ message: "Include examples",
52
+ options: options,
53
+ required: false,
54
+ initialValues: DEFAULT_CONFIG.examples?.filter((ex) => options.some((o) => o.value === ex)),
55
+ });
56
+
57
+ if (isCancel(response)) return exitCancelled("Operation cancelled");
58
+
59
+ return response;
60
+ }
@@ -0,0 +1,118 @@
1
+ import { isCancel, multiselect, select } from "@clack/prompts";
2
+ import { DEFAULT_CONFIG } from "../constants";
3
+ import type { Backend, Frontend } from "../types";
4
+ import { isFrontendAllowedWithBackend } from "../utils/compatibility-rules";
5
+ import { exitCancelled } from "../utils/errors";
6
+
7
+ export async function getFrontendChoice(
8
+ frontendOptions?: Frontend[],
9
+ backend?: Backend,
10
+ auth?: string,
11
+ ) {
12
+ if (frontendOptions !== undefined) return frontendOptions;
13
+
14
+ const frontendTypes = await multiselect({
15
+ message: "Select project type",
16
+ options: [
17
+ {
18
+ value: "web",
19
+ label: "Web",
20
+ hint: "React, Vue or Svelte Web Application",
21
+ },
22
+ {
23
+ value: "native",
24
+ label: "Native",
25
+ hint: "Create a React Native/Expo app",
26
+ },
27
+ ],
28
+ required: false,
29
+ initialValues: ["web"],
30
+ });
31
+
32
+ if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
33
+
34
+ const result: Frontend[] = [];
35
+
36
+ if (frontendTypes.includes("web")) {
37
+ const allWebOptions = [
38
+ {
39
+ value: "tanstack-router" as const,
40
+ label: "TanStack Router",
41
+ hint: "Modern and scalable routing for React Applications",
42
+ },
43
+ {
44
+ value: "react-router" as const,
45
+ label: "React Router",
46
+ hint: "A user‑obsessed, standards‑focused, multi‑strategy router",
47
+ },
48
+ {
49
+ value: "next" as const,
50
+ label: "Next.js",
51
+ hint: "The React Framework for the Web",
52
+ },
53
+ {
54
+ value: "nuxt" as const,
55
+ label: "Nuxt",
56
+ hint: "The Progressive Web Framework for Vue.js",
57
+ },
58
+ {
59
+ value: "svelte" as const,
60
+ label: "Svelte",
61
+ hint: "web development for the rest of us",
62
+ },
63
+ {
64
+ value: "solid" as const,
65
+ label: "Solid",
66
+ hint: "Simple and performant reactivity for building user interfaces",
67
+ },
68
+ {
69
+ value: "tanstack-start" as const,
70
+ label: "TanStack Start",
71
+ hint: "SSR, Server Functions, API Routes and more with TanStack Router",
72
+ },
73
+ ];
74
+
75
+ const webOptions = allWebOptions.filter((option) =>
76
+ isFrontendAllowedWithBackend(option.value, backend, auth),
77
+ );
78
+
79
+ const webFramework = await select<Frontend>({
80
+ message: "Choose web",
81
+ options: webOptions,
82
+ initialValue: DEFAULT_CONFIG.frontend[0],
83
+ });
84
+
85
+ if (isCancel(webFramework)) return exitCancelled("Operation cancelled");
86
+
87
+ result.push(webFramework);
88
+ }
89
+
90
+ if (frontendTypes.includes("native")) {
91
+ const nativeFramework = await select<Frontend>({
92
+ message: "Choose native",
93
+ options: [
94
+ {
95
+ value: "native-bare" as const,
96
+ label: "Bare",
97
+ hint: "Bare Expo without styling library",
98
+ },
99
+ {
100
+ value: "native-uniwind" as const,
101
+ label: "Uniwind",
102
+ hint: "Fastest Tailwind bindings for React Native with HeroUI Native",
103
+ },
104
+ {
105
+ value: "native-unistyles" as const,
106
+ label: "Unistyles",
107
+ hint: "Consistent styling for React Native",
108
+ },
109
+ ],
110
+ initialValue: "native-bare",
111
+ });
112
+
113
+ if (isCancel(nativeFramework)) return exitCancelled("Operation cancelled");
114
+ result.push(nativeFramework);
115
+ }
116
+
117
+ return result;
118
+ }