create-better-t-stack 3.31.0 → 3.32.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/README.md +16 -16
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +1 -1
- package/dist/{src-ByCnjYDk.mjs → src-BZV4gWGl.mjs} +128 -18
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -29,21 +29,21 @@ Follow the prompts to configure your project or use the `--yes` flag for default
|
|
|
29
29
|
|
|
30
30
|
## Features
|
|
31
31
|
|
|
32
|
-
| Category | Options
|
|
33
|
-
| ------------------------ |
|
|
34
|
-
| **TypeScript** | End-to-end type safety across all parts of your application
|
|
35
|
-
| **Frontend** | • React with TanStack Router<br>• React with React Router<br>• React with TanStack Start (SSR)<br>• Next.js<br>• SvelteKit<br>• Nuxt (Vue)<br>• SolidJS<br>• Astro<br>• React Native bare Expo<br>• React Native with NativeWind (via Expo)<br>• React Native with Unistyles (via Expo)<br>• None
|
|
36
|
-
| **Backend** | • Hono<br>• Express<br>• Elysia<br>• Fastify<br>• Self (fullstack inside the web app)<br>• Convex<br>• None
|
|
37
|
-
| **API Layer** | • tRPC (type-safe APIs)<br>• oRPC (OpenAPI-compatible type-safe APIs)<br>• None
|
|
38
|
-
| **Runtime** | • Bun<br>• Node.js<br>• Cloudflare Workers<br>• None
|
|
39
|
-
| **Database** | • SQLite<br>• PostgreSQL<br>• MySQL<br>• MongoDB<br>• None
|
|
40
|
-
| **ORM** | • Drizzle (TypeScript-first)<br>• Prisma (feature-rich)<br>• Mongoose (for MongoDB)<br>• None
|
|
41
|
-
| **Database Setup** | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres<br>• MongoDB Atlas<br>• None (manual setup)
|
|
42
|
-
| **Authentication** | • Better Auth<br>• Clerk
|
|
43
|
-
| **Styling** | Tailwind CSS with a shared shadcn/ui package for React web apps
|
|
44
|
-
| **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Electrobun (lightweight desktop shell)<br>• Starlight and Fumadocs (documentation sites)<br>• Biome, Oxlint, Ultracite (linting and formatting)<br>• Lefthook, Husky (Git hooks)<br>• evlog (request logging for server/fullstack backends)<br>• MCP, Skills (agent tooling)<br>• OpenTUI, WXT (platform extensions)<br>• Turborepo or
|
|
45
|
-
| **Examples** | • Todo app<br>• AI Chat interface (using Vercel AI SDK)
|
|
46
|
-
| **Developer Experience** | • Automatic Git initialization<br>• Package manager choice (npm, pnpm, bun)<br>• Automatic dependency installation
|
|
32
|
+
| Category | Options |
|
|
33
|
+
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| **TypeScript** | End-to-end type safety across all parts of your application |
|
|
35
|
+
| **Frontend** | • React with TanStack Router<br>• React with React Router<br>• React with TanStack Start (SSR)<br>• Next.js<br>• SvelteKit<br>• Nuxt (Vue)<br>• SolidJS<br>• Astro<br>• React Native bare Expo<br>• React Native with NativeWind (via Expo)<br>• React Native with Unistyles (via Expo)<br>• None |
|
|
36
|
+
| **Backend** | • Hono<br>• Express<br>• Elysia<br>• Fastify<br>• Self (fullstack inside the web app)<br>• Convex<br>• None |
|
|
37
|
+
| **API Layer** | • tRPC (type-safe APIs)<br>• oRPC (OpenAPI-compatible type-safe APIs)<br>• None |
|
|
38
|
+
| **Runtime** | • Bun<br>• Node.js<br>• Cloudflare Workers<br>• None |
|
|
39
|
+
| **Database** | • SQLite<br>• PostgreSQL<br>• MySQL<br>• MongoDB<br>• None |
|
|
40
|
+
| **ORM** | • Drizzle (TypeScript-first)<br>• Prisma (feature-rich)<br>• Mongoose (for MongoDB)<br>• None |
|
|
41
|
+
| **Database Setup** | • Turso (SQLite)<br>• Cloudflare D1 (SQLite)<br>• Neon (PostgreSQL)<br>• Supabase (PostgreSQL)<br>• Prisma Postgres<br>• MongoDB Atlas<br>• None (manual setup) |
|
|
42
|
+
| **Authentication** | • Better Auth<br>• Clerk |
|
|
43
|
+
| **Styling** | Tailwind CSS with a shared shadcn/ui package for React web apps |
|
|
44
|
+
| **Addons** | • PWA support<br>• Tauri (desktop applications)<br>• Electrobun (lightweight desktop shell)<br>• Starlight and Fumadocs (documentation sites)<br>• Biome, Oxlint, Ultracite, or Vite+ (linting and formatting)<br>• Lefthook, Husky (Git hooks)<br>• evlog (request logging for server/fullstack backends)<br>• MCP, Skills (agent tooling)<br>• OpenTUI, WXT (platform extensions)<br>• Turborepo, Nx, or Vite+ (monorepo orchestration) |
|
|
45
|
+
| **Examples** | • Todo app<br>• AI Chat interface (using Vercel AI SDK) |
|
|
46
|
+
| **Developer Experience** | • Automatic Git initialization<br>• Package manager choice (npm, pnpm, bun)<br>• Automatic dependency installation |
|
|
47
47
|
|
|
48
48
|
## Usage
|
|
49
49
|
|
|
@@ -60,7 +60,7 @@ Options:
|
|
|
60
60
|
--auth <provider> Authentication (better-auth, clerk, none)
|
|
61
61
|
--payments <provider> Payments provider (polar, none)
|
|
62
62
|
--frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, astro, native-bare, native-uniwind, native-unistyles, none)
|
|
63
|
-
--addons <types...> Additional addons (pwa, tauri, electrobun, starlight, biome, lefthook, husky, mcp, turborepo, nx, fumadocs, ultracite, oxlint, opentui, wxt, skills, evlog, none)
|
|
63
|
+
--addons <types...> Additional addons (pwa, tauri, electrobun, starlight, biome, lefthook, husky, mcp, turborepo, nx, vite-plus, fumadocs, ultracite, oxlint, opentui, wxt, skills, evlog, none)
|
|
64
64
|
--examples <types...> Examples to include (todo, ai, none)
|
|
65
65
|
--git Initialize git repository
|
|
66
66
|
--no-git Skip git initialization
|
package/dist/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { _ as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-
|
|
2
|
+
import { _ as types_exports, i as SchemaNameSchema, l as create, m as getSchemaResult, s as add, u as createBtsCli, v as getLatestCLIVersion } from "./src-BZV4gWGl.mjs";
|
|
3
3
|
import z from "zod";
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/dist/index.d.mts
CHANGED
|
@@ -175,7 +175,7 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
|
|
|
175
175
|
auth?: "none" | "better-auth" | "clerk" | undefined;
|
|
176
176
|
payments?: "none" | "polar" | undefined;
|
|
177
177
|
frontend?: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "astro")[] | undefined;
|
|
178
|
-
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
178
|
+
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "vite-plus" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
179
179
|
examples?: ("none" | "todo" | "ai")[] | undefined;
|
|
180
180
|
git?: boolean | undefined;
|
|
181
181
|
packageManager?: "bun" | "npm" | "pnpm" | undefined;
|
|
@@ -228,7 +228,7 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
|
|
|
228
228
|
devPort?: number | undefined;
|
|
229
229
|
search?: "orama" | "orama-cloud" | undefined;
|
|
230
230
|
ogImage?: "next-og" | "takumi" | undefined;
|
|
231
|
-
aiChat?: "openrouter" | "inkeep" | undefined;
|
|
231
|
+
aiChat?: "openrouter" | "llmgateway" | "inkeep" | undefined;
|
|
232
232
|
} | undefined;
|
|
233
233
|
opentui?: {
|
|
234
234
|
template: "solid" | "react" | "core";
|
|
@@ -274,7 +274,7 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
|
|
|
274
274
|
auth?: "none" | "better-auth" | "clerk" | undefined;
|
|
275
275
|
payments?: "none" | "polar" | undefined;
|
|
276
276
|
frontend?: ("none" | "tanstack-router" | "react-router" | "tanstack-start" | "next" | "nuxt" | "native-bare" | "native-uniwind" | "native-unistyles" | "svelte" | "solid" | "astro")[] | undefined;
|
|
277
|
-
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
277
|
+
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "vite-plus" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
278
278
|
examples?: ("none" | "todo" | "ai")[] | undefined;
|
|
279
279
|
git?: boolean | undefined;
|
|
280
280
|
packageManager?: "bun" | "npm" | "pnpm" | undefined;
|
|
@@ -317,7 +317,7 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
|
|
|
317
317
|
}>;
|
|
318
318
|
add: _$_trpc_server0.TRPCMutationProcedure<{
|
|
319
319
|
input: {
|
|
320
|
-
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
320
|
+
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "vite-plus" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
321
321
|
install?: boolean | undefined;
|
|
322
322
|
packageManager?: "bun" | "npm" | "pnpm" | undefined;
|
|
323
323
|
projectDir?: string | undefined;
|
|
@@ -327,7 +327,7 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
|
|
|
327
327
|
}>;
|
|
328
328
|
addJson: _$_trpc_server0.TRPCMutationProcedure<{
|
|
329
329
|
input: {
|
|
330
|
-
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
330
|
+
addons?: ("none" | "pwa" | "tauri" | "electrobun" | "starlight" | "biome" | "lefthook" | "husky" | "mcp" | "turborepo" | "nx" | "vite-plus" | "fumadocs" | "ultracite" | "oxlint" | "opentui" | "wxt" | "skills" | "evlog")[] | undefined;
|
|
331
331
|
addonOptions?: {
|
|
332
332
|
wxt?: {
|
|
333
333
|
template: "svelte" | "solid" | "vanilla" | "vue" | "react";
|
|
@@ -338,7 +338,7 @@ declare const router: _$_trpc_server0.TRPCBuiltRouter<{
|
|
|
338
338
|
devPort?: number | undefined;
|
|
339
339
|
search?: "orama" | "orama-cloud" | undefined;
|
|
340
340
|
ogImage?: "next-og" | "takumi" | undefined;
|
|
341
|
-
aiChat?: "openrouter" | "inkeep" | undefined;
|
|
341
|
+
aiChat?: "openrouter" | "llmgateway" | "inkeep" | undefined;
|
|
342
342
|
} | undefined;
|
|
343
343
|
opentui?: {
|
|
344
344
|
template: "solid" | "react" | "core";
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-
|
|
2
|
+
import { C as ProjectCreationError, S as DirectoryConflictError, T as ValidationError, a as TEMPLATE_COUNT, b as CompatibilityError, c as builder, d as createVirtual, f as docs, g as sponsors, h as router, i as SchemaNameSchema, l as create, m as getSchemaResult, n as GeneratorError, o as VirtualFileSystem, p as generate, r as Result, s as add, t as EMBEDDED_TEMPLATES, u as createBtsCli, w as UserCancelledError, x as DatabaseSetupError, y as CLIError } from "./src-BZV4gWGl.mjs";
|
|
3
3
|
export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, SchemaNameSchema, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, getSchemaResult, router, sponsors };
|
|
@@ -12,7 +12,7 @@ import envPaths from "env-paths";
|
|
|
12
12
|
import fs from "fs-extra";
|
|
13
13
|
import { fileURLToPath } from "node:url";
|
|
14
14
|
import { desktopWebFrontends as desktopWebFrontends$3 } from "@better-t-stack/types";
|
|
15
|
-
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps } from "@better-t-stack/template-generator";
|
|
15
|
+
import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps, processNxConfig, processPackageConfigs, processTemplateString, processTurboConfig, processVitePlusConfig } from "@better-t-stack/template-generator";
|
|
16
16
|
import { consola, createConsola } from "consola";
|
|
17
17
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
18
|
import gradient from "gradient-string";
|
|
@@ -78,6 +78,7 @@ const ADDON_COMPATIBILITY = {
|
|
|
78
78
|
lefthook: [],
|
|
79
79
|
turborepo: [],
|
|
80
80
|
nx: [],
|
|
81
|
+
"vite-plus": [],
|
|
81
82
|
starlight: [],
|
|
82
83
|
ultracite: [],
|
|
83
84
|
mcp: [],
|
|
@@ -729,6 +730,11 @@ const WEB_FRAMEWORKS = [
|
|
|
729
730
|
];
|
|
730
731
|
//#endregion
|
|
731
732
|
//#region src/utils/compatibility-rules.ts
|
|
733
|
+
const TASK_RUNNER_ADDONS$1 = [
|
|
734
|
+
"turborepo",
|
|
735
|
+
"nx",
|
|
736
|
+
"vite-plus"
|
|
737
|
+
];
|
|
732
738
|
const CONVEX_BETTER_AUTH_INCOMPATIBLE_FRONTENDS = [
|
|
733
739
|
"nuxt",
|
|
734
740
|
"svelte",
|
|
@@ -887,12 +893,13 @@ function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth, bac
|
|
|
887
893
|
return allAddons.filter((addon) => {
|
|
888
894
|
if (existingAddons.includes(addon)) return false;
|
|
889
895
|
if (addon === "none") return false;
|
|
896
|
+
if (TASK_RUNNER_ADDONS$1.includes(addon) && existingAddons.some((existingAddon) => TASK_RUNNER_ADDONS$1.includes(existingAddon))) return false;
|
|
890
897
|
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);
|
|
891
898
|
return isCompatible;
|
|
892
899
|
});
|
|
893
900
|
}
|
|
894
901
|
function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime) {
|
|
895
|
-
if (addons.
|
|
902
|
+
if (addons.filter((addon) => TASK_RUNNER_ADDONS$1.includes(addon)).length > 1) return validationErr$1("Cannot combine 'turborepo', 'nx', and 'vite-plus' addons. Choose one task runner.");
|
|
896
903
|
for (const addon of addons) {
|
|
897
904
|
if (addon === "none") continue;
|
|
898
905
|
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth, backend, runtime);
|
|
@@ -1178,6 +1185,10 @@ function getAddonDisplay(addon) {
|
|
|
1178
1185
|
label = "Nx";
|
|
1179
1186
|
hint = "Smart monorepo orchestration and task graph";
|
|
1180
1187
|
break;
|
|
1188
|
+
case "vite-plus":
|
|
1189
|
+
label = "Vite+";
|
|
1190
|
+
hint = "Unified Vite toolchain and workspace task runner";
|
|
1191
|
+
break;
|
|
1181
1192
|
case "pwa":
|
|
1182
1193
|
label = "PWA";
|
|
1183
1194
|
hint = "Make your app installable and work offline";
|
|
@@ -1248,7 +1259,11 @@ function getAddonDisplay(addon) {
|
|
|
1248
1259
|
};
|
|
1249
1260
|
}
|
|
1250
1261
|
const ADDON_GROUPS = {
|
|
1251
|
-
"Monorepo & Tasks": [
|
|
1262
|
+
"Monorepo & Tasks": [
|
|
1263
|
+
"turborepo",
|
|
1264
|
+
"nx",
|
|
1265
|
+
"vite-plus"
|
|
1266
|
+
],
|
|
1252
1267
|
"Code Quality": [
|
|
1253
1268
|
"biome",
|
|
1254
1269
|
"oxlint",
|
|
@@ -1289,7 +1304,11 @@ function sortAndPruneGroupedOptions(groupedOptions) {
|
|
|
1289
1304
|
});
|
|
1290
1305
|
}
|
|
1291
1306
|
function validateAddonSelection(selected) {
|
|
1292
|
-
if (selected?.
|
|
1307
|
+
if ((selected?.filter((addon) => [
|
|
1308
|
+
"turborepo",
|
|
1309
|
+
"nx",
|
|
1310
|
+
"vite-plus"
|
|
1311
|
+
].includes(addon)) ?? []).length > 1) return "Choose Turborepo, Nx, or Vite+ as your task runner, not more than one.";
|
|
1293
1312
|
}
|
|
1294
1313
|
async function getAddonsChoice(addons, frontends, auth, backend, runtime) {
|
|
1295
1314
|
if (addons !== void 0) return addons;
|
|
@@ -2142,6 +2161,14 @@ const DEFAULT_DEV_PORT$1 = 4e3;
|
|
|
2142
2161
|
function aiChatDisabledForTemplate(template) {
|
|
2143
2162
|
return template === "next-mdx-static" || template.endsWith("-spa");
|
|
2144
2163
|
}
|
|
2164
|
+
function getFumadocsLinter(addons) {
|
|
2165
|
+
if (addons.includes("oxlint")) return "oxlint";
|
|
2166
|
+
if (addons.includes("biome") || addons.includes("ultracite")) return "biome";
|
|
2167
|
+
if (addons.includes("vite-plus")) return "oxlint";
|
|
2168
|
+
}
|
|
2169
|
+
function getFumadocsAddonContext(currentAddons, persistedAddons) {
|
|
2170
|
+
return Array.from(new Set([...persistedAddons ?? [], ...currentAddons]));
|
|
2171
|
+
}
|
|
2145
2172
|
async function setupFumadocs(config) {
|
|
2146
2173
|
if (shouldSkipExternalCommands()) return Result.ok(void 0);
|
|
2147
2174
|
const { packageManager, projectDir } = config;
|
|
@@ -2215,6 +2242,10 @@ async function setupFumadocs(config) {
|
|
|
2215
2242
|
label: "AI SDK",
|
|
2216
2243
|
hint: "default to OpenRouter"
|
|
2217
2244
|
},
|
|
2245
|
+
{
|
|
2246
|
+
value: "llmgateway",
|
|
2247
|
+
label: "LLM Gateway"
|
|
2248
|
+
},
|
|
2218
2249
|
{
|
|
2219
2250
|
value: "inkeep",
|
|
2220
2251
|
label: "Inkeep AI",
|
|
@@ -2251,7 +2282,9 @@ async function setupFumadocs(config) {
|
|
|
2251
2282
|
"--no-git"
|
|
2252
2283
|
];
|
|
2253
2284
|
if (isNextTemplate) options.push("--src");
|
|
2254
|
-
|
|
2285
|
+
const persistedConfig = await readBtsConfig(projectDir);
|
|
2286
|
+
const linter = getFumadocsLinter(getFumadocsAddonContext(config.addons, persistedConfig?.addons));
|
|
2287
|
+
if (linter) options.push(`--linter ${linter}`);
|
|
2255
2288
|
if (search) options.push(`--search ${search}`);
|
|
2256
2289
|
if (ogImage) options.push(`--og-image ${ogImage}`);
|
|
2257
2290
|
if (aiChat) options.push(`--ai-chat ${aiChat}`);
|
|
@@ -3630,6 +3663,7 @@ async function setupAddons(config) {
|
|
|
3630
3663
|
const hasHusky = addons.includes("husky");
|
|
3631
3664
|
const hasLefthook = addons.includes("lefthook");
|
|
3632
3665
|
const hasOxlint = addons.includes("oxlint");
|
|
3666
|
+
const hasVitePlus = addons.includes("vite-plus");
|
|
3633
3667
|
if (hasUltracite) {
|
|
3634
3668
|
const gitHooks = [];
|
|
3635
3669
|
if (hasHusky) gitHooks.push("husky");
|
|
@@ -3642,6 +3676,7 @@ async function setupAddons(config) {
|
|
|
3642
3676
|
let linter;
|
|
3643
3677
|
if (hasOxlint) linter = "oxlint";
|
|
3644
3678
|
else if (hasBiome) linter = "biome";
|
|
3679
|
+
else if (hasVitePlus) linter = "vite-plus";
|
|
3645
3680
|
if (hasHusky) await runAddonStep("husky", () => setupHusky(projectDir, linter));
|
|
3646
3681
|
if (hasLefthook) await runAddonStep("lefthook", () => setupLefthook(projectDir));
|
|
3647
3682
|
}
|
|
@@ -3683,6 +3718,7 @@ async function setupHusky(projectDir, linter) {
|
|
|
3683
3718
|
};
|
|
3684
3719
|
if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
|
|
3685
3720
|
else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
|
|
3721
|
+
else if (linter === "vite-plus") packageJson["lint-staged"] = { "*.{js,ts,jsx,tsx,vue,svelte,json,jsonc,css,md}": ["vp check --fix"] };
|
|
3686
3722
|
else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
|
|
3687
3723
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3688
3724
|
}
|
|
@@ -3750,6 +3786,35 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
3750
3786
|
}
|
|
3751
3787
|
//#endregion
|
|
3752
3788
|
//#region src/helpers/core/add-handler.ts
|
|
3789
|
+
const ADD_PACKAGE_JSON_PATHS = [
|
|
3790
|
+
"package.json",
|
|
3791
|
+
"apps/server/package.json",
|
|
3792
|
+
"apps/web/package.json",
|
|
3793
|
+
"apps/native/package.json",
|
|
3794
|
+
"apps/desktop/package.json",
|
|
3795
|
+
"apps/fumadocs/package.json",
|
|
3796
|
+
"apps/docs/package.json",
|
|
3797
|
+
"packages/api/package.json",
|
|
3798
|
+
"packages/db/package.json",
|
|
3799
|
+
"packages/auth/package.json",
|
|
3800
|
+
"packages/backend/package.json",
|
|
3801
|
+
"packages/config/package.json",
|
|
3802
|
+
"packages/env/package.json",
|
|
3803
|
+
"packages/infra/package.json",
|
|
3804
|
+
"packages/ui/package.json"
|
|
3805
|
+
];
|
|
3806
|
+
const ADD_TEXT_FILE_PATHS = ["apps/web/vite.config.ts", "lefthook.yml"];
|
|
3807
|
+
const HOOK_ADDONS = ["husky", "lefthook"];
|
|
3808
|
+
const HOOK_LINTER_ADDONS = [
|
|
3809
|
+
"biome",
|
|
3810
|
+
"oxlint",
|
|
3811
|
+
"vite-plus"
|
|
3812
|
+
];
|
|
3813
|
+
const TASK_RUNNER_ADDONS = [
|
|
3814
|
+
"turborepo",
|
|
3815
|
+
"nx",
|
|
3816
|
+
"vite-plus"
|
|
3817
|
+
];
|
|
3753
3818
|
function mergeAddonOptions(existingAddonOptions, nextAddonOptions) {
|
|
3754
3819
|
if (!existingAddonOptions && !nextAddonOptions) return;
|
|
3755
3820
|
const mergedAddonOptions = { ...existingAddonOptions };
|
|
@@ -3763,6 +3828,29 @@ function mergeAddonOptions(existingAddonOptions, nextAddonOptions) {
|
|
|
3763
3828
|
}
|
|
3764
3829
|
return Object.keys(mergedAddonOptions).length > 0 ? mergedAddonOptions : void 0;
|
|
3765
3830
|
}
|
|
3831
|
+
function getSetupAddons(addonsToAdd, updatedAddons) {
|
|
3832
|
+
const setupAddons = new Set(addonsToAdd);
|
|
3833
|
+
if (addonsToAdd.some((addon) => HOOK_ADDONS.includes(addon) || HOOK_LINTER_ADDONS.includes(addon))) {
|
|
3834
|
+
for (const addon of [...HOOK_ADDONS, ...HOOK_LINTER_ADDONS]) if (updatedAddons.includes(addon)) setupAddons.add(addon);
|
|
3835
|
+
}
|
|
3836
|
+
return [...setupAddons];
|
|
3837
|
+
}
|
|
3838
|
+
function shouldRefreshLefthook(addonsToAdd, updatedAddons) {
|
|
3839
|
+
return updatedAddons.includes("lefthook") && addonsToAdd.some((addon) => addon === "lefthook" || HOOK_LINTER_ADDONS.includes(addon));
|
|
3840
|
+
}
|
|
3841
|
+
function refreshLefthookTemplate(vfs, config) {
|
|
3842
|
+
const template = EMBEDDED_TEMPLATES.get("addons/lefthook/lefthook.yml.hbs");
|
|
3843
|
+
if (!template) return;
|
|
3844
|
+
vfs.writeFile("lefthook.yml", processTemplateString(template, config));
|
|
3845
|
+
}
|
|
3846
|
+
function updateViteConfigImportsForVitePlus(vfs) {
|
|
3847
|
+
for (const viteConfigPath of ["apps/web/vite.config.ts"]) {
|
|
3848
|
+
const content = vfs.readFile(viteConfigPath);
|
|
3849
|
+
if (!content) continue;
|
|
3850
|
+
const updatedContent = content.replaceAll("from \"vite\";", "from \"vite-plus\";").replaceAll("from 'vite';", "from 'vite-plus';");
|
|
3851
|
+
if (updatedContent !== content) vfs.writeFile(viteConfigPath, updatedContent);
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3766
3854
|
async function addHandler(input, options = {}) {
|
|
3767
3855
|
const { silent = false } = options;
|
|
3768
3856
|
return runWithContextAsync({ silent }, async () => {
|
|
@@ -3840,10 +3928,10 @@ async function addHandlerInternal(input) {
|
|
|
3840
3928
|
}
|
|
3841
3929
|
addonsToAdd = selectedAddons;
|
|
3842
3930
|
}
|
|
3843
|
-
const
|
|
3931
|
+
const updatedAddons = [...existingConfig.addons, ...addonsToAdd];
|
|
3932
|
+
const addonsValidationResult = validateAddonsAgainstConfig(updatedAddons, existingConfig);
|
|
3844
3933
|
if (addonsValidationResult.isErr()) return Result.err(new CLIError({ message: addonsValidationResult.error.message }));
|
|
3845
3934
|
if (!isSilent()) log.info(pc.cyan(`Adding addons: ${addonsToAdd.join(", ")}`));
|
|
3846
|
-
const updatedAddons = [...existingConfig.addons, ...addonsToAdd];
|
|
3847
3935
|
const mergedAddonOptions = mergeAddonOptions(existingConfig.addonOptions, input.addonOptions);
|
|
3848
3936
|
const config = {
|
|
3849
3937
|
projectName: existingConfig.projectName,
|
|
@@ -3867,27 +3955,39 @@ async function addHandlerInternal(input) {
|
|
|
3867
3955
|
webDeploy: existingConfig.webDeploy,
|
|
3868
3956
|
serverDeploy: existingConfig.serverDeploy
|
|
3869
3957
|
};
|
|
3958
|
+
const updatedConfig = {
|
|
3959
|
+
...config,
|
|
3960
|
+
addons: updatedAddons
|
|
3961
|
+
};
|
|
3870
3962
|
if (!isSilent()) log.info(pc.dim("Installing addon files..."));
|
|
3871
3963
|
const vfs = new VirtualFileSystem();
|
|
3872
|
-
for (const pkgPath of
|
|
3873
|
-
"package.json",
|
|
3874
|
-
"apps/web/package.json",
|
|
3875
|
-
"apps/server/package.json",
|
|
3876
|
-
"apps/native/package.json"
|
|
3877
|
-
]) {
|
|
3964
|
+
for (const pkgPath of ADD_PACKAGE_JSON_PATHS) {
|
|
3878
3965
|
const fullPath = path.join(projectDir, pkgPath);
|
|
3879
3966
|
if (await fs.pathExists(fullPath)) {
|
|
3880
3967
|
const content = await fs.readFile(fullPath, "utf-8");
|
|
3881
3968
|
vfs.writeFile(pkgPath, content);
|
|
3882
3969
|
}
|
|
3883
3970
|
}
|
|
3971
|
+
for (const filePath of ADD_TEXT_FILE_PATHS) {
|
|
3972
|
+
const fullPath = path.join(projectDir, filePath);
|
|
3973
|
+
if (await fs.pathExists(fullPath)) {
|
|
3974
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
3975
|
+
vfs.writeFile(filePath, content);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3884
3978
|
await processAddonTemplates(vfs, EMBEDDED_TEMPLATES, config);
|
|
3885
3979
|
processAddonsDeps(vfs, config);
|
|
3980
|
+
if (addonsToAdd.includes("turborepo")) processTurboConfig(vfs, updatedConfig);
|
|
3981
|
+
if (addonsToAdd.includes("nx")) processNxConfig(vfs, updatedConfig);
|
|
3982
|
+
if (addonsToAdd.includes("vite-plus")) processVitePlusConfig(vfs, updatedConfig);
|
|
3983
|
+
if (updatedAddons.some((addon) => TASK_RUNNER_ADDONS.includes(addon))) processPackageConfigs(vfs, updatedConfig);
|
|
3984
|
+
if (updatedAddons.includes("vite-plus")) updateViteConfigImportsForVitePlus(vfs);
|
|
3985
|
+
if (shouldRefreshLefthook(addonsToAdd, updatedAddons)) refreshLefthookTemplate(vfs, updatedConfig);
|
|
3886
3986
|
const tree = {
|
|
3887
3987
|
root: vfs.toTree(""),
|
|
3888
3988
|
fileCount: vfs.getFileCount(),
|
|
3889
3989
|
directoryCount: vfs.getDirectoryCount(),
|
|
3890
|
-
config
|
|
3990
|
+
config: updatedConfig
|
|
3891
3991
|
};
|
|
3892
3992
|
if (input.dryRun) {
|
|
3893
3993
|
if (!isSilent()) {
|
|
@@ -3907,7 +4007,10 @@ async function addHandlerInternal(input) {
|
|
|
3907
4007
|
if (writeResult.isErr()) return Result.err(new CLIError({ message: `Failed to write addon files: ${writeResult.error.message}` }));
|
|
3908
4008
|
if (vfs.getFileCount() > 0 && !isSilent()) log.info(pc.dim(`Wrote ${vfs.getFileCount()} addon files`));
|
|
3909
4009
|
const setupResult = await Result.tryPromise({
|
|
3910
|
-
try: () => setupAddons(
|
|
4010
|
+
try: () => setupAddons({
|
|
4011
|
+
...config,
|
|
4012
|
+
addons: getSetupAddons(addonsToAdd, updatedAddons)
|
|
4013
|
+
}),
|
|
3911
4014
|
catch: (e) => {
|
|
3912
4015
|
if (UserCancelledError.is(e)) return e;
|
|
3913
4016
|
return new CLIError({
|
|
@@ -3919,7 +4022,7 @@ async function addHandlerInternal(input) {
|
|
|
3919
4022
|
if (setupResult.isErr()) return Result.err(setupResult.error);
|
|
3920
4023
|
await updateBtsConfig(projectDir, {
|
|
3921
4024
|
addons: updatedAddons,
|
|
3922
|
-
addonOptions:
|
|
4025
|
+
addonOptions: updatedConfig.addonOptions
|
|
3923
4026
|
});
|
|
3924
4027
|
if (input.install) {
|
|
3925
4028
|
if (!isSilent()) log.info(pc.dim("Installing dependencies..."));
|
|
@@ -6713,12 +6816,15 @@ async function displayPostInstallInstructions(config) {
|
|
|
6713
6816
|
const cdCmd = `cd ${relativePath}`;
|
|
6714
6817
|
const hasHusky = addons?.includes("husky");
|
|
6715
6818
|
const hasLefthook = addons?.includes("lefthook");
|
|
6716
|
-
const
|
|
6819
|
+
const hasVitePlus = addons?.includes("vite-plus");
|
|
6820
|
+
const hasVitePlusNativeHooks = hasVitePlus && !hasHusky && !hasLefthook;
|
|
6821
|
+
const hasGitHooksOrLinting = addons?.includes("husky") || addons?.includes("biome") || addons?.includes("lefthook") || addons?.includes("oxlint") || hasVitePlus;
|
|
6717
6822
|
const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, webDeploy, serverDeploy, backend) : "";
|
|
6718
6823
|
const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd, frontend) : "";
|
|
6719
6824
|
const electrobunInstructions = addons?.includes("electrobun") ? getElectrobunInstructions(runCmd, frontend) : "";
|
|
6720
6825
|
const huskyInstructions = hasHusky ? getHuskyInstructions(runCmd) : "";
|
|
6721
6826
|
const lefthookInstructions = hasLefthook ? getLefthookInstructions(packageManager) : "";
|
|
6827
|
+
const vitePlusNativeHooksInstructions = hasVitePlusNativeHooks ? getVitePlusNativeHooksInstructions(runCmd) : "";
|
|
6722
6828
|
const lintingInstructions = hasGitHooksOrLinting ? getLintingInstructions(runCmd) : "";
|
|
6723
6829
|
const nativeInstructions = (frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles")) && backend !== "none" ? getNativeInstructions(isConvex, isBackendSelf, frontend || [], runCmd) : "";
|
|
6724
6830
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
@@ -6774,6 +6880,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
6774
6880
|
if (electrobunInstructions) output += `\n${electrobunInstructions.trim()}\n`;
|
|
6775
6881
|
if (huskyInstructions) output += `\n${huskyInstructions.trim()}\n`;
|
|
6776
6882
|
if (lefthookInstructions) output += `\n${lefthookInstructions.trim()}\n`;
|
|
6883
|
+
if (vitePlusNativeHooksInstructions) output += `\n${vitePlusNativeHooksInstructions.trim()}\n`;
|
|
6777
6884
|
if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
|
|
6778
6885
|
if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
|
|
6779
6886
|
if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
|
|
@@ -6805,12 +6912,15 @@ function getHuskyInstructions(runCmd) {
|
|
|
6805
6912
|
return `${pc.bold("Git hooks with Husky:")}\n${pc.cyan("•")} Initialize hooks: ${`${runCmd} prepare`}\n`;
|
|
6806
6913
|
}
|
|
6807
6914
|
function getLintingInstructions(runCmd) {
|
|
6808
|
-
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")}
|
|
6915
|
+
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Run checks: ${`${runCmd} check`}\n`;
|
|
6809
6916
|
}
|
|
6810
6917
|
function getLefthookInstructions(packageManager) {
|
|
6811
6918
|
const cmd = packageManager === "npm" ? "npx" : packageManager;
|
|
6812
6919
|
return `${pc.bold("Git hooks with Lefthook:")}\n${pc.cyan("•")} Install hooks: ${cmd} lefthook install\n`;
|
|
6813
6920
|
}
|
|
6921
|
+
function getVitePlusNativeHooksInstructions(runCmd) {
|
|
6922
|
+
return `${pc.bold("Vite+ native Git hooks:")}\n${pc.cyan("•")} Optional hook setup: ${`${runCmd} hooks:setup`}\n${pc.dim(" (runs vp config; hooks install into .vite-hooks and use vp staged)")}\n`;
|
|
6923
|
+
}
|
|
6814
6924
|
async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, webDeploy, serverDeploy, backend) {
|
|
6815
6925
|
const instructions = [];
|
|
6816
6926
|
const isD1Alchemy = dbSetup === "d1" && (serverDeploy === "cloudflare" || backend === "self" && webDeploy === "cloudflare");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.32.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",
|
|
@@ -69,8 +69,8 @@
|
|
|
69
69
|
"prepublishOnly": "npm run build"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@better-t-stack/template-generator": "^3.
|
|
73
|
-
"@better-t-stack/types": "^3.
|
|
72
|
+
"@better-t-stack/template-generator": "^3.32.0",
|
|
73
|
+
"@better-t-stack/types": "^3.32.0",
|
|
74
74
|
"@clack/core": "^1.3.1",
|
|
75
75
|
"@clack/prompts": "^1.4.0",
|
|
76
76
|
"@modelcontextprotocol/sdk": "1.29.0",
|