feat(web): improve docs and refactor cli (#476)

This commit is contained in:
Aman Varshney
2025-08-08 16:00:10 +05:30
committed by GitHub
parent defa0e9464
commit 51cfb35912
34 changed files with 1336 additions and 1154 deletions

View File

@@ -25,33 +25,36 @@ pnpm create better-t-stack@latest
## Features ## Features
- **Zero-config setup** with interactive CLI wizard - Frontend: React (TanStack Router, React Router, TanStack Start), Next.js, Nuxt, Svelte, Solid, React Native (NativeWind/Unistyles), or none
- **End-to-end type safety** from database to frontend via tRPC - Backend: Hono, Express, Fastify, Elysia, Next API Routes, Convex, or none
- **Modern stack** with React, Hono/Elysia, and TanStack libraries - API: tRPC or oRPC (or none)
- **Multi-platform** supporting web, mobile (Expo), and desktop applications - Runtime: Bun, Node.js, or Cloudflare Workers
- **Database flexibility** with SQLite (Turso) or PostgreSQL options - Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none)
- **ORM choice** between Drizzle or Prisma - ORMs: Drizzle, Prisma, Mongoose (or none)
- **Built-in authentication** with Better-Auth - Auth: Better-Auth (optional)
- **Optional PWA support** for installable web applications - Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ultracite, Oxlint
- **Desktop app capabilities** with Tauri integration - Examples: Todo, AI
- **Monorepo architecture** powered by Turborepo - DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker
- Web Deploy: Cloudflare Workers
Type safety end-to-end, clean monorepo layout, and zerolockin: you choose only what you need.
## Repository Structure ## Repository Structure
This repository is organized as a monorepo containing: This repository is organized as a monorepo containing:
- **CLI**: [`create-better-t-stack`](apps/cli) - The scaffolding CLI tool - **CLI**: [`apps/cli`](apps/cli) - The scaffolding CLI tool
- **Documentation**: [`web`](apps/web) - Official website and documentation - **Documentation**: [`apps/web`](apps/web) - Official website and documentation
## Documentation ## Documentation
Visit [better-t-stack.dev](https://better-t-stack.dev) for full documentation, guides, and examples. Visit [better-t-stack.dev](https://better-t-stack.dev) for full documentation, guides, and examples. You can also use the visual Stack Builder at `https://better-t-stack.dev/new` to generate a command for your stack.
## Development ## Development
```bash ```bash
# Clone the repository # Clone the repository
git clone https://github.com/better-t-stack/create-better-t-stack.git git clone https://github.com/AmanVarshney01/create-better-t-stack.git
# Install dependencies # Install dependencies
bun install bun install
@@ -65,7 +68,10 @@ bun dev:web
## Want to contribute? ## Want to contribute?
Just fork the repository and submit a pull request! Please read the Contribution Guide first and open an issue before starting new features to ensure alignment with project goals.
- Docs: [`Contributing`](/apps/web/content/docs/contributing.mdx)
- Repo guide: [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md)
## Star History ## Star History

View File

@@ -139,14 +139,3 @@ export const ADDON_COMPATIBILITY: Record<Addons, readonly Frontend[]> = {
fumadocs: [], fumadocs: [],
none: [], none: [],
} as const; } as const;
// TODO: need to refactor this
export const WEB_FRAMEWORKS: readonly Frontend[] = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
];

View File

@@ -1,6 +1,7 @@
import { cancel, isCancel, select } from "@clack/prompts"; import { cancel, isCancel, select } from "@clack/prompts";
import pc from "picocolors"; import pc from "picocolors";
import type { API, Backend, Frontend } from "../types"; import type { API, Backend, Frontend } from "../types";
import { allowedApisForFrontends } from "../utils/compatibility-rules";
export async function getApiChoice( export async function getApiChoice(
Api?: API | undefined, Api?: API | undefined,
@@ -11,46 +12,30 @@ export async function getApiChoice(
return "none"; return "none";
} }
if (Api) return Api; const allowed = allowedApisForFrontends(frontend ?? []);
const includesNuxt = frontend?.includes("nuxt"); if (Api) {
const includesSvelte = frontend?.includes("svelte"); return allowed.includes(Api) ? Api : allowed[0];
const includesSolid = frontend?.includes("solid");
let apiOptions = [
{
value: "trpc" as const,
label: "tRPC",
hint: "End-to-end typesafe APIs made easy",
},
{
value: "orpc" as const,
label: "oRPC",
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards",
},
{
value: "none" as const,
label: "None",
hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)",
},
];
if (includesNuxt || includesSvelte || includesSolid) {
apiOptions = [
{
value: "orpc" as const,
label: "oRPC",
hint: `End-to-end type-safe APIs (Recommended for ${
includesNuxt ? "Nuxt" : includesSvelte ? "Svelte" : "Solid"
} frontend)`,
},
{
value: "none" as const,
label: "None",
hint: "No API layer",
},
];
} }
const apiOptions = allowed.map((a) =>
a === "trpc"
? {
value: "trpc" as const,
label: "tRPC",
hint: "End-to-end typesafe APIs made easy",
}
: a === "orpc"
? {
value: "orpc" as const,
label: "oRPC",
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards",
}
: {
value: "none" as const,
label: "None",
hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)",
},
);
const apiType = await select<API>({ const apiType = await select<API>({
message: "Select API type", message: "Select API type",

View File

@@ -2,6 +2,10 @@ import { cancel, isCancel, multiselect } from "@clack/prompts";
import pc from "picocolors"; import pc from "picocolors";
import { DEFAULT_CONFIG } from "../constants"; import { DEFAULT_CONFIG } from "../constants";
import type { API, Backend, Database, Examples, Frontend } from "../types"; import type { API, Backend, Database, Examples, Frontend } from "../types";
import {
isExampleAIAllowed,
isExampleTodoAllowed,
} from "../utils/compatibility-rules";
export async function getExamplesChoice( export async function getExamplesChoice(
examples?: Examples[], examples?: Examples[],
@@ -30,15 +34,17 @@ export async function getExamplesChoice(
if (noFrontendSelected) return []; if (noFrontendSelected) return [];
let response: Examples[] | symbol = []; let response: Examples[] | symbol = [];
const options: { value: Examples; label: string; hint: string }[] = [ const options: { value: Examples; label: string; hint: string }[] = [];
{
if (isExampleTodoAllowed(backend, database)) {
options.push({
value: "todo" as const, value: "todo" as const,
label: "Todo App", label: "Todo App",
hint: "A simple CRUD example app", hint: "A simple CRUD example app",
}, });
]; }
if (backend !== "elysia" && !frontends?.includes("solid")) { if (isExampleAIAllowed(backend, frontends ?? [])) {
options.push({ options.push({
value: "ai" as const, value: "ai" as const,
label: "AI Chat", label: "AI Chat",
@@ -46,11 +52,15 @@ export async function getExamplesChoice(
}); });
} }
if (options.length === 0) return [];
response = await multiselect<Examples>({ response = await multiselect<Examples>({
message: "Include examples", message: "Include examples",
options: options, options: options,
required: false, required: false,
initialValues: DEFAULT_CONFIG.examples, initialValues: DEFAULT_CONFIG.examples?.filter((ex) =>
options.some((o) => o.value === ex),
),
}); });
if (isCancel(response)) { if (isCancel(response)) {

View File

@@ -2,6 +2,7 @@ import { cancel, isCancel, multiselect, select } from "@clack/prompts";
import pc from "picocolors"; import pc from "picocolors";
import { DEFAULT_CONFIG } from "../constants"; import { DEFAULT_CONFIG } from "../constants";
import type { Backend, Frontend } from "../types"; import type { Backend, Frontend } from "../types";
import { isFrontendAllowedWithBackend } from "../utils/compatibility-rules";
export async function getFrontendChoice( export async function getFrontendChoice(
frontendOptions?: Frontend[], frontendOptions?: Frontend[],
@@ -73,12 +74,9 @@ export async function getFrontendChoice(
}, },
]; ];
const webOptions = allWebOptions.filter((option) => { const webOptions = allWebOptions.filter((option) =>
if (backend === "convex") { isFrontendAllowedWithBackend(option.value, backend),
return option.value !== "solid"; );
}
return true;
});
const webFramework = await select<Frontend>({ const webFramework = await select<Frontend>({
message: "Choose web", message: "Choose web",

View File

@@ -1,10 +1,17 @@
import path from "node:path"; import path from "node:path";
import { cancel, isCancel, text } from "@clack/prompts"; import { cancel, isCancel, text } from "@clack/prompts";
import consola from "consola";
import fs from "fs-extra"; import fs from "fs-extra";
import pc from "picocolors"; import pc from "picocolors";
import { DEFAULT_CONFIG } from "../constants"; import { DEFAULT_CONFIG } from "../constants";
import { ProjectNameSchema } from "../types"; import { ProjectNameSchema } from "../types";
function isPathWithinCwd(targetPath: string): boolean {
const resolved = path.resolve(targetPath);
const rel = path.relative(process.cwd(), resolved);
return !rel.startsWith("..") && !path.isAbsolute(rel);
}
function validateDirectoryName(name: string): string | undefined { function validateDirectoryName(name: string): string | undefined {
if (name === ".") return undefined; if (name === ".") return undefined;
@@ -23,7 +30,11 @@ export async function getProjectName(initialName?: string): Promise<string> {
const finalDirName = path.basename(initialName); const finalDirName = path.basename(initialName);
const validationError = validateDirectoryName(finalDirName); const validationError = validateDirectoryName(finalDirName);
if (!validationError) { if (!validationError) {
return initialName; const projectDir = path.resolve(process.cwd(), initialName);
if (isPathWithinCwd(projectDir)) {
return initialName;
}
consola.error(pc.red("Project path must be within current directory"));
} }
} }
@@ -56,7 +67,7 @@ export async function getProjectName(initialName?: string): Promise<string> {
if (nameToUse !== ".") { if (nameToUse !== ".") {
const projectDir = path.resolve(process.cwd(), nameToUse); const projectDir = path.resolve(process.cwd(), nameToUse);
if (!projectDir.startsWith(process.cwd())) { if (!isPathWithinCwd(projectDir)) {
return "Project path must be within current directory"; return "Project path must be within current directory";
} }
} }

View File

@@ -1,7 +1,8 @@
import { cancel, isCancel, select } from "@clack/prompts"; import { cancel, isCancel, select } from "@clack/prompts";
import pc from "picocolors"; import pc from "picocolors";
import { DEFAULT_CONFIG, WEB_FRAMEWORKS } from "../constants"; import { DEFAULT_CONFIG } from "../constants";
import type { Backend, Frontend, Runtime, WebDeploy } from "../types"; import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
import { WEB_FRAMEWORKS } from "../utils/compatibility";
function hasWebFrontend(frontends: Frontend[]): boolean { function hasWebFrontend(frontends: Frontend[]): boolean {
return frontends.some((f) => WEB_FRAMEWORKS.includes(f)); return frontends.some((f) => WEB_FRAMEWORKS.includes(f));

View File

@@ -17,9 +17,7 @@ export type Backend = z.infer<typeof BackendSchema>;
export const RuntimeSchema = z export const RuntimeSchema = z
.enum(["bun", "node", "workers", "none"]) .enum(["bun", "node", "workers", "none"])
.describe( .describe("Runtime environment");
"Runtime environment (workers only available with hono backend and drizzle orm)",
);
export type Runtime = z.infer<typeof RuntimeSchema>; export type Runtime = z.infer<typeof RuntimeSchema>;
export const FrontendSchema = z export const FrontendSchema = z
@@ -176,5 +174,3 @@ export interface BetterTStackConfig {
api: API; api: API;
webDeploy: WebDeploy; webDeploy: WebDeploy;
} }
export type AvailablePackageManagers = "npm" | "pnpm" | "bun";

View File

@@ -0,0 +1,315 @@
import { consola } from "consola";
import type {
Addons,
API,
CLIInput,
Frontend,
ProjectConfig,
WebDeploy,
} from "../types";
import { validateAddonCompatibility } from "./addon-compatibility";
import { WEB_FRAMEWORKS } from "./compatibility";
export function isWebFrontend(value: Frontend): boolean {
return WEB_FRAMEWORKS.includes(value);
}
export function splitFrontends(values: Frontend[] = []): {
web: Frontend[];
native: Frontend[];
} {
const web = values.filter((f) => isWebFrontend(f));
const native = values.filter(
(f) => f === "native-nativewind" || f === "native-unistyles",
);
return { web, native };
}
export function ensureSingleWebAndNative(frontends: Frontend[]) {
const { web, native } = splitFrontends(frontends);
if (web.length > 1) {
consola.fatal(
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid",
);
process.exit(1);
}
if (native.length > 1) {
consola.fatal(
"Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles",
);
process.exit(1);
}
}
export function validateWorkersCompatibility(
providedFlags: Set<string>,
options: CLIInput,
config: Partial<ProjectConfig>,
) {
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.backend &&
config.backend !== "hono"
) {
consola.fatal(
`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("backend") &&
config.backend &&
config.backend !== "hono" &&
config.runtime === "workers"
) {
consola.fatal(
`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.orm &&
config.orm !== "drizzle" &&
config.orm !== "none"
) {
consola.fatal(
`Cloudflare Workers runtime (--runtime workers) is only supported with Drizzle ORM (--orm drizzle) or no ORM (--orm none). Current ORM: ${config.orm}. Please use '--orm drizzle', '--orm none', or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("orm") &&
config.orm &&
config.orm !== "drizzle" &&
config.orm !== "none" &&
config.runtime === "workers"
) {
consola.fatal(
`ORM '${config.orm}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Please use '--orm drizzle', '--orm none', or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.database === "mongodb"
) {
consola.fatal(
"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
);
process.exit(1);
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.dbSetup === "docker"
) {
consola.fatal(
"Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
);
process.exit(1);
}
if (
providedFlags.has("database") &&
config.database === "mongodb" &&
config.runtime === "workers"
) {
consola.fatal(
"MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
);
process.exit(1);
}
if (
providedFlags.has("dbSetup") &&
options.dbSetup === "docker" &&
config.runtime === "workers"
) {
consola.fatal(
"Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
);
process.exit(1);
}
}
export function coerceBackendPresets(config: Partial<ProjectConfig>) {
if (config.backend === "convex") {
config.auth = false;
config.database = "none";
config.orm = "none";
config.api = "none";
config.runtime = "none";
config.dbSetup = "none";
config.examples = ["todo"] as ProjectConfig["examples"];
}
if (config.backend === "none") {
config.auth = false;
config.database = "none";
config.orm = "none";
config.api = "none";
config.runtime = "none";
config.dbSetup = "none";
config.examples = [] as ProjectConfig["examples"];
}
}
export function incompatibleFlagsForBackend(
backend: ProjectConfig["backend"] | undefined,
providedFlags: Set<string>,
options: CLIInput,
): string[] {
const list: string[] = [];
if (backend === "convex") {
if (providedFlags.has("auth") && options.auth === true) list.push("--auth");
if (providedFlags.has("database") && options.database !== "none")
list.push(`--database ${options.database}`);
if (providedFlags.has("orm") && options.orm !== "none")
list.push(`--orm ${options.orm}`);
if (providedFlags.has("api") && options.api !== "none")
list.push(`--api ${options.api}`);
if (providedFlags.has("runtime") && options.runtime !== "none")
list.push(`--runtime ${options.runtime}`);
if (providedFlags.has("dbSetup") && options.dbSetup !== "none")
list.push(`--db-setup ${options.dbSetup}`);
}
if (backend === "none") {
if (providedFlags.has("auth") && options.auth === true) list.push("--auth");
if (providedFlags.has("database") && options.database !== "none")
list.push(`--database ${options.database}`);
if (providedFlags.has("orm") && options.orm !== "none")
list.push(`--orm ${options.orm}`);
if (providedFlags.has("api") && options.api !== "none")
list.push(`--api ${options.api}`);
if (providedFlags.has("runtime") && options.runtime !== "none")
list.push(`--runtime ${options.runtime}`);
if (providedFlags.has("dbSetup") && options.dbSetup !== "none")
list.push(`--db-setup ${options.dbSetup}`);
if (providedFlags.has("examples") && options.examples) {
const hasNonNoneExamples = options.examples.some((ex) => ex !== "none");
if (hasNonNoneExamples) list.push("--examples");
}
}
return list;
}
export function validateApiFrontendCompatibility(
api: API | undefined,
frontends: Frontend[] = [],
) {
const includesNuxt = frontends.includes("nuxt");
const includesSvelte = frontends.includes("svelte");
const includesSolid = frontends.includes("solid");
if ((includesNuxt || includesSvelte || includesSolid) && api === "trpc") {
consola.fatal(
`tRPC API is not supported with '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' frontend. Please use --api orpc or --api none or remove '${includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"}' from --frontend.`,
);
process.exit(1);
}
}
export function isFrontendAllowedWithBackend(
frontend: Frontend,
backend?: ProjectConfig["backend"],
) {
if (backend === "convex" && frontend === "solid") return false;
return true;
}
export function allowedApisForFrontends(frontends: Frontend[] = []): API[] {
const includesNuxt = frontends.includes("nuxt");
const includesSvelte = frontends.includes("svelte");
const includesSolid = frontends.includes("solid");
const base: API[] = ["trpc", "orpc", "none"];
if (includesNuxt || includesSvelte || includesSolid) {
return ["orpc", "none"];
}
return base;
}
export function isExampleTodoAllowed(
backend?: ProjectConfig["backend"],
database?: ProjectConfig["database"],
) {
return !(backend !== "convex" && backend !== "none" && database === "none");
}
export function isExampleAIAllowed(
backend?: ProjectConfig["backend"],
frontends: Frontend[] = [],
) {
const includesSolid = frontends.includes("solid");
if (backend === "elysia") return false;
if (includesSolid) return false;
return true;
}
export function validateWebDeployRequiresWebFrontend(
webDeploy: WebDeploy | undefined,
hasWebFrontendFlag: boolean,
) {
if (webDeploy && webDeploy !== "none" && !hasWebFrontendFlag) {
consola.fatal(
"'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.",
);
process.exit(1);
}
}
export function validateAddonsAgainstFrontends(
addons: Addons[] = [],
frontends: Frontend[] = [],
) {
for (const addon of addons) {
if (addon === "none") continue;
const { isCompatible, reason } = validateAddonCompatibility(
addon,
frontends,
);
if (!isCompatible) {
consola.fatal(`Incompatible addon/frontend combination: ${reason}`);
process.exit(1);
}
}
}
export function validateExamplesCompatibility(
examples: string[] | undefined,
backend: ProjectConfig["backend"] | undefined,
database: ProjectConfig["database"] | undefined,
frontend?: Frontend[],
) {
const examplesArr = examples ?? [];
if (examplesArr.length === 0 || examplesArr.includes("none")) return;
if (
examplesArr.includes("todo") &&
backend !== "convex" &&
backend !== "none" &&
database === "none"
) {
consola.fatal(
"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.",
);
process.exit(1);
}
if (examplesArr.includes("ai") && backend === "elysia") {
consola.fatal(
"The 'ai' example is not compatible with the Elysia backend.",
);
process.exit(1);
}
if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) {
consola.fatal(
"The 'ai' example is not compatible with the Solid frontend.",
);
process.exit(1);
}
}

View File

@@ -0,0 +1,11 @@
import type { Frontend } from "../types";
export const WEB_FRAMEWORKS: readonly Frontend[] = [
"tanstack-router",
"react-router",
"tanstack-start",
"next",
"nuxt",
"svelte",
"solid",
] as const;

View File

@@ -34,15 +34,15 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
flags.push(`--package-manager ${config.packageManager}`); flags.push(`--package-manager ${config.packageManager}`);
flags.push(config.install ? "--install" : "--no-install"); flags.push(config.install ? "--install" : "--no-install");
let baseCommand = ""; let baseCommand = "npx create-better-t-stack@latest";
const pkgManager = config.packageManager; const pkgManager = config.packageManager;
if (pkgManager === "npm") { if (pkgManager === "bun") {
baseCommand = "npx create-better-t-stack@latest"; baseCommand = "bun create better-t-stack@latest";
} else if (pkgManager === "pnpm") { } else if (pkgManager === "pnpm") {
baseCommand = "pnpm create better-t-stack@latest"; baseCommand = "pnpm create better-t-stack@latest";
} else if (pkgManager === "bun") { } else if (pkgManager === "npm") {
baseCommand = "bun create better-t-stack@latest"; baseCommand = "npx create-better-t-stack@latest";
} }
const projectPathArg = config.relativePath ? ` ${config.relativePath}` : ""; const projectPathArg = config.relativePath ? ` ${config.relativePath}` : "";

View File

@@ -1,6 +1,5 @@
import path from "node:path"; import path from "node:path";
import { consola } from "consola"; import { consola } from "consola";
import { WEB_FRAMEWORKS } from "./constants";
import { import {
type Addons, type Addons,
type API, type API,
@@ -17,6 +16,17 @@ import {
type Runtime, type Runtime,
type WebDeploy, type WebDeploy,
} from "./types"; } from "./types";
import {
coerceBackendPresets,
ensureSingleWebAndNative,
incompatibleFlagsForBackend,
isWebFrontend,
validateAddonsAgainstFrontends,
validateApiFrontendCompatibility,
validateExamplesCompatibility,
validateWebDeployRequiresWebFrontend,
validateWorkersCompatibility,
} from "./utils/compatibility-rules";
export function processAndValidateFlags( export function processAndValidateFlags(
options: CLIInput, options: CLIInput,
@@ -126,28 +136,20 @@ export function processAndValidateFlags(
const validOptions = options.frontend.filter( const validOptions = options.frontend.filter(
(f): f is Frontend => f !== "none", (f): f is Frontend => f !== "none",
); );
const webFrontends = validOptions.filter((f) => ensureSingleWebAndNative(validOptions);
WEB_FRAMEWORKS.includes(f),
);
const nativeFrontends = validOptions.filter(
(f) => f === "native-nativewind" || f === "native-unistyles",
);
if (webFrontends.length > 1) {
consola.fatal(
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid",
);
process.exit(1);
}
if (nativeFrontends.length > 1) {
consola.fatal(
"Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles",
);
process.exit(1);
}
config.frontend = validOptions; config.frontend = validOptions;
} }
} }
if (
providedFlags.has("api") &&
providedFlags.has("frontend") &&
config.api &&
config.frontend &&
config.frontend.length > 0
) {
validateApiFrontendCompatibility(config.api, config.frontend);
}
if (options.addons && options.addons.length > 0) { if (options.addons && options.addons.length > 0) {
if (options.addons.includes("none")) { if (options.addons.includes("none")) {
if (options.addons.length > 1) { if (options.addons.length > 1) {
@@ -178,32 +180,26 @@ export function processAndValidateFlags(
} }
} }
if (config.backend === "convex") { if (config.backend === "convex" || config.backend === "none") {
const incompatibleFlags: string[] = []; const incompatibleFlags = incompatibleFlagsForBackend(
config.backend,
if (providedFlags.has("auth") && options.auth === true) providedFlags,
incompatibleFlags.push("--auth"); options,
if (providedFlags.has("database") && options.database !== "none") );
incompatibleFlags.push(`--database ${options.database}`);
if (providedFlags.has("orm") && options.orm !== "none")
incompatibleFlags.push(`--orm ${options.orm}`);
if (providedFlags.has("api") && options.api !== "none")
incompatibleFlags.push(`--api ${options.api}`);
if (providedFlags.has("runtime") && options.runtime !== "none")
incompatibleFlags.push(`--runtime ${options.runtime}`);
if (providedFlags.has("dbSetup") && options.dbSetup !== "none")
incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
if (incompatibleFlags.length > 0) { if (incompatibleFlags.length > 0) {
consola.fatal( consola.fatal(
`The following flags are incompatible with '--backend convex': ${incompatibleFlags.join( `The following flags are incompatible with '--backend ${config.backend}': ${incompatibleFlags.join(
", ", ", ",
)}. Please remove them.`, )}. Please remove them.`,
); );
process.exit(1); process.exit(1);
} }
if (providedFlags.has("frontend") && options.frontend) { if (
config.backend === "convex" &&
providedFlags.has("frontend") &&
options.frontend
) {
const incompatibleFrontends = options.frontend.filter( const incompatibleFrontends = options.frontend.filter(
(f) => f === "solid", (f) => f === "solid",
); );
@@ -217,54 +213,15 @@ export function processAndValidateFlags(
} }
} }
config.auth = false; coerceBackendPresets(config);
config.database = "none";
config.orm = "none";
config.api = "none";
config.runtime = "none";
config.dbSetup = "none";
config.examples = ["todo"];
} else if (config.backend === "none") {
const incompatibleFlags: string[] = [];
if (providedFlags.has("auth") && options.auth === true)
incompatibleFlags.push("--auth");
if (providedFlags.has("database") && options.database !== "none")
incompatibleFlags.push(`--database ${options.database}`);
if (providedFlags.has("orm") && options.orm !== "none")
incompatibleFlags.push(`--orm ${options.orm}`);
if (providedFlags.has("api") && options.api !== "none")
incompatibleFlags.push(`--api ${options.api}`);
if (providedFlags.has("runtime") && options.runtime !== "none")
incompatibleFlags.push(`--runtime ${options.runtime}`);
if (providedFlags.has("dbSetup") && options.dbSetup !== "none")
incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
if (providedFlags.has("examples") && options.examples) {
const hasNonNoneExamples = options.examples.some((ex) => ex !== "none");
if (hasNonNoneExamples) {
incompatibleFlags.push("--examples");
}
}
if (incompatibleFlags.length > 0) {
consola.fatal(
`The following flags are incompatible with '--backend none': ${incompatibleFlags.join(
", ",
)}. Please remove them.`,
);
process.exit(1);
}
config.auth = false;
config.database = "none";
config.orm = "none";
config.api = "none";
config.runtime = "none";
config.dbSetup = "none";
config.examples = [];
} }
if (config.orm === "mongoose" && config.database !== "mongodb") { if (
providedFlags.has("orm") &&
providedFlags.has("database") &&
config.orm === "mongoose" &&
config.database !== "mongodb"
) {
consola.fatal( consola.fatal(
"Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.", "Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.",
); );
@@ -272,6 +229,8 @@ export function processAndValidateFlags(
} }
if ( if (
providedFlags.has("database") &&
providedFlags.has("orm") &&
config.database === "mongodb" && config.database === "mongodb" &&
config.orm && config.orm &&
config.orm !== "mongoose" && config.orm !== "mongoose" &&
@@ -283,28 +242,50 @@ export function processAndValidateFlags(
process.exit(1); process.exit(1);
} }
if (config.orm === "drizzle" && config.database === "mongodb") { if (
providedFlags.has("orm") &&
providedFlags.has("database") &&
config.orm === "drizzle" &&
config.database === "mongodb"
) {
consola.fatal( consola.fatal(
"Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.", "Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.",
); );
process.exit(1); process.exit(1);
} }
if (config.database && config.database !== "none" && config.orm === "none") { if (
providedFlags.has("database") &&
providedFlags.has("orm") &&
config.database &&
config.database !== "none" &&
config.orm === "none"
) {
consola.fatal( consola.fatal(
"Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.", "Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.",
); );
process.exit(1); process.exit(1);
} }
if (config.orm && config.orm !== "none" && config.database === "none") { if (
providedFlags.has("orm") &&
providedFlags.has("database") &&
config.orm &&
config.orm !== "none" &&
config.database === "none"
) {
consola.fatal( consola.fatal(
"ORM selection requires a database. Please choose a database or set '--orm none'.", "ORM selection requires a database. Please choose a database or set '--orm none'.",
); );
process.exit(1); process.exit(1);
} }
if (config.auth && config.database === "none") { if (
providedFlags.has("auth") &&
providedFlags.has("database") &&
config.auth &&
config.database === "none"
) {
consola.fatal( consola.fatal(
"Authentication requires a database. Please choose a database or set '--no-auth'.", "Authentication requires a database. Please choose a database or set '--no-auth'.",
); );
@@ -312,6 +293,8 @@ export function processAndValidateFlags(
} }
if ( if (
providedFlags.has("dbSetup") &&
providedFlags.has("database") &&
config.dbSetup && config.dbSetup &&
config.dbSetup !== "none" && config.dbSetup !== "none" &&
config.database === "none" config.database === "none"
@@ -322,35 +305,60 @@ export function processAndValidateFlags(
process.exit(1); process.exit(1);
} }
if (config.dbSetup === "turso" && config.database !== "sqlite") { if (
providedFlags.has("dbSetup") &&
(config.database ? providedFlags.has("database") : true) &&
config.dbSetup === "turso" &&
config.database !== "sqlite"
) {
consola.fatal( consola.fatal(
"Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup.", "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup.",
); );
process.exit(1); process.exit(1);
} }
if (config.dbSetup === "neon" && config.database !== "postgres") { if (
providedFlags.has("dbSetup") &&
(config.database ? providedFlags.has("database") : true) &&
config.dbSetup === "neon" &&
config.database !== "postgres"
) {
consola.fatal( consola.fatal(
"Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.", "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.",
); );
process.exit(1); process.exit(1);
} }
if (config.dbSetup === "prisma-postgres" && config.database !== "postgres") { if (
providedFlags.has("dbSetup") &&
(config.database ? providedFlags.has("database") : true) &&
config.dbSetup === "prisma-postgres" &&
config.database !== "postgres"
) {
consola.fatal( consola.fatal(
"Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.", "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.",
); );
process.exit(1); process.exit(1);
} }
if (config.dbSetup === "mongodb-atlas" && config.database !== "mongodb") { if (
providedFlags.has("dbSetup") &&
(config.database ? providedFlags.has("database") : true) &&
config.dbSetup === "mongodb-atlas" &&
config.database !== "mongodb"
) {
consola.fatal( consola.fatal(
"MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup.", "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup.",
); );
process.exit(1); process.exit(1);
} }
if (config.dbSetup === "supabase" && config.database !== "postgres") { if (
providedFlags.has("dbSetup") &&
(config.database ? providedFlags.has("database") : true) &&
config.dbSetup === "supabase" &&
config.database !== "postgres"
) {
consola.fatal( consola.fatal(
"Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.", "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.",
); );
@@ -358,144 +366,61 @@ export function processAndValidateFlags(
} }
if (config.dbSetup === "d1") { if (config.dbSetup === "d1") {
if (config.database !== "sqlite") { if (
consola.fatal( (providedFlags.has("dbSetup") && providedFlags.has("database")) ||
"Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.", (providedFlags.has("dbSetup") && !config.database)
); ) {
process.exit(1); if (config.database !== "sqlite") {
consola.fatal(
"Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.",
);
process.exit(1);
}
} }
if (config.runtime !== "workers") { if (
consola.fatal( (providedFlags.has("dbSetup") && providedFlags.has("runtime")) ||
"Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.", (providedFlags.has("dbSetup") && !config.runtime)
); ) {
process.exit(1); if (config.runtime !== "workers") {
consola.fatal(
"Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.",
);
process.exit(1);
}
} }
} }
if (config.dbSetup === "docker" && config.database === "sqlite") { if (
providedFlags.has("dbSetup") &&
providedFlags.has("database") &&
config.dbSetup === "docker" &&
config.database === "sqlite"
) {
consola.fatal( consola.fatal(
"Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.", "Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.",
); );
process.exit(1); process.exit(1);
} }
if (config.dbSetup === "docker" && config.runtime === "workers") { if (
providedFlags.has("dbSetup") &&
providedFlags.has("runtime") &&
config.dbSetup === "docker" &&
config.runtime === "workers"
) {
consola.fatal( consola.fatal(
"Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.", "Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
); );
process.exit(1); process.exit(1);
} }
if ( validateWorkersCompatibility(providedFlags, options, config);
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.backend &&
config.backend !== "hono"
) {
consola.fatal(
`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("backend") &&
config.backend &&
config.backend !== "hono" &&
config.runtime === "workers"
) {
consola.fatal(
`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.orm &&
config.orm !== "drizzle" &&
config.orm !== "none"
) {
consola.fatal(
`Cloudflare Workers runtime (--runtime workers) is only supported with Drizzle ORM (--orm drizzle) or no ORM (--orm none). Current ORM: ${config.orm}. Please use '--orm drizzle', '--orm none', or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("orm") &&
config.orm &&
config.orm !== "drizzle" &&
config.orm !== "none" &&
config.runtime === "workers"
) {
consola.fatal(
`ORM '${config.orm}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Please use '--orm drizzle', '--orm none', or choose a different runtime.`,
);
process.exit(1);
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.database === "mongodb"
) {
consola.fatal(
"Cloudflare Workers runtime (--runtime workers) is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
);
process.exit(1);
}
if (
providedFlags.has("runtime") &&
options.runtime === "workers" &&
config.dbSetup === "docker"
) {
consola.fatal(
"Cloudflare Workers runtime (--runtime workers) is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
);
process.exit(1);
}
if (
providedFlags.has("database") &&
config.database === "mongodb" &&
config.runtime === "workers"
) {
consola.fatal(
"MongoDB database is not compatible with Cloudflare Workers runtime. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
);
process.exit(1);
}
if (
providedFlags.has("db-setup") &&
options.dbSetup === "docker" &&
config.runtime === "workers"
) {
consola.fatal(
"Docker setup (--db-setup docker) is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.",
);
process.exit(1);
}
const hasWebFrontendFlag = (config.frontend ?? []).some((f) => const hasWebFrontendFlag = (config.frontend ?? []).some((f) =>
WEB_FRAMEWORKS.includes(f), isWebFrontend(f),
); );
validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
if (
config.webDeploy &&
config.webDeploy !== "none" &&
!hasWebFrontendFlag &&
providedFlags.has("frontend")
) {
consola.fatal(
"'--web-deploy' requires a web frontend. Please select a web frontend or set '--web-deploy none'.",
);
process.exit(1);
}
return config; return config;
} }
@@ -505,140 +430,20 @@ export function validateConfigCompatibility(config: Partial<ProjectConfig>) {
const effectiveBackend = config.backend; const effectiveBackend = config.backend;
const effectiveFrontend = config.frontend; const effectiveFrontend = config.frontend;
const effectiveApi = config.api; const effectiveApi = config.api;
const effectiveRuntime = config.runtime;
if (effectiveRuntime === "workers" && effectiveBackend !== "hono") { validateApiFrontendCompatibility(effectiveApi, effectiveFrontend);
consola.fatal(
`Cloudflare Workers runtime is only supported with Hono backend. Current backend: ${effectiveBackend}. Please use a different runtime or change to Hono backend.`,
);
process.exit(1);
}
const effectiveOrm = config.orm;
if (
effectiveRuntime === "workers" &&
effectiveOrm !== "drizzle" &&
effectiveOrm !== "none"
) {
consola.fatal(
`Cloudflare Workers runtime is only supported with Drizzle ORM or no ORM. Current ORM: ${effectiveOrm}. Please use a different runtime or change to Drizzle ORM or no ORM.`,
);
process.exit(1);
}
if (effectiveRuntime === "workers" && effectiveDatabase === "mongodb") {
consola.fatal(
"Cloudflare Workers runtime is not compatible with MongoDB database. MongoDB requires Prisma or Mongoose ORM, but Workers runtime only supports Drizzle ORM. Please use a different database or runtime.",
);
process.exit(1);
}
if (effectiveRuntime === "workers" && config.dbSetup === "docker") {
consola.fatal(
"Cloudflare Workers runtime is not compatible with Docker setup. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use a different runtime or change to D1 database setup.",
);
process.exit(1);
}
const includesNuxt = effectiveFrontend?.includes("nuxt");
const includesSvelte = effectiveFrontend?.includes("svelte");
const includesSolid = effectiveFrontend?.includes("solid");
if (
(includesNuxt || includesSvelte || includesSolid) &&
effectiveApi === "trpc"
) {
consola.fatal(
`tRPC API is not supported with '${
includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"
}' frontend. Please use --api orpc or --api none or remove '${
includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"
}' from --frontend.`,
);
process.exit(1);
}
if (config.addons && config.addons.length > 0) { if (config.addons && config.addons.length > 0) {
const webSpecificAddons = ["pwa", "tauri"]; validateAddonsAgainstFrontends(config.addons, effectiveFrontend);
const hasWebSpecificAddons = config.addons.some((addon) =>
webSpecificAddons.includes(addon),
);
const hasCompatibleWebFrontend = effectiveFrontend?.some((f) => {
const isPwaCompatible =
f === "tanstack-router" ||
f === "react-router" ||
f === "solid" ||
f === "next";
const isTauriCompatible =
f === "tanstack-router" ||
f === "react-router" ||
f === "nuxt" ||
f === "svelte" ||
f === "solid" ||
f === "next";
if (config.addons?.includes("pwa") && config.addons?.includes("tauri")) {
return isPwaCompatible && isTauriCompatible;
}
if (config.addons?.includes("pwa")) {
return isPwaCompatible;
}
if (config.addons?.includes("tauri")) {
return isTauriCompatible;
}
return true;
});
if (hasWebSpecificAddons && !hasCompatibleWebFrontend) {
let incompatibleReason = "Selected frontend is not compatible.";
if (config.addons.includes("pwa")) {
incompatibleReason =
"PWA requires tanstack-router, react-router, next, or solid.";
}
if (config.addons.includes("tauri")) {
incompatibleReason =
"Tauri requires tanstack-router, react-router, nuxt, svelte, solid, or next.";
}
consola.fatal(
`Incompatible addon/frontend combination: ${incompatibleReason}`,
);
process.exit(1);
}
config.addons = [...new Set(config.addons)]; config.addons = [...new Set(config.addons)];
} }
if ( validateExamplesCompatibility(
config.examples && config.examples ?? [],
config.examples.length > 0 && effectiveBackend,
!config.examples.includes("none") effectiveDatabase,
) { effectiveFrontend ?? [],
if ( );
config.examples.includes("todo") &&
effectiveBackend !== "convex" &&
effectiveBackend !== "none" &&
effectiveDatabase === "none"
) {
consola.fatal(
"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.",
);
process.exit(1);
}
if (config.examples.includes("ai") && effectiveBackend === "elysia") {
consola.fatal(
"The 'ai' example is not compatible with the Elysia backend.",
);
process.exit(1);
}
if (config.examples.includes("ai") && includesSolid) {
consola.fatal(
"The 'ai' example is not compatible with the Solid frontend.",
);
process.exit(1);
}
}
} }
export function getProvidedFlags(options: CLIInput): Set<string> { export function getProvidedFlags(options: CLIInput): Set<string> {

View File

@@ -0,0 +1,55 @@
---
title: Analytics & Telemetry
description: What we collect, how to disable it, and where to view aggregated insights
---
## What is collected
On successful project creation, the CLI sends a single event (`project_created`) with:
- Selected options (stack choices): `frontend`, `backend`, `runtime`, `database`, `orm`, `api`, `auth`, `addons`, `examples`, `dbSetup`, `webDeploy`, `packageManager`
- Environment data: `cli_version`, `node_version`, `platform`
- A random session id: `distinct_id` like `cli_<random>`
- IP removed: `$ip: null`
Not collected:
- Project name, path, or file contents (explicitly omitted)
- Secrets or environment variables from your machine
## Disable telemetry
Telemetry is enabled by default. To disable:
```bash
# Disable for a single run
BTS_TELEMETRY_DISABLED=1 bun create better-t-stack@latest
```
Add `export BTS_TELEMETRY_DISABLED=1` to your shell profile to make it permanent.
## Where to view analytics
- Charts: [`/analytics`](/analytics)
- Raw JSON snapshot: `https://r2.amanv.dev/analytics-data.json`
- CSV export: `https://r2.amanv.dev/export.csv`
Notes:
- Aggregates are periodically regenerated from incoming events
- Raw data is not publicly exposed; the `/analytics` page presents only summary statistics
## Full transparency
Single event per scaffold; randomized id; no IP or project identifiers. See source code below.
If in doubt, set `BTS_TELEMETRY_DISABLED=1` and proceed. You can still use all CLI features.
## Source code
- CLI event sender: [`apps/cli/src/utils/analytics.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/analytics.ts)
- Telemetry toggle logic: [`apps/cli/src/utils/telemetry.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/telemetry.ts)
- Aggregation script (builds the charts data): [`apps/web/scripts/generate-analytics.ts`](https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/web/scripts/generate-analytics.ts)

View File

@@ -0,0 +1,54 @@
---
title: bts.jsonc
description: What bts.jsonc does and why it matters
---
## What is it?
`bts.jsonc` is a small config file written to your project root when you create a project. It captures the stack choices you selected (frontend, backend, API, DB/ORM, auth, addons, etc.). The file uses JSONC (JSON with comments) and includes a schema for editor hints.
Where: `./bts.jsonc`
## Why it exists
- Required for the `add` command to detect your current stack
- Helps validate compatibility and prefill sensible defaults
If `bts.jsonc` is missing, the `add` command cannot run because the project cannot be detected.
## Safe to delete (with a caveat)
Its safe to delete for normal development; the generated code in `apps/*` and `packages/*` remains the source of truth. However, if you plan to use the `add` command later, you must keep `bts.jsonc` (or recreate it) so the CLI can detect your project.
## Format
The file is JSONC with comments enabled and includes a `$schema` URL for tooling.
```jsonc
// Better-T-Stack configuration file
// safe to delete
{
"$schema": "https://r2.better-t-stack.dev/schema.json",
"version": "x.y.z",
"createdAt": "2025-01-01T00:00:00.000Z",
"frontend": ["tanstack-router"],
"backend": "hono",
"runtime": "bun",
"database": "sqlite",
"orm": "drizzle",
"api": "trpc",
"auth": true,
"addons": ["turborepo"],
"examples": [],
"dbSetup": "none",
"webDeploy": "none",
"packageManager": "bun"
}
```
Notes:
- Values mirror what you selected during project creation
- The file may be updated when you run `add` (e.g., addons or webDeploy)
See also: [`add` command](/docs/cli/add)

View File

@@ -3,6 +3,32 @@ title: CLI Commands
description: Complete reference for all Better-T-Stack CLI commands and options description: Complete reference for all Better-T-Stack CLI commands and options
--- ---
## Quick Usage
<Tabs items={['bun', 'pnpm', 'npm']}>
<Tab value="bun">
```bash
# Interactive setup
bun create better-t-stack@latest
# With project name
bun create better-t-stack@latest my-project
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest
pnpm create better-t-stack@latest my-project
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest
npx create-better-t-stack@latest my-project
```
</Tab>
</Tabs>
## Commands Overview ## Commands Overview
| Command | Description | | Command | Description |
@@ -21,19 +47,39 @@ The primary command for creating new Better-T-Stack projects.
### Basic Usage ### Basic Usage
```bash <Tabs items={['bun', 'pnpm', 'npm']}>
# Interactive setup <Tab value="bun">
npx create-better-t-stack@latest ```bash
# Interactive setup
bun create better-t-stack@latest
# With project name # With project name
npx create-better-t-stack@latest my-project bun create better-t-stack@latest my-project
# With specific directory # With specific directory
npx create-better-t-stack@latest ./path/to/project bun create better-t-stack@latest ./path/to/project
# Use current directory # Use current directory
npx create-better-t-stack@latest . bun create better-t-stack@latest .
``` ```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest
pnpm create better-t-stack@latest my-project
pnpm create better-t-stack@latest ./path/to/project
pnpm create better-t-stack@latest .
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest
npx create-better-t-stack@latest my-project
npx create-better-t-stack@latest ./path/to/project
npx create-better-t-stack@latest .
```
</Tab>
</Tabs>
### Flags Reference ### Flags Reference
@@ -71,13 +117,13 @@ npx create-better-t-stack@latest .
# Web + Mobile # Web + Mobile
--frontend tanstack-router native-nativewind --frontend tanstack-router native-nativewind
# Multiple frontends
--frontend next solid
# No frontend (API-only) # No frontend (API-only)
--frontend none --frontend none
``` ```
Note: Select at most one web frontend (one of: tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid) and at most one native frontend (one of: native-nativewind, native-unistyles).
Do not combine `none` with other frontend values.
#### Backend Options #### Backend Options
| Flag | Values | Default | Description | | Flag | Values | Default | Description |
@@ -200,8 +246,13 @@ npx create-better-t-stack@latest .
- `biome` - Biome linter/formatter - `biome` - Biome linter/formatter
- `husky` - Git hooks with Husky - `husky` - Git hooks with Husky
- `starlight` - Documentation site - `starlight` - Documentation site
- `fumadocs` - Fumadocs docs site
- `ultracite` - Markdown/MDX processing
- `oxlint` - Fast linter
- `none` - No addons - `none` - No addons
Note: Do not combine `none` with other addon values.
**Examples:** **Examples:**
```bash ```bash
--addons turborepo pwa --addons turborepo pwa
@@ -227,6 +278,8 @@ npx create-better-t-stack@latest .
--examples none --examples none
``` ```
Note: Do not combine `none` with other example values.
#### Database Setup Options #### Database Setup Options
| Flag | Values | Default | Description | | Flag | Values | Default | Description |
@@ -295,19 +348,39 @@ Add addons or deployment configurations to existing Better-T-Stack projects.
### Usage ### Usage
```bash <Tabs items={['bun', 'pnpm', 'npm']}>
# Interactive addon selection <Tab value="bun">
npx create-better-t-stack@latest add ```bash
# Interactive addon selection
bun create better-t-stack@latest add
# Add specific addons # Add specific addons
npx create-better-t-stack@latest add --addons pwa tauri bun create better-t-stack@latest add --addons pwa tauri
# Add deployment # Add deployment
npx create-better-t-stack@latest add --web-deploy workers bun create better-t-stack@latest add --web-deploy workers
# Add both # Add both
npx create-better-t-stack@latest add --addons biome --web-deploy workers bun create better-t-stack@latest add --addons biome --web-deploy workers
``` ```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest add
pnpm create better-t-stack@latest add --addons pwa tauri
pnpm create better-t-stack@latest add --web-deploy workers
pnpm create better-t-stack@latest add --addons biome --web-deploy workers
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest add
npx create-better-t-stack@latest add --addons pwa tauri
npx create-better-t-stack@latest add --web-deploy workers
npx create-better-t-stack@latest add --addons biome --web-deploy workers
```
</Tab>
</Tabs>
### Flags ### Flags
@@ -341,9 +414,23 @@ Display Better-T-Stack project sponsors.
### Usage ### Usage
```bash <Tabs items={['bun', 'pnpm', 'npm']}>
npx create-better-t-stack@latest sponsors <Tab value="bun">
``` ```bash
bun create better-t-stack@latest sponsors
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest sponsors
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest sponsors
```
</Tab>
</Tabs>
Shows a list of project sponsors with their GitHub profiles and websites. Shows a list of project sponsors with their GitHub profiles and websites.
@@ -355,9 +442,23 @@ Open Better-T-Stack documentation in your default browser.
### Usage ### Usage
```bash <Tabs items={['bun', 'pnpm', 'npm']}>
npx create-better-t-stack@latest docs <Tab value="bun">
``` ```bash
bun create better-t-stack@latest docs
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest docs
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest docs
```
</Tab>
</Tabs>
Opens: https://better-t-stack.dev/docs Opens: https://better-t-stack.dev/docs
@@ -369,9 +470,23 @@ Open the web-based interactive stack builder.
### Usage ### Usage
```bash <Tabs items={['bun', 'pnpm', 'npm']}>
npx create-better-t-stack@latest builder <Tab value="bun">
``` ```bash
bun create better-t-stack@latest builder
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest builder
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest builder
```
</Tab>
</Tabs>
Opens: https://better-t-stack.dev/new Opens: https://better-t-stack.dev/new
@@ -384,7 +499,7 @@ The web builder provides a visual interface for configuring your stack and gener
### Full-Stack Web Application ### Full-Stack Web Application
```bash ```bash
npx create-better-t-stack@latest my-webapp \ bun create better-t-stack@latest my-webapp \
--frontend tanstack-router \ --frontend tanstack-router \
--backend hono \ --backend hono \
--runtime bun \ --runtime bun \
@@ -400,7 +515,7 @@ npx create-better-t-stack@latest my-webapp \
### Mobile + Web Application ### Mobile + Web Application
```bash ```bash
npx create-better-t-stack@latest my-mobile-app \ bun create better-t-stack@latest my-mobile-app \
--frontend tanstack-router native-nativewind \ --frontend tanstack-router native-nativewind \
--backend hono \ --backend hono \
--database sqlite \ --database sqlite \
@@ -413,7 +528,7 @@ npx create-better-t-stack@latest my-mobile-app \
### Cloudflare Workers Project ### Cloudflare Workers Project
```bash ```bash
npx create-better-t-stack@latest my-workers-app \ bun create better-t-stack@latest my-workers-app \
--frontend tanstack-router \ --frontend tanstack-router \
--backend hono \ --backend hono \
--runtime workers \ --runtime workers \
@@ -426,7 +541,7 @@ npx create-better-t-stack@latest my-workers-app \
### API-Only Project ### API-Only Project
```bash ```bash
npx create-better-t-stack@latest my-api \ bun create better-t-stack@latest my-api \
--frontend none \ --frontend none \
--backend fastify \ --backend fastify \
--runtime node \ --runtime node \
@@ -440,10 +555,10 @@ npx create-better-t-stack@latest my-api \
```bash ```bash
# Use all defaults with immediate setup # Use all defaults with immediate setup
npx create-better-t-stack@latest my-project --yes bun create better-t-stack@latest my-project --yes
# Convex full-stack (automatically configures compatible options) # Convex full-stack (automatically configures compatible options)
npx create-better-t-stack@latest my-convex-app --backend convex bun create better-t-stack@latest my-convex-app --backend convex
``` ```
--- ---
@@ -452,11 +567,29 @@ npx create-better-t-stack@latest my-convex-app --backend convex
Some options have compatibility requirements: Some options have compatibility requirements:
- **Cloudflare Workers**: Only with `--backend hono`, `--orm drizzle`, `--runtime workers` - **Cloudflare Workers**: Only with `--backend hono`, `--orm drizzle` or `--orm none`, `--runtime workers`
- **MongoDB**: Requires `--orm mongoose` or `--orm prisma` - **MongoDB**: Requires `--orm mongoose` or `--orm prisma`
- **Convex**: Incompatible with custom database/ORM/API options - **Convex**: Forces `--no-auth`, `--database none`, `--orm none`, `--api none`, `--runtime none`, `--db-setup none`; sets `--examples todo`; Solid frontend is not supported with Convex
- **PWA**: Requires compatible web frontend - **PWA**: Requires compatible web frontend
- **Tauri**: Requires compatible web frontend - **Tauri**: Requires compatible web frontend
- **AI Examples**: Not compatible with `--backend elysia` or `--frontend solid` - **AI Examples**: Not compatible with `--backend elysia` or `--frontend solid`
- **tRPC + Nuxt/Svelte/Solid**: Use `--api orpc` (tRPC is not supported with `nuxt`, `svelte`, or `solid`)
- **Todo Example**: Requires a database when a backend (other than Convex) is selected
- **Web Deploy**: `--web-deploy workers` requires selecting a web frontend
- **Examples with `--api none`**: Cannot select examples when `--api none` (unless `--backend convex`)
- **Runtime `none`**: Only allowed with `--backend convex` or `--backend none`
- **Mongoose ORM**: Requires `--database mongodb`
- **Drizzle ORM**: Not compatible with `--database mongodb`
- **Database requires ORM**: Selecting a database requires a non-`none` ORM
- **ORM requires database**: Selecting a non-`none` ORM requires a database
- **Auth requires database**: `--auth` requires a non-`none` database
- **DB Setup constraints**:
- `--db-setup turso` → requires `--database sqlite`
- `--db-setup neon` → requires `--database postgres`
- `--db-setup prisma-postgres` → requires `--database postgres`
- `--db-setup mongodb-atlas` → requires `--database mongodb`
- `--db-setup supabase` → requires `--database postgres`
- `--db-setup d1` → requires `--database sqlite` and `--runtime workers`
- `--db-setup docker` → not compatible with `--database sqlite` or `--runtime workers`
The CLI will validate compatibility and show helpful error messages for invalid combinations. The CLI will validate compatibility and show helpful error messages for invalid combinations.

View File

@@ -0,0 +1,35 @@
---
title: add
description: Add addons or deployment to an existing project
---
## Usage
<Tabs items={['bun', 'pnpm', 'npm']}>
<Tab value="bun">
```bash
bun create better-t-stack@latest add [options]
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest add [options]
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest add [options]
```
</Tab>
</Tabs>
### Options
- `--addons` Multiple values
- `--web-deploy` One of: `workers`, `none`
- `--project-dir` Path
- `--install` / `--no-install`
- `--package-manager` One of: `bun`, `pnpm`, `npm`
See also: [Compatibility](/docs/compatibility)

View File

@@ -0,0 +1,25 @@
---
title: builder
description: Open the web-based stack builder
---
## Usage
<Tabs items={['bun', 'pnpm', 'npm']}>
<Tab value="bun">
```bash
bun create better-t-stack@latest builder
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest builder
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest builder
```
</Tab>
</Tabs>

View File

@@ -0,0 +1,25 @@
---
title: docs
description: Open the documentation
---
## Usage
<Tabs items={['bun', 'pnpm', 'npm']}>
<Tab value="bun">
```bash
bun create better-t-stack@latest docs
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest docs
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest docs
```
</Tab>
</Tabs>

View File

@@ -0,0 +1,23 @@
---
title: CLI
description: Command reference index
---
<Cards>
<Card href="/docs/cli/init" title="init">
Create a new BetterTStack project (prompts or --yes)
</Card>
<Card href="/docs/cli/add" title="add">
Add addons or deployment to an existing project
</Card>
<Card href="/docs/cli/builder" title="builder">
Open the web-based Stack Builder
</Card>
<Card href="/docs/cli/docs" title="docs">
Open documentation
</Card>
<Card href="/docs/cli/sponsors" title="sponsors">
View project sponsors
</Card>
</Cards>

View File

@@ -0,0 +1,35 @@
---
title: init
description: Create a new Better-T-Stack project
---
## Usage
<Tabs items={['bun', 'pnpm', 'npm']}>
<Tab value="bun">
```bash
bun create better-t-stack@latest [project-directory]
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest [project-directory]
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest [project-directory]
```
</Tab>
</Tabs>
You can pass `.` to use the current directory.
### Skip prompts
```bash
bun create better-t-stack@latest my-app --yes
```
See also: [Compatibility](/docs/compatibility)

View File

@@ -0,0 +1,5 @@
{
"title": "CLI",
"defaultOpen": true,
"pages": ["index", "init", "add", "builder", "docs", "sponsors"]
}

View File

@@ -0,0 +1,25 @@
---
title: sponsors
description: View project sponsors
---
## Usage
<Tabs items={['bun', 'pnpm', 'npm']}>
<Tab value="bun">
```bash
bun create better-t-stack@latest sponsors
```
</Tab>
<Tab value="pnpm">
```bash
pnpm create better-t-stack@latest sponsors
```
</Tab>
<Tab value="npm">
```bash
npx create-better-t-stack@latest sponsors
```
</Tab>
</Tabs>

View File

@@ -0,0 +1,29 @@
---
title: Compatibility
description: Valid and invalid combinations across frontend, backend, runtime, database, and addons
---
## Rules
- **Convex backend**: Disables authentication, database, ORM, and API options
- **Backend `none`**: Forces API, ORM, database, authentication, and runtime to `none`; disables examples
- **Frontend `none`**: Backend-only project; PWA/Tauri/examples may be disabled
- **API `none`**: No tRPC/oRPC setup; use framework-native APIs
- **Database `none`**: Disables ORM and authentication
- **ORM `none`**: No ORM setup; manage DB manually
- **Runtime `none`**: Only with Convex backend or when backend is `none`
## Cloudflare Workers
- Backend: `hono` only
- Database: `sqlite` with Cloudflare D1
- ORM: `drizzle` (or none)
- Not compatible with MongoDB
## Framework Notes
- SvelteKit, Nuxt, and SolidJS frontends are only compatible with `orpc` API layer
- PWA addon requires React (TanStack Router/React Router) or SolidJS
- Tauri addon requires React (TanStack Router/React Router), Nuxt, SvelteKit, SolidJS, or Next.js
- AI example is not compatible with Elysia backend or SolidJS frontend

View File

@@ -0,0 +1,94 @@
---
title: Contributing
description: How to set up your environment and contribute changes
---
## Overview
This project is a monorepo with two main apps:
- CLI: `apps/cli`
- Documentation site: `apps/web`
> Note: Planning to add a new feature? Please open an issue first to confirm it aligns with the project's future goals. We don't want you to spend time on work that might not land.
## Setup
### Prerequisites
- Node.js 20+
- Bun (recommended)
- Git
### Install
```bash
git clone https://github.com/AmanVarshney01/create-better-t-stack.git
cd create-better-t-stack
bun install
```
## Develop the CLI
```bash
cd apps/cli
# optional global link for testing anywhere
bun link
# run in watch mode
bun dev:cli
```
## Develop the Docs
```bash
# from repo root
bun dev:web
```
## Contribution Flow
1. Open an issue/discussion before starting major work
2. Create a feature branch
3. Make changes following existing code style
4. Update docs as needed
5. Test and format
```bash
# CLI
cd apps/cli && bun dev:cli
# Web
bun dev:web
# Format
bun run format
# Type checks
bun check
```
6. (CLI changes) Add a changeset
```bash
bunx changeset select create-better-t-stack
# Choose 'patch' for small fixes, 'minor' for features
# Never choose 'major'
```
7. Commit and push
```bash
git add .
git commit -m "feat(web): ..." # or fix(cli): ...
git push origin <your-branch>
```
8. Open a Pull Request and link any related issues
## Help
- Issues and Discussions on GitHub
- Discord: https://discord.gg/ZYsbjpDaM5
See full contributor guide in the repository: `.github/CONTRIBUTING.md`.

View File

@@ -1,334 +1,46 @@
--- ---
title: Frequently Asked Questions title: Frequently Asked Questions
description: Common questions and answers about Better-T-Stack CLI description: Short answers to common beginner questions
--- ---
## General Questions ## General
### What is Better-T-Stack? ### What is BetterTStack?
An opinionated CLI that scaffolds fullstack TypeScript projects (frontend, backend, API, DB/ORM, auth, addons) with a clean monorepo. See the Quick Start on the docs home.
Better-T-Stack is a modern CLI tool that helps you scaffold end-to-end type-safe TypeScript projects. It provides opinionated, production-ready configurations for full-stack applications with support for multiple frontend frameworks, backend frameworks, databases, and deployment options. ### Do I need to install anything globally?
No. Run the CLI directly with your package manager. See Quick Start and the percommand pages under CLI.
### How is Better-T-Stack different from other scaffolding tools? ### Which package manager can I use?
`npm`, `pnpm`, or `bun` (all supported).
- **End-to-End Type Safety**: TypeScript across your entire stack with proper type sharing ### What Node.js version is required?
- **Modern Stack Focus**: Latest versions of popular frameworks and tools Node.js 18+ (LTS recommended).
- **Production Ready**: Configurations used in real production applications
- **Highly Customizable**: Mix and match technologies based on your needs
- **Monorepo Structure**: Organized project structure with shared packages
- **Database Integration**: Built-in database setup and ORM configuration
- **Authentication**: Integrated auth with Better-Auth
- **Multi-Platform**: Web, mobile, and desktop app support in one project
### Is Better-T-Stack free to use? ### Can I use this with an existing project?
The CLI is for new projects. You can migrate gradually or use `add` to extend a BetterTStack project.
Yes, Better-T-Stack is completely free and open-source under the MIT license. You can use it for personal and commercial projects without any restrictions. ### Where do generated files live?
See Project Structure for highlevel layouts (serverbased vs. Convex, optional web/native).
### Do I need to know all these technologies to use Better-T-Stack? ## Choosing options
No! Better-T-Stack is designed to help you learn modern full-stack development. Each generated project includes: ### Does the CLI recommend a stack?
- Comprehensive README with setup instructions No. Pick what fits your needs. The CLI validates compatibility. See CLI (per command) and Compatibility for rules.
- Example code and patterns
- TypeScript for better developer experience
- Best practices and folder structure
--- ### Im unsure between tRPC and oRPC / Drizzle and Prisma
See Compatibility for guidance and constraints. Both pairs work well; choose based on team and hosting needs.
## Installation & Setup ## Common issues
### Which package manager should I use? ### My mobile app cant connect to the backend (Expo)
Set `EXPO_PUBLIC_SERVER_URL` in `apps/native/.env` to your machine IP (not `localhost`), check firewall, or try `npx expo start --tunnel`.
You can use any of the major package managers:
- **npm**: Most widely supported, comes with Node.js
- **pnpm**: Faster installs, better disk space efficiency
- **bun**: All-in-one runtime, extremely fast
For monorepo projects, we recommend **pnpm** for its excellent workspace support.
### Do I need to install the CLI globally?
No, you can use npx to run the latest version without installation:
```bash
npx create-better-t-stack@latest my-project
```
This ensures you always use the latest version with the newest features and bug fixes.
### What Node.js version do I need?
Better-T-Stack requires **Node.js 18 or higher**. We recommend using the latest LTS version for the best experience.
### Can I use Better-T-Stack with existing projects?
Better-T-Stack is designed for new projects. For existing projects, you can:
1. Create a new Better-T-Stack project
2. Gradually migrate your code
3. Use the `add` command to add specific features to existing Better-T-Stack projects
---
## Configuration & Stack Choices
### Can I change my stack choices after creating a project?
Some changes are possible:
- **Easy**: Add addons, examples, or deployment configurations using the `add` command
- **Medium**: Switch between compatible ORMs or databases (requires manual migration)
- **Hard**: Change frontend/backend frameworks (requires significant refactoring)
It's best to plan your stack carefully during initial setup.
### What's the difference between tRPC and oRPC?
- **tRPC**: End-to-end type safety with TypeScript inference, great for TypeScript-only projects
- **oRPC**: OpenAPI-compatible type-safe APIs, better for teams using multiple languages or requiring OpenAPI specs
Both provide excellent type safety, choose based on your team's needs.
### Should I use Drizzle or Prisma?
- **Drizzle**: TypeScript-first, lightweight, great for edge deployments, SQL-like syntax
- **Prisma**: Feature-rich, mature ecosystem, great tooling, GraphQL-like schema
Choose Drizzle for modern TypeScript projects and Prisma for feature-rich applications.
### What's the recommended stack for beginners?
For beginners, we recommend:
```bash
npx create-better-t-stack@latest my-first-project \
--frontend tanstack-router \
--backend hono \
--database sqlite \
--orm drizzle \
--auth \
--addons turborepo biome
```
This provides a simple but powerful full-stack setup that's easy to understand and deploy.
---
## Compatibility Questions
### Can I use MongoDB with Drizzle?
No, Drizzle doesn't support MongoDB. For MongoDB, use:
- **Prisma ORM**: Full ORM support for MongoDB
- **Mongoose**: Traditional MongoDB object modeling
### Why can't I use tRPC with Nuxt/SvelteKit/SolidJS?
tRPC is primarily designed for React ecosystems. For these frameworks, use:
- **oRPC**: Provides similar type safety with broader framework support
- **None**: Use the framework's built-in API capabilities
### Can I use Cloudflare Workers with any backend?
Cloudflare Workers runtime only supports:
- **Backend**: Hono only
- **Database**: SQLite with Cloudflare D1
- **ORM**: Drizzle only
This is due to the serverless nature and limitations of the Workers environment.
### Which addons work with which frontends?
| Addon | Compatible Frontends |
|-------|---------------------|
| PWA | TanStack Router, React Router, SolidJS, Next.js |
| Tauri | TanStack Router, React Router, Nuxt, SvelteKit, SolidJS, Next.js |
| Turborepo | All frontends |
| Biome | All frontends |
| Husky | All frontends |
| Starlight | All frontends |
---
## Database & Hosting
### What database should I choose for production?
**For small to medium applications:**
- **SQLite + Turso**: Excellent performance, easy scaling
- **PostgreSQL + Neon**: Serverless PostgreSQL, great for startups
**For large applications:**
- **PostgreSQL + Supabase**: Full backend-as-a-service
- **MongoDB + Atlas**: NoSQL flexibility with managed hosting
### Do I need Docker for development?
Docker is optional and only required for:
- **Database Setup**: If you choose `--db-setup docker`
- **Local Development**: Some setups like Supabase local development
Many database options (Turso, Neon, MongoDB Atlas) don't require Docker.
### How do I deploy my Better-T-Stack application?
Better-T-Stack projects are configured for easy deployment:
**Frontend:**
- **Vercel**: Zero-config deployment for Next.js, React apps
- **Netlify**: Static site deployment
- **Cloudflare Workers**: Edge deployment with `--web-deploy workers`
**Backend:**
- **Railway**: Easy backend deployment
- **Fly.io**: Global application deployment
- **Cloudflare Workers**: Serverless edge deployment
---
## Development & Troubleshooting
### My project won't start after creation. What should I do?
1. **Check Node.js version**: Ensure you're using Node.js 18+
2. **Install dependencies**: Run `npm install` in the project directory
3. **Check environment variables**: Copy `.env.example` to `.env` and fill in values
4. **Database setup**: Run database migrations if using a database
5. **Check the README**: Each project includes detailed setup instructions
### I'm getting TypeScript errors. How do I fix them?
1. **Install dependencies**: Ensure all packages are installed
2. **Restart TypeScript server**: In VS Code, use Ctrl/Cmd + Shift + P → "TypeScript: Restart TS Server"
3. **Check imports**: Ensure all imports are correct and packages are installed
4. **Update types**: Run `npm run check-types` to see detailed errors
### How do I update dependencies in my project?
```bash
# Check for updates
npx taze -r
# Update all dependencies
npm update
# Or with other package managers
pnpm update
bun update
```
### Can I use Better-T-Stack with VS Code?
Yes! Better-T-Stack projects work excellently with VS Code. We recommend these extensions:
- TypeScript and JavaScript Language Features (built-in)
- Tailwind CSS IntelliSense
- Prisma or Drizzle Kit extensions
- ESLint (if using Biome addon)
### My mobile app won't connect to the backend. What's wrong?
This is a common issue with Expo and local development:
1. **Check environment variables**: Update `EXPO_PUBLIC_SERVER_URL` in `apps/native/.env`
2. **Use your local IP**: Replace `localhost` with your computer's IP address
3. **Check firewall**: Ensure your firewall allows connections on port 3000
4. **Use tunnel**: Consider using `npx expo start --tunnel`
---
## Advanced Usage
### Can I customize the generated templates?
Currently, Better-T-Stack doesn't support custom templates, but you can:
1. Fork the repository and modify templates
2. Create a feature request for specific customizations
3. Modify the generated project after creation
### How do I contribute to Better-T-Stack?
We welcome contributions! Here's how to get started:
1. **Fork the repository** on GitHub
2. **Clone your fork** locally
3. **Install dependencies**: `pnpm install`
4. **Make your changes** and test them
5. **Submit a pull request** with a clear description
### Can I use Better-T-Stack in my company/team?
Absolutely! Better-T-Stack is perfect for:
- **Standardizing project structure** across teams
- **Onboarding new developers** with consistent setup
- **Rapid prototyping** and MVP development
- **Client projects** with proven, production-ready configurations
### How do I disable telemetry? ### How do I disable telemetry?
Set `BTS_TELEMETRY_DISABLED=1` (shell env). For one run, prefix the command; to make it permanent, export it in your shell profile.
Better-T-Stack collects anonymous usage data to improve the tool. To disable: ## Getting help
```bash - Docs: Quick Start, CLI, Project Structure, Compatibility
# Disable for single run - Ask/Report: GitHub Issues & Discussions
BTS_TELEMETRY_DISABLED=1 npx create-better-t-stack@latest my-app - Community: Discord
# Disable globally
export BTS_TELEMETRY_DISABLED=1
```
Add the export to your shell profile (`.bashrc`, `.zshrc`, etc.) to make it permanent.
---
## Getting Help
### Where can I get help?
- **Documentation**: Comprehensive guides at [better-t-stack.dev/docs](https://better-t-stack.dev/docs)
- **GitHub Issues**: Report bugs or request features
- **GitHub Discussions**: Community support and questions
- **Discord/Twitter**: Follow for updates and community discussion
### How do I report a bug?
1. **Search existing issues** to avoid duplicates
2. **Create a new issue** with:
- Clear description of the problem
- Steps to reproduce
- Your system information (OS, Node.js version, etc.)
- Generated project configuration
- Error messages or screenshots
### How do I request a new feature?
1. **Check existing feature requests** in GitHub Issues
2. **Create a new issue** with the "feature request" label
3. **Describe the feature** and its use case
4. **Explain why** it would benefit the community
### Is there a community?
Yes! You can connect with other Better-T-Stack users:
- **GitHub Discussions**: Ask questions and share projects
- **Twitter**: Follow [@AmanVarshney01](https://twitter.com/AmanVarshney01) for updates
- **Show your projects**: Tag us when you build something with Better-T-Stack!
---
## Sponsorship & Support
### How can I support Better-T-Stack?
- **⭐ Star the repository** on GitHub
- **🐛 Report bugs** and suggest improvements
- **💰 Sponsor the project** on GitHub Sponsors
- **📢 Share with others** who might find it useful
- **🤝 Contribute code** or documentation
### Who sponsors Better-T-Stack?
View current sponsors by running:
```bash
npx create-better-t-stack@latest sponsors
```
Or visit: [github.com/sponsors/AmanVarshney01](https://github.com/sponsors/AmanVarshney01)
---
*Still have questions? Feel free to ask in [GitHub Discussions](https://github.com/AmanVarshney01/create-better-t-stack/discussions) or check our [documentation](https://better-t-stack.dev/docs).*

View File

@@ -1,118 +1,127 @@
--- ---
title: Getting Started title: Quick Start
description: A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations description: Create your first Better-T-Stack project in minutes
--- ---
> ⚠️ **Warning:** Documentation is a work in progress. ## Philosophy
## What is Better-T-Stack? - With Better T Stack, you freely choose only the parts you want; nothing extra is added.
- Pick your frontend, backend, API layer, database, ORM, auth, addons — or skip any of them. Compatibility is enforced.
- No lockin: its a generator, not a framework fork.
Better-T-Stack is designed to eliminate the complexity of setting up modern TypeScript projects. Instead of spending hours configuring build tools, type systems, databases, and deployment pipelines, you can get a production-ready project structure in minutes. ## Get Started
### Key Features ### Stack Builder (UI)
- **🏗️ Full-Stack Ready**: Choose from multiple frontend and backend frameworks - Visit [/new](/new) to pick your stack and copy the generated command
- **🔒 End-to-End Type Safety**: TypeScript across your entire application stack - Or open it via:
- **🗄️ Database Integration**: Support for SQLite, PostgreSQL, MySQL, and MongoDB
- **🔐 Built-in Authentication**: Email/password auth with Better-Auth
- **📱 Multi-Platform**: Web, mobile (React Native), and desktop (Tauri) support
- **☁️ Deployment Ready**: Configured for modern hosting platforms
- **⚡ Modern Tooling**: Latest versions of your favorite tools and frameworks
### Supported Technologies
#### Frontend Frameworks
- **React** with TanStack Router or React Router
- **Next.js** - Full-stack React framework
- **SvelteKit** - Web framework for Svelte
- **Nuxt** - Vue.js framework
- **SolidJS** - Performant reactive framework
- **TanStack Start** - SSR with TanStack Router
- **React Native** - Mobile development with Expo
#### Backend Frameworks
- **Hono** - Lightweight, ultrafast web framework
- **Express** - Popular Node.js framework
- **Fastify** - Fast, low-overhead framework
- **Elysia** - Type-safe, high-performance framework
- **Convex** - Reactive backend-as-a-service
- **Next.js API Routes** - Full-stack React
#### Databases & ORMs
- **Databases**: SQLite/Turso, PostgreSQL, MySQL, MongoDB
- **ORMs**: Drizzle (TypeScript-first), Prisma (feature-rich), Mongoose
- **Hosting**: Neon, Supabase, MongoDB Atlas, Cloudflare D1
#### API Layers
- **tRPC** - End-to-end type-safe APIs
- **oRPC** - OpenAPI-compatible type-safe APIs
### Why Choose Better-T-Stack?
#### Traditional Setup Problems
- ⏰ Hours of configuration and setup
- 🔧 Complex toolchain integration
- 📚 Overwhelming technology choices
- 🐛 Configuration bugs and compatibility issues
- 🏗️ Inconsistent project structures
#### Better-T-Stack Solutions
- ⚡ **Quick Setup**: Get started in under 2 minutes
- 🎯 **Curated Choices**: Pre-selected, compatible technology combinations
- 📋 **Best Practices**: Industry-standard configurations out of the box
- 🔄 **Consistent Structure**: Standardized monorepo organization
- 🧪 **Battle-Tested**: Configurations used in production applications
## Quick Example
```bash ```bash
# Create a new project bun create better-t-stack@latest builder
npx create-better-t-stack@latest my-app
# Choose your stack interactively or use flags
npx create-better-t-stack@latest my-app \
--frontend tanstack-router \
--backend hono \
--database postgres \
--orm drizzle \
--auth \
--addons pwa turborepo
``` ```
## Project Types ### CLI (prompts)
Better-T-Stack supports various project configurations: ```bash
bun create better-t-stack@latest
```
### Full-Stack Web Applications Follow the interactive prompts to choose your frontend, backend, database, ORM, API layer, and addons.
Perfect for modern web apps with React, Vue, or Svelte frontends backed by type-safe APIs.
### Mobile Applications Skip prompts and use the default stack:
Build React Native apps with Expo, sharing type definitions with your backend.
### Desktop Applications ```bash
Create cross-platform desktop apps using Tauri with your web frontend. bun create better-t-stack@latest my-app --yes
```
### API-Only Projects Prefer a visual flow? Use the Stack Builder at [/new](/new) to pick your stack and copy the generated command. You can also open it via:
Build standalone APIs and microservices with your preferred backend framework.
### Monorepo Projects ```bash
Organize multiple applications (web, mobile, API) in a single repository with shared packages. bun create better-t-stack@latest builder
```
## Who Should Use Better-T-Stack? ## Common Setups
- **Indie Developers**: Quickly prototype and build full-stack applications ### Default Stack
- **Startups**: Get to market faster with production-ready project structure
- **Teams**: Standardize project setup across your organization
- **Students**: Learn modern full-stack development with best practices
- **Agencies**: Rapidly scaffold client projects with consistent quality
## What's Next? ```bash
bun create better-t-stack@latest my-webapp \
--frontend tanstack-router \
--backend hono \
--database sqlite \
--orm drizzle \
--auth \
--addons turborepo
```
Ready to get started? Check out our [Quick Start Guide](/docs/quick-start) to create your first Better-T-Stack project, or explore the [Configuration Options](/docs/frontend) to learn about all available technologies and features. ### Convex + React
### Need Help? ```bash
bun create better-t-stack@latest my-convex-app \
--frontend tanstack-router \
--backend convex
```
- 📖 **Documentation**: Comprehensive guides and references ### API Only
- 🐛 **Issues**: Report bugs on [GitHub](https://github.com/AmanVarshney01/create-better-t-stack/issues)
- 💬 **Discussions**: Community support and questions ```bash
- 🌟 **Star us**: Show support on [GitHub](https://github.com/AmanVarshney01/create-better-t-stack) bun create better-t-stack@latest my-api \
--frontend none \
--backend fastify \
--runtime node \
--database postgres \
--orm prisma \
--api trpc
```
### Mobile App (Expo)
```bash
bun create better-t-stack@latest my-native \
--frontend native-nativewind \
--backend hono \
--database sqlite \
--orm drizzle \
--auth
```
### Empty Monorepo
```bash
bun create better-t-stack@latest my-workspace \
--frontend none \
--backend none
```
## Flags Cheat Sheet
See the full list in the [CLI Reference](/docs/cli). Key flags:
- `--frontend`: tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none
- `--backend`: hono, express, fastify, elysia, next, convex, none
- `--runtime`: bun, node, workers, none
- `--database`: sqlite, postgres, mysql, mongodb, none
- `--orm`: drizzle, prisma, mongoose, none
- `--api`: trpc, orpc, none
- `--addons`: turborepo, pwa, tauri, biome, husky, starlight, none
- `--examples`: todo, ai, none
## Next Steps
<Cards>
<Card href="/docs/cli" title="CLI (per-command)">
Flags, usage, and examples for each command
</Card>
<Card href="/docs/project-structure" title="Project Structure">
See how web/server/native and Convex layouts are generated
</Card>
<Card href="/docs/compatibility" title="Compatibility">
Valid combinations for backend, runtime, database, ORM, API
</Card>
<Card href="/docs/bts-config" title="bts.jsonc">
Required for the add command; safe to delete if you dont use add
</Card>
<Card href="/docs/contributing" title="Contributing">
Dev setup and contribution flow
</Card>
</Cards>

View File

@@ -1,236 +0,0 @@
---
title: Installation
description: How to install and set up Better-T-Stack CLI
---
## System Requirements
Before installing Better-T-Stack, ensure your system meets these requirements:
- **Node.js**: Version 18 or higher
- **Package Manager**: npm, pnpm, or bun
- **Git**: For repository initialization (optional but recommended)
### Optional Dependencies
Depending on your project configuration, you may need:
- **Docker**: For local database development with Docker Compose
- **Rust & System Dependencies**: For Tauri desktop applications
- **Cloudflare CLI**: For Workers deployment
## Quick Start (Recommended)
The fastest way to get started is using npx, which runs the latest version without installation:
```bash
npx create-better-t-stack@latest my-project
```
This command will:
1. Download the latest version of the CLI
2. Run the interactive setup wizard
3. Create your project in the `my-project` directory
## Package Manager Specific Commands
### npm
```bash
# Run without installing
npx create-better-t-stack@latest my-project
# Or install globally
npm install -g create-better-t-stack
create-better-t-stack my-project
```
### pnpm
```bash
# Run without installing (recommended)
pnpm create better-t-stack@latest my-project
# Or install globally
pnpm add -g create-better-t-stack
create-better-t-stack my-project
```
### bun
```bash
# Run without installing (recommended)
bun create better-t-stack@latest my-project
# Or install globally
bun add -g create-better-t-stack
create-better-t-stack my-project
```
## Global Installation
If you frequently create new projects, you might want to install the CLI globally:
<Tabs items={['npm', 'pnpm', 'bun']}>
<Tab value="npm">
```bash
npm install -g create-better-t-stack
```
</Tab>
<Tab value="pnpm">
```bash
pnpm add -g create-better-t-stack
```
</Tab>
<Tab value="bun">
```bash
bun add -g create-better-t-stack
```
</Tab>
</Tabs>
After global installation, you can run:
```bash
create-better-t-stack my-project
```
## Verification
Verify your installation by checking the version:
```bash
# If installed globally
create-better-t-stack --version
# Or with npx
npx create-better-t-stack@latest --version
```
You should see output similar to:
```
2.26.3
```
## Development Installation
For contributing to Better-T-Stack or running the latest development version:
```bash
# Clone the repository
git clone https://github.com/AmanVarshney01/create-better-t-stack.git
cd create-better-t-stack
# Install dependencies
pnpm install
# Build the CLI
cd apps/cli
pnpm build
# Link for local development
pnpm link --global
```
## Troubleshooting
### Common Issues
#### Permission Errors (npm)
If you encounter permission errors with npm global installation:
```bash
# Use npx instead (recommended)
npx create-better-t-stack@latest my-project
# Or configure npm to use a different directory
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
```
#### Node.js Version Issues
Ensure you're using Node.js 18 or higher:
```bash
node --version
```
If you need to upgrade Node.js:
- **Using nvm**: `nvm install 18 && nvm use 18`
- **Using n**: `n 18`
- **Download**: Visit [nodejs.org](https://nodejs.org/)
#### Package Manager Not Found
If you prefer a specific package manager but it's not installed:
```bash
# Install pnpm
npm install -g pnpm
# Install bun
curl -fsSL https://bun.sh/install | bash
```
#### Network Issues
If you experience network timeouts or connection issues:
```bash
# Try with different registry
npm config set registry https://registry.npmjs.org/
# Or clear npm cache
npm cache clean --force
```
## Next Steps
Once installed, you're ready to create your first project:
1. **Quick Start**: Follow our [Quick Start Guide](/docs/quick-start)
2. **Configuration**: Learn about [Configuration Options](/docs/frontend)
3. **CLI Reference**: Explore all [CLI Commands](/docs/cli-commands)
## Staying Updated
To ensure you're always using the latest version with bug fixes and new features:
### Using npx (Automatic)
When using `npx create-better-t-stack@latest`, you automatically get the latest version.
### Global Installation Updates
If you have it installed globally, update regularly:
<Tabs items={['npm', 'pnpm', 'bun']}>
<Tab value="npm">
```bash
npm update -g create-better-t-stack
```
</Tab>
<Tab value="pnpm">
```bash
pnpm update -g create-better-t-stack
```
</Tab>
<Tab value="bun">
```bash
bun update -g create-better-t-stack
```
</Tab>
</Tabs>
### Release Notes
Stay informed about new features and changes:
- **GitHub Releases**: [View releases](https://github.com/AmanVarshney01/create-better-t-stack/releases)
- **Changelog**: Check the repository for detailed changes
- **Breaking Changes**: Major version updates may include breaking changes

View File

@@ -1,32 +1,12 @@
{ {
"pages": [ "pages": [
"index", "index",
"installation", "cli",
"quick-start",
"project-structure", "project-structure",
"---Configuration Options---", "bts-config",
"frontend", "analytics",
"backend", "contributing",
"database", "compatibility",
"orm", "faq"
"authentication",
"runtime",
"api-layer",
"---Addons & Features---",
"addons",
"examples",
"deployment",
"---Database Setup---",
"database-providers",
"---CLI Reference---",
"cli-commands",
"cli-flags",
"---Guides---",
"migration-guide",
"troubleshooting",
"best-practices",
"---FAQ---",
"faq",
"compatibility"
] ]
} }

View File

@@ -0,0 +1,60 @@
---
title: Project Structure
description: High-level overview of the generated monorepo layout
---
import { File, Folder, Files } from 'fumadocs-ui/components/files';
### Server-based projects
<Files>
<Folder name="apps" defaultOpen>
<Folder name="server" defaultOpen>
<Folder name="src" />
</Folder>
<Folder name="web" defaultOpen disabled>
<Folder name="src" />
<Folder name="public" />
</Folder>
<Folder name="native" defaultOpen disabled>
<Folder name="app" />
</Folder>
</Folder>
</Files>
Notes:
- `apps/server` is present for backends like `hono`, `express`, `fastify`, `elysia`, `next`.
- `apps/web` and `apps/native` are optional; they appear only if you select a web or native frontend.
### Convex-based projects
<Files>
<Folder name="apps" defaultOpen>
<Folder name="web" defaultOpen disabled>
<Folder name="src" />
<Folder name="public" />
</Folder>
<Folder name="native" defaultOpen disabled>
<Folder name="app" />
</Folder>
</Folder>
<Folder name="packages" defaultOpen>
<Folder name="backend" defaultOpen>
<Folder name="convex" />
</Folder>
</Folder>
</Files>
Notes:
- Convex replaces the server app; `packages/backend` is generated instead of `apps/server`.
- Auth, DB/ORM, and API scaffolding are disabled for Convex presets.
### Where features land (high level)
- API layer: merged into `apps/server` and `apps/web` when applicable.
- Database & ORM: merged into `apps/server` when both are selected.
- Authentication: merged into `apps/server`, `apps/web`, and `apps/native` when enabled and compatible.
- Addons: PWA merges into `apps/web`; others merge at the appropriate location.
- Web deployment (Workers): deployment files merge into `apps/web` per frontend.
- Runtime extras (Workers): Workers-specific files added at the repo root.

View File

@@ -18,7 +18,7 @@ const Footer = () => {
</p> </p>
<div className="flex space-x-4"> <div className="flex space-x-4">
<Link <Link
href="https://github.com/better-t-stack/create-better-t-stack" href="https://github.com/AmanVarshney01/create-better-t-stack"
target="_blank" target="_blank"
className="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2" className="inline-flex items-center justify-center rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"
aria-label="GitHub Repository" aria-label="GitHub Repository"
@@ -44,7 +44,7 @@ const Footer = () => {
<li> <li>
<Link <Link
target="_blank" target="_blank"
href="https://github.com/better-t-stack/create-better-t-stack" href="https://github.com/AmanVarshney01/create-better-t-stack"
className="inline-block transition-colors hover:text-primary focus:text-primary focus:outline-none" className="inline-block transition-colors hover:text-primary focus:text-primary focus:outline-none"
> >
GitHub Repository GitHub Repository

View File

@@ -85,7 +85,7 @@ export default function Navbar() {
target: "_blank", target: "_blank",
}, },
{ {
href: "https://www.github.com/better-t-stack/create-better-t-stack", href: "https://www.github.com/AmanVarshney01/create-better-t-stack",
label: "GitHub", label: "GitHub",
icon: <Github className="size-4" />, icon: <Github className="size-4" />,
target: "_blank", target: "_blank",

View File

@@ -48,7 +48,7 @@ export function AnalyticsHeader({
<span className=" text-muted-foreground"> <span className=" text-muted-foreground">
Source:{" "} Source:{" "}
<Link <Link
href="https://github.com/amanvarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/analytics.ts" href="https://github.com/AmanVarshney01/create-better-t-stack/blob/main/apps/cli/src/utils/analytics.ts"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-accent underline hover:text-primary" className="text-accent underline hover:text-primary"

View File

@@ -1,4 +1,3 @@
import { Banner } from "fumadocs-ui/components/banner";
import { DocsLayout, type DocsLayoutProps } from "fumadocs-ui/layouts/docs"; import { DocsLayout, type DocsLayoutProps } from "fumadocs-ui/layouts/docs";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import { baseOptions } from "@/app/layout.config"; import { baseOptions } from "@/app/layout.config";
@@ -15,12 +14,5 @@ const docsOptions: DocsLayoutProps = {
}; };
export default function Layout({ children }: { children: ReactNode }) { export default function Layout({ children }: { children: ReactNode }) {
return ( return <DocsLayout {...docsOptions}>{children}</DocsLayout>;
<>
<Banner variant="rainbow">
WORK IN PROGRESS DONT TAKE REFERENCE!!!
</Banner>
<DocsLayout {...docsOptions}>{children}</DocsLayout>
</>
);
} }

View File

@@ -14,7 +14,7 @@
}, },
"apps/cli": { "apps/cli": {
"name": "create-better-t-stack", "name": "create-better-t-stack",
"version": "2.29.2", "version": "2.29.3",
"bin": { "bin": {
"create-better-t-stack": "dist/index.js", "create-better-t-stack": "dist/index.js",
}, },