make backend optional

This commit is contained in:
Aman Varshney
2025-05-07 08:31:54 +05:30
parent af1e8efbdc
commit d09a284ce7
11 changed files with 116 additions and 25 deletions

View File

@@ -0,0 +1,5 @@
---
"create-better-t-stack": patch
---
make backend optional

View File

@@ -39,7 +39,7 @@ export async function setupExamples(config: ProjectConfig): Promise<void> {
});
}
if (serverDirExists) {
if (serverDirExists && backend !== "none") {
await addPackageDependency({
dependencies: ["ai", "@ai-sdk/google"],
projectDir: serverDir,

View File

@@ -213,15 +213,23 @@ export async function setupBackendFramework(
projectDir: string,
context: ProjectConfig,
): Promise<void> {
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") {

View File

@@ -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);
}

View File

@@ -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<ProjectApi> {
if (backend === "convex") {
if (backend === "convex" || backend === "none") {
return "none";
}

View File

@@ -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";

View File

@@ -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,

View File

@@ -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<ProjectDatabase> {
if (backend === "convex") {
if (backend === "convex" || backend === "none") {
return "none";
}

View File

@@ -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 =

View File

@@ -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<ProjectRuntime> {
if (backend === "convex") {
if (backend === "convex" || backend === "none") {
return "none";
}

View File

@@ -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 =