From d09a284ce7b406c803a523301235b6b5cad39b2f Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Wed, 7 May 2025 08:31:54 +0530 Subject: [PATCH] make backend optional --- .changeset/few-dogs-grow.md | 5 ++ apps/cli/src/helpers/examples-setup.ts | 2 +- apps/cli/src/helpers/template-manager.ts | 25 +++++---- apps/cli/src/index.ts | 66 ++++++++++++++++++++--- apps/cli/src/prompts/api.ts | 4 +- apps/cli/src/prompts/backend-framework.ts | 7 +++ apps/cli/src/prompts/config-prompts.ts | 10 ++++ apps/cli/src/prompts/database.ts | 4 +- apps/cli/src/prompts/examples.ts | 6 ++- apps/cli/src/prompts/runtime.ts | 4 +- apps/cli/src/types.ts | 8 ++- 11 files changed, 116 insertions(+), 25 deletions(-) create mode 100644 .changeset/few-dogs-grow.md diff --git a/.changeset/few-dogs-grow.md b/.changeset/few-dogs-grow.md new file mode 100644 index 0000000..ffceb19 --- /dev/null +++ b/.changeset/few-dogs-grow.md @@ -0,0 +1,5 @@ +--- +"create-better-t-stack": patch +--- + +make backend optional diff --git a/apps/cli/src/helpers/examples-setup.ts b/apps/cli/src/helpers/examples-setup.ts index 1c39076..74b9d97 100644 --- a/apps/cli/src/helpers/examples-setup.ts +++ b/apps/cli/src/helpers/examples-setup.ts @@ -39,7 +39,7 @@ export async function setupExamples(config: ProjectConfig): Promise { }); } - if (serverDirExists) { + if (serverDirExists && backend !== "none") { await addPackageDependency({ dependencies: ["ai", "@ai-sdk/google"], projectDir: serverDir, diff --git a/apps/cli/src/helpers/template-manager.ts b/apps/cli/src/helpers/template-manager.ts index 450afe9..2c17529 100644 --- a/apps/cli/src/helpers/template-manager.ts +++ b/apps/cli/src/helpers/template-manager.ts @@ -213,15 +213,23 @@ export async function setupBackendFramework( projectDir: string, context: ProjectConfig, ): Promise { + if (context.backend === "none") { + return; + } + + const serverAppDir = path.join(projectDir, "apps/server"); + if (context.backend === "convex") { + if (await fs.pathExists(serverAppDir)) { + await fs.remove(serverAppDir); + } + const convexBackendDestDir = path.join(projectDir, "packages/backend"); const convexSrcDir = path.join( PKG_ROOT, "templates/backend/convex/packages/backend", ); - await fs.ensureDir(convexBackendDestDir); - if (await fs.pathExists(convexSrcDir)) { await processAndCopyFiles( "**/*", @@ -229,17 +237,10 @@ export async function setupBackendFramework( convexBackendDestDir, context, ); - } else { - } - - const serverAppDir = path.join(projectDir, "apps/server"); - if (await fs.pathExists(serverAppDir)) { - await fs.remove(serverAppDir); } return; } - const serverAppDir = path.join(projectDir, "apps/server"); await fs.ensureDir(serverAppDir); const serverBaseDir = path.join( @@ -540,7 +541,11 @@ export async function setupExamplesTemplate( const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`); - if (serverAppDirExists && context.backend !== "convex") { + if ( + serverAppDirExists && + context.backend !== "convex" && + context.backend !== "none" + ) { const exampleServerSrc = path.join(exampleBaseDir, "server"); if (example === "ai" && context.backend === "next") { diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index dd8abd2..a039652 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -132,7 +132,7 @@ async function main() { .option("backend", { type: "string", describe: "Backend framework", - choices: ["hono", "express", "next", "elysia", "convex"], + choices: ["hono", "express", "next", "elysia", "convex", "none"], }) .option("runtime", { type: "string", @@ -303,6 +303,14 @@ async function main() { config.runtime = "none"; config.dbSetup = "none"; config.examples = ["todo"]; + } else if (config.backend === "none") { + config.auth = false; + config.database = "none"; + config.orm = "none"; + config.api = "none"; + config.runtime = "none"; + config.dbSetup = "none"; + config.examples = []; } else if (config.database === "none") { config.orm = "none"; config.auth = false; @@ -389,17 +397,18 @@ function processAndValidateFlags( if ( providedFlags.has("backend") && config.backend && - config.backend !== "convex" + config.backend !== "convex" && + config.backend !== "none" ) { if (providedFlags.has("api") && options.api === "none") { consola.fatal( - `'--api none' is only supported with '--backend convex'. Please choose 'trpc', 'orpc', or remove the --api flag.`, + `'--api none' is only supported with '--backend convex' or '--backend none'. Please choose 'trpc', 'orpc', or remove the --api flag.`, ); process.exit(1); } if (providedFlags.has("runtime") && options.runtime === "none") { consola.fatal( - `'--runtime none' is only supported with '--backend convex'. Please choose 'bun', 'node', or remove the --runtime flag.`, + `'--runtime none' is only supported with '--backend convex' or '--backend none'. Please choose 'bun', 'node', or remove the --runtime flag.`, ); process.exit(1); } @@ -544,6 +553,48 @@ function processAndValidateFlags( 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 (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"; + if ( + options.examples && + !options.examples.includes("none") && + options.examples.length > 0 + ) { + consola.fatal( + "Cannot select examples when backend is 'none'. Please remove the --examples flag or set --examples none.", + ); + process.exit(1); + } + config.examples = []; } else { const effectiveDatabase = config.database ?? (options.yes ? DEFAULT_CONFIG.database : undefined); @@ -635,7 +686,9 @@ function processAndValidateFlags( } if (effectiveOrm !== "drizzle") { consola.fatal( - `Turso setup requires Drizzle ORM. Cannot use --db-setup turso with --orm ${effectiveOrm ?? "none"}.`, + `Turso setup requires Drizzle ORM. Cannot use --db-setup turso with --orm ${ + effectiveOrm ?? "none" + }.`, ); process.exit(1); } @@ -782,10 +835,11 @@ function processAndValidateFlags( if ( config.examples.includes("todo") && effectiveBackend !== "convex" && + effectiveBackend !== "none" && effectiveDatabase === "none" ) { consola.fatal( - "The 'todo' example requires a database (unless using Convex). Cannot use --examples todo when database is 'none'.", + "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); } diff --git a/apps/cli/src/prompts/api.ts b/apps/cli/src/prompts/api.ts index 5087de3..b1d9961 100644 --- a/apps/cli/src/prompts/api.ts +++ b/apps/cli/src/prompts/api.ts @@ -1,4 +1,4 @@ -import { cancel, isCancel, log, select } from "@clack/prompts"; +import { cancel, isCancel, select } from "@clack/prompts"; import pc from "picocolors"; import { DEFAULT_CONFIG } from "../constants"; import type { ProjectApi, ProjectBackend, ProjectFrontend } from "../types"; @@ -8,7 +8,7 @@ export async function getApiChoice( frontend?: ProjectFrontend[], backend?: ProjectBackend, ): Promise { - if (backend === "convex") { + if (backend === "convex" || backend === "none") { return "none"; } diff --git a/apps/cli/src/prompts/backend-framework.ts b/apps/cli/src/prompts/backend-framework.ts index 8ae4f79..404bfbe 100644 --- a/apps/cli/src/prompts/backend-framework.ts +++ b/apps/cli/src/prompts/backend-framework.ts @@ -48,6 +48,13 @@ export async function getBackendFrameworkChoice( }); } + // Add "None" option + backendOptions.push({ + value: "none" as const, + label: "None", + hint: "No backend server (e.g., for a static site or client-only app)", + }); + let initialValue = DEFAULT_CONFIG.backend; if (hasIncompatibleFrontend && initialValue === "convex") { initialValue = "hono"; diff --git a/apps/cli/src/prompts/config-prompts.ts b/apps/cli/src/prompts/config-prompts.ts index df98904..c89a1dc 100644 --- a/apps/cli/src/prompts/config-prompts.ts +++ b/apps/cli/src/prompts/config-prompts.ts @@ -106,6 +106,16 @@ export async function gatherConfig( result.dbSetup = "none"; } + if (result.backend === "none") { + result.runtime = "none"; + result.database = "none"; + result.orm = "none"; + result.api = "none"; + result.auth = false; + result.dbSetup = "none"; + result.examples = []; + } + return { projectName: projectName, projectDir: projectDir, diff --git a/apps/cli/src/prompts/database.ts b/apps/cli/src/prompts/database.ts index 7d08e19..d3eed78 100644 --- a/apps/cli/src/prompts/database.ts +++ b/apps/cli/src/prompts/database.ts @@ -1,4 +1,4 @@ -import { cancel, isCancel, log, select } from "@clack/prompts"; +import { cancel, isCancel, select } from "@clack/prompts"; import pc from "picocolors"; import { DEFAULT_CONFIG } from "../constants"; import type { ProjectBackend, ProjectDatabase } from "../types"; @@ -7,7 +7,7 @@ export async function getDatabaseChoice( database?: ProjectDatabase, backend?: ProjectBackend, ): Promise { - if (backend === "convex") { + if (backend === "convex" || backend === "none") { return "none"; } diff --git a/apps/cli/src/prompts/examples.ts b/apps/cli/src/prompts/examples.ts index f9fdbf3..3f7d294 100644 --- a/apps/cli/src/prompts/examples.ts +++ b/apps/cli/src/prompts/examples.ts @@ -1,4 +1,4 @@ -import { cancel, isCancel, log, multiselect } from "@clack/prompts"; +import { cancel, isCancel, multiselect } from "@clack/prompts"; import pc from "picocolors"; import { DEFAULT_CONFIG } from "../constants"; import type { @@ -20,6 +20,10 @@ export async function getExamplesChoice( return ["todo"]; } + if (backend === "none") { + return []; + } + if (database === "none") return []; const onlyNative = diff --git a/apps/cli/src/prompts/runtime.ts b/apps/cli/src/prompts/runtime.ts index 9940f67..50dfe0e 100644 --- a/apps/cli/src/prompts/runtime.ts +++ b/apps/cli/src/prompts/runtime.ts @@ -1,4 +1,4 @@ -import { cancel, isCancel, log, select } from "@clack/prompts"; +import { cancel, isCancel, select } from "@clack/prompts"; import pc from "picocolors"; import { DEFAULT_CONFIG } from "../constants"; import type { ProjectBackend, ProjectRuntime } from "../types"; @@ -7,7 +7,7 @@ export async function getRuntimeChoice( runtime?: ProjectRuntime, backend?: ProjectBackend, ): Promise { - if (backend === "convex") { + if (backend === "convex" || backend === "none") { return "none"; } diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index 1a3f325..4837138 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -14,7 +14,13 @@ export type ProjectAddons = | "starlight" | "turborepo" | "none"; -export type ProjectBackend = "hono" | "elysia" | "express" | "next" | "convex"; +export type ProjectBackend = + | "hono" + | "express" + | "next" + | "elysia" + | "convex" + | "none"; export type ProjectRuntime = "node" | "bun" | "none"; export type ProjectExamples = "todo" | "ai" | "none"; export type ProjectFrontend =