fix Stack Architect logic and cli dir check

This commit is contained in:
Aman Varshney
2025-04-18 00:32:40 +05:30
parent 23f80dfa08
commit 13241ee406
5 changed files with 217 additions and 201 deletions

View File

@@ -0,0 +1,5 @@
---
"create-better-t-stack": patch
---
fix cli project name check

View File

@@ -1,11 +1,14 @@
import path from "node:path";
import { cancel, intro, log, outro } from "@clack/prompts"; import { cancel, intro, log, outro } from "@clack/prompts";
import { consola } from "consola"; import { consola } from "consola";
import fs from "fs-extra";
import pc from "picocolors"; import pc from "picocolors";
import yargs from "yargs"; import yargs from "yargs";
import { hideBin } from "yargs/helpers"; import { hideBin } from "yargs/helpers";
import { DEFAULT_CONFIG } from "./constants"; import { DEFAULT_CONFIG } from "./constants";
import { createProject } from "./helpers/create-project"; import { createProject } from "./helpers/create-project";
import { gatherConfig } from "./prompts/config-prompts"; import { gatherConfig } from "./prompts/config-prompts";
import { getProjectName } from "./prompts/project-name";
import type { import type {
ProjectAddons, ProjectAddons,
ProjectApi, ProjectApi,
@@ -155,18 +158,28 @@ async function main() {
log.message(""); log.message("");
} }
const config = options.yes let config: ProjectConfig;
? {
...DEFAULT_CONFIG,
projectName: projectDirectory ?? DEFAULT_CONFIG.projectName,
...flagConfig,
}
: await gatherConfig(flagConfig);
if (options.yes) { if (options.yes) {
log.info(pc.yellow("Using these default options:")); config = {
...DEFAULT_CONFIG,
projectName: projectDirectory ?? DEFAULT_CONFIG.projectName,
...flagConfig,
};
log.info(pc.yellow("Using these default/flag options:"));
log.message(displayConfig(config)); log.message(displayConfig(config));
log.message(""); log.message("");
} else {
config = await gatherConfig(flagConfig);
}
const projectDir = path.resolve(process.cwd(), config.projectName);
if (
fs.pathExistsSync(projectDir) &&
fs.readdirSync(projectDir).length > 0
) {
const newProjectName = await getProjectName();
config.projectName = newProjectName;
} }
await createProject(config); await createProject(config);
@@ -210,7 +223,6 @@ function processAndValidateFlags(
): Partial<ProjectConfig> { ): Partial<ProjectConfig> {
const config: Partial<ProjectConfig> = {}; const config: Partial<ProjectConfig> = {};
// --- Database and ORM validation ---
if (options.database) { if (options.database) {
config.database = options.database as ProjectDatabase; config.database = options.database as ProjectDatabase;
} }
@@ -388,7 +400,6 @@ function processAndValidateFlags(
} }
} }
// --- Addons validation ---
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) {
@@ -433,7 +444,6 @@ function processAndValidateFlags(
} }
} }
// --- Examples validation ---
if (options.examples && options.examples.length > 0) { if (options.examples && options.examples.length > 0) {
if (options.examples.includes("none")) { if (options.examples.includes("none")) {
if (options.examples.length > 1) { if (options.examples.length > 1) {
@@ -483,7 +493,6 @@ function processAndValidateFlags(
} }
} }
// --- Other flags ---
if (options.packageManager) { if (options.packageManager) {
config.packageManager = options.packageManager as ProjectPackageManager; config.packageManager = options.packageManager as ProjectPackageManager;
} }

View File

@@ -100,7 +100,7 @@ const StackArchitect = ({
} }
}, []); }, []);
// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation> // biome-ignore lint/correctness/useExhaustiveDependencies: Intentionally managing complex dependencies
useEffect(() => { useEffect(() => {
let changed = false; let changed = false;
const nextStack = { ...stack }; const nextStack = { ...stack };
@@ -371,6 +371,12 @@ const StackArchitect = ({
"MongoDB Atlas setup requires the MongoDB database.", "MongoDB Atlas setup requires the MongoDB database.",
); );
} }
if (stack.orm !== "prisma") {
notes.dbSetup.push(
"MongoDB Atlas setup requires the Prisma ORM (implicitly selected).",
);
}
} else if (stack.dbSetup === "neon") { } else if (stack.dbSetup === "neon") {
if (stack.database !== "postgres") { if (stack.database !== "postgres") {
notes.dbSetup.push("Neon setup requires the PostgreSQL database."); notes.dbSetup.push("Neon setup requires the PostgreSQL database.");
@@ -394,6 +400,11 @@ const StackArchitect = ({
if (stack.database === "mongodb" && stack.orm === "drizzle") { if (stack.database === "mongodb" && stack.orm === "drizzle") {
notes.database.push("MongoDB is only compatible with the Prisma ORM."); notes.database.push("MongoDB is only compatible with the Prisma ORM.");
} }
if (stack.dbSetup !== "none") {
notes.database.push(
`Changing the database might reset the '${TECH_OPTIONS.dbSetup.find((db) => db.id === stack.dbSetup)?.name}' setup if it becomes incompatible.`,
);
}
notes.orm = []; notes.orm = [];
if (stack.database === "none") { if (stack.database === "none") {
@@ -445,7 +456,7 @@ const StackArchitect = ({
const currentStack = { ...prev }; const currentStack = { ...prev };
if (category === "frontend") { if (category === "frontend") {
let currentSelection = [...currentStack.frontend]; const currentSelection = [...currentStack.frontend];
const webTypes = [ const webTypes = [
"tanstack-router", "tanstack-router",
"react-router", "react-router",
@@ -464,25 +475,25 @@ const StackArchitect = ({
return prev; return prev;
} }
if (currentSelection.includes(techId)) {
currentSelection = currentSelection.filter((id) => id !== techId);
if (currentSelection.length === 0) {
}
return { ...currentStack, frontend: currentSelection };
}
let newSelection = [...currentSelection]; let newSelection = [...currentSelection];
if (newSelection.includes("none")) { if (newSelection.includes("none")) {
newSelection = newSelection.filter((id) => id !== "none"); newSelection = newSelection.filter((id) => id !== "none");
} }
if (webTypes.includes(techId)) { if (newSelection.includes(techId)) {
newSelection = newSelection.filter((id) => !webTypes.includes(id)); newSelection = newSelection.filter((id) => id !== techId);
}
newSelection.push(techId); if (newSelection.length === 0) {
}
} else {
if (webTypes.includes(techId)) {
newSelection = newSelection.filter(
(id) => !webTypes.includes(id),
);
}
newSelection.push(techId);
}
return { ...currentStack, frontend: newSelection }; return { ...currentStack, frontend: newSelection };
} }
@@ -490,9 +501,12 @@ const StackArchitect = ({
if (category === "addons" || category === "examples") { if (category === "addons" || category === "examples") {
const currentArray = [...(currentStack[category] || [])]; const currentArray = [...(currentStack[category] || [])];
const index = currentArray.indexOf(techId); const index = currentArray.indexOf(techId);
const nextArray = [...currentArray];
if (index >= 0) { if (index >= 0) {
currentArray.splice(index, 1); nextArray.splice(index, 1);
if (techId === "biome" && nextArray.includes("husky")) {
}
} else { } else {
if (category === "examples") { if (category === "examples") {
if (!hasWebFrontend && (techId === "todo" || techId === "ai")) if (!hasWebFrontend && (techId === "todo" || techId === "ai"))
@@ -506,27 +520,56 @@ const StackArchitect = ({
(techId === "pwa" || techId === "tauri") (techId === "pwa" || techId === "tauri")
) )
return prev; return prev;
if (techId === "husky" && !currentArray.includes("biome")) { if (techId === "husky" && !nextArray.includes("biome")) {
currentArray.push("biome"); nextArray.push("biome");
} }
} }
currentArray.push(techId); nextArray.push(techId);
} }
return { ...currentStack, [category]: currentArray }; return { ...currentStack, [category]: nextArray };
} }
if (category === "database") { if (category === "database") {
if (currentStack.database === techId) return prev; if (currentStack.database === techId) return prev;
const updatedState = { ...currentStack, database: techId }; const updatedState: Partial<StackState> = { database: techId };
const currentDbSetup = currentStack.dbSetup;
let resetDbSetup = false;
if (currentDbSetup === "turso" && techId !== "sqlite")
resetDbSetup = true;
if (currentDbSetup === "prisma-postgres" && techId !== "postgres")
resetDbSetup = true;
if (currentDbSetup === "mongodb-atlas" && techId !== "mongodb")
resetDbSetup = true;
if (currentDbSetup === "neon" && techId !== "postgres")
resetDbSetup = true;
if (techId === "none") resetDbSetup = true;
if (resetDbSetup && currentDbSetup !== "none") {
updatedState.dbSetup = "none";
}
if (techId === "none") { if (techId === "none") {
} else if (prev.database === "none") { updatedState.orm = "none";
updatedState.orm = techId === "mongodb" ? "prisma" : "drizzle"; updatedState.auth = "false";
updatedState.dbSetup = "none";
} else { } else {
if (prev.database === "none") {
updatedState.orm = techId === "mongodb" ? "prisma" : "drizzle";
} else {
if (techId === "mongodb" && currentStack.orm === "drizzle") {
updatedState.orm = "prisma";
} else if (
techId !== "mongodb" &&
currentStack.orm === "prisma" &&
currentDbSetup === "turso" &&
techId === "sqlite"
) {
}
}
} }
return updatedState;
return { ...currentStack, ...updatedState };
} }
if (category === "orm") { if (category === "orm") {
@@ -541,7 +584,6 @@ const StackArchitect = ({
return prev; return prev;
if (currentStack.orm === techId) return prev; if (currentStack.orm === techId) return prev;
return { ...currentStack, orm: techId }; return { ...currentStack, orm: techId };
} }
@@ -549,8 +591,19 @@ const StackArchitect = ({
if (currentStack.database === "none" && techId !== "none") if (currentStack.database === "none" && techId !== "none")
return prev; return prev;
if (currentStack.dbSetup === techId) return prev; if (techId === "turso") {
if (currentStack.database !== "sqlite") return prev;
if (currentStack.orm === "prisma") return prev;
} else if (techId === "prisma-postgres") {
if (currentStack.database !== "postgres") return prev;
if (currentStack.orm !== "prisma") return prev;
} else if (techId === "mongodb-atlas") {
if (currentStack.database !== "mongodb") return prev;
} else if (techId === "neon") {
if (currentStack.database !== "postgres") return prev;
}
if (currentStack.dbSetup === techId) return prev;
return { ...currentStack, dbSetup: techId }; return { ...currentStack, dbSetup: techId };
} }
@@ -575,7 +628,19 @@ const StackArchitect = ({
category === "install" category === "install"
) { ) {
if (currentStack[category] === techId) return prev; if (currentStack[category] === techId) return prev;
return { ...currentStack, [category]: techId };
const updatedState: Partial<StackState> = { [category]: techId };
if (category === "backendFramework" && techId === "elysia") {
const currentExamples = currentStack.examples || [];
if (currentExamples.includes("ai")) {
updatedState.examples = currentExamples.filter(
(ex) => ex !== "ai",
);
}
} else if (category === "backendFramework" && techId === "hono") {
}
return { ...currentStack, ...updatedState };
} }
return prev; return prev;
@@ -606,10 +671,14 @@ const StackArchitect = ({
if (saveButton && saveTextSpan) { if (saveButton && saveTextSpan) {
const originalText = saveTextSpan.textContent; const originalText = saveTextSpan.textContent;
saveTextSpan.textContent = "Saved!"; saveTextSpan.textContent = "Saved!";
saveButton.classList.add("bg-green-200", "dark:bg-green-800/70");
saveButton.classList.remove("bg-green-100", "dark:bg-green-900/50");
setTimeout(() => { setTimeout(() => {
if (saveTextSpan.textContent === "Saved!") { if (saveTextSpan.textContent === "Saved!") {
saveTextSpan.textContent = originalText; saveTextSpan.textContent = originalText;
saveButton.classList.remove("bg-green-200", "dark:bg-green-800/70");
saveButton.classList.add("bg-green-100", "dark:bg-green-900/50");
} }
}, 2000); }, 2000);
} }
@@ -621,6 +690,7 @@ const StackArchitect = ({
setProjectNameError( setProjectNameError(
validateProjectName(lastSavedStack.projectName || ""), validateProjectName(lastSavedStack.projectName || ""),
); );
setActiveTab("frontend");
} }
}, [lastSavedStack]); }, [lastSavedStack]);
@@ -632,6 +702,7 @@ const StackArchitect = ({
setStack(preset.stack); setStack(preset.stack);
setProjectNameError(validateProjectName(preset.stack.projectName || "")); setProjectNameError(validateProjectName(preset.stack.projectName || ""));
setShowPresets(false); setShowPresets(false);
setActiveTab("frontend");
} }
}, []); }, []);
@@ -650,15 +721,25 @@ const StackArchitect = ({
techId === "prisma" && techId === "prisma" &&
stack.dbSetup === "turso" stack.dbSetup === "turso"
) )
return "Prisma ORM is not compatible with Turso."; return "Prisma ORM is not compatible with Turso DB setup (requires Drizzle).";
} }
if (category === "dbSetup" && techId !== "none") { if (category === "dbSetup" && techId !== "none") {
if (stack.database === "none") if (stack.database === "none")
return "Select a database before choosing a cloud setup."; return "Select a database before choosing a cloud setup.";
if (techId === "turso") { if (techId === "turso") {
if (stack.orm === "prisma") if (stack.database !== "sqlite")
return "Turso is not compatible with the Prisma ORM."; return "Turso requires SQLite database.";
if (stack.orm === "prisma") return "Turso requires Drizzle ORM.";
} else if (techId === "prisma-postgres") {
if (stack.database !== "postgres")
return "Requires PostgreSQL database.";
if (stack.orm !== "prisma") return "Requires Prisma ORM.";
} else if (techId === "mongodb-atlas") {
if (stack.database !== "mongodb") return "Requires MongoDB database.";
} else if (techId === "neon") {
if (stack.database !== "postgres")
return "Requires PostgreSQL database.";
} }
} }
if ( if (
@@ -716,9 +797,11 @@ const StackArchitect = ({
<div className="h-3 w-3 rounded-full bg-yellow-500" /> <div className="h-3 w-3 rounded-full bg-yellow-500" />
<div className="h-3 w-3 rounded-full bg-green-500" /> <div className="h-3 w-3 rounded-full bg-green-500" />
</div> </div>
<div className="hidden font-mono text-gray-600 text-xs sm:block dark:text-gray-400"> <div className="hidden font-mono text-gray-600 text-xs sm:block dark:text-gray-400">
Stack Architect Terminal Stack Architect Terminal
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">
<button <button
type="button" type="button"
@@ -773,7 +856,7 @@ const StackArchitect = ({
</li> </li>
<li> <li>
Some selections may disable or automatically change other Some selections may disable or automatically change other
options based on compatibility. options based on compatibility (check notes!).
</li> </li>
<li> <li>
The command will automatically update based on your selections. The command will automatically update based on your selections.
@@ -799,6 +882,7 @@ const StackArchitect = ({
</ul> </ul>
</div> </div>
)} )}
{showPresets && ( {showPresets && (
<div className="flex-shrink-0 border-gray-300 border-b bg-amber-50 p-3 sm:p-4 dark:border-gray-700 dark:bg-amber-900/20"> <div className="flex-shrink-0 border-gray-300 border-b bg-amber-50 p-3 sm:p-4 dark:border-gray-700 dark:bg-amber-900/20">
<h3 className="mb-2 font-medium text-amber-800 text-sm dark:text-amber-300"> <h3 className="mb-2 font-medium text-amber-800 text-sm dark:text-amber-300">
@@ -855,7 +939,7 @@ const StackArchitect = ({
<button <button
type="button" type="button"
onClick={resetStack} onClick={resetStack}
className="flex items-center gap-1 rounded border border-gray-300 bg-gray-200 px-2 py-1 text-gray-700 text-xs hover:bg-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700" className="flex items-center gap-1 rounded border border-gray-300 bg-gray-200 px-2 py-1 text-gray-700 text-xs transition-colors hover:bg-gray-300 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
title="Reset to defaults" title="Reset to defaults"
> >
<RefreshCw className="h-3 w-3" /> <RefreshCw className="h-3 w-3" />
@@ -866,7 +950,7 @@ const StackArchitect = ({
<button <button
type="button" type="button"
onClick={loadSavedStack} onClick={loadSavedStack}
className="flex items-center gap-1 rounded border border-blue-300 bg-blue-100 px-2 py-1 text-blue-700 text-xs hover:bg-blue-200 dark:border-blue-700 dark:bg-blue-900/50 dark:text-blue-300 dark:hover:bg-blue-800/50" className="flex items-center gap-1 rounded border border-blue-300 bg-blue-100 px-2 py-1 text-blue-700 text-xs transition-colors hover:bg-blue-200 dark:border-blue-700 dark:bg-blue-900/50 dark:text-blue-300 dark:hover:bg-blue-800/50"
title="Load saved preferences" title="Load saved preferences"
> >
<Settings className="h-3 w-3" /> <Settings className="h-3 w-3" />
@@ -878,7 +962,7 @@ const StackArchitect = ({
id="save-stack-button" id="save-stack-button"
type="button" type="button"
onClick={saveCurrentStack} onClick={saveCurrentStack}
className="flex items-center gap-1 rounded border border-green-300 bg-green-100 px-2 py-1 text-green-700 text-xs hover:bg-green-200 dark:border-green-700 dark:bg-green-900/50 dark:text-green-300 dark:hover:bg-green-800/50" className="flex items-center gap-1 rounded border border-green-300 bg-green-100 px-2 py-1 text-green-700 text-xs transition-colors hover:bg-green-200 dark:border-green-700 dark:bg-green-900/50 dark:text-green-300 dark:hover:bg-green-800/50"
title="Save current preferences" title="Save current preferences"
> >
<Star className="h-3 w-3" /> <Star className="h-3 w-3" />
@@ -920,7 +1004,8 @@ const StackArchitect = ({
<Terminal className="mr-2 h-4 w-4" /> <Terminal className="mr-2 h-4 w-4" />
<span> <span>
Configure{" "} Configure{" "}
{activeTab.charAt(0).toUpperCase() + activeTab.slice(1)} {activeTab.charAt(0).toUpperCase() +
activeTab.slice(1).replace(/([A-Z])/g, " $1")}
</span> </span>
</div> </div>
<div className="mb-4 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3"> <div className="mb-4 grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3">
@@ -928,9 +1013,11 @@ const StackArchitect = ({
(tech) => { (tech) => {
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 as "addons" | "examples"] || []
).includes(tech.id);
} else if (activeTab === "frontend") { } else if (activeTab === "frontend") {
isSelected = stack.frontend.includes(tech.id); isSelected = (stack.frontend || []).includes(tech.id);
} else { } else {
isSelected = isSelected =
stack[activeTab as keyof StackState] === tech.id; stack[activeTab as keyof StackState] === tech.id;
@@ -1015,149 +1102,38 @@ const StackArchitect = ({
</div> </div>
</div> </div>
<div className="flex flex-wrap gap-1"> <div className="flex flex-wrap gap-1">
{stack.frontend {Object.entries(TECH_OPTIONS).flatMap(([category, options]) => {
.map((id) => TECH_OPTIONS.frontend.find((f) => f.id === id)) const categoryKey = category as keyof StackState;
.filter(Boolean) const selectedValue = stack[categoryKey];
.map((tech) => (
<span if (Array.isArray(selectedValue)) {
key={tech?.id} return selectedValue
className="inline-flex items-center gap-1 rounded border border-blue-300 bg-blue-100 px-1.5 py-0.5 text-blue-800 text-xs dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300" .map((id) => options.find((opt) => opt.id === id))
> .filter(Boolean)
{tech?.icon} {tech?.name} .map((tech) => (
</span> <span
))} key={`${category}-${tech?.id}`}
{TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime) && ( className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-xs ${getBadgeColors(category)}`}
<span className="inline-flex items-center gap-1 rounded border border-amber-300 bg-amber-100 px-1.5 py-0.5 text-amber-800 text-xs dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300"> >
{ {tech?.icon} {tech?.name}
TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime) </span>
?.icon ));
}{" "} }
{ const tech = options.find((opt) => opt.id === selectedValue);
TECH_OPTIONS.runtime.find((t) => t.id === stack.runtime)
?.name if (tech && tech.id !== "none" && tech.id !== "false") {
} return (
</span> <span
)} key={`${category}-${tech.id}`}
{TECH_OPTIONS.backendFramework.find( className={`inline-flex items-center gap-1 rounded border px-1.5 py-0.5 text-xs ${getBadgeColors(category)}`}
(t) => t.id === stack.backendFramework, >
) && ( {tech.icon} {tech.name}
<span className="inline-flex items-center gap-1 rounded border border-blue-300 bg-blue-100 px-1.5 py-0.5 text-blue-800 text-xs dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300"> </span>
{ );
TECH_OPTIONS.backendFramework.find( }
(t) => t.id === stack.backendFramework,
)?.icon return [];
}{" "} })}
{
TECH_OPTIONS.backendFramework.find(
(t) => t.id === stack.backendFramework,
)?.name
}
</span>
)}
{TECH_OPTIONS.api.find((t) => t.id === stack.api) && (
<span className="inline-flex items-center gap-1 rounded border border-indigo-300 bg-indigo-100 px-1.5 py-0.5 text-indigo-800 text-xs dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300">
{TECH_OPTIONS.api.find((t) => t.id === stack.api)?.icon}{" "}
{TECH_OPTIONS.api.find((t) => t.id === stack.api)?.name}
</span>
)}
{TECH_OPTIONS.database.find((t) => t.id === stack.database) &&
stack.database !== "none" && (
<span className="inline-flex items-center gap-1 rounded border border-indigo-300 bg-indigo-100 px-1.5 py-0.5 text-indigo-800 text-xs dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300">
{
TECH_OPTIONS.database.find(
(t) => t.id === stack.database,
)?.icon
}{" "}
{
TECH_OPTIONS.database.find(
(t) => t.id === stack.database,
)?.name
}
</span>
)}
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm) &&
stack.orm !== "none" && (
<span className="inline-flex items-center gap-1 rounded border border-cyan-300 bg-cyan-100 px-1.5 py-0.5 text-cyan-800 text-xs dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300">
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.icon}{" "}
{TECH_OPTIONS.orm.find((t) => t.id === stack.orm)?.name}
</span>
)}
{TECH_OPTIONS.auth.find((t) => t.id === stack.auth) &&
stack.auth === "true" && (
<span className="inline-flex items-center gap-1 rounded border border-green-300 bg-green-100 px-1.5 py-0.5 text-green-800 text-xs dark:border-green-700/30 dark:bg-green-900/30 dark:text-green-300">
{TECH_OPTIONS.auth.find((t) => t.id === stack.auth)?.icon}{" "}
{TECH_OPTIONS.auth.find((t) => t.id === stack.auth)?.name}
</span>
)}
{TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup) &&
stack.dbSetup !== "none" && (
<span className="inline-flex items-center gap-1 rounded border border-pink-300 bg-pink-100 px-1.5 py-0.5 text-pink-800 text-xs dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300">
{
TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup)
?.icon
}{" "}
{
TECH_OPTIONS.dbSetup.find((t) => t.id === stack.dbSetup)
?.name
}
</span>
)}
{stack.addons
.map((id) => TECH_OPTIONS.addons.find((a) => a.id === id))
.filter(Boolean)
.map((tech) => (
<span
key={tech?.id}
className="inline-flex items-center gap-1 rounded border border-violet-300 bg-violet-100 px-1.5 py-0.5 text-violet-800 text-xs dark:border-violet-700/30 dark:bg-violet-900/30 dark:text-violet-300"
>
{tech?.icon} {tech?.name}
</span>
))}
{stack.examples
.map((id) => TECH_OPTIONS.examples.find((e) => e.id === id))
.filter(Boolean)
.map((tech) => (
<span
key={tech?.id}
className="inline-flex items-center gap-1 rounded border border-teal-300 bg-teal-100 px-1.5 py-0.5 text-teal-800 text-xs dark:border-teal-700/30 dark:bg-teal-900/30 dark:text-teal-300"
>
{tech?.icon} {tech?.name}
</span>
))}
{TECH_OPTIONS.packageManager.find(
(t) => t.id === stack.packageManager,
) && (
<span className="inline-flex items-center gap-1 rounded border border-orange-300 bg-orange-100 px-1.5 py-0.5 text-orange-800 text-xs dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300">
{
TECH_OPTIONS.packageManager.find(
(t) => t.id === stack.packageManager,
)?.icon
}{" "}
{
TECH_OPTIONS.packageManager.find(
(t) => t.id === stack.packageManager,
)?.name
}
</span>
)}
{TECH_OPTIONS.git.find((t) => t.id === stack.git) && (
<span className="inline-flex items-center gap-1 rounded border border-gray-300 bg-gray-100 px-1.5 py-0.5 text-gray-800 text-xs dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300">
{TECH_OPTIONS.git.find((t) => t.id === stack.git)?.icon}{" "}
{TECH_OPTIONS.git.find((t) => t.id === stack.git)?.name}
</span>
)}
{TECH_OPTIONS.install.find((t) => t.id === stack.install) && (
<span className="inline-flex items-center gap-1 rounded border border-lime-300 bg-lime-100 px-1.5 py-0.5 text-lime-800 text-xs dark:border-lime-700/30 dark:bg-lime-900/30 dark:text-lime-300">
{
TECH_OPTIONS.install.find((t) => t.id === stack.install)
?.icon
}{" "}
{
TECH_OPTIONS.install.find((t) => t.id === stack.install)
?.name
}
</span>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -1204,4 +1180,37 @@ const StackArchitect = ({
); );
}; };
const getBadgeColors = (category: string): string => {
switch (category) {
case "frontend":
return "border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300";
case "runtime":
return "border-amber-300 bg-amber-100 text-amber-800 dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300";
case "backendFramework":
return "border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300";
case "api":
return "border-indigo-300 bg-indigo-100 text-indigo-800 dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300";
case "database":
return "border-indigo-300 bg-indigo-100 text-indigo-800 dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300";
case "orm":
return "border-cyan-300 bg-cyan-100 text-cyan-800 dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300";
case "auth":
return "border-green-300 bg-green-100 text-green-800 dark:border-green-700/30 dark:bg-green-900/30 dark:text-green-300";
case "dbSetup":
return "border-pink-300 bg-pink-100 text-pink-800 dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300";
case "addons":
return "border-violet-300 bg-violet-100 text-violet-800 dark:border-violet-700/30 dark:bg-violet-900/30 dark:text-violet-300";
case "examples":
return "border-teal-300 bg-teal-100 text-teal-800 dark:border-teal-700/30 dark:bg-teal-900/30 dark:text-teal-300";
case "packageManager":
return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
case "git":
return "border-gray-300 bg-gray-100 text-gray-800 dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300";
case "install":
return "border-lime-300 bg-lime-100 text-lime-800 dark:border-lime-700/30 dark:bg-lime-900/30 dark:text-lime-300";
default:
return "border-gray-300 bg-gray-100 text-gray-800 dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300";
}
};
export default StackArchitect; export default StackArchitect;

View File

@@ -175,13 +175,6 @@ export const TECH_OPTIONS = {
icon: "◮", icon: "◮",
color: "from-purple-400 to-purple-600", color: "from-purple-400 to-purple-600",
}, },
{
id: "none",
name: "No ORM",
description: "Skip ORM integration",
icon: "🚫",
color: "from-gray-400 to-gray-600",
},
], ],
dbSetup: [ dbSetup: [
{ {

View File

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