Add frontend selection options and improve CLI command generation

This commit is contained in:
Aman Varshney
2025-03-29 16:29:00 +05:30
parent 4efcf216cc
commit bd487b7e66

View File

@@ -69,6 +69,32 @@ const triggerConfetti = () => {
}; };
const TECH_OPTIONS = { const TECH_OPTIONS = {
frontend: [
{
id: "web",
name: "React Web",
description: "React with TanStack Router",
icon: "🌐",
color: "from-blue-400 to-blue-600",
default: true,
},
{
id: "native",
name: "React Native",
description: "Expo with NativeWind",
icon: "📱",
color: "from-purple-400 to-purple-600",
default: false,
},
{
id: "none",
name: "No Frontend",
description: "API-only backend",
icon: "⚙️",
color: "from-gray-400 to-gray-600",
default: false,
},
],
runtime: [ runtime: [
{ {
id: "bun", id: "bun",
@@ -284,6 +310,7 @@ const TECH_OPTIONS = {
}; };
interface StackState { interface StackState {
frontend: string[];
runtime: string; runtime: string;
backendFramework: string; backendFramework: string;
database: string; database: string;
@@ -298,6 +325,7 @@ interface StackState {
} }
const DEFAULT_STACK: StackState = { const DEFAULT_STACK: StackState = {
frontend: ["web"],
runtime: "bun", runtime: "bun",
backendFramework: "hono", backendFramework: "hono",
database: "sqlite", database: "sqlite",
@@ -323,11 +351,21 @@ const StackArchitect = () => {
}, [stack]); }, [stack]);
const generateCommand = useCallback((stackState: StackState) => { const generateCommand = useCallback((stackState: StackState) => {
const base = "npx create-better-t-stack"; let base: string;
const projectName = "my-better-t-app"; if (stackState.packageManager === "npm") {
const flags: string[] = ["-y"]; base = "npx create-better-t-stack@latest";
} else if (stackState.packageManager === "pnpm") {
base = "pnpm create better-t-stack@latest";
} else {
base = "bun create better-t-stack@latest";
}
const isDefault = const projectName = "my-better-t-app";
const flags: string[] = [];
const isAllDefault =
stackState.frontend.length === 1 &&
stackState.frontend[0] === "web" &&
stackState.runtime === "bun" && stackState.runtime === "bun" &&
stackState.backendFramework === "hono" && stackState.backendFramework === "hono" &&
stackState.database === "sqlite" && stackState.database === "sqlite" &&
@@ -340,14 +378,26 @@ const StackArchitect = () => {
stackState.git === "true" && stackState.git === "true" &&
stackState.install === "true"; stackState.install === "true";
if (isDefault) return `${base} ${projectName} -y`; if (isAllDefault) {
return `${base} ${projectName} -y`;
if (stackState.runtime === "node") {
flags.push("--runtime node");
} }
if (stackState.backendFramework === "elysia") { flags.push("-y");
flags.push("--elysia");
if (!stackState.frontend.includes("web")) {
flags.push("--no-web");
}
if (stackState.frontend.includes("native")) {
flags.push("--native");
}
if (stackState.runtime !== "bun") {
flags.push(`--runtime ${stackState.runtime}`);
}
if (stackState.backendFramework !== "hono") {
flags.push(`--${stackState.backendFramework}`);
} }
if (stackState.database === "postgres") { if (stackState.database === "postgres") {
@@ -356,7 +406,7 @@ const StackArchitect = () => {
flags.push("--no-database"); flags.push("--no-database");
} }
if (stackState.orm === "prisma") { if (stackState.orm === "prisma" && stackState.database !== "none") {
flags.push("--prisma"); flags.push("--prisma");
} }
@@ -364,7 +414,7 @@ const StackArchitect = () => {
flags.push("--no-auth"); flags.push("--no-auth");
} }
if (stackState.turso === "true") { if (stackState.turso === "true" && stackState.database === "sqlite") {
flags.push("--turso"); flags.push("--turso");
} }
@@ -390,14 +440,35 @@ const StackArchitect = () => {
flags.push("--no-install"); flags.push("--no-install");
} }
return flags.length > 0 return `${base} ${projectName} ${flags.join(" ")}`;
? `${base} ${projectName} ${flags.join(" ")}`
: `${base} ${projectName}`;
}, []); }, []);
const handleTechSelect = useCallback( const handleTechSelect = useCallback(
(category: keyof typeof TECH_OPTIONS, techId: string) => { (category: keyof typeof TECH_OPTIONS, techId: string) => {
setStack((prev) => { setStack((prev) => {
if (category === "frontend") {
const currentSelection = [...prev.frontend];
if (techId === "none") {
return {
...prev,
frontend: [],
};
}
if (currentSelection.includes(techId)) {
return {
...prev,
frontend: currentSelection.filter((id) => id !== techId),
};
}
return {
...prev,
frontend: [...currentSelection, techId],
};
}
if (category === "addons" || category === "examples") { if (category === "addons" || category === "examples") {
const currentArray = [...(prev[category] || [])]; const currentArray = [...(prev[category] || [])];
const index = currentArray.indexOf(techId); const index = currentArray.indexOf(techId);
@@ -518,6 +589,8 @@ const StackArchitect = () => {
let isSelected = false; let isSelected = false;
if (activeTab === "addons" || activeTab === "examples") { if (activeTab === "addons" || activeTab === "examples") {
isSelected = stack[activeTab].includes(tech.id); isSelected = stack[activeTab].includes(tech.id);
} else if (activeTab === "frontend") {
isSelected = stack.frontend.includes(tech.id);
} else { } else {
isSelected = isSelected =
stack[activeTab as keyof StackState] === tech.id; stack[activeTab as keyof StackState] === tech.id;
@@ -591,6 +664,19 @@ const StackArchitect = () => {
Selected Stack Selected Stack
</div> </div>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{stack.frontend.map((frontendId) => {
const frontend = TECH_OPTIONS.frontend.find(
(f) => f.id === frontendId,
);
return frontend ? (
<span
key={frontendId}
className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300 border border-blue-300 dark:border-blue-700/30"
>
{frontend.icon} {frontend.name}
</span>
) : null;
})}
<span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300 border border-amber-300 dark:border-amber-700/30"> <span className="inline-flex items-center px-1.5 py-0.5 rounded text-xs bg-amber-100 dark:bg-amber-900/30 text-amber-800 dark:text-amber-300 border border-amber-300 dark:border-amber-700/30">
{ {
TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime) TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime)