Skip to content

Commit 51cfb35

Browse files
feat(web): improve docs and refactor cli (#476)
1 parent defa0e9 commit 51cfb35

34 files changed

+1329
-1147
lines changed

README.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,33 +25,36 @@ pnpm create better-t-stack@latest
2525

2626
## Features
2727

28-
- **Zero-config setup** with interactive CLI wizard
29-
- **End-to-end type safety** from database to frontend via tRPC
30-
- **Modern stack** with React, Hono/Elysia, and TanStack libraries
31-
- **Multi-platform** supporting web, mobile (Expo), and desktop applications
32-
- **Database flexibility** with SQLite (Turso) or PostgreSQL options
33-
- **ORM choice** between Drizzle or Prisma
34-
- **Built-in authentication** with Better-Auth
35-
- **Optional PWA support** for installable web applications
36-
- **Desktop app capabilities** with Tauri integration
37-
- **Monorepo architecture** powered by Turborepo
28+
- Frontend: React (TanStack Router, React Router, TanStack Start), Next.js, Nuxt, Svelte, Solid, React Native (NativeWind/Unistyles), or none
29+
- Backend: Hono, Express, Fastify, Elysia, Next API Routes, Convex, or none
30+
- API: tRPC or oRPC (or none)
31+
- Runtime: Bun, Node.js, or Cloudflare Workers
32+
- Databases: SQLite, PostgreSQL, MySQL, MongoDB (or none)
33+
- ORMs: Drizzle, Prisma, Mongoose (or none)
34+
- Auth: Better-Auth (optional)
35+
- Addons: Turborepo, PWA, Tauri, Biome, Husky, Starlight, Fumadocs, Ultracite, Oxlint
36+
- Examples: Todo, AI
37+
- DB Setup: Turso, Neon, Supabase, Prisma PostgreSQL, MongoDB Atlas, Cloudflare D1, Docker
38+
- Web Deploy: Cloudflare Workers
39+
40+
Type safety end-to-end, clean monorepo layout, and zero‑lock‑in: you choose only what you need.
3841

3942
## Repository Structure
4043

4144
This repository is organized as a monorepo containing:
4245

43-
- **CLI**: [`create-better-t-stack`](apps/cli) - The scaffolding CLI tool
44-
- **Documentation**: [`web`](apps/web) - Official website and documentation
46+
- **CLI**: [`apps/cli`](apps/cli) - The scaffolding CLI tool
47+
- **Documentation**: [`apps/web`](apps/web) - Official website and documentation
4548

4649
## Documentation
4750

48-
Visit [better-t-stack.dev](https://better-t-stack.dev) for full documentation, guides, and examples.
51+
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.
4952

5053
## Development
5154

5255
```bash
5356
# Clone the repository
54-
git clone https://github.com/better-t-stack/create-better-t-stack.git
57+
git clone https://github.com/AmanVarshney01/create-better-t-stack.git
5558

5659
# Install dependencies
5760
bun install
@@ -65,7 +68,10 @@ bun dev:web
6568

6669
## Want to contribute?
6770

68-
Just fork the repository and submit a pull request!
71+
Please read the Contribution Guide first and open an issue before starting new features to ensure alignment with project goals.
72+
73+
- Docs: [`Contributing`](/apps/web/content/docs/contributing.mdx)
74+
- Repo guide: [`.github/CONTRIBUTING.md`](.github/CONTRIBUTING.md)
6975

7076
## Star History
7177

apps/cli/src/constants.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,3 @@ export const ADDON_COMPATIBILITY: Record<Addons, readonly Frontend[]> = {
139139
fumadocs: [],
140140
none: [],
141141
} as const;
142-
143-
// TODO: need to refactor this
144-
export const WEB_FRAMEWORKS: readonly Frontend[] = [
145-
"tanstack-router",
146-
"react-router",
147-
"tanstack-start",
148-
"next",
149-
"nuxt",
150-
"svelte",
151-
"solid",
152-
];

apps/cli/src/prompts/api.ts

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { cancel, isCancel, select } from "@clack/prompts";
22
import pc from "picocolors";
33
import type { API, Backend, Frontend } from "../types";
4+
import { allowedApisForFrontends } from "../utils/compatibility-rules";
45

56
export async function getApiChoice(
67
Api?: API | undefined,
@@ -11,46 +12,30 @@ export async function getApiChoice(
1112
return "none";
1213
}
1314

14-
if (Api) return Api;
15+
const allowed = allowedApisForFrontends(frontend ?? []);
1516

16-
const includesNuxt = frontend?.includes("nuxt");
17-
const includesSvelte = frontend?.includes("svelte");
18-
const includesSolid = frontend?.includes("solid");
19-
20-
let apiOptions = [
21-
{
22-
value: "trpc" as const,
23-
label: "tRPC",
24-
hint: "End-to-end typesafe APIs made easy",
25-
},
26-
{
27-
value: "orpc" as const,
28-
label: "oRPC",
29-
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards",
30-
},
31-
{
32-
value: "none" as const,
33-
label: "None",
34-
hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)",
35-
},
36-
];
37-
38-
if (includesNuxt || includesSvelte || includesSolid) {
39-
apiOptions = [
40-
{
41-
value: "orpc" as const,
42-
label: "oRPC",
43-
hint: `End-to-end type-safe APIs (Recommended for ${
44-
includesNuxt ? "Nuxt" : includesSvelte ? "Svelte" : "Solid"
45-
} frontend)`,
46-
},
47-
{
48-
value: "none" as const,
49-
label: "None",
50-
hint: "No API layer",
51-
},
52-
];
17+
if (Api) {
18+
return allowed.includes(Api) ? Api : allowed[0];
5319
}
20+
const apiOptions = allowed.map((a) =>
21+
a === "trpc"
22+
? {
23+
value: "trpc" as const,
24+
label: "tRPC",
25+
hint: "End-to-end typesafe APIs made easy",
26+
}
27+
: a === "orpc"
28+
? {
29+
value: "orpc" as const,
30+
label: "oRPC",
31+
hint: "End-to-end type-safe APIs that adhere to OpenAPI standards",
32+
}
33+
: {
34+
value: "none" as const,
35+
label: "None",
36+
hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)",
37+
},
38+
);
5439

5540
const apiType = await select<API>({
5641
message: "Select API type",

apps/cli/src/prompts/examples.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { cancel, isCancel, multiselect } from "@clack/prompts";
22
import pc from "picocolors";
33
import { DEFAULT_CONFIG } from "../constants";
44
import type { API, Backend, Database, Examples, Frontend } from "../types";
5+
import {
6+
isExampleAIAllowed,
7+
isExampleTodoAllowed,
8+
} from "../utils/compatibility-rules";
59

610
export async function getExamplesChoice(
711
examples?: Examples[],
@@ -30,27 +34,33 @@ export async function getExamplesChoice(
3034
if (noFrontendSelected) return [];
3135

3236
let response: Examples[] | symbol = [];
33-
const options: { value: Examples; label: string; hint: string }[] = [
34-
{
37+
const options: { value: Examples; label: string; hint: string }[] = [];
38+
39+
if (isExampleTodoAllowed(backend, database)) {
40+
options.push({
3541
value: "todo" as const,
3642
label: "Todo App",
3743
hint: "A simple CRUD example app",
38-
},
39-
];
44+
});
45+
}
4046

41-
if (backend !== "elysia" && !frontends?.includes("solid")) {
47+
if (isExampleAIAllowed(backend, frontends ?? [])) {
4248
options.push({
4349
value: "ai" as const,
4450
label: "AI Chat",
4551
hint: "A simple AI chat interface using AI SDK",
4652
});
4753
}
4854

55+
if (options.length === 0) return [];
56+
4957
response = await multiselect<Examples>({
5058
message: "Include examples",
5159
options: options,
5260
required: false,
53-
initialValues: DEFAULT_CONFIG.examples,
61+
initialValues: DEFAULT_CONFIG.examples?.filter((ex) =>
62+
options.some((o) => o.value === ex),
63+
),
5464
});
5565

5666
if (isCancel(response)) {

apps/cli/src/prompts/frontend.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { cancel, isCancel, multiselect, select } from "@clack/prompts";
22
import pc from "picocolors";
33
import { DEFAULT_CONFIG } from "../constants";
44
import type { Backend, Frontend } from "../types";
5+
import { isFrontendAllowedWithBackend } from "../utils/compatibility-rules";
56

67
export async function getFrontendChoice(
78
frontendOptions?: Frontend[],
@@ -73,12 +74,9 @@ export async function getFrontendChoice(
7374
},
7475
];
7576

76-
const webOptions = allWebOptions.filter((option) => {
77-
if (backend === "convex") {
78-
return option.value !== "solid";
79-
}
80-
return true;
81-
});
77+
const webOptions = allWebOptions.filter((option) =>
78+
isFrontendAllowedWithBackend(option.value, backend),
79+
);
8280

8381
const webFramework = await select<Frontend>({
8482
message: "Choose web",

apps/cli/src/prompts/project-name.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
import path from "node:path";
22
import { cancel, isCancel, text } from "@clack/prompts";
3+
import consola from "consola";
34
import fs from "fs-extra";
45
import pc from "picocolors";
56
import { DEFAULT_CONFIG } from "../constants";
67
import { ProjectNameSchema } from "../types";
78

9+
function isPathWithinCwd(targetPath: string): boolean {
10+
const resolved = path.resolve(targetPath);
11+
const rel = path.relative(process.cwd(), resolved);
12+
return !rel.startsWith("..") && !path.isAbsolute(rel);
13+
}
14+
815
function validateDirectoryName(name: string): string | undefined {
916
if (name === ".") return undefined;
1017

@@ -23,7 +30,11 @@ export async function getProjectName(initialName?: string): Promise<string> {
2330
const finalDirName = path.basename(initialName);
2431
const validationError = validateDirectoryName(finalDirName);
2532
if (!validationError) {
26-
return initialName;
33+
const projectDir = path.resolve(process.cwd(), initialName);
34+
if (isPathWithinCwd(projectDir)) {
35+
return initialName;
36+
}
37+
consola.error(pc.red("Project path must be within current directory"));
2738
}
2839
}
2940

@@ -56,7 +67,7 @@ export async function getProjectName(initialName?: string): Promise<string> {
5667

5768
if (nameToUse !== ".") {
5869
const projectDir = path.resolve(process.cwd(), nameToUse);
59-
if (!projectDir.startsWith(process.cwd())) {
70+
if (!isPathWithinCwd(projectDir)) {
6071
return "Project path must be within current directory";
6172
}
6273
}

apps/cli/src/prompts/web-deploy.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { cancel, isCancel, select } from "@clack/prompts";
22
import pc from "picocolors";
3-
import { DEFAULT_CONFIG, WEB_FRAMEWORKS } from "../constants";
3+
import { DEFAULT_CONFIG } from "../constants";
44
import type { Backend, Frontend, Runtime, WebDeploy } from "../types";
5+
import { WEB_FRAMEWORKS } from "../utils/compatibility";
56

67
function hasWebFrontend(frontends: Frontend[]): boolean {
78
return frontends.some((f) => WEB_FRAMEWORKS.includes(f));

apps/cli/src/types.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,7 @@ export type Backend = z.infer<typeof BackendSchema>;
1717

1818
export const RuntimeSchema = z
1919
.enum(["bun", "node", "workers", "none"])
20-
.describe(
21-
"Runtime environment (workers only available with hono backend and drizzle orm)",
22-
);
20+
.describe("Runtime environment");
2321
export type Runtime = z.infer<typeof RuntimeSchema>;
2422

2523
export const FrontendSchema = z
@@ -176,5 +174,3 @@ export interface BetterTStackConfig {
176174
api: API;
177175
webDeploy: WebDeploy;
178176
}
179-
180-
export type AvailablePackageManagers = "npm" | "pnpm" | "bun";

0 commit comments

Comments
 (0)