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.
- package/bin/create-better-t-stack +98 -0
- package/package.json +40 -30
- package/src/api.ts +203 -0
- package/src/cli.ts +185 -0
- package/src/constants.ts +270 -0
- package/src/helpers/addons/addons-setup.ts +201 -0
- package/src/helpers/addons/examples-setup.ts +137 -0
- package/src/helpers/addons/fumadocs-setup.ts +99 -0
- package/src/helpers/addons/oxlint-setup.ts +36 -0
- package/src/helpers/addons/ruler-setup.ts +135 -0
- package/src/helpers/addons/starlight-setup.ts +45 -0
- package/src/helpers/addons/tauri-setup.ts +90 -0
- package/src/helpers/addons/tui-setup.ts +64 -0
- package/src/helpers/addons/ultracite-setup.ts +228 -0
- package/src/helpers/addons/vite-pwa-setup.ts +59 -0
- package/src/helpers/addons/wxt-setup.ts +86 -0
- package/src/helpers/core/add-addons.ts +85 -0
- package/src/helpers/core/add-deployment.ts +102 -0
- package/src/helpers/core/api-setup.ts +280 -0
- package/src/helpers/core/auth-setup.ts +203 -0
- package/src/helpers/core/backend-setup.ts +69 -0
- package/src/helpers/core/command-handlers.ts +354 -0
- package/src/helpers/core/convex-codegen.ts +14 -0
- package/src/helpers/core/create-project.ts +134 -0
- package/src/helpers/core/create-readme.ts +694 -0
- package/src/helpers/core/db-setup.ts +184 -0
- package/src/helpers/core/detect-project-config.ts +41 -0
- package/src/helpers/core/env-setup.ts +481 -0
- package/src/helpers/core/git.ts +23 -0
- package/src/helpers/core/install-dependencies.ts +29 -0
- package/src/helpers/core/payments-setup.ts +48 -0
- package/src/helpers/core/post-installation.ts +403 -0
- package/src/helpers/core/project-config.ts +250 -0
- package/src/helpers/core/runtime-setup.ts +76 -0
- package/src/helpers/core/template-manager.ts +917 -0
- package/src/helpers/core/workspace-setup.ts +184 -0
- package/src/helpers/database-providers/d1-setup.ts +28 -0
- package/src/helpers/database-providers/docker-compose-setup.ts +50 -0
- package/src/helpers/database-providers/mongodb-atlas-setup.ts +182 -0
- package/src/helpers/database-providers/neon-setup.ts +240 -0
- package/src/helpers/database-providers/planetscale-setup.ts +78 -0
- package/src/helpers/database-providers/prisma-postgres-setup.ts +193 -0
- package/src/helpers/database-providers/supabase-setup.ts +196 -0
- package/src/helpers/database-providers/turso-setup.ts +309 -0
- package/src/helpers/deployment/alchemy/alchemy-combined-setup.ts +80 -0
- package/src/helpers/deployment/alchemy/alchemy-next-setup.ts +52 -0
- package/src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts +105 -0
- package/src/helpers/deployment/alchemy/alchemy-react-router-setup.ts +33 -0
- package/src/helpers/deployment/alchemy/alchemy-solid-setup.ts +33 -0
- package/src/helpers/deployment/alchemy/alchemy-svelte-setup.ts +99 -0
- package/src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts +34 -0
- package/src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts +99 -0
- package/src/helpers/deployment/alchemy/env-dts-setup.ts +76 -0
- package/src/helpers/deployment/alchemy/index.ts +7 -0
- package/src/helpers/deployment/server-deploy-setup.ts +55 -0
- package/src/helpers/deployment/web-deploy-setup.ts +58 -0
- package/src/index.ts +51 -0
- package/src/prompts/addons.ts +200 -0
- package/src/prompts/api.ts +49 -0
- package/src/prompts/auth.ts +84 -0
- package/src/prompts/backend.ts +83 -0
- package/src/prompts/config-prompts.ts +138 -0
- package/src/prompts/database-setup.ts +112 -0
- package/src/prompts/database.ts +57 -0
- package/src/prompts/examples.ts +60 -0
- package/src/prompts/frontend.ts +118 -0
- package/src/prompts/git.ts +16 -0
- package/src/prompts/install.ts +16 -0
- package/src/prompts/orm.ts +53 -0
- package/src/prompts/package-manager.ts +32 -0
- package/src/prompts/payments.ts +50 -0
- package/src/prompts/project-name.ts +86 -0
- package/src/prompts/runtime.ts +47 -0
- package/src/prompts/server-deploy.ts +91 -0
- package/src/prompts/web-deploy.ts +107 -0
- package/src/tui/app.tsx +1062 -0
- package/src/types.ts +70 -0
- package/src/utils/add-package-deps.ts +57 -0
- package/src/utils/analytics.ts +39 -0
- package/src/utils/better-auth-plugin-setup.ts +71 -0
- package/src/utils/bts-config.ts +122 -0
- package/src/utils/command-exists.ts +16 -0
- package/src/utils/compatibility-rules.ts +337 -0
- package/src/utils/compatibility.ts +11 -0
- package/src/utils/config-processing.ts +130 -0
- package/src/utils/config-validation.ts +470 -0
- package/src/utils/display-config.ts +96 -0
- package/src/utils/docker-utils.ts +70 -0
- package/src/utils/errors.ts +30 -0
- package/src/utils/file-formatter.ts +11 -0
- package/src/utils/generate-reproducible-command.ts +53 -0
- package/src/utils/get-latest-cli-version.ts +27 -0
- package/src/utils/get-package-manager.ts +13 -0
- package/src/utils/open-url.ts +18 -0
- package/src/utils/package-runner.ts +23 -0
- package/src/utils/project-directory.ts +102 -0
- package/src/utils/project-name-validation.ts +43 -0
- package/src/utils/render-title.ts +48 -0
- package/src/utils/setup-catalogs.ts +192 -0
- package/src/utils/sponsors.ts +101 -0
- package/src/utils/telemetry.ts +19 -0
- package/src/utils/template-processor.ts +64 -0
- package/src/utils/templates.ts +94 -0
- package/src/utils/ts-morph.ts +26 -0
- package/src/validation.ts +117 -0
- package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +1 -1
- package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +17 -0
- package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +9 -0
- package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +67 -0
- package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +301 -3
- package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +296 -10
- package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +180 -1
- package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +172 -9
- package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +156 -6
- package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +156 -4
- package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +159 -6
- package/templates/frontend/react/web-base/src/index.css.hbs +1 -1
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +0 -8
- package/dist/index.d.mts +0 -347
- package/dist/index.mjs +0 -4
- package/dist/src-QkFdHtZE.mjs +0 -7072
- package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +0 -7
- package/templates/examples/ai/web/react/base/src/components/response.tsx.hbs +0 -22
package/src/constants.ts
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { getUserPkgManager } from "./utils/get-package-manager";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect if running as a compiled Bun binary.
|
|
8
|
+
* Compiled binaries have Bun.main pointing to the executable itself,
|
|
9
|
+
* not a .ts or .js file.
|
|
10
|
+
*/
|
|
11
|
+
function isCompiledBinary(): boolean {
|
|
12
|
+
if (typeof Bun === "undefined") return false;
|
|
13
|
+
const main = Bun.main;
|
|
14
|
+
// Compiled binaries don't have .ts, .js, or .mjs extensions
|
|
15
|
+
return !main.endsWith(".ts") && !main.endsWith(".js") && !main.endsWith(".mjs");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find the main create-better-t-stack package in node_modules.
|
|
20
|
+
* Used when running as a compiled binary from a platform package.
|
|
21
|
+
*/
|
|
22
|
+
function findMainPackage(): string | null {
|
|
23
|
+
// Use process.execPath to get actual filesystem path (Bun.main returns bunfs virtual path)
|
|
24
|
+
let current = path.dirname(process.execPath);
|
|
25
|
+
|
|
26
|
+
// Search up the directory tree for node_modules/create-better-t-stack
|
|
27
|
+
while (current !== path.dirname(current)) {
|
|
28
|
+
const candidate = path.join(current, "node_modules", "create-better-t-stack");
|
|
29
|
+
const templatesPath = path.join(candidate, "templates");
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(templatesPath)) {
|
|
32
|
+
return candidate;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
current = path.dirname(current);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get the package root directory.
|
|
43
|
+
* - In JS mode (development/tsdown build): relative to dist/
|
|
44
|
+
* - In compiled binary mode: find main package in node_modules, or use dev fallback
|
|
45
|
+
*/
|
|
46
|
+
function getPkgRoot(): string {
|
|
47
|
+
if (isCompiledBinary()) {
|
|
48
|
+
// First try: find main package in node_modules (production npm install)
|
|
49
|
+
const mainPkg = findMainPackage();
|
|
50
|
+
if (mainPkg) {
|
|
51
|
+
return mainPkg;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Second try: development mode - binary is in dist/create-better-t-stack-{platform}-{arch}/bin/
|
|
55
|
+
// Use process.execPath to get actual filesystem path (Bun.main returns bunfs virtual path)
|
|
56
|
+
const binaryPath = process.execPath;
|
|
57
|
+
const binaryDir = path.dirname(binaryPath);
|
|
58
|
+
|
|
59
|
+
// Templates are at ../../../templates relative to the binary (bin -> platform-dir -> dist -> apps/cli)
|
|
60
|
+
const devTemplatesPath = path.resolve(binaryDir, "..", "..", "..", "templates");
|
|
61
|
+
if (fs.existsSync(devTemplatesPath)) {
|
|
62
|
+
return path.resolve(binaryDir, "..", "..", "..");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Third try: templates might be at ../../templates (if binary is in dist/bin/)
|
|
66
|
+
const altTemplatesPath = path.resolve(binaryDir, "..", "..", "templates");
|
|
67
|
+
if (fs.existsSync(altTemplatesPath)) {
|
|
68
|
+
return path.resolve(binaryDir, "..", "..");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Fourth try: templates at ../templates
|
|
72
|
+
const simpleTemplatesPath = path.resolve(binaryDir, "..", "templates");
|
|
73
|
+
if (fs.existsSync(simpleTemplatesPath)) {
|
|
74
|
+
return path.resolve(binaryDir, "..");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Standard JS mode - relative to this file in dist/
|
|
79
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
80
|
+
const distPath = path.dirname(__filename);
|
|
81
|
+
return path.join(distPath, "../");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const PKG_ROOT = getPkgRoot();
|
|
85
|
+
|
|
86
|
+
export const DEFAULT_CONFIG_BASE = {
|
|
87
|
+
projectName: "my-better-t-app",
|
|
88
|
+
relativePath: "my-better-t-app",
|
|
89
|
+
frontend: ["tanstack-router"],
|
|
90
|
+
database: "sqlite",
|
|
91
|
+
orm: "drizzle",
|
|
92
|
+
auth: "better-auth",
|
|
93
|
+
payments: "none",
|
|
94
|
+
addons: ["turborepo"],
|
|
95
|
+
examples: [],
|
|
96
|
+
git: true,
|
|
97
|
+
install: true,
|
|
98
|
+
dbSetup: "none",
|
|
99
|
+
backend: "hono",
|
|
100
|
+
runtime: "bun",
|
|
101
|
+
api: "trpc",
|
|
102
|
+
webDeploy: "none",
|
|
103
|
+
serverDeploy: "none",
|
|
104
|
+
} as const;
|
|
105
|
+
|
|
106
|
+
export function getDefaultConfig() {
|
|
107
|
+
return {
|
|
108
|
+
...DEFAULT_CONFIG_BASE,
|
|
109
|
+
projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
|
|
110
|
+
packageManager: getUserPkgManager(),
|
|
111
|
+
frontend: [...DEFAULT_CONFIG_BASE.frontend],
|
|
112
|
+
addons: [...DEFAULT_CONFIG_BASE.addons],
|
|
113
|
+
examples: [...DEFAULT_CONFIG_BASE.examples],
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const DEFAULT_CONFIG = getDefaultConfig();
|
|
118
|
+
|
|
119
|
+
export const dependencyVersionMap = {
|
|
120
|
+
typescript: "^5",
|
|
121
|
+
|
|
122
|
+
"better-auth": "^1.4.7",
|
|
123
|
+
"@better-auth/expo": "^1.4.7",
|
|
124
|
+
|
|
125
|
+
"@clerk/nextjs": "^6.31.5",
|
|
126
|
+
"@clerk/clerk-react": "^5.45.0",
|
|
127
|
+
"@clerk/tanstack-react-start": "^0.26.3",
|
|
128
|
+
"@clerk/clerk-expo": "^2.14.25",
|
|
129
|
+
|
|
130
|
+
"drizzle-orm": "^0.45.1",
|
|
131
|
+
"drizzle-kit": "^0.31.8",
|
|
132
|
+
"@planetscale/database": "^1.19.0",
|
|
133
|
+
|
|
134
|
+
"@libsql/client": "0.15.15",
|
|
135
|
+
libsql: "0.5.22",
|
|
136
|
+
|
|
137
|
+
"@neondatabase/serverless": "^1.0.2",
|
|
138
|
+
pg: "^8.16.3",
|
|
139
|
+
"@types/pg": "^8.15.6",
|
|
140
|
+
"@types/ws": "^8.18.1",
|
|
141
|
+
ws: "^8.18.3",
|
|
142
|
+
|
|
143
|
+
mysql2: "^3.14.0",
|
|
144
|
+
|
|
145
|
+
"@prisma/client": "^7.1.0",
|
|
146
|
+
prisma: "^7.1.0",
|
|
147
|
+
"@prisma/adapter-d1": "^7.1.0",
|
|
148
|
+
"@prisma/adapter-neon": "^7.1.0",
|
|
149
|
+
"@prisma/adapter-mariadb": "^7.1.0",
|
|
150
|
+
"@prisma/adapter-libsql": "^7.1.0",
|
|
151
|
+
"@prisma/adapter-better-sqlite3": "^7.1.0",
|
|
152
|
+
"@prisma/adapter-pg": "^7.1.0",
|
|
153
|
+
"@prisma/adapter-planetscale": "^7.1.0",
|
|
154
|
+
|
|
155
|
+
mongoose: "^8.14.0",
|
|
156
|
+
|
|
157
|
+
"vite-plugin-pwa": "^1.0.1",
|
|
158
|
+
"@vite-pwa/assets-generator": "^1.0.0",
|
|
159
|
+
|
|
160
|
+
"@tauri-apps/cli": "^2.4.0",
|
|
161
|
+
|
|
162
|
+
"@biomejs/biome": "^2.2.0",
|
|
163
|
+
|
|
164
|
+
oxlint: "^1.34.0",
|
|
165
|
+
oxfmt: "^0.19.0",
|
|
166
|
+
|
|
167
|
+
husky: "^9.1.7",
|
|
168
|
+
"lint-staged": "^16.1.2",
|
|
169
|
+
|
|
170
|
+
tsx: "^4.19.2",
|
|
171
|
+
"@types/node": "^22.13.11",
|
|
172
|
+
|
|
173
|
+
"@types/bun": "^1.3.4",
|
|
174
|
+
|
|
175
|
+
"@elysiajs/node": "^1.3.1",
|
|
176
|
+
|
|
177
|
+
"@elysiajs/cors": "^1.3.3",
|
|
178
|
+
"@elysiajs/trpc": "^1.1.0",
|
|
179
|
+
elysia: "^1.3.21",
|
|
180
|
+
|
|
181
|
+
"@hono/node-server": "^1.14.4",
|
|
182
|
+
"@hono/trpc-server": "^0.4.0",
|
|
183
|
+
hono: "^4.8.2",
|
|
184
|
+
|
|
185
|
+
cors: "^2.8.5",
|
|
186
|
+
express: "^5.1.0",
|
|
187
|
+
"@types/express": "^5.0.1",
|
|
188
|
+
"@types/cors": "^2.8.17",
|
|
189
|
+
|
|
190
|
+
fastify: "^5.3.3",
|
|
191
|
+
"@fastify/cors": "^11.0.1",
|
|
192
|
+
|
|
193
|
+
turbo: "^2.6.3",
|
|
194
|
+
|
|
195
|
+
ai: "^5.0.49",
|
|
196
|
+
"@ai-sdk/google": "^2.0.51",
|
|
197
|
+
"@ai-sdk/vue": "^2.0.49",
|
|
198
|
+
"@ai-sdk/svelte": "^3.0.39",
|
|
199
|
+
"@ai-sdk/react": "^2.0.39",
|
|
200
|
+
streamdown: "^1.6.10",
|
|
201
|
+
shiki: "^3.12.2",
|
|
202
|
+
|
|
203
|
+
"@orpc/server": "^1.12.2",
|
|
204
|
+
"@orpc/client": "^1.12.2",
|
|
205
|
+
"@orpc/openapi": "^1.12.2",
|
|
206
|
+
"@orpc/zod": "^1.12.2",
|
|
207
|
+
"@orpc/tanstack-query": "^1.12.2",
|
|
208
|
+
|
|
209
|
+
"@trpc/tanstack-react-query": "^11.7.2",
|
|
210
|
+
"@trpc/server": "^11.7.2",
|
|
211
|
+
"@trpc/client": "^11.7.2",
|
|
212
|
+
|
|
213
|
+
next: "^16.0.10",
|
|
214
|
+
|
|
215
|
+
convex: "^1.31.2",
|
|
216
|
+
"@convex-dev/react-query": "^0.1.0",
|
|
217
|
+
"@convex-dev/agent": "^0.3.2",
|
|
218
|
+
"convex-svelte": "^0.0.12",
|
|
219
|
+
"convex-nuxt": "0.1.5",
|
|
220
|
+
"convex-vue": "^0.1.5",
|
|
221
|
+
"@convex-dev/better-auth": "^0.10.6",
|
|
222
|
+
|
|
223
|
+
"@tanstack/svelte-query": "^5.85.3",
|
|
224
|
+
"@tanstack/svelte-query-devtools": "^5.85.3",
|
|
225
|
+
|
|
226
|
+
"@tanstack/vue-query-devtools": "^5.90.2",
|
|
227
|
+
"@tanstack/vue-query": "^5.90.2",
|
|
228
|
+
|
|
229
|
+
"@tanstack/react-query-devtools": "^5.91.1",
|
|
230
|
+
"@tanstack/react-query": "^5.90.12",
|
|
231
|
+
|
|
232
|
+
"@tanstack/solid-query": "^5.87.4",
|
|
233
|
+
"@tanstack/solid-query-devtools": "^5.87.4",
|
|
234
|
+
"@tanstack/solid-router-devtools": "^1.131.44",
|
|
235
|
+
|
|
236
|
+
wrangler: "^4.54.0",
|
|
237
|
+
"@cloudflare/vite-plugin": "^1.17.1",
|
|
238
|
+
"@opennextjs/cloudflare": "^1.14.6",
|
|
239
|
+
"nitro-cloudflare-dev": "^0.2.2",
|
|
240
|
+
"@sveltejs/adapter-cloudflare": "^7.2.4",
|
|
241
|
+
"@cloudflare/workers-types": "^4.20251213.0",
|
|
242
|
+
|
|
243
|
+
alchemy: "^0.81.2",
|
|
244
|
+
|
|
245
|
+
dotenv: "^17.2.2",
|
|
246
|
+
tsdown: "^0.16.5",
|
|
247
|
+
zod: "^4.1.13",
|
|
248
|
+
srvx: "0.8.15",
|
|
249
|
+
|
|
250
|
+
"@polar-sh/better-auth": "^1.1.3",
|
|
251
|
+
"@polar-sh/sdk": "^0.34.16",
|
|
252
|
+
} as const;
|
|
253
|
+
|
|
254
|
+
export type AvailableDependencies = keyof typeof dependencyVersionMap;
|
|
255
|
+
|
|
256
|
+
export const ADDON_COMPATIBILITY = {
|
|
257
|
+
pwa: ["tanstack-router", "react-router", "solid", "next"],
|
|
258
|
+
tauri: ["tanstack-router", "react-router", "nuxt", "svelte", "solid", "next"],
|
|
259
|
+
biome: [],
|
|
260
|
+
husky: [],
|
|
261
|
+
turborepo: [],
|
|
262
|
+
starlight: [],
|
|
263
|
+
ultracite: [],
|
|
264
|
+
ruler: [],
|
|
265
|
+
oxlint: [],
|
|
266
|
+
fumadocs: [],
|
|
267
|
+
opentui: [],
|
|
268
|
+
wxt: [],
|
|
269
|
+
none: [],
|
|
270
|
+
} as const;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { log } from "@clack/prompts";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import type { Frontend, ProjectConfig } from "../../types";
|
|
6
|
+
import { addPackageDependency } from "../../utils/add-package-deps";
|
|
7
|
+
import { setupFumadocs } from "./fumadocs-setup";
|
|
8
|
+
import { setupOxlint } from "./oxlint-setup";
|
|
9
|
+
import { setupRuler } from "./ruler-setup";
|
|
10
|
+
import { setupStarlight } from "./starlight-setup";
|
|
11
|
+
import { setupTauri } from "./tauri-setup";
|
|
12
|
+
import { setupTui } from "./tui-setup";
|
|
13
|
+
import { setupUltracite } from "./ultracite-setup";
|
|
14
|
+
import { setupWxt } from "./wxt-setup";
|
|
15
|
+
import { addPwaToViteConfig } from "./vite-pwa-setup";
|
|
16
|
+
|
|
17
|
+
export async function setupAddons(config: ProjectConfig, isAddCommand = false) {
|
|
18
|
+
const { addons, frontend, projectDir, packageManager } = config;
|
|
19
|
+
const hasReactWebFrontend =
|
|
20
|
+
frontend.includes("react-router") ||
|
|
21
|
+
frontend.includes("tanstack-router") ||
|
|
22
|
+
frontend.includes("next");
|
|
23
|
+
const hasNuxtFrontend = frontend.includes("nuxt");
|
|
24
|
+
const hasSvelteFrontend = frontend.includes("svelte");
|
|
25
|
+
const hasSolidFrontend = frontend.includes("solid");
|
|
26
|
+
const hasNextFrontend = frontend.includes("next");
|
|
27
|
+
|
|
28
|
+
if (addons.includes("turborepo")) {
|
|
29
|
+
await addPackageDependency({
|
|
30
|
+
devDependencies: ["turbo"],
|
|
31
|
+
projectDir,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (isAddCommand) {
|
|
35
|
+
log.info(`${pc.yellow("Update your package.json scripts:")}
|
|
36
|
+
|
|
37
|
+
${pc.dim("Replace:")} ${pc.yellow('"pnpm -r dev"')} ${pc.dim("→")} ${pc.green('"turbo dev"')}
|
|
38
|
+
${pc.dim("Replace:")} ${pc.yellow('"pnpm --filter web dev"')} ${pc.dim(
|
|
39
|
+
"→",
|
|
40
|
+
)} ${pc.green('"turbo -F web dev"')}
|
|
41
|
+
|
|
42
|
+
${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) {
|
|
48
|
+
await setupPwa(projectDir, frontend);
|
|
49
|
+
}
|
|
50
|
+
if (
|
|
51
|
+
addons.includes("tauri") &&
|
|
52
|
+
(hasReactWebFrontend ||
|
|
53
|
+
hasNuxtFrontend ||
|
|
54
|
+
hasSvelteFrontend ||
|
|
55
|
+
hasSolidFrontend ||
|
|
56
|
+
hasNextFrontend)
|
|
57
|
+
) {
|
|
58
|
+
await setupTauri(config);
|
|
59
|
+
}
|
|
60
|
+
const hasUltracite = addons.includes("ultracite");
|
|
61
|
+
const hasBiome = addons.includes("biome");
|
|
62
|
+
const hasHusky = addons.includes("husky");
|
|
63
|
+
const hasOxlint = addons.includes("oxlint");
|
|
64
|
+
|
|
65
|
+
if (hasUltracite) {
|
|
66
|
+
await setupUltracite(config, hasHusky);
|
|
67
|
+
} else {
|
|
68
|
+
if (hasBiome) {
|
|
69
|
+
await setupBiome(projectDir);
|
|
70
|
+
}
|
|
71
|
+
if (hasHusky) {
|
|
72
|
+
let linter: "biome" | "oxlint" | undefined;
|
|
73
|
+
if (hasOxlint) {
|
|
74
|
+
linter = "oxlint";
|
|
75
|
+
} else if (hasBiome) {
|
|
76
|
+
linter = "biome";
|
|
77
|
+
}
|
|
78
|
+
await setupHusky(projectDir, linter);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (hasOxlint) {
|
|
83
|
+
await setupOxlint(projectDir, packageManager);
|
|
84
|
+
}
|
|
85
|
+
if (addons.includes("starlight")) {
|
|
86
|
+
await setupStarlight(config);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (addons.includes("ruler")) {
|
|
90
|
+
await setupRuler(config);
|
|
91
|
+
}
|
|
92
|
+
if (addons.includes("fumadocs")) {
|
|
93
|
+
await setupFumadocs(config);
|
|
94
|
+
}
|
|
95
|
+
if (addons.includes("opentui")) {
|
|
96
|
+
await setupTui(config);
|
|
97
|
+
}
|
|
98
|
+
if (addons.includes("wxt")) {
|
|
99
|
+
await setupWxt(config);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getWebAppDir(projectDir: string, frontends: Frontend[]) {
|
|
104
|
+
if (
|
|
105
|
+
frontends.some((f) =>
|
|
106
|
+
["react-router", "tanstack-router", "nuxt", "svelte", "solid"].includes(f),
|
|
107
|
+
)
|
|
108
|
+
) {
|
|
109
|
+
return path.join(projectDir, "apps/web");
|
|
110
|
+
}
|
|
111
|
+
return path.join(projectDir, "apps/web");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function setupBiome(projectDir: string) {
|
|
115
|
+
await addPackageDependency({
|
|
116
|
+
devDependencies: ["@biomejs/biome"],
|
|
117
|
+
projectDir,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
121
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
122
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
123
|
+
|
|
124
|
+
packageJson.scripts = {
|
|
125
|
+
...packageJson.scripts,
|
|
126
|
+
check: "biome check --write .",
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function setupHusky(projectDir: string, linter?: "biome" | "oxlint") {
|
|
134
|
+
await addPackageDependency({
|
|
135
|
+
devDependencies: ["husky", "lint-staged"],
|
|
136
|
+
projectDir,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const packageJsonPath = path.join(projectDir, "package.json");
|
|
140
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
141
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
142
|
+
|
|
143
|
+
packageJson.scripts = {
|
|
144
|
+
...packageJson.scripts,
|
|
145
|
+
prepare: "husky",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (linter === "oxlint") {
|
|
149
|
+
packageJson["lint-staged"] = {
|
|
150
|
+
"*": ["oxlint", "oxfmt --write"],
|
|
151
|
+
};
|
|
152
|
+
} else if (linter === "biome") {
|
|
153
|
+
packageJson["lint-staged"] = {
|
|
154
|
+
"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."],
|
|
155
|
+
};
|
|
156
|
+
} else {
|
|
157
|
+
packageJson["lint-staged"] = {
|
|
158
|
+
"**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function setupPwa(projectDir: string, frontends: Frontend[]) {
|
|
167
|
+
const isCompatibleFrontend = frontends.some((f) =>
|
|
168
|
+
["react-router", "tanstack-router", "solid"].includes(f),
|
|
169
|
+
);
|
|
170
|
+
if (!isCompatibleFrontend) return;
|
|
171
|
+
|
|
172
|
+
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
173
|
+
|
|
174
|
+
if (!(await fs.pathExists(clientPackageDir))) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await addPackageDependency({
|
|
179
|
+
dependencies: ["vite-plugin-pwa"],
|
|
180
|
+
devDependencies: ["@vite-pwa/assets-generator"],
|
|
181
|
+
projectDir: clientPackageDir,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
|
|
185
|
+
if (await fs.pathExists(clientPackageJsonPath)) {
|
|
186
|
+
const packageJson = await fs.readJson(clientPackageJsonPath);
|
|
187
|
+
|
|
188
|
+
packageJson.scripts = {
|
|
189
|
+
...packageJson.scripts,
|
|
190
|
+
"generate-pwa-assets": "pwa-assets-generator",
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
|
|
197
|
+
|
|
198
|
+
if (await fs.pathExists(viteConfigTs)) {
|
|
199
|
+
await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import type { AvailableDependencies } from "../../constants";
|
|
4
|
+
import type { ProjectConfig } from "../../types";
|
|
5
|
+
import { addPackageDependency } from "../../utils/add-package-deps";
|
|
6
|
+
|
|
7
|
+
export async function setupExamples(config: ProjectConfig) {
|
|
8
|
+
const { examples, backend } = config;
|
|
9
|
+
|
|
10
|
+
if (!examples || examples.length === 0 || examples[0] === "none") {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (examples.includes("todo") && backend !== "convex" && backend !== "none") {
|
|
15
|
+
await setupTodoDependencies(config);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (examples.includes("ai")) {
|
|
19
|
+
await setupAIDependencies(config);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function setupTodoDependencies(config: ProjectConfig) {
|
|
24
|
+
const { projectDir, orm, database, backend } = config;
|
|
25
|
+
|
|
26
|
+
const apiDir = path.join(projectDir, "packages/api");
|
|
27
|
+
const apiDirExists = await fs.pathExists(apiDir);
|
|
28
|
+
|
|
29
|
+
if (!apiDirExists || backend === "none") {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (orm === "drizzle") {
|
|
34
|
+
const dependencies: AvailableDependencies[] = ["drizzle-orm"];
|
|
35
|
+
if (database === "postgres") {
|
|
36
|
+
dependencies.push("@types/pg");
|
|
37
|
+
}
|
|
38
|
+
await addPackageDependency({
|
|
39
|
+
dependencies,
|
|
40
|
+
projectDir: apiDir,
|
|
41
|
+
});
|
|
42
|
+
} else if (orm === "prisma") {
|
|
43
|
+
await addPackageDependency({
|
|
44
|
+
dependencies: ["@prisma/client"],
|
|
45
|
+
projectDir: apiDir,
|
|
46
|
+
});
|
|
47
|
+
} else if (orm === "mongoose") {
|
|
48
|
+
await addPackageDependency({
|
|
49
|
+
dependencies: ["mongoose"],
|
|
50
|
+
projectDir: apiDir,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function setupAIDependencies(config: ProjectConfig) {
|
|
56
|
+
const { frontend, backend, projectDir } = config;
|
|
57
|
+
|
|
58
|
+
const webClientDir = path.join(projectDir, "apps/web");
|
|
59
|
+
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
60
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
61
|
+
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
62
|
+
|
|
63
|
+
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
64
|
+
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
65
|
+
const serverDirExists = await fs.pathExists(serverDir);
|
|
66
|
+
const convexBackendDirExists = await fs.pathExists(convexBackendDir);
|
|
67
|
+
|
|
68
|
+
const hasReactWeb =
|
|
69
|
+
frontend.includes("react-router") ||
|
|
70
|
+
frontend.includes("tanstack-router") ||
|
|
71
|
+
frontend.includes("next") ||
|
|
72
|
+
frontend.includes("tanstack-start");
|
|
73
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
74
|
+
const hasSvelte = frontend.includes("svelte");
|
|
75
|
+
|
|
76
|
+
const hasReactNative =
|
|
77
|
+
frontend.includes("native-bare") ||
|
|
78
|
+
frontend.includes("native-uniwind") ||
|
|
79
|
+
frontend.includes("native-unistyles");
|
|
80
|
+
|
|
81
|
+
if (backend === "convex" && convexBackendDirExists) {
|
|
82
|
+
await addPackageDependency({
|
|
83
|
+
dependencies: ["@convex-dev/agent", "ai", "@ai-sdk/google"],
|
|
84
|
+
projectDir: convexBackendDir,
|
|
85
|
+
});
|
|
86
|
+
} else if (backend === "self" && webClientDirExists) {
|
|
87
|
+
await addPackageDependency({
|
|
88
|
+
dependencies: ["ai", "@ai-sdk/google"],
|
|
89
|
+
projectDir: webClientDir,
|
|
90
|
+
});
|
|
91
|
+
} else if (serverDirExists && backend !== "none") {
|
|
92
|
+
await addPackageDependency({
|
|
93
|
+
dependencies: ["ai", "@ai-sdk/google"],
|
|
94
|
+
projectDir: serverDir,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (webClientDirExists) {
|
|
99
|
+
const dependencies: AvailableDependencies[] = [];
|
|
100
|
+
|
|
101
|
+
if (backend === "convex") {
|
|
102
|
+
if (hasReactWeb) {
|
|
103
|
+
dependencies.push("@convex-dev/agent", "streamdown");
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
dependencies.push("ai");
|
|
107
|
+
if (hasNuxt) {
|
|
108
|
+
dependencies.push("@ai-sdk/vue");
|
|
109
|
+
} else if (hasSvelte) {
|
|
110
|
+
dependencies.push("@ai-sdk/svelte");
|
|
111
|
+
} else if (hasReactWeb) {
|
|
112
|
+
dependencies.push("@ai-sdk/react", "streamdown");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (dependencies.length > 0) {
|
|
117
|
+
await addPackageDependency({
|
|
118
|
+
dependencies,
|
|
119
|
+
projectDir: webClientDir,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (nativeClientDirExists && hasReactNative) {
|
|
125
|
+
if (backend === "convex") {
|
|
126
|
+
await addPackageDependency({
|
|
127
|
+
dependencies: ["@convex-dev/agent"],
|
|
128
|
+
projectDir: nativeClientDir,
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
await addPackageDependency({
|
|
132
|
+
dependencies: ["ai", "@ai-sdk/react"],
|
|
133
|
+
projectDir: nativeClientDir,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { isCancel, log, select, spinner } from "@clack/prompts";
|
|
3
|
+
import consola from "consola";
|
|
4
|
+
import { $ } from "bun";
|
|
5
|
+
import fs from "fs-extra";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
import type { ProjectConfig } from "../../types";
|
|
8
|
+
import { exitCancelled } from "../../utils/errors";
|
|
9
|
+
import { getPackageExecutionCommand } from "../../utils/package-runner";
|
|
10
|
+
|
|
11
|
+
type FumadocsTemplate =
|
|
12
|
+
| "next-mdx"
|
|
13
|
+
| "waku"
|
|
14
|
+
| "react-router"
|
|
15
|
+
| "react-router-spa"
|
|
16
|
+
| "tanstack-start";
|
|
17
|
+
|
|
18
|
+
const TEMPLATES = {
|
|
19
|
+
"next-mdx": {
|
|
20
|
+
label: "Next.js: Fumadocs MDX",
|
|
21
|
+
hint: "Recommended template with MDX support",
|
|
22
|
+
value: "+next+fuma-docs-mdx",
|
|
23
|
+
},
|
|
24
|
+
waku: {
|
|
25
|
+
label: "Waku: Content Collections",
|
|
26
|
+
hint: "Template using Waku with content collections",
|
|
27
|
+
value: "waku",
|
|
28
|
+
},
|
|
29
|
+
"react-router": {
|
|
30
|
+
label: "React Router: MDX Remote",
|
|
31
|
+
hint: "Template for React Router with MDX remote",
|
|
32
|
+
value: "react-router",
|
|
33
|
+
},
|
|
34
|
+
"react-router-spa": {
|
|
35
|
+
label: "React Router: SPA",
|
|
36
|
+
hint: "Template for React Router SPA",
|
|
37
|
+
value: "react-router-spa",
|
|
38
|
+
},
|
|
39
|
+
"tanstack-start": {
|
|
40
|
+
label: "Tanstack Start: MDX Remote",
|
|
41
|
+
hint: "Template for Tanstack Start with MDX remote",
|
|
42
|
+
value: "tanstack-start",
|
|
43
|
+
},
|
|
44
|
+
} as const;
|
|
45
|
+
|
|
46
|
+
export async function setupFumadocs(config: ProjectConfig) {
|
|
47
|
+
const { packageManager, projectDir } = config;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
log.info("Setting up Fumadocs...");
|
|
51
|
+
|
|
52
|
+
const template = await select<FumadocsTemplate>({
|
|
53
|
+
message: "Choose a template",
|
|
54
|
+
options: Object.entries(TEMPLATES).map(([key, template]) => ({
|
|
55
|
+
value: key as FumadocsTemplate,
|
|
56
|
+
label: template.label,
|
|
57
|
+
hint: template.hint,
|
|
58
|
+
})),
|
|
59
|
+
initialValue: "next-mdx",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
63
|
+
|
|
64
|
+
const templateArg = TEMPLATES[template].value;
|
|
65
|
+
|
|
66
|
+
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --pm ${packageManager} --no-git`;
|
|
67
|
+
|
|
68
|
+
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
69
|
+
|
|
70
|
+
const appsDir = path.join(projectDir, "apps");
|
|
71
|
+
await fs.ensureDir(appsDir);
|
|
72
|
+
|
|
73
|
+
const s = spinner();
|
|
74
|
+
s.start("Running Fumadocs create command...");
|
|
75
|
+
|
|
76
|
+
await $`${{ raw: fumadocsInitCommand }}`.cwd(appsDir).env({ CI: "true" });
|
|
77
|
+
|
|
78
|
+
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
|
79
|
+
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
|
80
|
+
|
|
81
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
82
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
83
|
+
packageJson.name = "fumadocs";
|
|
84
|
+
|
|
85
|
+
if (packageJson.scripts?.dev) {
|
|
86
|
+
packageJson.scripts.dev = `${packageJson.scripts.dev} --port=4000`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
s.stop("Fumadocs setup complete!");
|
|
93
|
+
} catch (error) {
|
|
94
|
+
log.error(pc.red("Failed to set up Fumadocs"));
|
|
95
|
+
if (error instanceof Error) {
|
|
96
|
+
consola.error(pc.red(error.message));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|