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.
- package/dist/index.js +281 -349
- 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
|
|
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
|
|
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
|
-
}
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
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
|
-
|
|
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
|
|
3687
|
+
intro(pc.magenta("Creating a new Better-T Stack project"));
|
|
3692
3688
|
let currentPathInput;
|
|
3693
|
-
|
|
3694
|
-
|
|
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(
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
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 (!
|
|
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 (
|
|
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
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
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.
|
|
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
|
-
"
|
|
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": "^
|
|
70
|
-
"
|
|
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
|
}
|