create-better-t-stack 2.16.7 → 2.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +281 -349
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -4,13 +4,13 @@ import { cancel, confirm, group, intro, isCancel, log, multiselect, outro, passw
4
4
  import consola, { consola as consola$1 } from "consola";
5
5
  import fs from "fs-extra";
6
6
  import pc from "picocolors";
7
- import yargs from "yargs";
8
- import { hideBin } from "yargs/helpers";
7
+ import { createCli, trpcServer, zod } from "trpc-cli";
9
8
  import { fileURLToPath } from "node:url";
10
9
  import { $, execa } from "execa";
11
10
  import os from "node:os";
12
11
  import { globby } from "globby";
13
12
  import handlebars from "handlebars";
13
+ import { z } from "zod";
14
14
  import { PostHog } from "posthog-node";
15
15
  import gradient from "gradient-string";
16
16
 
@@ -79,7 +79,7 @@ const dependencyVersionMap = {
79
79
  "@types/cors": "^2.8.17",
80
80
  fastify: "^5.3.3",
81
81
  "@fastify/cors": "^11.0.1",
82
- turbo: "^2.4.2",
82
+ turbo: "^2.5.4",
83
83
  ai: "^4.3.16",
84
84
  "@ai-sdk/google": "^1.2.3",
85
85
  "@ai-sdk/vue": "^1.2.8",
@@ -102,143 +102,6 @@ const dependencyVersionMap = {
102
102
  "@tanstack/solid-query-devtools": "^5.75.0"
103
103
  };
104
104
 
105
- //#endregion
106
- //#region src/utils/get-latest-cli-version.ts
107
- const getLatestCLIVersion = () => {
108
- const packageJsonPath = path.join(PKG_ROOT, "package.json");
109
- const packageJsonContent = fs.readJSONSync(packageJsonPath);
110
- return packageJsonContent.version ?? "1.0.0";
111
- };
112
-
113
- //#endregion
114
- //#region src/cli.ts
115
- async function parseCliArguments() {
116
- const argv = await yargs(hideBin(process.argv)).scriptName("create-better-t-stack").usage("$0 [project-directory] [options]", "Create a new Better-T Stack project").positional("project-directory", {
117
- describe: "Project name/directory",
118
- type: "string"
119
- }).option("yes", {
120
- alias: "y",
121
- type: "boolean",
122
- describe: "Use default configuration and skip prompts",
123
- default: false
124
- }).option("database", {
125
- type: "string",
126
- describe: "Database type",
127
- choices: [
128
- "none",
129
- "sqlite",
130
- "postgres",
131
- "mysql",
132
- "mongodb"
133
- ]
134
- }).option("orm", {
135
- type: "string",
136
- describe: "ORM type",
137
- choices: [
138
- "drizzle",
139
- "prisma",
140
- "mongoose",
141
- "none"
142
- ]
143
- }).option("auth", {
144
- type: "boolean",
145
- describe: "Include authentication (use --no-auth to exclude)"
146
- }).option("frontend", {
147
- type: "array",
148
- string: true,
149
- describe: "Frontend types",
150
- choices: [
151
- "tanstack-router",
152
- "react-router",
153
- "tanstack-start",
154
- "next",
155
- "nuxt",
156
- "native-nativewind",
157
- "native-unistyles",
158
- "svelte",
159
- "solid",
160
- "none"
161
- ]
162
- }).option("addons", {
163
- type: "array",
164
- string: true,
165
- describe: "Additional addons",
166
- choices: [
167
- "pwa",
168
- "tauri",
169
- "starlight",
170
- "biome",
171
- "husky",
172
- "turborepo",
173
- "none"
174
- ]
175
- }).option("examples", {
176
- type: "array",
177
- string: true,
178
- describe: "Examples to include",
179
- choices: [
180
- "todo",
181
- "ai",
182
- "none"
183
- ]
184
- }).option("git", {
185
- type: "boolean",
186
- describe: "Initialize git repository (use --no-git to skip)"
187
- }).option("package-manager", {
188
- alias: "pm",
189
- type: "string",
190
- describe: "Package manager",
191
- choices: [
192
- "npm",
193
- "pnpm",
194
- "bun"
195
- ]
196
- }).option("install", {
197
- type: "boolean",
198
- describe: "Install dependencies (use --no-install to skip)"
199
- }).option("db-setup", {
200
- type: "string",
201
- describe: "Database setup",
202
- choices: [
203
- "turso",
204
- "neon",
205
- "prisma-postgres",
206
- "mongodb-atlas",
207
- "supabase",
208
- "none"
209
- ]
210
- }).option("backend", {
211
- type: "string",
212
- describe: "Backend framework",
213
- choices: [
214
- "hono",
215
- "express",
216
- "fastify",
217
- "next",
218
- "elysia",
219
- "convex",
220
- "none"
221
- ]
222
- }).option("runtime", {
223
- type: "string",
224
- describe: "Runtime",
225
- choices: [
226
- "bun",
227
- "node",
228
- "none"
229
- ]
230
- }).option("api", {
231
- type: "string",
232
- describe: "API type",
233
- choices: [
234
- "trpc",
235
- "orpc",
236
- "none"
237
- ]
238
- }).completion().recommendCommands().version(getLatestCLIVersion()).alias("version", "v").help().alias("help", "h").strict().wrap(null).parse();
239
- return argv;
240
- }
241
-
242
105
  //#endregion
243
106
  //#region src/utils/add-package-deps.ts
244
107
  const addPackageDependency = async (opts) => {
@@ -3321,6 +3184,88 @@ async function getProjectName(initialName) {
3321
3184
  return projectPath;
3322
3185
  }
3323
3186
 
3187
+ //#endregion
3188
+ //#region src/types.ts
3189
+ const DatabaseSchema = z.enum([
3190
+ "none",
3191
+ "sqlite",
3192
+ "postgres",
3193
+ "mysql",
3194
+ "mongodb"
3195
+ ]).describe("Database type");
3196
+ const ORMSchema = z.enum([
3197
+ "drizzle",
3198
+ "prisma",
3199
+ "mongoose",
3200
+ "none"
3201
+ ]).describe("ORM type");
3202
+ const BackendSchema = z.enum([
3203
+ "hono",
3204
+ "express",
3205
+ "fastify",
3206
+ "next",
3207
+ "elysia",
3208
+ "convex",
3209
+ "none"
3210
+ ]).describe("Backend framework");
3211
+ const RuntimeSchema = z.enum([
3212
+ "bun",
3213
+ "node",
3214
+ "none"
3215
+ ]).describe("Runtime environment");
3216
+ const FrontendSchema = z.enum([
3217
+ "tanstack-router",
3218
+ "react-router",
3219
+ "tanstack-start",
3220
+ "next",
3221
+ "nuxt",
3222
+ "native-nativewind",
3223
+ "native-unistyles",
3224
+ "svelte",
3225
+ "solid",
3226
+ "none"
3227
+ ]).describe("Frontend framework");
3228
+ const AddonsSchema = z.enum([
3229
+ "pwa",
3230
+ "tauri",
3231
+ "starlight",
3232
+ "biome",
3233
+ "husky",
3234
+ "turborepo",
3235
+ "none"
3236
+ ]).describe("Additional addons");
3237
+ const ExamplesSchema = z.enum([
3238
+ "todo",
3239
+ "ai",
3240
+ "none"
3241
+ ]).describe("Example templates to include");
3242
+ const PackageManagerSchema = z.enum([
3243
+ "npm",
3244
+ "pnpm",
3245
+ "bun"
3246
+ ]).describe("Package manager");
3247
+ const DatabaseSetupSchema = z.enum([
3248
+ "turso",
3249
+ "neon",
3250
+ "prisma-postgres",
3251
+ "mongodb-atlas",
3252
+ "supabase",
3253
+ "none"
3254
+ ]).describe("Database hosting setup");
3255
+ const APISchema = z.enum([
3256
+ "trpc",
3257
+ "orpc",
3258
+ "none"
3259
+ ]).describe("API type");
3260
+
3261
+ //#endregion
3262
+ //#region src/utils/get-latest-cli-version.ts
3263
+ const getLatestCLIVersion = () => {
3264
+ const packageJsonPath = path.join(PKG_ROOT, "package.json");
3265
+ const packageJsonContent = fs.readJSONSync(packageJsonPath);
3266
+ return packageJsonContent.version ?? "1.0.0";
3267
+ };
3268
+
3324
3269
  //#endregion
3325
3270
  //#region src/utils/analytics.ts
3326
3271
  const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
@@ -3471,9 +3416,8 @@ const renderTitle = () => {
3471
3416
 
3472
3417
  //#endregion
3473
3418
  //#region src/validation.ts
3474
- function processAndValidateFlags(options, projectName) {
3419
+ function processAndValidateFlags(options, providedFlags, projectName) {
3475
3420
  const config = {};
3476
- const providedFlags = new Set(Object.keys(options).filter((key) => key !== "_" && key !== "$0"));
3477
3421
  if (options.api) {
3478
3422
  config.api = options.api;
3479
3423
  if (options.api === "none") {
@@ -3571,6 +3515,10 @@ function processAndValidateFlags(options, projectName) {
3571
3515
  if (providedFlags.has("api") && options.api !== "none") incompatibleFlags.push(`--api ${options.api}`);
3572
3516
  if (providedFlags.has("runtime") && options.runtime !== "none") incompatibleFlags.push(`--runtime ${options.runtime}`);
3573
3517
  if (providedFlags.has("dbSetup") && options.dbSetup !== "none") incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
3518
+ if (providedFlags.has("examples") && options.examples) {
3519
+ const hasNonNoneExamples = options.examples.some((ex) => ex !== "none");
3520
+ if (hasNonNoneExamples) incompatibleFlags.push("--examples");
3521
+ }
3574
3522
  if (incompatibleFlags.length > 0) {
3575
3523
  consola$1.fatal(`The following flags are incompatible with '--backend none': ${incompatibleFlags.join(", ")}. Please remove them.`);
3576
3524
  process.exit(1);
@@ -3582,120 +3530,164 @@ function processAndValidateFlags(options, projectName) {
3582
3530
  config.runtime = "none";
3583
3531
  config.dbSetup = "none";
3584
3532
  config.examples = [];
3585
- } else {
3586
- if (config.database === "none") {
3587
- if (providedFlags.has("orm") && options.orm !== "none") {
3588
- consola$1.fatal(`'--orm ${options.orm}' is incompatible with '--database none'. Please use '--orm none' or choose a database.`);
3589
- process.exit(1);
3590
- }
3591
- if (providedFlags.has("auth") && options.auth === true) {
3592
- consola$1.fatal(`'--auth' requires a database. Cannot use '--auth' with '--database none'.`);
3593
- process.exit(1);
3594
- }
3595
- if (providedFlags.has("dbSetup") && options.dbSetup !== "none") {
3596
- consola$1.fatal(`'--db-setup ${options.dbSetup}' requires a database. Cannot use with '--database none'.`);
3597
- process.exit(1);
3598
- }
3599
- config.orm = "none";
3600
- config.auth = false;
3601
- config.dbSetup = "none";
3602
- log.info("Due to '--database none', '--orm' has been automatically set to 'none'.");
3603
- log.info("Due to '--database none', '--auth' has been automatically set to 'false'.");
3604
- log.info("Due to '--database none', '--db-setup' has been automatically set to 'none'.");
3605
- }
3606
- if (config.orm === "mongoose") {
3607
- if (!providedFlags.has("database")) {
3608
- config.database = "mongodb";
3609
- log.info("Due to '--orm mongoose', '--database' has been automatically set to 'mongodb'.");
3610
- } else if (config.database !== "mongodb") {
3611
- consola$1.fatal(`'--orm mongoose' requires '--database mongodb'. Cannot use '--orm mongoose' with '--database ${config.database}'.`);
3612
- process.exit(1);
3613
- }
3614
- }
3615
- if (config.dbSetup) {
3616
- if (config.dbSetup === "turso") {
3617
- if (!providedFlags.has("database")) {
3618
- config.database = "sqlite";
3619
- log.info("Due to '--db-setup turso', '--database' has been automatically set to 'sqlite'.");
3620
- } else if (config.database !== "sqlite") {
3621
- consola$1.fatal(`'--db-setup turso' requires '--database sqlite'. Cannot use with '--database ${config.database}'.`);
3622
- process.exit(1);
3623
- }
3624
- if (!providedFlags.has("orm")) {
3625
- config.orm = "drizzle";
3626
- log.info("Due to '--db-setup turso', '--orm' has been automatically set to 'drizzle'.");
3627
- } else if (config.orm !== "drizzle") {
3628
- consola$1.fatal(`'--db-setup turso' requires '--orm drizzle'. Cannot use with '--orm ${config.orm}'.`);
3629
- process.exit(1);
3630
- }
3631
- } else if (config.dbSetup === "prisma-postgres") {
3632
- if (!providedFlags.has("database")) {
3633
- config.database = "postgres";
3634
- log.info("Due to '--db-setup prisma-postgres', '--database' has been automatically set to 'postgres'.");
3635
- } else if (config.database !== "postgres") {
3636
- consola$1.fatal(`'--db-setup prisma-postgres' requires '--database postgres'. Cannot use with '--database ${config.database}'.`);
3637
- process.exit(1);
3638
- }
3639
- if (!providedFlags.has("orm")) {
3640
- config.orm = "prisma";
3641
- log.info("Due to '--db-setup prisma-postgres', '--orm' has been automatically set to 'prisma'.");
3642
- } else if (config.orm !== "prisma") {
3643
- consola$1.fatal(`'--db-setup prisma-postgres' requires '--orm prisma'. Cannot use with '--orm ${config.orm}'.`);
3644
- process.exit(1);
3645
- }
3646
- } else if (config.dbSetup === "supabase") {
3647
- if (!providedFlags.has("database")) {
3648
- config.database = "postgres";
3649
- log.info("Due to '--db-setup supabase', '--database' has been automatically set to 'postgres'.");
3650
- } else if (config.database !== "postgres") {
3651
- consola$1.fatal(`'--db-setup supabase' requires '--database postgres'. Cannot use with '--database ${config.database}'.`);
3652
- process.exit(1);
3653
- }
3654
- } else if (config.dbSetup === "neon") {
3655
- if (!providedFlags.has("database")) {
3656
- config.database = "postgres";
3657
- log.info("Due to '--db-setup neon', '--database' has been automatically set to 'postgres'.");
3658
- } else if (config.database !== "postgres") {
3659
- consola$1.fatal(`'--db-setup neon' requires '--database postgres'. Cannot use with '--database ${config.database}'.`);
3660
- process.exit(1);
3661
- }
3662
- } else if (config.dbSetup === "mongodb-atlas") {
3663
- if (!providedFlags.has("database")) {
3664
- config.database = "mongodb";
3665
- log.info("Due to '--db-setup mongodb-atlas', '--database' has been automatically set to 'mongodb'.");
3666
- } else if (config.database !== "mongodb") {
3667
- consola$1.fatal(`'--db-setup mongodb-atlas' requires '--database mongodb'. Cannot use with '--database ${config.database}'.`);
3668
- process.exit(1);
3669
- }
3670
- }
3671
- }
3672
- if (config.database === "mongodb" && config.orm === "drizzle") {
3673
- consola$1.fatal(`'--database mongodb' is incompatible with '--orm drizzle'. Use '--orm mongoose' or '--orm prisma' with MongoDB.`);
3674
- process.exit(1);
3675
- }
3533
+ }
3534
+ if (config.orm === "mongoose" && config.database !== "mongodb") {
3535
+ consola$1.fatal("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
3536
+ process.exit(1);
3537
+ }
3538
+ if (config.database === "mongodb" && config.orm && config.orm !== "mongoose" && config.orm !== "prisma") {
3539
+ consola$1.fatal("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3540
+ process.exit(1);
3541
+ }
3542
+ if (config.orm === "drizzle" && config.database === "mongodb") {
3543
+ consola$1.fatal("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
3544
+ process.exit(1);
3545
+ }
3546
+ if (config.database && config.database !== "none" && config.orm === "none") {
3547
+ consola$1.fatal("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
3548
+ process.exit(1);
3549
+ }
3550
+ if (config.orm && config.orm !== "none" && config.database === "none") {
3551
+ consola$1.fatal("ORM selection requires a database. Please choose a database or set '--orm none'.");
3552
+ process.exit(1);
3553
+ }
3554
+ if (config.auth && config.database === "none") {
3555
+ consola$1.fatal("Authentication requires a database. Please choose a database or set '--no-auth'.");
3556
+ process.exit(1);
3557
+ }
3558
+ if (config.dbSetup && config.dbSetup !== "none" && config.database === "none") {
3559
+ consola$1.fatal("Database setup requires a database. Please choose a database or set '--db-setup none'.");
3560
+ process.exit(1);
3561
+ }
3562
+ if (config.dbSetup === "turso" && config.database !== "sqlite") {
3563
+ consola$1.fatal("Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
3564
+ process.exit(1);
3565
+ }
3566
+ if (config.dbSetup === "neon" && config.database !== "postgres") {
3567
+ consola$1.fatal("Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
3568
+ process.exit(1);
3569
+ }
3570
+ if (config.dbSetup === "prisma-postgres" && config.database !== "postgres") {
3571
+ consola$1.fatal("Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
3572
+ process.exit(1);
3573
+ }
3574
+ if (config.dbSetup === "mongodb-atlas" && config.database !== "mongodb") {
3575
+ consola$1.fatal("MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup.");
3576
+ process.exit(1);
3577
+ }
3578
+ if (config.dbSetup === "supabase" && config.database !== "postgres") {
3579
+ consola$1.fatal("Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
3580
+ process.exit(1);
3676
3581
  }
3677
3582
  return config;
3678
3583
  }
3584
+ function getProvidedFlags(options) {
3585
+ return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
3586
+ }
3679
3587
 
3680
3588
  //#endregion
3681
3589
  //#region src/index.ts
3682
3590
  const exit = () => process.exit(0);
3683
3591
  process.on("SIGINT", exit);
3684
3592
  process.on("SIGTERM", exit);
3685
- async function main() {
3593
+ const t = trpcServer.initTRPC.create();
3594
+ async function handleDirectoryConflict(currentPathInput) {
3595
+ while (true) {
3596
+ const resolvedPath = path.resolve(process.cwd(), currentPathInput);
3597
+ const dirExists = fs.pathExistsSync(resolvedPath);
3598
+ const dirIsNotEmpty = dirExists && fs.readdirSync(resolvedPath).length > 0;
3599
+ if (!dirIsNotEmpty) return {
3600
+ finalPathInput: currentPathInput,
3601
+ shouldClearDirectory: false
3602
+ };
3603
+ log.warn(`Directory "${pc.yellow(currentPathInput)}" already exists and is not empty.`);
3604
+ const action = await select({
3605
+ message: "What would you like to do?",
3606
+ options: [
3607
+ {
3608
+ value: "overwrite",
3609
+ label: "Overwrite",
3610
+ hint: "Empty the directory and create the project"
3611
+ },
3612
+ {
3613
+ value: "merge",
3614
+ label: "Merge",
3615
+ hint: "Create project files inside, potentially overwriting conflicts"
3616
+ },
3617
+ {
3618
+ value: "rename",
3619
+ label: "Choose a different name/path",
3620
+ hint: "Keep the existing directory and create a new one"
3621
+ },
3622
+ {
3623
+ value: "cancel",
3624
+ label: "Cancel",
3625
+ hint: "Abort the process"
3626
+ }
3627
+ ],
3628
+ initialValue: "rename"
3629
+ });
3630
+ if (isCancel(action)) {
3631
+ cancel(pc.red("Operation cancelled."));
3632
+ process.exit(0);
3633
+ }
3634
+ switch (action) {
3635
+ case "overwrite": return {
3636
+ finalPathInput: currentPathInput,
3637
+ shouldClearDirectory: true
3638
+ };
3639
+ case "merge":
3640
+ log.info(`Proceeding into existing directory "${pc.yellow(currentPathInput)}". Files may be overwritten.`);
3641
+ return {
3642
+ finalPathInput: currentPathInput,
3643
+ shouldClearDirectory: false
3644
+ };
3645
+ case "rename": {
3646
+ log.info("Please choose a different project name or path.");
3647
+ const newPathInput = await getProjectName(void 0);
3648
+ return await handleDirectoryConflict(newPathInput);
3649
+ }
3650
+ case "cancel":
3651
+ cancel(pc.red("Operation cancelled."));
3652
+ process.exit(0);
3653
+ }
3654
+ }
3655
+ }
3656
+ async function setupProjectDirectory(finalPathInput, shouldClearDirectory) {
3657
+ let finalResolvedPath;
3658
+ let finalBaseName;
3659
+ if (finalPathInput === ".") {
3660
+ finalResolvedPath = process.cwd();
3661
+ finalBaseName = path.basename(finalResolvedPath);
3662
+ } else {
3663
+ finalResolvedPath = path.resolve(process.cwd(), finalPathInput);
3664
+ finalBaseName = path.basename(finalResolvedPath);
3665
+ }
3666
+ if (shouldClearDirectory) {
3667
+ const s = spinner();
3668
+ s.start(`Clearing directory "${finalResolvedPath}"...`);
3669
+ try {
3670
+ await fs.emptyDir(finalResolvedPath);
3671
+ s.stop(`Directory "${finalResolvedPath}" cleared.`);
3672
+ } catch (error) {
3673
+ s.stop(pc.red(`Failed to clear directory "${finalResolvedPath}".`));
3674
+ consola$1.error(error);
3675
+ process.exit(1);
3676
+ }
3677
+ } else await fs.ensureDir(finalResolvedPath);
3678
+ return {
3679
+ finalResolvedPath,
3680
+ finalBaseName
3681
+ };
3682
+ }
3683
+ async function createProjectHandler(input) {
3686
3684
  const startTime = Date.now();
3687
3685
  try {
3688
- const options = await parseCliArguments();
3689
- const cliProjectNameArg = options.projectDirectory;
3690
3686
  renderTitle();
3691
- intro(pc.magenta("Creating a new Better-T-Stack project"));
3687
+ intro(pc.magenta("Creating a new Better-T Stack project"));
3692
3688
  let currentPathInput;
3693
- let finalPathInput;
3694
- let finalResolvedPath;
3695
- let finalBaseName;
3696
- let shouldClearDirectory = false;
3697
- if (options.yes && cliProjectNameArg) currentPathInput = cliProjectNameArg;
3698
- else if (options.yes) {
3689
+ if (input.yes && input.projectName) currentPathInput = input.projectName;
3690
+ else if (input.yes) {
3699
3691
  let defaultName = DEFAULT_CONFIG.relativePath;
3700
3692
  let counter = 1;
3701
3693
  while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
@@ -3703,94 +3695,23 @@ async function main() {
3703
3695
  counter++;
3704
3696
  }
3705
3697
  currentPathInput = defaultName;
3706
- } else currentPathInput = await getProjectName(cliProjectNameArg);
3707
- while (true) {
3708
- const resolvedPath = path.resolve(process.cwd(), currentPathInput);
3709
- const dirExists = fs.pathExistsSync(resolvedPath);
3710
- const dirIsNotEmpty = dirExists && fs.readdirSync(resolvedPath).length > 0;
3711
- if (!dirIsNotEmpty) {
3712
- finalPathInput = currentPathInput;
3713
- shouldClearDirectory = false;
3714
- break;
3715
- }
3716
- log.warn(`Directory "${pc.yellow(currentPathInput)}" already exists and is not empty.`);
3717
- const action = await select({
3718
- message: "What would you like to do?",
3719
- options: [
3720
- {
3721
- value: "overwrite",
3722
- label: "Overwrite",
3723
- hint: "Empty the directory and create the project"
3724
- },
3725
- {
3726
- value: "merge",
3727
- label: "Merge",
3728
- hint: "Create project files inside, potentially overwriting conflicts"
3729
- },
3730
- {
3731
- value: "rename",
3732
- label: "Choose a different name/path",
3733
- hint: "Keep the existing directory and create a new one"
3734
- },
3735
- {
3736
- value: "cancel",
3737
- label: "Cancel",
3738
- hint: "Abort the process"
3739
- }
3740
- ],
3741
- initialValue: "rename"
3742
- });
3743
- if (isCancel(action)) {
3744
- cancel(pc.red("Operation cancelled."));
3745
- process.exit(0);
3746
- }
3747
- if (action === "overwrite") {
3748
- finalPathInput = currentPathInput;
3749
- shouldClearDirectory = true;
3750
- break;
3751
- }
3752
- if (action === "merge") {
3753
- finalPathInput = currentPathInput;
3754
- shouldClearDirectory = false;
3755
- log.info(`Proceeding into existing directory "${pc.yellow(currentPathInput)}". Files may be overwritten.`);
3756
- break;
3757
- }
3758
- if (action === "rename") {
3759
- log.info("Please choose a different project name or path.");
3760
- currentPathInput = await getProjectName(void 0);
3761
- } else if (action === "cancel") {
3762
- cancel(pc.red("Operation cancelled."));
3763
- process.exit(0);
3764
- }
3765
- }
3766
- if (finalPathInput === ".") {
3767
- finalResolvedPath = process.cwd();
3768
- finalBaseName = path.basename(finalResolvedPath);
3769
- } else {
3770
- finalResolvedPath = path.resolve(process.cwd(), finalPathInput);
3771
- finalBaseName = path.basename(finalResolvedPath);
3772
- }
3773
- if (shouldClearDirectory) {
3774
- const s = spinner();
3775
- s.start(`Clearing directory "${finalResolvedPath}"...`);
3776
- try {
3777
- await fs.emptyDir(finalResolvedPath);
3778
- s.stop(`Directory "${finalResolvedPath}" cleared.`);
3779
- } catch (error) {
3780
- s.stop(pc.red(`Failed to clear directory "${finalResolvedPath}".`));
3781
- consola$1.error(error);
3782
- process.exit(1);
3783
- }
3784
- } else await fs.ensureDir(finalResolvedPath);
3785
- const flagConfig = processAndValidateFlags(options, finalBaseName);
3698
+ } else currentPathInput = await getProjectName(input.projectName);
3699
+ const { finalPathInput, shouldClearDirectory } = await handleDirectoryConflict(currentPathInput);
3700
+ const { finalResolvedPath, finalBaseName } = await setupProjectDirectory(finalPathInput, shouldClearDirectory);
3701
+ const cliInput = {
3702
+ ...input,
3703
+ projectDirectory: input.projectName
3704
+ };
3705
+ const providedFlags = getProvidedFlags(cliInput);
3706
+ const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
3786
3707
  const { projectName: _projectNameFromFlags,...otherFlags } = flagConfig;
3787
- if (!options.yes && Object.keys(otherFlags).length > 0) {
3708
+ if (!input.yes && Object.keys(otherFlags).length > 0) {
3788
3709
  log.info(pc.yellow("Using these pre-selected options:"));
3789
3710
  log.message(displayConfig(otherFlags));
3790
3711
  log.message("");
3791
3712
  }
3792
3713
  let config;
3793
- if (options.yes) {
3714
+ if (input.yes) {
3794
3715
  config = {
3795
3716
  ...DEFAULT_CONFIG,
3796
3717
  ...flagConfig,
@@ -3811,29 +3732,40 @@ async function main() {
3811
3732
  const elapsedTimeInSeconds = ((Date.now() - startTime) / 1e3).toFixed(2);
3812
3733
  outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
3813
3734
  } catch (error) {
3814
- if (error instanceof Error) {
3815
- if (error.name === "YError") cancel(pc.red(`Invalid arguments: ${error.message}`));
3816
- else {
3817
- consola$1.error(`An unexpected error occurred: ${error.message}`);
3818
- if (!error.message.includes("is only supported with")) consola$1.error(error.stack);
3819
- }
3820
- process.exit(1);
3821
- } else {
3822
- consola$1.error("An unexpected error occurred.");
3823
- console.error(error);
3824
- process.exit(1);
3825
- }
3826
- }
3827
- }
3828
- main().catch((err) => {
3829
- consola$1.error("Aborting installation due to unexpected error...");
3830
- if (err instanceof Error) {
3831
- if (!err.message.includes("is only supported with") && !err.message.includes("incompatible with") && !err.message.includes("requires") && !err.message.includes("Cannot use") && !err.message.includes("Cannot select multiple") && !err.message.includes("Cannot combine") && !err.message.includes("not supported")) {
3832
- consola$1.error(err.message);
3833
- consola$1.error(err.stack);
3834
- }
3835
- } else console.error(err);
3836
- process.exit(1);
3837
- });
3735
+ console.error(error);
3736
+ process.exit(1);
3737
+ }
3738
+ }
3739
+ const router = t.router({ init: t.procedure.meta({
3740
+ description: "Create a new Better-T Stack project",
3741
+ default: true
3742
+ }).input(zod.tuple([zod.string().optional().describe("project-name"), zod.object({
3743
+ yes: zod.boolean().optional().default(false).describe("Use default configuration and skip prompts"),
3744
+ database: DatabaseSchema.optional(),
3745
+ orm: ORMSchema.optional(),
3746
+ auth: zod.boolean().optional().describe("Include authentication"),
3747
+ frontend: zod.array(FrontendSchema).optional().describe("Frontend frameworks"),
3748
+ addons: zod.array(AddonsSchema).optional().describe("Additional addons"),
3749
+ examples: zod.array(ExamplesSchema).optional().describe("Examples to include"),
3750
+ git: zod.boolean().optional().describe("Initialize git repository"),
3751
+ packageManager: PackageManagerSchema.optional(),
3752
+ install: zod.boolean().optional().describe("Install dependencies"),
3753
+ dbSetup: DatabaseSetupSchema.optional(),
3754
+ backend: BackendSchema.optional(),
3755
+ runtime: RuntimeSchema.optional(),
3756
+ api: APISchema.optional()
3757
+ }).optional().default({})])).mutation(async ({ input }) => {
3758
+ const [projectName, options] = input;
3759
+ const combinedInput = {
3760
+ projectName,
3761
+ ...options
3762
+ };
3763
+ await createProjectHandler(combinedInput);
3764
+ }) });
3765
+ createCli({
3766
+ router,
3767
+ name: "create-better-t-stack",
3768
+ version: getLatestCLIVersion()
3769
+ }).run();
3838
3770
 
3839
3771
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.16.7",
3
+ "version": "2.17.1",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -62,13 +62,13 @@
62
62
  "handlebars": "^4.7.8",
63
63
  "picocolors": "^1.1.1",
64
64
  "posthog-node": "^4.18.0",
65
- "yargs": "^18.0.0"
65
+ "trpc-cli": "^0.8.0",
66
+ "zod": "^3.25.57"
66
67
  },
67
68
  "devDependencies": {
68
69
  "@types/fs-extra": "^11.0.4",
69
- "@types/node": "^22.15.23",
70
- "@types/yargs": "^17.0.33",
71
- "tsdown": "^0.12.4",
70
+ "@types/node": "^24.0.0",
71
+ "tsdown": "^0.12.7",
72
72
  "typescript": "^5.8.3"
73
73
  }
74
74
  }