diff --git a/.changeset/early-falcons-itch.md b/.changeset/early-falcons-itch.md
new file mode 100644
index 0000000..61f4b37
--- /dev/null
+++ b/.changeset/early-falcons-itch.md
@@ -0,0 +1,5 @@
+---
+"create-better-t-stack": patch
+---
+
+rename features to addons
diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts
index 61f5b2b..419447d 100644
--- a/apps/cli/src/constants.ts
+++ b/apps/cli/src/constants.ts
@@ -11,7 +11,7 @@ export const DEFAULT_CONFIG: ProjectConfig = {
database: "sqlite",
orm: "drizzle",
auth: true,
- features: [],
+ addons: [],
git: true,
packageManager: "npm",
noInstall: false,
diff --git a/apps/cli/src/helpers/feature-setup.ts b/apps/cli/src/helpers/addons-setup.ts
similarity index 93%
rename from apps/cli/src/helpers/feature-setup.ts
rename to apps/cli/src/helpers/addons-setup.ts
index 35ccc0c..39eadac 100644
--- a/apps/cli/src/helpers/feature-setup.ts
+++ b/apps/cli/src/helpers/addons-setup.ts
@@ -2,21 +2,18 @@ import path from "node:path";
import { log } from "@clack/prompts";
import fs from "fs-extra";
import pc from "picocolors";
-import type { ProjectFeature } from "../types";
+import type { ProjectAddons } from "../types";
-export async function setupFeatures(
- projectDir: string,
- features: ProjectFeature[],
-) {
- if (features.includes("docker")) {
+export async function setupAddons(projectDir: string, addons: ProjectAddons[]) {
+ if (addons.includes("docker")) {
await setupDocker(projectDir);
}
- if (features.includes("github-actions")) {
+ if (addons.includes("github-actions")) {
await setupGithubActions(projectDir);
}
- if (features.includes("SEO")) {
+ if (addons.includes("SEO")) {
log.info(
pc.yellow(
"SEO feature is still a work-in-progress and will be available in a future update.",
diff --git a/apps/cli/src/helpers/create-project.ts b/apps/cli/src/helpers/create-project.ts
index 32799cb..f270ef0 100644
--- a/apps/cli/src/helpers/create-project.ts
+++ b/apps/cli/src/helpers/create-project.ts
@@ -5,10 +5,10 @@ import fs from "fs-extra";
import pc from "picocolors";
import { PKG_ROOT } from "../constants";
import type { ProjectConfig } from "../types";
+import { setupAddons } from "./addons-setup";
import { configureAuth } from "./auth-setup";
import { createReadme } from "./create-readme";
import { setupDatabase } from "./db-setup";
-import { setupFeatures } from "./feature-setup";
import { displayPostInstallInstructions } from "./post-installation";
export async function createProject(options: ProjectConfig): Promise {
@@ -92,8 +92,8 @@ export async function createProject(options: ProjectConfig): Promise {
await $({ cwd: projectDir })`git init`;
}
- if (options.features.length > 0) {
- await setupFeatures(projectDir, options.features);
+ if (options.addons.length > 0) {
+ await setupAddons(projectDir, options.addons);
}
const packageJsonPath = path.join(projectDir, "package.json");
diff --git a/apps/cli/src/helpers/create-readme.ts b/apps/cli/src/helpers/create-readme.ts
index ee8d77d..857de25 100644
--- a/apps/cli/src/helpers/create-readme.ts
+++ b/apps/cli/src/helpers/create-readme.ts
@@ -19,7 +19,7 @@ function generateReadmeContent(options: ProjectConfig): string {
packageManager,
database,
auth,
- features = [],
+ addons = [],
orm = "drizzle",
} = options;
@@ -32,7 +32,7 @@ This project was created with [Better-T-Stack](https://github.com/better-t-stack
## Features
-${generateFeaturesList(database, auth, features, orm)}
+${generateFeaturesList(database, auth, addons, orm)}
## Getting Started
diff --git a/apps/cli/src/helpers/db-setup.ts b/apps/cli/src/helpers/db-setup.ts
index 59fedf0..e3e27a8 100644
--- a/apps/cli/src/helpers/db-setup.ts
+++ b/apps/cli/src/helpers/db-setup.ts
@@ -15,7 +15,6 @@ export async function setupDatabase(
if (databaseType === "none") {
await fs.remove(path.join(serverDir, "src/db"));
- log.info(pc.yellow("Database configuration removed"));
return;
}
diff --git a/apps/cli/src/helpers/install-dependencies.ts b/apps/cli/src/helpers/install-dependencies.ts
index 8e7d08c..256f29d 100644
--- a/apps/cli/src/helpers/install-dependencies.ts
+++ b/apps/cli/src/helpers/install-dependencies.ts
@@ -13,7 +13,6 @@ export async function installDependencies({
packageManager,
}: InstallDependenciesOptions) {
const s = spinner();
- log.info(pc.blue(`Installing dependencies using ${packageManager}...`));
try {
s.start(`Running ${packageManager} install...`);
@@ -34,7 +33,7 @@ export async function installDependencies({
break;
}
- s.stop(pc.green("Dependencies installed successfully"));
+ s.stop("Dependencies installed successfully");
} catch (error) {
s.stop(pc.red("Failed to install dependencies"));
if (error instanceof Error) {
diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts
index 2c4cb76..4cae1bd 100644
--- a/apps/cli/src/index.ts
+++ b/apps/cli/src/index.ts
@@ -5,7 +5,7 @@ import { DEFAULT_CONFIG } from "./constants";
import { createProject } from "./helpers/create-project";
import { installDependencies } from "./helpers/install-dependencies";
import { gatherConfig } from "./prompts/config-prompts";
-import type { ProjectConfig, ProjectFeature } from "./types";
+import type { ProjectAddons, ProjectConfig } from "./types";
import { displayConfig } from "./utils/display-config";
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
import { getVersion } from "./utils/get-version";
@@ -33,7 +33,7 @@ async function main() {
.option("--docker", "Include Docker setup")
.option("--github-actions", "Include GitHub Actions")
.option("--seo", "Include SEO setup")
- .option("--no-features", "Skip all additional features")
+ .option("--no-addons", "Skip all additional addons")
.option("--git", "Include git setup")
.option("--no-git", "Skip git initialization")
.option("--npm", "Use npm package manager")
@@ -75,15 +75,15 @@ async function main() {
...((options.docker ||
options.githubActions ||
options.seo ||
- options.features === false) && {
- features:
- options.features === false
+ options.addons === false) && {
+ addons:
+ options.addons === false
? []
: ([
...(options.docker ? ["docker"] : []),
...(options.githubActions ? ["github-actions"] : []),
...(options.seo ? ["SEO"] : []),
- ] as ProjectFeature[]),
+ ] as ProjectAddons[]),
}),
};
@@ -117,9 +117,9 @@ async function main() {
: DEFAULT_CONFIG.noInstall,
packageManager:
flagConfig.packageManager ?? DEFAULT_CONFIG.packageManager,
- features: flagConfig.features?.length
- ? flagConfig.features
- : DEFAULT_CONFIG.features,
+ addons: flagConfig.addons?.length
+ ? flagConfig.addons
+ : DEFAULT_CONFIG.addons,
turso:
"turso" in options
? options.turso
diff --git a/apps/cli/src/prompts/features.ts b/apps/cli/src/prompts/addons.ts
similarity index 65%
rename from apps/cli/src/prompts/features.ts
rename to apps/cli/src/prompts/addons.ts
index 8560c6e..ad46f6c 100644
--- a/apps/cli/src/prompts/features.ts
+++ b/apps/cli/src/prompts/addons.ts
@@ -1,14 +1,14 @@
import { cancel, isCancel, multiselect } from "@clack/prompts";
import pc from "picocolors";
-import type { ProjectFeature } from "../types";
+import type { ProjectAddons } from "../types";
-export async function getFeaturesChoice(
- features?: ProjectFeature[],
-): Promise {
- if (features !== undefined) return features;
+export async function getAddonsChoice(
+ Addons?: ProjectAddons[],
+): Promise {
+ if (Addons !== undefined) return Addons;
- const response = await multiselect({
- message: "Which features would you like to add?",
+ const response = await multiselect({
+ message: "Which Addons would you like to add?",
options: [
{
value: "docker",
diff --git a/apps/cli/src/prompts/config-prompts.ts b/apps/cli/src/prompts/config-prompts.ts
index 8db4de3..eb0b31a 100644
--- a/apps/cli/src/prompts/config-prompts.ts
+++ b/apps/cli/src/prompts/config-prompts.ts
@@ -2,14 +2,14 @@ import { cancel, group } from "@clack/prompts";
import pc from "picocolors";
import type {
PackageManager,
+ ProjectAddons,
ProjectConfig,
ProjectDatabase,
- ProjectFeature,
ProjectOrm,
} from "../types";
+import { getAddonsChoice } from "./addons";
import { getAuthChoice } from "./auth";
import { getDatabaseChoice } from "./database";
-import { getFeaturesChoice } from "./features";
import { getGitChoice } from "./git";
import { getNoInstallChoice } from "./install";
import { getORMChoice } from "./orm";
@@ -22,7 +22,7 @@ interface PromptGroupResults {
database: ProjectDatabase;
orm: ProjectOrm;
auth: boolean;
- features: ProjectFeature[];
+ addons: ProjectAddons[];
git: boolean;
packageManager: PackageManager;
noInstall: boolean;
@@ -46,7 +46,7 @@ export async function gatherConfig(
results.database === "sqlite"
? getTursoSetupChoice(flags.turso)
: Promise.resolve(false),
- features: () => getFeaturesChoice(flags.features),
+ addons: () => getAddonsChoice(flags.addons),
git: () => getGitChoice(flags.git),
packageManager: () => getPackageManagerChoice(flags.packageManager),
noInstall: () => getNoInstallChoice(flags.noInstall),
@@ -64,7 +64,7 @@ export async function gatherConfig(
database: result.database,
orm: result.orm,
auth: result.auth,
- features: result.features,
+ addons: result.addons,
git: result.git,
packageManager: result.packageManager,
noInstall: result.noInstall,
diff --git a/apps/cli/src/prompts/install.ts b/apps/cli/src/prompts/install.ts
index 117c1dc..ec62f27 100644
--- a/apps/cli/src/prompts/install.ts
+++ b/apps/cli/src/prompts/install.ts
@@ -8,7 +8,7 @@ export async function getNoInstallChoice(
if (noInstall !== undefined) return noInstall;
const response = await confirm({
- message: "Install dependencies after creating project?",
+ message: "Install dependencies?",
initialValue: !DEFAULT_CONFIG.noInstall,
});
diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts
index cf4a527..f7b8bd0 100644
--- a/apps/cli/src/types.ts
+++ b/apps/cli/src/types.ts
@@ -1,14 +1,14 @@
export type ProjectDatabase = "sqlite" | "postgres" | "none";
export type ProjectOrm = "drizzle" | "prisma" | "none";
export type PackageManager = "npm" | "pnpm" | "yarn" | "bun";
-export type ProjectFeature = "docker" | "github-actions" | "SEO";
+export type ProjectAddons = "docker" | "github-actions" | "SEO";
export interface ProjectConfig {
projectName: string;
database: ProjectDatabase;
orm: ProjectOrm;
auth: boolean;
- features: ProjectFeature[];
+ addons: ProjectAddons[];
git: boolean;
packageManager: PackageManager;
noInstall?: boolean;
diff --git a/apps/cli/src/utils/display-config.ts b/apps/cli/src/utils/display-config.ts
index e497f3e..1f295a1 100644
--- a/apps/cli/src/utils/display-config.ts
+++ b/apps/cli/src/utils/display-config.ts
@@ -16,8 +16,8 @@ export function displayConfig(config: Partial) {
if (config.auth !== undefined) {
configDisplay.push(`${pc.blue("Authentication:")} ${config.auth}`);
}
- if (config.features?.length) {
- configDisplay.push(`${pc.blue("Features:")} ${config.features.join(", ")}`);
+ if (config.addons?.length) {
+ configDisplay.push(`${pc.blue("Addons:")} ${config.addons.join(", ")}`);
}
if (config.git !== undefined) {
configDisplay.push(`${pc.blue("Git Init:")} ${config.git}`);
diff --git a/apps/cli/src/utils/generate-reproducible-command.ts b/apps/cli/src/utils/generate-reproducible-command.ts
index ec9d91a..7a9a18d 100644
--- a/apps/cli/src/utils/generate-reproducible-command.ts
+++ b/apps/cli/src/utils/generate-reproducible-command.ts
@@ -41,12 +41,12 @@ export function generateReproducibleCommand(config: ProjectConfig): string {
flags.push(`--${config.packageManager}`);
}
- if (config.features.length > 0) {
- for (const feature of config.features) {
- flags.push(`--${feature}`);
+ if (config.addons.length > 0) {
+ for (const addon of config.addons) {
+ flags.push(`--${addon}`);
}
} else {
- flags.push("--no-features");
+ flags.push("--no-addons");
}
const baseCommand = "npx create-better-t-stack";
diff --git a/apps/web/src/app/(home)/_components/CodeContainer.tsx b/apps/web/src/app/(home)/_components/CodeContainer.tsx
index 0817bb7..922b439 100644
--- a/apps/web/src/app/(home)/_components/CodeContainer.tsx
+++ b/apps/web/src/app/(home)/_components/CodeContainer.tsx
@@ -175,7 +175,7 @@ const CodeContainer = () => {
yes
- Features:{" "}
+ Addons:{" "}
docker, github-actions, SEO
diff --git a/apps/web/src/app/(home)/_components/CustomizableStack.tsx b/apps/web/src/app/(home)/_components/CustomizableStack.tsx
index 6eb9178..195e968 100644
--- a/apps/web/src/app/(home)/_components/CustomizableStack.tsx
+++ b/apps/web/src/app/(home)/_components/CustomizableStack.tsx
@@ -45,7 +45,7 @@ interface ActiveNodes {
orm: string;
auth: string;
packageManager: string;
- features: {
+ addons: {
docker: boolean;
githubActions: boolean;
seo: boolean;
@@ -62,7 +62,7 @@ const CustomizableStack = () => {
orm: "drizzle",
auth: "better-auth",
packageManager: "npm",
- features: {
+ addons: {
docker: false,
githubActions: false,
seo: false,
@@ -128,10 +128,10 @@ const CustomizableStack = () => {
setActiveNodes((prev) => ({
...prev,
[category]: techId,
- ...(category === "features" && {
- features: {
- ...prev.features,
- [techId]: !prev.features[techId as keyof typeof prev.features],
+ ...(category === "addons" && {
+ addons: {
+ ...prev.addons,
+ [techId]: !prev.addons[techId as keyof typeof prev.addons],
},
}),
}));
@@ -342,16 +342,15 @@ const CustomizableStack = () => {
const command = "npx create-better-t-stack my-app";
const flags: string[] = [];
- // Check if all defaults are being used
const isAllDefaults =
activeNodes.database === "sqlite" &&
activeNodes.auth === "better-auth" &&
activeNodes.orm === "drizzle" &&
activeNodes.packageManager === "npm" &&
- activeNodes.features.git === true &&
- !activeNodes.features.docker &&
- !activeNodes.features.githubActions &&
- !activeNodes.features.seo;
+ activeNodes.addons.git === true &&
+ !activeNodes.addons.docker &&
+ !activeNodes.addons.githubActions &&
+ !activeNodes.addons.seo;
// If using all defaults, just use -y flag
if (isAllDefaults) {
@@ -387,19 +386,19 @@ const CustomizableStack = () => {
}
// Feature flags
- if (activeNodes.features.docker) {
+ if (activeNodes.addons.docker) {
flags.push("--docker");
}
- if (activeNodes.features.githubActions) {
+ if (activeNodes.addons.githubActions) {
flags.push("--github-actions");
}
- if (activeNodes.features.seo) {
+ if (activeNodes.addons.seo) {
flags.push("--seo");
}
- if (!activeNodes.features.git) {
+ if (!activeNodes.addons.git) {
flags.push("--no-git");
}
@@ -408,21 +407,17 @@ const CustomizableStack = () => {
return (
- {/* Command Display - Fixed at top with proper centering */}
- {/* Main container with proper layout */}
- {/* Tech selector fixed to the left side */}
- {/* Help text */}
Select technologies from the left panel to customize your stack. The
@@ -430,7 +425,6 @@ const CustomizableStack = () => {
- {/* Flow container with proper spacing from the selector */}
= {
{ id: "yarn", label: "Yarn", category: "packageManager" },
{ id: "bun", label: "Bun", category: "packageManager" },
],
- features: [
- { id: "docker", label: "Docker", category: "features" },
- { id: "githubActions", label: "GitHub Actions", category: "features" },
- { id: "seo", label: "SEO", category: "features" },
- { id: "git", label: "Git", category: "features" },
+ addons: [
+ { id: "docker", label: "Docker", category: "addons" },
+ { id: "githubActions", label: "GitHub Actions", category: "addons" },
+ { id: "seo", label: "SEO", category: "addons" },
+ { id: "git", label: "Git", category: "addons" },
],
};
@@ -60,7 +60,7 @@ export function TechSelector({ onSelect, activeNodes }: TechSelectorProps) {
{/* Regular tech options */}
{Object.entries(techOptions)
- .filter(([category]) => category !== "features")
+ .filter(([category]) => category !== "addons")
.map(([category, options]) => (
{category}
@@ -71,7 +71,7 @@ export function TechSelector({ onSelect, activeNodes }: TechSelectorProps) {
variant="secondary"
className={`cursor-pointer hover:bg-gray-700 ${
activeNodes[
- category as keyof Omit
+ category as keyof Omit
] === option.id && "bg-blue-600 text-white"
}`}
onClick={() => onSelect(category, option.id)}
@@ -85,18 +85,18 @@ export function TechSelector({ onSelect, activeNodes }: TechSelectorProps) {
{/* Feature toggles */}
-
Features
+
Addons
- {techOptions.features.map((option) => (
+ {techOptions.addons.map((option) => (
onSelect("features", option.id)}
+ onClick={() => onSelect("addons", option.id)}
>
{option.label}