mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
fix(cli): improve flags handling (#552)
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ import type {
|
||||
ProjectConfig,
|
||||
} from "../../types";
|
||||
import { trackProjectCreation } from "../../utils/analytics";
|
||||
import { coerceBackendPresets } from "../../utils/compatibility-rules";
|
||||
|
||||
import { displayConfig } from "../../utils/display-config";
|
||||
import { exitWithError, handleError } from "../../utils/errors";
|
||||
import { generateReproducibleCommand } from "../../utils/generate-reproducible-command";
|
||||
@@ -148,20 +148,8 @@ export async function createProjectHandler(
|
||||
relativePath: finalPathInput,
|
||||
};
|
||||
|
||||
coerceBackendPresets(config);
|
||||
|
||||
validateConfigCompatibility(config, providedFlags, cliInput);
|
||||
|
||||
if (config.backend === "convex") {
|
||||
log.info(
|
||||
`Due to '--backend convex' flag, the following options have been automatically set: database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo`,
|
||||
);
|
||||
} else if (config.backend === "none") {
|
||||
log.info(
|
||||
"Due to '--backend none', the following options have been automatically set: --auth none, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none",
|
||||
);
|
||||
}
|
||||
|
||||
log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
|
||||
log.message(displayConfig(config));
|
||||
log.message("");
|
||||
|
||||
@@ -133,72 +133,6 @@ export function validateWorkersCompatibility(
|
||||
}
|
||||
}
|
||||
|
||||
export function coerceBackendPresets(config: Partial<ProjectConfig>) {
|
||||
if (config.backend === "convex") {
|
||||
config.database = "none";
|
||||
config.orm = "none";
|
||||
config.api = "none";
|
||||
config.runtime = "none";
|
||||
config.dbSetup = "none";
|
||||
config.examples = ["todo"] as ProjectConfig["examples"];
|
||||
}
|
||||
if (config.backend === "none") {
|
||||
config.auth = "none" as ProjectConfig["auth"];
|
||||
config.database = "none";
|
||||
config.orm = "none";
|
||||
config.api = "none";
|
||||
config.runtime = "none";
|
||||
config.dbSetup = "none";
|
||||
config.examples = [] as ProjectConfig["examples"];
|
||||
}
|
||||
}
|
||||
|
||||
export function incompatibleFlagsForBackend(
|
||||
backend: ProjectConfig["backend"] | undefined,
|
||||
providedFlags: Set<string>,
|
||||
options: CLIInput,
|
||||
): string[] {
|
||||
const list: string[] = [];
|
||||
if (backend === "convex") {
|
||||
if (
|
||||
providedFlags.has("auth") &&
|
||||
options.auth &&
|
||||
options.auth !== "none" &&
|
||||
options.auth !== "clerk"
|
||||
)
|
||||
list.push(`--auth ${options.auth}`);
|
||||
if (providedFlags.has("database") && options.database !== "none")
|
||||
list.push(`--database ${options.database}`);
|
||||
if (providedFlags.has("orm") && options.orm !== "none")
|
||||
list.push(`--orm ${options.orm}`);
|
||||
if (providedFlags.has("api") && options.api !== "none")
|
||||
list.push(`--api ${options.api}`);
|
||||
if (providedFlags.has("runtime") && options.runtime !== "none")
|
||||
list.push(`--runtime ${options.runtime}`);
|
||||
if (providedFlags.has("dbSetup") && options.dbSetup !== "none")
|
||||
list.push(`--db-setup ${options.dbSetup}`);
|
||||
}
|
||||
if (backend === "none") {
|
||||
if (providedFlags.has("auth") && options.auth && options.auth !== "none")
|
||||
list.push(`--auth ${options.auth}`);
|
||||
if (providedFlags.has("database") && options.database !== "none")
|
||||
list.push(`--database ${options.database}`);
|
||||
if (providedFlags.has("orm") && options.orm !== "none")
|
||||
list.push(`--orm ${options.orm}`);
|
||||
if (providedFlags.has("api") && options.api !== "none")
|
||||
list.push(`--api ${options.api}`);
|
||||
if (providedFlags.has("runtime") && options.runtime !== "none")
|
||||
list.push(`--runtime ${options.runtime}`);
|
||||
if (providedFlags.has("dbSetup") && options.dbSetup !== "none")
|
||||
list.push(`--db-setup ${options.dbSetup}`);
|
||||
if (providedFlags.has("examples") && options.examples) {
|
||||
const hasNonNoneExamples = options.examples.some((ex) => ex !== "none");
|
||||
if (hasNonNoneExamples) list.push("--examples");
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
export function validateApiFrontendCompatibility(
|
||||
api: API | undefined,
|
||||
frontends: Frontend[] = [],
|
||||
|
||||
@@ -6,9 +6,7 @@ import type {
|
||||
Runtime,
|
||||
} from "../types";
|
||||
import {
|
||||
coerceBackendPresets,
|
||||
ensureSingleWebAndNative,
|
||||
incompatibleFlagsForBackend,
|
||||
isWebFrontend,
|
||||
validateAddonsAgainstFrontends,
|
||||
validateAlchemyCompatibility,
|
||||
@@ -226,38 +224,19 @@ export function validateBackendConstraints(
|
||||
}
|
||||
}
|
||||
|
||||
if (backend === "convex" || backend === "none") {
|
||||
const incompatibleFlags = incompatibleFlagsForBackend(
|
||||
backend,
|
||||
providedFlags,
|
||||
options,
|
||||
);
|
||||
if (incompatibleFlags.length > 0) {
|
||||
if (
|
||||
backend === "convex" &&
|
||||
providedFlags.has("frontend") &&
|
||||
options.frontend
|
||||
) {
|
||||
const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
|
||||
if (incompatibleFrontends.length > 0) {
|
||||
exitWithError(
|
||||
`The following flags are incompatible with '--backend ${backend}': ${incompatibleFlags.join(
|
||||
`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(
|
||||
", ",
|
||||
)}. Please remove them.`,
|
||||
)}. Please choose a different frontend or backend.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
backend === "convex" &&
|
||||
providedFlags.has("frontend") &&
|
||||
options.frontend
|
||||
) {
|
||||
const incompatibleFrontends = options.frontend.filter(
|
||||
(f) => f === "solid",
|
||||
);
|
||||
if (incompatibleFrontends.length > 0) {
|
||||
exitWithError(
|
||||
`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(
|
||||
", ",
|
||||
)}. Please choose a different frontend or backend.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
coerceBackendPresets(config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,39 @@ import {
|
||||
import { exitWithError } from "./utils/errors";
|
||||
import { extractAndValidateProjectName } from "./utils/project-name-validation";
|
||||
|
||||
const CORE_STACK_FLAGS = new Set([
|
||||
"database",
|
||||
"orm",
|
||||
"backend",
|
||||
"runtime",
|
||||
"frontend",
|
||||
"addons",
|
||||
"examples",
|
||||
"auth",
|
||||
"dbSetup",
|
||||
"api",
|
||||
"webDeploy",
|
||||
"serverDeploy",
|
||||
]);
|
||||
|
||||
function validateYesFlagCombination(
|
||||
options: CLIInput,
|
||||
providedFlags: Set<string>,
|
||||
) {
|
||||
if (!options.yes) return;
|
||||
|
||||
const coreStackFlagsProvided = Array.from(providedFlags).filter((flag) =>
|
||||
CORE_STACK_FLAGS.has(flag),
|
||||
);
|
||||
|
||||
if (coreStackFlagsProvided.length > 0) {
|
||||
exitWithError(
|
||||
`Cannot combine --yes with core stack configuration flags: ${coreStackFlagsProvided.map((f) => `--${f}`).join(", ")}. ` +
|
||||
"The --yes flag uses default configuration. Remove these flags or use --yes without them.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function processAndValidateFlags(
|
||||
options: CLIInput,
|
||||
providedFlags: Set<string>,
|
||||
@@ -29,6 +62,8 @@ export function processAndValidateFlags(
|
||||
return cfg;
|
||||
}
|
||||
|
||||
validateYesFlagCombination(options, providedFlags);
|
||||
|
||||
try {
|
||||
validateArrayOptions(options);
|
||||
} catch (error) {
|
||||
@@ -55,6 +90,11 @@ export function processProvidedFlagsWithoutValidation(
|
||||
options: CLIInput,
|
||||
projectName?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
if (!options.yolo) {
|
||||
const providedFlags = getProvidedFlags(options);
|
||||
validateYesFlagCombination(options, providedFlags);
|
||||
}
|
||||
|
||||
const config = processFlags(options, projectName);
|
||||
|
||||
const validatedProjectName = extractAndValidateProjectName(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -83,7 +83,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
describe("Core functionality", () => {
|
||||
test("creates minimal project successfully", async () => {
|
||||
const result = await init("test-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -100,7 +112,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("returns complete result structure", async () => {
|
||||
const result = await init("result-test", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -116,7 +140,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("handles project with custom name", async () => {
|
||||
const result = await init("custom-name", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -130,8 +166,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
describe("Configuration options", () => {
|
||||
test("creates project with Next.js frontend", async () => {
|
||||
const result = await init("next-app", {
|
||||
yes: true,
|
||||
frontend: ["next"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -144,8 +191,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with Fastify backend", async () => {
|
||||
const result = await init("fastify-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "fastify",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -158,9 +216,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with PostgreSQL + Prisma", async () => {
|
||||
const result = await init("pg-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "postgres",
|
||||
orm: "prisma",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -174,8 +242,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with oRPC API", async () => {
|
||||
const result = await init("orpc-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "orpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -188,8 +267,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with Node runtime", async () => {
|
||||
const result = await init("node-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "node",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -202,8 +292,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with Biome addon", async () => {
|
||||
const result = await init("biome-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["biome"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -216,7 +317,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with analytics disabled", async () => {
|
||||
const result = await init("no-analytics-app", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
disableAnalytics: true,
|
||||
install: false,
|
||||
git: false,
|
||||
@@ -231,7 +344,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
test("handles invalid project name", async () => {
|
||||
await expect(
|
||||
init("", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
}),
|
||||
@@ -241,7 +366,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
test("handles invalid characters in project name", async () => {
|
||||
await expect(
|
||||
init("invalid<name>", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
}),
|
||||
@@ -251,9 +388,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
test("handles incompatible database + ORM combination", async () => {
|
||||
await expect(
|
||||
init("incompatible", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "mongodb",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
yolo: false,
|
||||
@@ -264,19 +411,41 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
test("handles auth without database", async () => {
|
||||
await expect(
|
||||
init("auth-no-db", {
|
||||
yes: true,
|
||||
auth: "better-auth",
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "none",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
yolo: false,
|
||||
}),
|
||||
).rejects.toThrow(/Authentication requires/);
|
||||
).rejects.toThrow(/ORM selection requires a database/);
|
||||
});
|
||||
|
||||
test("handles directory conflict with error strategy", async () => {
|
||||
const result1 = await init("conflict-test", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -284,7 +453,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
expect(result1.success).toBe(true);
|
||||
|
||||
const result2 = await init("conflict-test", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
directoryConflict: "error",
|
||||
@@ -298,8 +479,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
describe("Advanced features", () => {
|
||||
test("creates project with multiple addons", async () => {
|
||||
const result = await init("multi-addon", {
|
||||
yes: true,
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["biome", "turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -312,10 +504,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("creates project with authentication enabled", async () => {
|
||||
const result = await init("auth-app", {
|
||||
yes: true,
|
||||
auth: "better-auth",
|
||||
frontend: ["tanstack-router"],
|
||||
backend: "hono",
|
||||
runtime: "bun",
|
||||
database: "sqlite",
|
||||
orm: "drizzle",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
@@ -330,11 +531,19 @@ describe("Programmatic API - Fast Tests", () => {
|
||||
|
||||
test("validates reproducible command format", async () => {
|
||||
const result = await init("repro-test", {
|
||||
yes: true,
|
||||
frontend: ["next"],
|
||||
backend: "fastify",
|
||||
runtime: "bun",
|
||||
database: "postgres",
|
||||
orm: "prisma",
|
||||
api: "trpc",
|
||||
auth: "better-auth",
|
||||
dbSetup: "none",
|
||||
webDeploy: "none",
|
||||
serverDeploy: "none",
|
||||
addons: ["turborepo"],
|
||||
examples: ["none"],
|
||||
packageManager: "bun",
|
||||
install: false,
|
||||
git: false,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user