create-better-t-stack 3.10.0 → 3.11.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/dist/cli.mjs +1 -1
- package/dist/index.d.mts +7 -3
- package/dist/index.mjs +1 -1
- package/dist/{src-QkFdHtZE.mjs → src-XVvJUQ_h.mjs} +257 -83
- package/package.json +8 -8
- 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/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/dist/cli.mjs
CHANGED
package/dist/index.d.mts
CHANGED
|
@@ -64,6 +64,8 @@ declare const router: {
|
|
|
64
64
|
fumadocs: "fumadocs";
|
|
65
65
|
ultracite: "ultracite";
|
|
66
66
|
oxlint: "oxlint";
|
|
67
|
+
opentui: "opentui";
|
|
68
|
+
wxt: "wxt";
|
|
67
69
|
}>>>;
|
|
68
70
|
examples: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
69
71
|
none: "none";
|
|
@@ -165,7 +167,7 @@ declare const router: {
|
|
|
165
167
|
backend: "none" | "hono" | "express" | "fastify" | "elysia" | "convex" | "self";
|
|
166
168
|
runtime: "none" | "bun" | "node" | "workers";
|
|
167
169
|
frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid")[];
|
|
168
|
-
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint")[];
|
|
170
|
+
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt")[];
|
|
169
171
|
examples: ("none" | "todo" | "ai")[];
|
|
170
172
|
auth: "none" | "better-auth" | "clerk";
|
|
171
173
|
payments: "none" | "polar";
|
|
@@ -223,7 +225,7 @@ declare const router: {
|
|
|
223
225
|
backend: "none" | "hono" | "express" | "fastify" | "elysia" | "convex" | "self";
|
|
224
226
|
runtime: "none" | "bun" | "node" | "workers";
|
|
225
227
|
frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid")[];
|
|
226
|
-
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint")[];
|
|
228
|
+
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt")[];
|
|
227
229
|
examples: ("none" | "todo" | "ai")[];
|
|
228
230
|
auth: "none" | "better-auth" | "clerk";
|
|
229
231
|
payments: "none" | "polar";
|
|
@@ -255,6 +257,8 @@ declare const router: {
|
|
|
255
257
|
fumadocs: "fumadocs";
|
|
256
258
|
ultracite: "ultracite";
|
|
257
259
|
oxlint: "oxlint";
|
|
260
|
+
opentui: "opentui";
|
|
261
|
+
wxt: "wxt";
|
|
258
262
|
}>>>>;
|
|
259
263
|
webDeploy: z.ZodOptional<z.ZodEnum<{
|
|
260
264
|
none: "none";
|
|
@@ -321,7 +325,7 @@ declare function init(projectName?: string, options?: CreateInput): Promise<{
|
|
|
321
325
|
backend: "none" | "hono" | "express" | "fastify" | "elysia" | "convex" | "self";
|
|
322
326
|
runtime: "none" | "bun" | "node" | "workers";
|
|
323
327
|
frontend: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid")[];
|
|
324
|
-
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint")[];
|
|
328
|
+
addons: ("none" | "pwa" | "tauri" | "starlight" | "biome" | "husky" | "ruler" | "turborepo" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt")[];
|
|
325
329
|
examples: ("none" | "todo" | "ai")[];
|
|
326
330
|
auth: "none" | "better-auth" | "clerk";
|
|
327
331
|
payments: "none" | "polar";
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-
|
|
2
|
+
import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-XVvJUQ_h.mjs";
|
|
3
3
|
|
|
4
4
|
export { builder, createBtsCli, docs, init, router, sponsors };
|
|
@@ -117,7 +117,7 @@ const dependencyVersionMap = {
|
|
|
117
117
|
"@fastify/cors": "^11.0.1",
|
|
118
118
|
turbo: "^2.6.3",
|
|
119
119
|
ai: "^5.0.49",
|
|
120
|
-
"@ai-sdk/google": "^2.0.
|
|
120
|
+
"@ai-sdk/google": "^2.0.51",
|
|
121
121
|
"@ai-sdk/vue": "^2.0.49",
|
|
122
122
|
"@ai-sdk/svelte": "^3.0.39",
|
|
123
123
|
"@ai-sdk/react": "^2.0.39",
|
|
@@ -132,12 +132,13 @@ const dependencyVersionMap = {
|
|
|
132
132
|
"@trpc/server": "^11.7.2",
|
|
133
133
|
"@trpc/client": "^11.7.2",
|
|
134
134
|
next: "^16.0.10",
|
|
135
|
-
convex: "^1.
|
|
135
|
+
convex: "^1.31.2",
|
|
136
136
|
"@convex-dev/react-query": "^0.1.0",
|
|
137
|
+
"@convex-dev/agent": "^0.3.2",
|
|
137
138
|
"convex-svelte": "^0.0.12",
|
|
138
139
|
"convex-nuxt": "0.1.5",
|
|
139
140
|
"convex-vue": "^0.1.5",
|
|
140
|
-
"@convex-dev/better-auth": "^0.10.
|
|
141
|
+
"@convex-dev/better-auth": "^0.10.6",
|
|
141
142
|
"@tanstack/svelte-query": "^5.85.3",
|
|
142
143
|
"@tanstack/svelte-query-devtools": "^5.85.3",
|
|
143
144
|
"@tanstack/vue-query-devtools": "^5.90.2",
|
|
@@ -184,6 +185,8 @@ const ADDON_COMPATIBILITY = {
|
|
|
184
185
|
ruler: [],
|
|
185
186
|
oxlint: [],
|
|
186
187
|
fumadocs: [],
|
|
188
|
+
opentui: [],
|
|
189
|
+
wxt: [],
|
|
187
190
|
none: []
|
|
188
191
|
};
|
|
189
192
|
|
|
@@ -289,8 +292,12 @@ function isExampleTodoAllowed(backend, database) {
|
|
|
289
292
|
return !(backend !== "convex" && backend !== "none" && database === "none");
|
|
290
293
|
}
|
|
291
294
|
function isExampleAIAllowed(backend, frontends = []) {
|
|
292
|
-
if (backend === "convex") return false;
|
|
293
295
|
if (frontends.includes("solid")) return false;
|
|
296
|
+
if (backend === "convex") {
|
|
297
|
+
const includesNuxt = frontends.includes("nuxt");
|
|
298
|
+
const includesSvelte = frontends.includes("svelte");
|
|
299
|
+
if (includesNuxt || includesSvelte) return false;
|
|
300
|
+
}
|
|
294
301
|
return true;
|
|
295
302
|
}
|
|
296
303
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
@@ -336,8 +343,13 @@ function validateExamplesCompatibility(examples, backend, database, frontend) {
|
|
|
336
343
|
const examplesArr = examples ?? [];
|
|
337
344
|
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
|
|
338
345
|
if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
|
|
339
|
-
if (examplesArr.includes("ai") && backend === "convex") exitWithError("The 'ai' example is not yet available with Convex backend.");
|
|
340
346
|
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
|
|
347
|
+
if (examplesArr.includes("ai") && backend === "convex") {
|
|
348
|
+
const frontendArr = frontend ?? [];
|
|
349
|
+
const includesNuxt = frontendArr.includes("nuxt");
|
|
350
|
+
const includesSvelte = frontendArr.includes("svelte");
|
|
351
|
+
if (includesNuxt || includesSvelte) exitWithError("The 'ai' example with Convex backend only supports React-based frontends (Next.js, TanStack Router, TanStack Start, React Router). Svelte and Nuxt are not supported with Convex AI.");
|
|
352
|
+
}
|
|
341
353
|
}
|
|
342
354
|
|
|
343
355
|
//#endregion
|
|
@@ -386,6 +398,14 @@ function getAddonDisplay(addon) {
|
|
|
386
398
|
label = "Fumadocs";
|
|
387
399
|
hint = "Build excellent documentation site";
|
|
388
400
|
break;
|
|
401
|
+
case "opentui":
|
|
402
|
+
label = "OpenTUI";
|
|
403
|
+
hint = "Build terminal user interfaces";
|
|
404
|
+
break;
|
|
405
|
+
case "wxt":
|
|
406
|
+
label = "WXT";
|
|
407
|
+
hint = "Build browser extensions";
|
|
408
|
+
break;
|
|
389
409
|
default:
|
|
390
410
|
label = addon;
|
|
391
411
|
hint = `Add ${addon}`;
|
|
@@ -404,10 +424,12 @@ const ADDON_GROUPS = {
|
|
|
404
424
|
],
|
|
405
425
|
Other: [
|
|
406
426
|
"ruler",
|
|
407
|
-
"turborepo",
|
|
408
427
|
"pwa",
|
|
409
428
|
"tauri",
|
|
410
|
-
"husky"
|
|
429
|
+
"husky",
|
|
430
|
+
"opentui",
|
|
431
|
+
"wxt",
|
|
432
|
+
"turborepo"
|
|
411
433
|
]
|
|
412
434
|
};
|
|
413
435
|
async function getAddonsChoice(addons, frontends, auth) {
|
|
@@ -434,6 +456,12 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
434
456
|
}
|
|
435
457
|
Object.keys(groupedOptions).forEach((group$1) => {
|
|
436
458
|
if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
|
|
459
|
+
else {
|
|
460
|
+
const groupOrder = ADDON_GROUPS[group$1] || [];
|
|
461
|
+
groupedOptions[group$1].sort((a, b) => {
|
|
462
|
+
return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
437
465
|
});
|
|
438
466
|
const response = await groupMultiselect({
|
|
439
467
|
message: "Select addons",
|
|
@@ -466,6 +494,12 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
|
|
|
466
494
|
}
|
|
467
495
|
Object.keys(groupedOptions).forEach((group$1) => {
|
|
468
496
|
if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
|
|
497
|
+
else {
|
|
498
|
+
const groupOrder = ADDON_GROUPS[group$1] || [];
|
|
499
|
+
groupedOptions[group$1].sort((a, b) => {
|
|
500
|
+
return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
|
|
501
|
+
});
|
|
502
|
+
}
|
|
469
503
|
});
|
|
470
504
|
if (Object.keys(groupedOptions).length === 0) return [];
|
|
471
505
|
const response = await groupMultiselect({
|
|
@@ -1966,7 +2000,7 @@ function getPackageExecutionCommand(packageManager, commandWithArgs) {
|
|
|
1966
2000
|
|
|
1967
2001
|
//#endregion
|
|
1968
2002
|
//#region src/helpers/addons/fumadocs-setup.ts
|
|
1969
|
-
const TEMPLATES = {
|
|
2003
|
+
const TEMPLATES$2 = {
|
|
1970
2004
|
"next-mdx": {
|
|
1971
2005
|
label: "Next.js: Fumadocs MDX",
|
|
1972
2006
|
hint: "Recommended template with MDX support",
|
|
@@ -1999,7 +2033,7 @@ async function setupFumadocs(config) {
|
|
|
1999
2033
|
log.info("Setting up Fumadocs...");
|
|
2000
2034
|
const template = await select({
|
|
2001
2035
|
message: "Choose a template",
|
|
2002
|
-
options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
|
|
2036
|
+
options: Object.entries(TEMPLATES$2).map(([key, template$1]) => ({
|
|
2003
2037
|
value: key,
|
|
2004
2038
|
label: template$1.label,
|
|
2005
2039
|
hint: template$1.hint
|
|
@@ -2007,7 +2041,7 @@ async function setupFumadocs(config) {
|
|
|
2007
2041
|
initialValue: "next-mdx"
|
|
2008
2042
|
});
|
|
2009
2043
|
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2010
|
-
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --pm ${packageManager} --no-git`);
|
|
2044
|
+
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES$2[template].value} --src --pm ${packageManager} --no-git`);
|
|
2011
2045
|
const appsDir = path.join(projectDir, "apps");
|
|
2012
2046
|
await fs.ensureDir(appsDir);
|
|
2013
2047
|
const s = spinner();
|
|
@@ -2235,6 +2269,53 @@ async function setupTauri(config) {
|
|
|
2235
2269
|
}
|
|
2236
2270
|
}
|
|
2237
2271
|
|
|
2272
|
+
//#endregion
|
|
2273
|
+
//#region src/helpers/addons/tui-setup.ts
|
|
2274
|
+
const TEMPLATES$1 = {
|
|
2275
|
+
core: {
|
|
2276
|
+
label: "Core",
|
|
2277
|
+
hint: "Basic OpenTUI template"
|
|
2278
|
+
},
|
|
2279
|
+
react: {
|
|
2280
|
+
label: "React",
|
|
2281
|
+
hint: "React-based OpenTUI template"
|
|
2282
|
+
},
|
|
2283
|
+
solid: {
|
|
2284
|
+
label: "Solid",
|
|
2285
|
+
hint: "SolidJS-based OpenTUI template"
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
async function setupTui(config) {
|
|
2289
|
+
const { packageManager, projectDir } = config;
|
|
2290
|
+
try {
|
|
2291
|
+
log.info("Setting up OpenTUI...");
|
|
2292
|
+
const template = await select({
|
|
2293
|
+
message: "Choose a template",
|
|
2294
|
+
options: Object.entries(TEMPLATES$1).map(([key, template$1]) => ({
|
|
2295
|
+
value: key,
|
|
2296
|
+
label: template$1.label,
|
|
2297
|
+
hint: template$1.hint
|
|
2298
|
+
})),
|
|
2299
|
+
initialValue: "core"
|
|
2300
|
+
});
|
|
2301
|
+
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2302
|
+
const tuiInitCommand = getPackageExecutionCommand(packageManager, `create-tui@latest --template ${template} --no-git --no-install tui`);
|
|
2303
|
+
const appsDir = path.join(projectDir, "apps");
|
|
2304
|
+
await fs.ensureDir(appsDir);
|
|
2305
|
+
const s = spinner();
|
|
2306
|
+
s.start("Running OpenTUI create command...");
|
|
2307
|
+
await execa(tuiInitCommand, {
|
|
2308
|
+
cwd: appsDir,
|
|
2309
|
+
env: { CI: "true" },
|
|
2310
|
+
shell: true
|
|
2311
|
+
});
|
|
2312
|
+
s.stop("OpenTUI setup complete!");
|
|
2313
|
+
} catch (error) {
|
|
2314
|
+
log.error(pc.red("Failed to set up OpenTUI"));
|
|
2315
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2238
2319
|
//#endregion
|
|
2239
2320
|
//#region src/helpers/addons/ultracite-setup.ts
|
|
2240
2321
|
const EDITORS = {
|
|
@@ -2347,9 +2428,72 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2347
2428
|
}
|
|
2348
2429
|
}
|
|
2349
2430
|
|
|
2431
|
+
//#endregion
|
|
2432
|
+
//#region src/helpers/addons/wxt-setup.ts
|
|
2433
|
+
const TEMPLATES = {
|
|
2434
|
+
vanilla: {
|
|
2435
|
+
label: "Vanilla",
|
|
2436
|
+
hint: "Vanilla JavaScript template"
|
|
2437
|
+
},
|
|
2438
|
+
vue: {
|
|
2439
|
+
label: "Vue",
|
|
2440
|
+
hint: "Vue.js template"
|
|
2441
|
+
},
|
|
2442
|
+
react: {
|
|
2443
|
+
label: "React",
|
|
2444
|
+
hint: "React template"
|
|
2445
|
+
},
|
|
2446
|
+
solid: {
|
|
2447
|
+
label: "Solid",
|
|
2448
|
+
hint: "SolidJS template"
|
|
2449
|
+
},
|
|
2450
|
+
svelte: {
|
|
2451
|
+
label: "Svelte",
|
|
2452
|
+
hint: "Svelte template"
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
async function setupWxt(config) {
|
|
2456
|
+
const { packageManager, projectDir } = config;
|
|
2457
|
+
try {
|
|
2458
|
+
log.info("Setting up WXT...");
|
|
2459
|
+
const template = await select({
|
|
2460
|
+
message: "Choose a template",
|
|
2461
|
+
options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
|
|
2462
|
+
value: key,
|
|
2463
|
+
label: template$1.label,
|
|
2464
|
+
hint: template$1.hint
|
|
2465
|
+
})),
|
|
2466
|
+
initialValue: "react"
|
|
2467
|
+
});
|
|
2468
|
+
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2469
|
+
const wxtInitCommand = getPackageExecutionCommand(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
|
|
2470
|
+
const appsDir = path.join(projectDir, "apps");
|
|
2471
|
+
await fs.ensureDir(appsDir);
|
|
2472
|
+
const s = spinner();
|
|
2473
|
+
s.start("Running WXT init command...");
|
|
2474
|
+
await execa(wxtInitCommand, {
|
|
2475
|
+
cwd: appsDir,
|
|
2476
|
+
env: { CI: "true" },
|
|
2477
|
+
shell: true
|
|
2478
|
+
});
|
|
2479
|
+
const extensionDir = path.join(projectDir, "apps", "extension");
|
|
2480
|
+
const packageJsonPath = path.join(extensionDir, "package.json");
|
|
2481
|
+
if (await fs.pathExists(packageJsonPath)) {
|
|
2482
|
+
const packageJson = await fs.readJson(packageJsonPath);
|
|
2483
|
+
packageJson.name = "extension";
|
|
2484
|
+
if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port 5555`;
|
|
2485
|
+
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
2486
|
+
}
|
|
2487
|
+
s.stop("WXT setup complete!");
|
|
2488
|
+
} catch (error) {
|
|
2489
|
+
log.error(pc.red("Failed to set up WXT"));
|
|
2490
|
+
if (error instanceof Error) console.error(pc.red(error.message));
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2350
2494
|
//#endregion
|
|
2351
2495
|
//#region src/utils/ts-morph.ts
|
|
2352
|
-
const tsProject = new Project({
|
|
2496
|
+
const tsProject$1 = new Project({
|
|
2353
2497
|
useInMemoryFileSystem: false,
|
|
2354
2498
|
skipAddingFilesFromTsConfig: true,
|
|
2355
2499
|
manipulationSettings: {
|
|
@@ -2367,7 +2511,7 @@ function ensureArrayProperty(obj, name) {
|
|
|
2367
2511
|
//#endregion
|
|
2368
2512
|
//#region src/helpers/addons/vite-pwa-setup.ts
|
|
2369
2513
|
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2370
|
-
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2514
|
+
const sourceFile = tsProject$1.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2371
2515
|
if (!sourceFile) throw new Error("vite config not found");
|
|
2372
2516
|
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
|
|
2373
2517
|
namedImports: ["VitePWA"],
|
|
@@ -2392,7 +2536,7 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
|
2392
2536
|
pwaAssets: { disabled: false, config: true },
|
|
2393
2537
|
devOptions: { enabled: true },
|
|
2394
2538
|
})`);
|
|
2395
|
-
await tsProject.save();
|
|
2539
|
+
await tsProject$1.save();
|
|
2396
2540
|
}
|
|
2397
2541
|
|
|
2398
2542
|
//#endregion
|
|
@@ -2437,6 +2581,8 @@ ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
|
|
|
2437
2581
|
if (addons.includes("starlight")) await setupStarlight(config);
|
|
2438
2582
|
if (addons.includes("ruler")) await setupRuler(config);
|
|
2439
2583
|
if (addons.includes("fumadocs")) await setupFumadocs(config);
|
|
2584
|
+
if (addons.includes("opentui")) await setupTui(config);
|
|
2585
|
+
if (addons.includes("wxt")) await setupWxt(config);
|
|
2440
2586
|
}
|
|
2441
2587
|
function getWebAppDir(projectDir, frontends) {
|
|
2442
2588
|
if (frontends.some((f) => [
|
|
@@ -2623,7 +2769,7 @@ handlebars.registerHelper("includes", (array, value) => Array.isArray(array) &&
|
|
|
2623
2769
|
|
|
2624
2770
|
//#endregion
|
|
2625
2771
|
//#region src/helpers/deployment/alchemy/env-dts-setup.ts
|
|
2626
|
-
const tsProject
|
|
2772
|
+
const tsProject = new Project({
|
|
2627
2773
|
useInMemoryFileSystem: false,
|
|
2628
2774
|
skipAddingFilesFromTsConfig: true
|
|
2629
2775
|
});
|
|
@@ -2642,7 +2788,7 @@ function determineImportPath(envDtsPath, projectDir, config) {
|
|
|
2642
2788
|
async function setupEnvDtsImport(envDtsPath, projectDir, config) {
|
|
2643
2789
|
if (!await fs.pathExists(envDtsPath)) return;
|
|
2644
2790
|
const importPath = determineImportPath(envDtsPath, projectDir, config);
|
|
2645
|
-
const sourceFile = tsProject
|
|
2791
|
+
const sourceFile = tsProject.addSourceFileAtPath(envDtsPath);
|
|
2646
2792
|
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === importPath && imp.getNamedImports().some((named) => named.getName() === "server"))) sourceFile.insertImportDeclaration(0, {
|
|
2647
2793
|
moduleSpecifier: importPath,
|
|
2648
2794
|
namedImports: [{
|
|
@@ -3071,10 +3217,6 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3071
3217
|
if (hasReactWeb) {
|
|
3072
3218
|
const exampleWebSrc = path.join(exampleBaseDir, "web/react");
|
|
3073
3219
|
if (await fs.pathExists(exampleWebSrc)) {
|
|
3074
|
-
if (example === "ai") {
|
|
3075
|
-
const exampleWebBaseSrc = path.join(exampleWebSrc, "base");
|
|
3076
|
-
if (await fs.pathExists(exampleWebBaseSrc)) await processAndCopyFiles("**/*", exampleWebBaseSrc, webAppDir, context, false);
|
|
3077
|
-
}
|
|
3078
3220
|
const reactFramework = context.frontend.find((f) => [
|
|
3079
3221
|
"next",
|
|
3080
3222
|
"react-router",
|
|
@@ -3836,62 +3978,84 @@ async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
|
|
|
3836
3978
|
//#endregion
|
|
3837
3979
|
//#region src/helpers/addons/examples-setup.ts
|
|
3838
3980
|
async function setupExamples(config) {
|
|
3839
|
-
const { examples,
|
|
3840
|
-
if (
|
|
3981
|
+
const { examples, backend } = config;
|
|
3982
|
+
if (!examples || examples.length === 0 || examples[0] === "none") return;
|
|
3983
|
+
if (examples.includes("todo") && backend !== "convex" && backend !== "none") await setupTodoDependencies(config);
|
|
3984
|
+
if (examples.includes("ai")) await setupAIDependencies(config);
|
|
3985
|
+
}
|
|
3986
|
+
async function setupTodoDependencies(config) {
|
|
3987
|
+
const { projectDir, orm, database, backend } = config;
|
|
3841
3988
|
const apiDir = path.join(projectDir, "packages/api");
|
|
3842
|
-
if (await fs.pathExists(apiDir)
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
|
|
3848
|
-
projectDir: apiDir
|
|
3849
|
-
});
|
|
3850
|
-
} else if (orm === "prisma") await addPackageDependency({
|
|
3851
|
-
dependencies: ["@prisma/client"],
|
|
3852
|
-
projectDir: apiDir
|
|
3853
|
-
});
|
|
3854
|
-
else if (orm === "mongoose") await addPackageDependency({
|
|
3855
|
-
dependencies: ["mongoose"],
|
|
3989
|
+
if (!await fs.pathExists(apiDir) || backend === "none") return;
|
|
3990
|
+
if (orm === "drizzle") {
|
|
3991
|
+
const dependencies = ["drizzle-orm"];
|
|
3992
|
+
if (database === "postgres") dependencies.push("@types/pg");
|
|
3993
|
+
await addPackageDependency({
|
|
3994
|
+
dependencies,
|
|
3856
3995
|
projectDir: apiDir
|
|
3857
3996
|
});
|
|
3858
|
-
}
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3997
|
+
} else if (orm === "prisma") await addPackageDependency({
|
|
3998
|
+
dependencies: ["@prisma/client"],
|
|
3999
|
+
projectDir: apiDir
|
|
4000
|
+
});
|
|
4001
|
+
else if (orm === "mongoose") await addPackageDependency({
|
|
4002
|
+
dependencies: ["mongoose"],
|
|
4003
|
+
projectDir: apiDir
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
async function setupAIDependencies(config) {
|
|
4007
|
+
const { frontend, backend, projectDir } = config;
|
|
4008
|
+
const webClientDir = path.join(projectDir, "apps/web");
|
|
4009
|
+
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
4010
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
4011
|
+
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4012
|
+
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
4013
|
+
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
4014
|
+
const serverDirExists = await fs.pathExists(serverDir);
|
|
4015
|
+
const convexBackendDirExists = await fs.pathExists(convexBackendDir);
|
|
4016
|
+
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
4017
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
4018
|
+
const hasSvelte = frontend.includes("svelte");
|
|
4019
|
+
const hasReactNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
4020
|
+
if (backend === "convex" && convexBackendDirExists) await addPackageDependency({
|
|
4021
|
+
dependencies: [
|
|
4022
|
+
"@convex-dev/agent",
|
|
4023
|
+
"ai",
|
|
4024
|
+
"@ai-sdk/google"
|
|
4025
|
+
],
|
|
4026
|
+
projectDir: convexBackendDir
|
|
4027
|
+
});
|
|
4028
|
+
else if (backend === "self" && webClientDirExists) await addPackageDependency({
|
|
4029
|
+
dependencies: ["ai", "@ai-sdk/google"],
|
|
4030
|
+
projectDir: webClientDir
|
|
4031
|
+
});
|
|
4032
|
+
else if (serverDirExists && backend !== "none") await addPackageDependency({
|
|
4033
|
+
dependencies: ["ai", "@ai-sdk/google"],
|
|
4034
|
+
projectDir: serverDir
|
|
4035
|
+
});
|
|
4036
|
+
if (webClientDirExists) {
|
|
4037
|
+
const dependencies = [];
|
|
4038
|
+
if (backend === "convex") {
|
|
4039
|
+
if (hasReactWeb) dependencies.push("@convex-dev/agent", "streamdown");
|
|
4040
|
+
} else {
|
|
4041
|
+
dependencies.push("ai");
|
|
3873
4042
|
if (hasNuxt) dependencies.push("@ai-sdk/vue");
|
|
3874
4043
|
else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
|
|
3875
4044
|
else if (hasReactWeb) dependencies.push("@ai-sdk/react", "streamdown");
|
|
3876
|
-
if (hasNext) dependencies.push("shiki");
|
|
3877
|
-
await addPackageDependency({
|
|
3878
|
-
dependencies,
|
|
3879
|
-
projectDir: webClientDir
|
|
3880
|
-
});
|
|
3881
4045
|
}
|
|
3882
|
-
if (
|
|
3883
|
-
dependencies
|
|
3884
|
-
projectDir: nativeClientDir
|
|
3885
|
-
});
|
|
3886
|
-
if (apiDirExists && backend !== "none") await addPackageDependency({
|
|
3887
|
-
dependencies: ["ai", "@ai-sdk/google"],
|
|
3888
|
-
projectDir: apiDir$1
|
|
3889
|
-
});
|
|
3890
|
-
if (backend === "self" && webClientDirExists) await addPackageDependency({
|
|
3891
|
-
dependencies: ["ai", "@ai-sdk/google"],
|
|
4046
|
+
if (dependencies.length > 0) await addPackageDependency({
|
|
4047
|
+
dependencies,
|
|
3892
4048
|
projectDir: webClientDir
|
|
3893
4049
|
});
|
|
3894
4050
|
}
|
|
4051
|
+
if (nativeClientDirExists && hasReactNative) if (backend === "convex") await addPackageDependency({
|
|
4052
|
+
dependencies: ["@convex-dev/agent"],
|
|
4053
|
+
projectDir: nativeClientDir
|
|
4054
|
+
});
|
|
4055
|
+
else await addPackageDependency({
|
|
4056
|
+
dependencies: ["ai", "@ai-sdk/react"],
|
|
4057
|
+
projectDir: nativeClientDir
|
|
4058
|
+
});
|
|
3895
4059
|
}
|
|
3896
4060
|
|
|
3897
4061
|
//#endregion
|
|
@@ -4089,7 +4253,7 @@ async function setupApi(config) {
|
|
|
4089
4253
|
//#endregion
|
|
4090
4254
|
//#region src/helpers/core/backend-setup.ts
|
|
4091
4255
|
async function setupBackendDependencies(config) {
|
|
4092
|
-
const { backend, runtime, api, auth,
|
|
4256
|
+
const { backend, runtime, api, auth, projectDir } = config;
|
|
4093
4257
|
if (backend === "convex") {
|
|
4094
4258
|
await addPackageDependency({
|
|
4095
4259
|
dependencies: ["convex"],
|
|
@@ -4117,7 +4281,6 @@ async function setupBackendDependencies(config) {
|
|
|
4117
4281
|
else if (framework === "elysia") dependencies.push("@elysiajs/trpc");
|
|
4118
4282
|
} else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
|
|
4119
4283
|
if (auth === "better-auth") dependencies.push("better-auth");
|
|
4120
|
-
if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
|
|
4121
4284
|
if (runtime === "node") devDependencies.push("tsx", "@types/node");
|
|
4122
4285
|
else if (runtime === "bun") devDependencies.push("@types/bun");
|
|
4123
4286
|
if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
|
|
@@ -4131,7 +4294,7 @@ async function setupBackendDependencies(config) {
|
|
|
4131
4294
|
//#region src/utils/better-auth-plugin-setup.ts
|
|
4132
4295
|
async function setupBetterAuthPlugins(projectDir, config) {
|
|
4133
4296
|
const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
|
|
4134
|
-
const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
|
|
4297
|
+
const authIndexFile = tsProject$1.addSourceFileAtPath(authIndexPath);
|
|
4135
4298
|
if (!authIndexFile) return;
|
|
4136
4299
|
const pluginsToAdd = [];
|
|
4137
4300
|
const importsToAdd = [];
|
|
@@ -4479,21 +4642,32 @@ async function setupEnvironmentVariables(config) {
|
|
|
4479
4642
|
}
|
|
4480
4643
|
}
|
|
4481
4644
|
if (backend === "convex") {
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4645
|
+
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4646
|
+
if (await fs.pathExists(convexBackendDir)) {
|
|
4647
|
+
const envLocalPath = path.join(convexBackendDir, ".env.local");
|
|
4648
|
+
let commentBlocks = "";
|
|
4649
|
+
if (examples?.includes("ai")) commentBlocks += `# Set Google AI API key for AI agent
|
|
4650
|
+
# npx convex env set GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
|
|
4651
|
+
|
|
4652
|
+
`;
|
|
4653
|
+
if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
|
|
4654
|
+
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
4655
|
+
${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`;
|
|
4656
|
+
if (commentBlocks) {
|
|
4657
|
+
let existingContent = "";
|
|
4658
|
+
if (await fs.pathExists(envLocalPath)) existingContent = await fs.readFile(envLocalPath, "utf8");
|
|
4659
|
+
await fs.writeFile(envLocalPath, commentBlocks + existingContent);
|
|
4660
|
+
}
|
|
4661
|
+
const convexBackendVars = [];
|
|
4662
|
+
if (examples?.includes("ai")) convexBackendVars.push({
|
|
4663
|
+
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4664
|
+
value: "",
|
|
4665
|
+
condition: true,
|
|
4666
|
+
comment: "Google AI API key for AI agent"
|
|
4667
|
+
});
|
|
4668
|
+
if (auth === "better-auth") {
|
|
4486
4669
|
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
4487
4670
|
const hasWeb = hasWebFrontend$1;
|
|
4488
|
-
if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
|
|
4489
|
-
let siteUrlComments = "";
|
|
4490
|
-
if (hasWeb) siteUrlComments += "# npx convex env set SITE_URL http://localhost:3001\n";
|
|
4491
|
-
const convexCommands = `# Set Convex environment variables
|
|
4492
|
-
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
4493
|
-
${siteUrlComments}`;
|
|
4494
|
-
await fs.appendFile(envLocalPath, convexCommands);
|
|
4495
|
-
}
|
|
4496
|
-
const convexBackendVars = [];
|
|
4497
4671
|
if (hasNative) convexBackendVars.push({
|
|
4498
4672
|
key: "EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
4499
4673
|
value: "",
|
|
@@ -4511,8 +4685,8 @@ ${siteUrlComments}`;
|
|
|
4511
4685
|
condition: true,
|
|
4512
4686
|
comment: "Web app URL for authentication"
|
|
4513
4687
|
});
|
|
4514
|
-
await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
4515
4688
|
}
|
|
4689
|
+
if (convexBackendVars.length > 0) await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
4516
4690
|
}
|
|
4517
4691
|
return;
|
|
4518
4692
|
}
|
|
@@ -6597,8 +6771,8 @@ async function createProject(options, cliInput) {
|
|
|
6597
6771
|
if (!isConvex) {
|
|
6598
6772
|
if (needsServerSetup) await setupRuntime(options);
|
|
6599
6773
|
await setupDatabase(options, cliInput);
|
|
6600
|
-
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
|
|
6601
6774
|
}
|
|
6775
|
+
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
|
|
6602
6776
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
6603
6777
|
if (options.auth && options.auth !== "none") await setupAuth(options);
|
|
6604
6778
|
if (options.payments && options.payments !== "none") await setupPayments(options);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.11.0",
|
|
4
4
|
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"better-auth",
|
|
@@ -67,12 +67,12 @@
|
|
|
67
67
|
"prepublishOnly": "npm run build"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@better-t-stack/types": "^3.
|
|
70
|
+
"@better-t-stack/types": "^3.11.0",
|
|
71
71
|
"@clack/prompts": "^1.0.0-alpha.8",
|
|
72
|
-
"@orpc/server": "^1.
|
|
72
|
+
"@orpc/server": "^1.13.0",
|
|
73
73
|
"consola": "^3.4.2",
|
|
74
74
|
"execa": "^9.6.1",
|
|
75
|
-
"fs-extra": "^11.3.
|
|
75
|
+
"fs-extra": "^11.3.3",
|
|
76
76
|
"gradient-string": "^3.0.0",
|
|
77
77
|
"handlebars": "^4.7.8",
|
|
78
78
|
"jsonc-parser": "^3.3.1",
|
|
@@ -82,14 +82,14 @@
|
|
|
82
82
|
"trpc-cli": "^0.12.1",
|
|
83
83
|
"ts-morph": "^27.0.2",
|
|
84
84
|
"yaml": "^2.8.2",
|
|
85
|
-
"zod": "^4.1
|
|
85
|
+
"zod": "^4.2.1"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
|
-
"@types/bun": "^1.
|
|
88
|
+
"@types/bun": "^1.3.5",
|
|
89
89
|
"@types/fs-extra": "^11.0.4",
|
|
90
|
-
"@types/node": "^
|
|
90
|
+
"@types/node": "^25.0.3",
|
|
91
91
|
"publint": "^0.3.16",
|
|
92
|
-
"tsdown": "^0.
|
|
92
|
+
"tsdown": "^0.18.2",
|
|
93
93
|
"typescript": "^5.9.3"
|
|
94
94
|
}
|
|
95
95
|
}
|