mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
use spaces instead of commas in flags
This commit is contained in:
@@ -3,13 +3,18 @@ import { Command } from "commander";
|
||||
import pc from "picocolors";
|
||||
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 {
|
||||
CLIOptions,
|
||||
ProjectAddons,
|
||||
ProjectBackend,
|
||||
ProjectConfig,
|
||||
ProjectDatabase,
|
||||
ProjectExamples,
|
||||
ProjectFrontend,
|
||||
ProjectOrm,
|
||||
ProjectPackageManager,
|
||||
ProjectRuntime,
|
||||
} from "./types";
|
||||
import { displayConfig } from "./utils/display-config";
|
||||
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
|
||||
@@ -36,22 +41,13 @@ async function main() {
|
||||
.option("--orm <type>", "ORM type (none, drizzle, prisma)")
|
||||
.option("--auth", "Include authentication")
|
||||
.option("--no-auth", "Exclude authentication")
|
||||
.option("--frontend <types...>", "Frontend types (web, native, none)")
|
||||
.option(
|
||||
"--frontend <types>",
|
||||
"Frontend types (web,native or both)",
|
||||
(val) => val.split(",") as ProjectFrontend[],
|
||||
)
|
||||
.option(
|
||||
"--addons <types>",
|
||||
"Additional addons (pwa,tauri,biome,husky)",
|
||||
(val) => val.split(",") as ProjectAddons[],
|
||||
"--addons <types...>",
|
||||
"Additional addons (pwa, tauri, biome, husky)",
|
||||
)
|
||||
.option("--no-addons", "Skip all additional addons")
|
||||
.option(
|
||||
"--examples <types>",
|
||||
"Examples to include (todo,ai)",
|
||||
(val) => val.split(",") as ProjectExamples[],
|
||||
)
|
||||
.option("--examples <types...>", "Examples to include (todo, ai)")
|
||||
.option("--no-examples", "Skip all examples")
|
||||
.option("--git", "Initialize git repository")
|
||||
.option("--no-git", "Skip git initialization")
|
||||
@@ -70,93 +66,12 @@ async function main() {
|
||||
renderTitle();
|
||||
intro(pc.magenta("Creating a new Better-T-Stack project"));
|
||||
|
||||
const options = program.opts();
|
||||
const options = program.opts() as CLIOptions;
|
||||
const projectDirectory = program.args[0];
|
||||
|
||||
if (
|
||||
options.database &&
|
||||
!["none", "sqlite", "postgres"].includes(options.database)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database type: ${options.database}. Must be none, sqlite, or postgres.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
validateOptions(options);
|
||||
|
||||
if (options.orm && !["none", "drizzle", "prisma"].includes(options.orm)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid ORM type: ${options.orm}. Must be none, drizzle, or prisma.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.packageManager &&
|
||||
!["npm", "pnpm", "bun"].includes(options.packageManager)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid package manager: ${options.packageManager}. Must be npm, pnpm, or bun.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.backend && !["hono", "elysia"].includes(options.backend)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid backend framework: ${options.backend}. Must be hono or elysia.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.runtime && !["bun", "node"].includes(options.runtime)) {
|
||||
cancel(
|
||||
pc.red(`Invalid runtime: ${options.runtime}. Must be bun or node.`),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.examples && options.examples.length > 0) {
|
||||
const validExamples = ["todo", "ai"];
|
||||
const invalidExamples = options.examples.filter(
|
||||
(example: ProjectExamples) => !validExamples.includes(example),
|
||||
);
|
||||
|
||||
if (invalidExamples.length > 0) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid example(s): ${invalidExamples.join(", ")}. Valid options are: ${validExamples.join(", ")}.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const flagConfig: Partial<ProjectConfig> = {
|
||||
...(projectDirectory && { projectName: projectDirectory }),
|
||||
...(options.database && { database: options.database }),
|
||||
...(options.orm && { orm: options.orm }),
|
||||
...("auth" in options && { auth: options.auth }),
|
||||
...(options.packageManager && { packageManager: options.packageManager }),
|
||||
...("git" in options && { git: options.git }),
|
||||
...("install" in options && { noInstall: !options.install }),
|
||||
...("turso" in options && { turso: options.turso }),
|
||||
...(options.backend && { backend: options.backend }),
|
||||
...(options.runtime && { runtime: options.runtime }),
|
||||
...(options.frontend && { frontend: options.frontend }),
|
||||
...((options.addons || options.addons === false) && {
|
||||
addons: options.addons === false ? [] : options.addons,
|
||||
}),
|
||||
...((options.examples || options.examples === false) && {
|
||||
examples: options.examples === false ? [] : options.examples,
|
||||
}),
|
||||
};
|
||||
const flagConfig = processFlags(options, projectDirectory);
|
||||
|
||||
if (!options.yes && Object.keys(flagConfig).length > 0) {
|
||||
log.info(pc.yellow("Using these pre-selected options:"));
|
||||
@@ -178,15 +93,7 @@ async function main() {
|
||||
log.message("");
|
||||
}
|
||||
|
||||
const projectDir = await createProject(config);
|
||||
|
||||
if (!config.noInstall) {
|
||||
await installDependencies({
|
||||
projectDir,
|
||||
packageManager: config.packageManager,
|
||||
addons: config.addons,
|
||||
});
|
||||
}
|
||||
await createProject(config);
|
||||
|
||||
log.success(
|
||||
pc.blue(
|
||||
@@ -211,6 +118,322 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
function validateOptions(options: CLIOptions): void {
|
||||
if (
|
||||
options.database &&
|
||||
!["none", "sqlite", "postgres"].includes(options.database)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid database type: ${options.database}. Must be none, sqlite, or postgres.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.orm && !["none", "drizzle", "prisma"].includes(options.orm)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid ORM type: ${options.orm}. Must be none, drizzle, or prisma.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.database === "none") {
|
||||
if (options.auth === true) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Authentication requires a database. Cannot use --auth with --database none.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.orm && options.orm !== "none") {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Cannot use ORM with no database. Cannot use --orm ${options.orm} with --database none.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if ("turso" in options && options.turso === true) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Turso setup requires a SQLite database. Cannot use --turso with --database none.",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
"turso" in options &&
|
||||
options.turso === true &&
|
||||
options.database &&
|
||||
options.database !== "sqlite"
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Turso setup requires a SQLite database. Cannot use --turso with --database ${options.database}`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
"turso" in options &&
|
||||
options.turso === true &&
|
||||
options.orm === "prisma"
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Turso setup is not compatible with Prisma. Cannot use --turso with --orm prisma",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.packageManager &&
|
||||
!["npm", "pnpm", "bun"].includes(options.packageManager)
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid package manager: ${options.packageManager}. Must be npm, pnpm, or bun.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.backend && !["hono", "elysia"].includes(options.backend)) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid backend framework: ${options.backend}. Must be hono or elysia.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.runtime && !["bun", "node"].includes(options.runtime)) {
|
||||
cancel(pc.red(`Invalid runtime: ${options.runtime}. Must be bun or node.`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.examples &&
|
||||
Array.isArray(options.examples) &&
|
||||
options.examples.length > 0
|
||||
) {
|
||||
const validExamples = ["todo", "ai"];
|
||||
const invalidExamples = options.examples.filter(
|
||||
(example: string) => !validExamples.includes(example),
|
||||
);
|
||||
|
||||
if (invalidExamples.length > 0) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid example(s): ${invalidExamples.join(", ")}. Valid options are: ${validExamples.join(", ")}.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.examples.includes("ai") && options.backend === "elysia") {
|
||||
cancel(
|
||||
pc.red(
|
||||
"AI example is only compatible with Hono backend. Cannot use --examples ai with --backend elysia",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (
|
||||
options.frontend &&
|
||||
!options.frontend.includes("web") &&
|
||||
!options.frontend.includes("none")
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
"Examples require a web frontend. Cannot use --examples with --frontend native only",
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.frontend && options.frontend.length > 0) {
|
||||
const validFrontends = ["web", "native", "none"];
|
||||
const invalidFrontends = options.frontend.filter(
|
||||
(frontend: string) => !validFrontends.includes(frontend),
|
||||
);
|
||||
|
||||
if (invalidFrontends.length > 0) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid frontend(s): ${invalidFrontends.join(", ")}. Valid options are: ${validFrontends.join(", ")}.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (options.frontend.includes("none") && options.frontend.length > 1) {
|
||||
cancel(pc.red(`Cannot combine 'none' with other frontend options.`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.addons && options.addons.length > 0) {
|
||||
const validAddons = ["pwa", "tauri", "biome", "husky"];
|
||||
const invalidAddons = options.addons.filter(
|
||||
(addon: string) => !validAddons.includes(addon),
|
||||
);
|
||||
|
||||
if (invalidAddons.length > 0) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`Invalid addon(s): ${invalidAddons.join(", ")}. Valid options are: ${validAddons.join(", ")}.`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const webSpecificAddons = ["pwa", "tauri"];
|
||||
const hasWebSpecificAddons = options.addons.some((addon) =>
|
||||
webSpecificAddons.includes(addon),
|
||||
);
|
||||
|
||||
if (
|
||||
hasWebSpecificAddons &&
|
||||
options.frontend &&
|
||||
!options.frontend.includes("web") &&
|
||||
!options.frontend.includes("none")
|
||||
) {
|
||||
cancel(
|
||||
pc.red(
|
||||
`PWA and Tauri addons require a web frontend. Cannot use --addons ${options.addons
|
||||
.filter((a) => webSpecificAddons.includes(a))
|
||||
.join(", ")} with --frontend native only`,
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processFlags(
|
||||
options: CLIOptions,
|
||||
projectDirectory?: string,
|
||||
): Partial<ProjectConfig> {
|
||||
let frontend: ProjectFrontend[] | undefined = undefined;
|
||||
if (options.frontend) {
|
||||
if (options.frontend.includes("none")) {
|
||||
frontend = [];
|
||||
} else {
|
||||
frontend = options.frontend.filter(
|
||||
(f): f is ProjectFrontend => f === "web" || f === "native",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const database = options.database as ProjectDatabase | undefined;
|
||||
let orm: ProjectOrm | undefined;
|
||||
if (options.orm) {
|
||||
orm = options.orm as ProjectOrm;
|
||||
}
|
||||
|
||||
let auth: boolean | undefined = "auth" in options ? options.auth : undefined;
|
||||
let tursoOption: boolean | undefined =
|
||||
"turso" in options ? options.turso : undefined;
|
||||
|
||||
if (database === "none") {
|
||||
orm = "none";
|
||||
auth = false;
|
||||
tursoOption = false;
|
||||
}
|
||||
|
||||
let examples: ProjectExamples[] | undefined;
|
||||
if ("examples" in options) {
|
||||
if (options.examples === false) {
|
||||
examples = [];
|
||||
} else if (Array.isArray(options.examples)) {
|
||||
examples = options.examples.filter(
|
||||
(ex): ex is ProjectExamples => ex === "todo" || ex === "ai",
|
||||
);
|
||||
|
||||
if (frontend && frontend.length > 0 && !frontend.includes("web")) {
|
||||
examples = [];
|
||||
log.warn(
|
||||
pc.yellow("Examples require web frontend - ignoring examples flag"),
|
||||
);
|
||||
}
|
||||
|
||||
if (examples.includes("ai") && options.backend === "elysia") {
|
||||
examples = examples.filter((ex) => ex !== "ai");
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"AI example is not compatible with Elysia - removing AI example",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let addons: ProjectAddons[] | undefined;
|
||||
if ("addons" in options) {
|
||||
if (options.addons === undefined) {
|
||||
addons = [];
|
||||
} else if (options.addons) {
|
||||
addons = options.addons.filter(
|
||||
(addon): addon is ProjectAddons =>
|
||||
addon === "pwa" ||
|
||||
addon === "tauri" ||
|
||||
addon === "biome" ||
|
||||
addon === "husky",
|
||||
);
|
||||
|
||||
if (frontend && frontend.length > 0 && !frontend.includes("web")) {
|
||||
addons = addons.filter((addon) => !["pwa", "tauri"].includes(addon));
|
||||
if (addons.length !== options.addons.length) {
|
||||
log.warn(
|
||||
pc.yellow(
|
||||
"PWA and Tauri addons require web frontend - removing these addons",
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (addons.includes("husky") && !addons.includes("biome")) {
|
||||
addons.push("biome");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const backend = options.backend as ProjectBackend | undefined;
|
||||
const runtime = options.runtime as ProjectRuntime | undefined;
|
||||
const packageManager = options.packageManager as
|
||||
| ProjectPackageManager
|
||||
| undefined;
|
||||
|
||||
return {
|
||||
...(projectDirectory && { projectName: projectDirectory }),
|
||||
...(database !== undefined && { database }),
|
||||
...(orm !== undefined && { orm }),
|
||||
...(auth !== undefined && { auth }),
|
||||
...(packageManager && { packageManager }),
|
||||
...("git" in options && { git: options.git }),
|
||||
...("install" in options && { noInstall: !options.install }),
|
||||
...(tursoOption !== undefined && { turso: tursoOption }),
|
||||
...(backend && { backend }),
|
||||
...(runtime && { runtime }),
|
||||
...(frontend !== undefined && { frontend }),
|
||||
...(addons !== undefined && { addons }),
|
||||
...(examples !== undefined && { examples }),
|
||||
};
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
log.error("Aborting installation...");
|
||||
if (err instanceof Error) {
|
||||
|
||||
Reference in New Issue
Block a user