create-better-t-stack 3.2.5 → 3.2.6
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/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-ljR3uw5T.js → src-DW5V85iA.js} +112 -168
- package/package.json +12 -12
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { a as router, i as init, n as createBtsCli, o as sponsors, r as docs, t as builder } from "./src-DW5V85iA.js";
|
|
3
3
|
|
|
4
4
|
export { builder, createBtsCli, docs, init, router, sponsors };
|
|
@@ -334,11 +334,9 @@ function isWebFrontend(value) {
|
|
|
334
334
|
return WEB_FRAMEWORKS.includes(value);
|
|
335
335
|
}
|
|
336
336
|
function splitFrontends(values = []) {
|
|
337
|
-
const web = values.filter((f) => isWebFrontend(f));
|
|
338
|
-
const native = values.filter((f) => f === "native-nativewind" || f === "native-unistyles");
|
|
339
337
|
return {
|
|
340
|
-
web,
|
|
341
|
-
native
|
|
338
|
+
web: values.filter((f) => isWebFrontend(f)),
|
|
339
|
+
native: values.filter((f) => f === "native-nativewind" || f === "native-unistyles")
|
|
342
340
|
};
|
|
343
341
|
}
|
|
344
342
|
function ensureSingleWebAndNative(frontends) {
|
|
@@ -411,13 +409,10 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
|
411
409
|
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
412
410
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
413
411
|
if (compatibleFrontends.length > 0) {
|
|
414
|
-
if (!frontend.some((f) => compatibleFrontends.includes(f))) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
reason: `${addon} addon requires one of these frontends: ${frontendList}`
|
|
419
|
-
};
|
|
420
|
-
}
|
|
412
|
+
if (!frontend.some((f) => compatibleFrontends.includes(f))) return {
|
|
413
|
+
isCompatible: false,
|
|
414
|
+
reason: `${addon} addon requires one of these frontends: ${compatibleFrontends.join(", ")}`
|
|
415
|
+
};
|
|
421
416
|
}
|
|
422
417
|
return { isCompatible: true };
|
|
423
418
|
}
|
|
@@ -546,11 +541,10 @@ async function getAddonsChoice(addons, frontends, auth) {
|
|
|
546
541
|
Object.keys(groupedOptions).forEach((group$1) => {
|
|
547
542
|
if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
|
|
548
543
|
});
|
|
549
|
-
const initialValues = DEFAULT_CONFIG.addons.filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue)));
|
|
550
544
|
const response = await groupMultiselect({
|
|
551
545
|
message: "Select addons",
|
|
552
546
|
options: groupedOptions,
|
|
553
|
-
initialValues,
|
|
547
|
+
initialValues: DEFAULT_CONFIG.addons.filter((addonValue) => Object.values(groupedOptions).some((options) => options.some((opt) => opt.value === addonValue))),
|
|
554
548
|
required: false,
|
|
555
549
|
selectableGroups: false
|
|
556
550
|
});
|
|
@@ -924,46 +918,45 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
924
918
|
if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
|
|
925
919
|
const result = [];
|
|
926
920
|
if (frontendTypes.includes("web")) {
|
|
927
|
-
const webOptions = [
|
|
928
|
-
{
|
|
929
|
-
value: "tanstack-router",
|
|
930
|
-
label: "TanStack Router",
|
|
931
|
-
hint: "Modern and scalable routing for React Applications"
|
|
932
|
-
},
|
|
933
|
-
{
|
|
934
|
-
value: "react-router",
|
|
935
|
-
label: "React Router",
|
|
936
|
-
hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
|
|
937
|
-
},
|
|
938
|
-
{
|
|
939
|
-
value: "next",
|
|
940
|
-
label: "Next.js",
|
|
941
|
-
hint: "The React Framework for the Web"
|
|
942
|
-
},
|
|
943
|
-
{
|
|
944
|
-
value: "nuxt",
|
|
945
|
-
label: "Nuxt",
|
|
946
|
-
hint: "The Progressive Web Framework for Vue.js"
|
|
947
|
-
},
|
|
948
|
-
{
|
|
949
|
-
value: "svelte",
|
|
950
|
-
label: "Svelte",
|
|
951
|
-
hint: "web development for the rest of us"
|
|
952
|
-
},
|
|
953
|
-
{
|
|
954
|
-
value: "solid",
|
|
955
|
-
label: "Solid",
|
|
956
|
-
hint: "Simple and performant reactivity for building user interfaces"
|
|
957
|
-
},
|
|
958
|
-
{
|
|
959
|
-
value: "tanstack-start",
|
|
960
|
-
label: "TanStack Start",
|
|
961
|
-
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
962
|
-
}
|
|
963
|
-
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
964
921
|
const webFramework = await select({
|
|
965
922
|
message: "Choose web",
|
|
966
|
-
options:
|
|
923
|
+
options: [
|
|
924
|
+
{
|
|
925
|
+
value: "tanstack-router",
|
|
926
|
+
label: "TanStack Router",
|
|
927
|
+
hint: "Modern and scalable routing for React Applications"
|
|
928
|
+
},
|
|
929
|
+
{
|
|
930
|
+
value: "react-router",
|
|
931
|
+
label: "React Router",
|
|
932
|
+
hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
|
|
933
|
+
},
|
|
934
|
+
{
|
|
935
|
+
value: "next",
|
|
936
|
+
label: "Next.js",
|
|
937
|
+
hint: "The React Framework for the Web"
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
value: "nuxt",
|
|
941
|
+
label: "Nuxt",
|
|
942
|
+
hint: "The Progressive Web Framework for Vue.js"
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
value: "svelte",
|
|
946
|
+
label: "Svelte",
|
|
947
|
+
hint: "web development for the rest of us"
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
value: "solid",
|
|
951
|
+
label: "Solid",
|
|
952
|
+
hint: "Simple and performant reactivity for building user interfaces"
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
value: "tanstack-start",
|
|
956
|
+
label: "TanStack Start",
|
|
957
|
+
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
958
|
+
}
|
|
959
|
+
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth)),
|
|
967
960
|
initialValue: DEFAULT_CONFIG.frontend[0]
|
|
968
961
|
});
|
|
969
962
|
if (isCancel(webFramework)) return exitCancelled("Operation cancelled");
|
|
@@ -1036,10 +1029,9 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
|
|
|
1036
1029
|
if (backend === "convex") return "none";
|
|
1037
1030
|
if (!hasDatabase) return "none";
|
|
1038
1031
|
if (orm !== void 0) return orm;
|
|
1039
|
-
const options = [...database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma]];
|
|
1040
1032
|
const response = await select({
|
|
1041
1033
|
message: "Select ORM",
|
|
1042
|
-
options,
|
|
1034
|
+
options: [...database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma]],
|
|
1043
1035
|
initialValue: database === "mongodb" ? "prisma" : runtime === "workers" ? "drizzle" : DEFAULT_CONFIG.orm
|
|
1044
1036
|
});
|
|
1045
1037
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
@@ -1050,7 +1042,6 @@ async function getORMChoice(orm, hasDatabase, database, backend, runtime) {
|
|
|
1050
1042
|
//#region src/prompts/package-manager.ts
|
|
1051
1043
|
async function getPackageManagerChoice(packageManager) {
|
|
1052
1044
|
if (packageManager !== void 0) return packageManager;
|
|
1053
|
-
const detectedPackageManager = getUserPkgManager();
|
|
1054
1045
|
const response = await select({
|
|
1055
1046
|
message: "Choose package manager",
|
|
1056
1047
|
options: [
|
|
@@ -1070,7 +1061,7 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
1070
1061
|
hint: "All-in-one JavaScript runtime & toolkit"
|
|
1071
1062
|
}
|
|
1072
1063
|
],
|
|
1073
|
-
initialValue:
|
|
1064
|
+
initialValue: getUserPkgManager()
|
|
1074
1065
|
});
|
|
1075
1066
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
1076
1067
|
return response;
|
|
@@ -1219,21 +1210,20 @@ function getDeploymentDisplay(deployment) {
|
|
|
1219
1210
|
async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
|
|
1220
1211
|
if (deployment !== void 0) return deployment;
|
|
1221
1212
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1222
|
-
const options = [
|
|
1223
|
-
"wrangler",
|
|
1224
|
-
"alchemy",
|
|
1225
|
-
"none"
|
|
1226
|
-
].map((deploy) => {
|
|
1227
|
-
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1228
|
-
return {
|
|
1229
|
-
value: deploy,
|
|
1230
|
-
label,
|
|
1231
|
-
hint
|
|
1232
|
-
};
|
|
1233
|
-
});
|
|
1234
1213
|
const response = await select({
|
|
1235
1214
|
message: "Select web deployment",
|
|
1236
|
-
options
|
|
1215
|
+
options: [
|
|
1216
|
+
"wrangler",
|
|
1217
|
+
"alchemy",
|
|
1218
|
+
"none"
|
|
1219
|
+
].map((deploy) => {
|
|
1220
|
+
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1221
|
+
return {
|
|
1222
|
+
value: deploy,
|
|
1223
|
+
label,
|
|
1224
|
+
hint
|
|
1225
|
+
};
|
|
1226
|
+
}),
|
|
1237
1227
|
initialValue: DEFAULT_CONFIG.webDeploy
|
|
1238
1228
|
});
|
|
1239
1229
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
@@ -1333,10 +1323,8 @@ function validateDirectoryName(name) {
|
|
|
1333
1323
|
async function getProjectName(initialName) {
|
|
1334
1324
|
if (initialName) {
|
|
1335
1325
|
if (initialName === ".") return initialName;
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
const projectDir = path.resolve(process.cwd(), initialName);
|
|
1339
|
-
if (isPathWithinCwd(projectDir)) return initialName;
|
|
1326
|
+
if (!validateDirectoryName(path.basename(initialName))) {
|
|
1327
|
+
if (isPathWithinCwd(path.resolve(process.cwd(), initialName))) return initialName;
|
|
1340
1328
|
consola.error(pc.red("Project path must be within current directory"));
|
|
1341
1329
|
}
|
|
1342
1330
|
}
|
|
@@ -1356,12 +1344,10 @@ async function getProjectName(initialName) {
|
|
|
1356
1344
|
defaultValue: defaultName,
|
|
1357
1345
|
validate: (value) => {
|
|
1358
1346
|
const nameToUse = String(value ?? "").trim() || defaultName;
|
|
1359
|
-
const
|
|
1360
|
-
const validationError = validateDirectoryName(finalDirName);
|
|
1347
|
+
const validationError = validateDirectoryName(path.basename(nameToUse));
|
|
1361
1348
|
if (validationError) return validationError;
|
|
1362
1349
|
if (nameToUse !== ".") {
|
|
1363
|
-
|
|
1364
|
-
if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
|
|
1350
|
+
if (!isPathWithinCwd(path.resolve(process.cwd(), nameToUse))) return "Project path must be within current directory";
|
|
1365
1351
|
}
|
|
1366
1352
|
}
|
|
1367
1353
|
});
|
|
@@ -1552,11 +1538,9 @@ async function handleDirectoryConflict(currentPathInput, silent = false) {
|
|
|
1552
1538
|
finalPathInput: currentPathInput,
|
|
1553
1539
|
shouldClearDirectory: false
|
|
1554
1540
|
};
|
|
1555
|
-
case "rename":
|
|
1541
|
+
case "rename":
|
|
1556
1542
|
log.info("Please choose a different project name or path.");
|
|
1557
|
-
|
|
1558
|
-
return await handleDirectoryConflict(newPathInput);
|
|
1559
|
-
}
|
|
1543
|
+
return await handleDirectoryConflict(await getProjectName(void 0));
|
|
1560
1544
|
case "cancel": return exitCancelled("Operation cancelled.");
|
|
1561
1545
|
}
|
|
1562
1546
|
}
|
|
@@ -1621,8 +1605,7 @@ const catppuccinTheme = {
|
|
|
1621
1605
|
const renderTitle = () => {
|
|
1622
1606
|
const terminalWidth = process.stdout.columns || 80;
|
|
1623
1607
|
const titleLines = TITLE_TEXT.split("\n");
|
|
1624
|
-
|
|
1625
|
-
if (terminalWidth < titleWidth) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
|
|
1608
|
+
if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
|
|
1626
1609
|
╔══════════════════╗
|
|
1627
1610
|
║ Better T Stack ║
|
|
1628
1611
|
╚══════════════════╝
|
|
@@ -1904,10 +1887,7 @@ function processAndValidateFlags(options, providedFlags, projectName) {
|
|
|
1904
1887
|
return config;
|
|
1905
1888
|
}
|
|
1906
1889
|
function processProvidedFlagsWithoutValidation(options, projectName) {
|
|
1907
|
-
if (!options.yolo)
|
|
1908
|
-
const providedFlags = getProvidedFlags(options);
|
|
1909
|
-
validateYesFlagCombination(options, providedFlags);
|
|
1910
|
-
}
|
|
1890
|
+
if (!options.yolo) validateYesFlagCombination(options, getProvidedFlags(options));
|
|
1911
1891
|
const config = processFlags(options, projectName);
|
|
1912
1892
|
const validatedProjectName = extractAndValidateProjectName(projectName, options.projectDirectory, true);
|
|
1913
1893
|
if (validatedProjectName) config.projectName = validatedProjectName;
|
|
@@ -2088,8 +2068,7 @@ async function setupFumadocs(config) {
|
|
|
2088
2068
|
initialValue: "next-mdx"
|
|
2089
2069
|
});
|
|
2090
2070
|
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2091
|
-
const
|
|
2092
|
-
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2071
|
+
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`);
|
|
2093
2072
|
const s = spinner();
|
|
2094
2073
|
s.start("Setting up Fumadocs...");
|
|
2095
2074
|
const appsDir = path.join(projectDir, "apps");
|
|
@@ -2176,8 +2155,7 @@ async function setupRuler(config) {
|
|
|
2176
2155
|
const s = spinner();
|
|
2177
2156
|
s.start("Applying rules with Ruler...");
|
|
2178
2157
|
try {
|
|
2179
|
-
|
|
2180
|
-
await execa(rulerApplyCmd, {
|
|
2158
|
+
await execa(getPackageExecutionCommand(packageManager, `@intellectronica/ruler@latest apply --agents ${selectedEditors.join(",")} --local-only`), {
|
|
2181
2159
|
cwd: projectDir,
|
|
2182
2160
|
env: { CI: "true" },
|
|
2183
2161
|
shell: true
|
|
@@ -2211,7 +2189,7 @@ async function setupStarlight(config) {
|
|
|
2211
2189
|
const s = spinner();
|
|
2212
2190
|
try {
|
|
2213
2191
|
s.start("Setting up Starlight docs...");
|
|
2214
|
-
const
|
|
2192
|
+
const starlightInitCommand = getPackageExecutionCommand(packageManager, `create-astro@latest ${[
|
|
2215
2193
|
"docs",
|
|
2216
2194
|
"--template",
|
|
2217
2195
|
"starlight",
|
|
@@ -2220,8 +2198,7 @@ async function setupStarlight(config) {
|
|
|
2220
2198
|
"tailwind",
|
|
2221
2199
|
"--no-git",
|
|
2222
2200
|
"--skip-houston"
|
|
2223
|
-
].join(" ")}
|
|
2224
|
-
const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2201
|
+
].join(" ")}`);
|
|
2225
2202
|
const appsDir = path.join(projectDir, "apps");
|
|
2226
2203
|
await fs.ensureDir(appsDir);
|
|
2227
2204
|
await execa(starlightInitCommand, {
|
|
@@ -2268,7 +2245,7 @@ async function setupTauri(config) {
|
|
|
2268
2245
|
const hasNext = frontend.includes("next");
|
|
2269
2246
|
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
|
|
2270
2247
|
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
|
|
2271
|
-
|
|
2248
|
+
await execa(getPackageExecutionCommand(packageManager, `@tauri-apps/cli@latest ${[
|
|
2272
2249
|
"init",
|
|
2273
2250
|
`--app-name=${path.basename(projectDir)}`,
|
|
2274
2251
|
`--window-title=${path.basename(projectDir)}`,
|
|
@@ -2276,9 +2253,7 @@ async function setupTauri(config) {
|
|
|
2276
2253
|
`--dev-url=${devUrl}`,
|
|
2277
2254
|
`--before-dev-command="${packageManager} run dev"`,
|
|
2278
2255
|
`--before-build-command="${packageManager} run build"`
|
|
2279
|
-
].join(" ")}
|
|
2280
|
-
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2281
|
-
await execa(tauriInitCommand, {
|
|
2256
|
+
].join(" ")}`), {
|
|
2282
2257
|
cwd: clientPackageDir,
|
|
2283
2258
|
env: { CI: "true" },
|
|
2284
2259
|
shell: true
|
|
@@ -2350,8 +2325,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2350
2325
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2351
2326
|
if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
|
|
2352
2327
|
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2353
|
-
const
|
|
2354
|
-
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2328
|
+
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`);
|
|
2355
2329
|
const s = spinner();
|
|
2356
2330
|
s.start("Setting up Ultracite...");
|
|
2357
2331
|
await execa(ultraciteInitCommand, {
|
|
@@ -2738,8 +2712,7 @@ async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, contex
|
|
|
2738
2712
|
}
|
|
2739
2713
|
}
|
|
2740
2714
|
async function copyBaseTemplate(projectDir, context) {
|
|
2741
|
-
|
|
2742
|
-
await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
|
|
2715
|
+
await processAndCopyFiles(["**/*"], path.join(PKG_ROOT, "templates/base"), projectDir, context);
|
|
2743
2716
|
}
|
|
2744
2717
|
async function setupFrontendTemplates(projectDir, context) {
|
|
2745
2718
|
const hasReactWeb = context.frontend.some((f) => [
|
|
@@ -3191,8 +3164,7 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3191
3164
|
if (context.webDeploy === "alchemy" && (context.serverDeploy === "alchemy" || isBackendSelf)) {
|
|
3192
3165
|
if (await fs.pathExists(alchemyTemplateSrc)) {
|
|
3193
3166
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3194
|
-
|
|
3195
|
-
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, destDir, context);
|
|
3167
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, isBackendSelf && await fs.pathExists(webAppDir) ? webAppDir : projectDir, context);
|
|
3196
3168
|
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3197
3169
|
}
|
|
3198
3170
|
} else {
|
|
@@ -3289,16 +3261,14 @@ async function addAddonsToProject(input) {
|
|
|
3289
3261
|
await setupAddonsTemplate(projectDir, config);
|
|
3290
3262
|
await setupAddons(config, true);
|
|
3291
3263
|
const currentAddons = detectedConfig.addons || [];
|
|
3292
|
-
|
|
3293
|
-
await updateBtsConfig(projectDir, { addons: mergedAddons });
|
|
3264
|
+
await updateBtsConfig(projectDir, { addons: [...new Set([...currentAddons, ...input.addons])] });
|
|
3294
3265
|
if (config.install) await installDependencies({
|
|
3295
3266
|
projectDir,
|
|
3296
3267
|
packageManager: config.packageManager
|
|
3297
3268
|
});
|
|
3298
3269
|
else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
|
|
3299
3270
|
} catch (error) {
|
|
3300
|
-
|
|
3301
|
-
exitWithError(`Error adding addons: ${message}`);
|
|
3271
|
+
exitWithError(`Error adding addons: ${error instanceof Error ? error.message : String(error)}`);
|
|
3302
3272
|
}
|
|
3303
3273
|
}
|
|
3304
3274
|
|
|
@@ -3342,8 +3312,7 @@ async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
|
|
|
3342
3312
|
const s = spinner();
|
|
3343
3313
|
try {
|
|
3344
3314
|
s.start("Generating Cloudflare Workers types...");
|
|
3345
|
-
|
|
3346
|
-
await execa(runCmd, {
|
|
3315
|
+
await execa(getPackageExecutionCommand(packageManager, "wrangler types --env-interface CloudflareBindings"), {
|
|
3347
3316
|
cwd: serverDir,
|
|
3348
3317
|
shell: true
|
|
3349
3318
|
});
|
|
@@ -4017,8 +3986,7 @@ async function addDeploymentToProject(input) {
|
|
|
4017
3986
|
});
|
|
4018
3987
|
else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
|
|
4019
3988
|
} catch (error) {
|
|
4020
|
-
|
|
4021
|
-
exitWithError(`Error adding deployment: ${message}`);
|
|
3989
|
+
exitWithError(`Error adding deployment: ${error instanceof Error ? error.message : String(error)}`);
|
|
4022
3990
|
}
|
|
4023
3991
|
}
|
|
4024
3992
|
|
|
@@ -4446,8 +4414,7 @@ async function setupBetterAuthPlugins(projectDir, config) {
|
|
|
4446
4414
|
if (betterAuthCall) {
|
|
4447
4415
|
const configObject = betterAuthCall.getArguments()[0];
|
|
4448
4416
|
if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
|
|
4449
|
-
const
|
|
4450
|
-
const pluginsArray = ensureArrayProperty(objLiteral, "plugins");
|
|
4417
|
+
const pluginsArray = ensureArrayProperty(configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression), "plugins");
|
|
4451
4418
|
pluginsToAdd.forEach((plugin) => {
|
|
4452
4419
|
pluginsArray.addElement(plugin);
|
|
4453
4420
|
});
|
|
@@ -4685,11 +4652,9 @@ async function setupEnvironmentVariables(config) {
|
|
|
4685
4652
|
const clientDir = path.join(projectDir, "apps/web");
|
|
4686
4653
|
if (await fs.pathExists(clientDir)) {
|
|
4687
4654
|
const baseVar = getClientServerVar(frontend, backend);
|
|
4688
|
-
const envVarName = backend === "convex" ? getConvexVar(frontend) : baseVar.key;
|
|
4689
|
-
const serverUrl = backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value;
|
|
4690
4655
|
const clientVars = [{
|
|
4691
|
-
key:
|
|
4692
|
-
value:
|
|
4656
|
+
key: backend === "convex" ? getConvexVar(frontend) : baseVar.key,
|
|
4657
|
+
value: backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value,
|
|
4693
4658
|
condition: backend === "convex" ? true : baseVar.write
|
|
4694
4659
|
}];
|
|
4695
4660
|
if (backend === "convex" && auth === "clerk") {
|
|
@@ -4864,14 +4829,12 @@ ${hasWeb ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}
|
|
|
4864
4829
|
} else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
|
|
4865
4830
|
const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
|
|
4866
4831
|
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4867
|
-
if (isUnifiedAlchemy) {
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
}]);
|
|
4874
|
-
} else if (isIndividualAlchemy) {
|
|
4832
|
+
if (isUnifiedAlchemy) await addEnvVariablesToFile(path.join(projectDir, ".env"), [{
|
|
4833
|
+
key: "ALCHEMY_PASSWORD",
|
|
4834
|
+
value: "please-change-this",
|
|
4835
|
+
condition: true
|
|
4836
|
+
}]);
|
|
4837
|
+
else if (isIndividualAlchemy) {
|
|
4875
4838
|
if (webDeploy === "alchemy") {
|
|
4876
4839
|
const webDir = path.join(projectDir, "apps/web");
|
|
4877
4840
|
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
|
|
@@ -4933,10 +4896,9 @@ async function setupCloudflareD1(config) {
|
|
|
4933
4896
|
try {
|
|
4934
4897
|
await addEnvVariablesToFile(envPath, variables);
|
|
4935
4898
|
} catch (_err) {}
|
|
4936
|
-
const serverDir = path.join(projectDir, backend === "self" ? "apps/web" : "apps/server");
|
|
4937
4899
|
await addPackageDependency({
|
|
4938
4900
|
dependencies: ["@prisma/adapter-d1"],
|
|
4939
|
-
projectDir:
|
|
4901
|
+
projectDir: path.join(projectDir, backend === "self" ? "apps/web" : "apps/server")
|
|
4940
4902
|
});
|
|
4941
4903
|
}
|
|
4942
4904
|
}
|
|
@@ -4954,13 +4916,11 @@ async function setupDockerCompose(config) {
|
|
|
4954
4916
|
}
|
|
4955
4917
|
async function writeEnvFile$4(projectDir, database, projectName, backend) {
|
|
4956
4918
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
4957
|
-
|
|
4958
|
-
const variables = [{
|
|
4919
|
+
await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
|
|
4959
4920
|
key: "DATABASE_URL",
|
|
4960
4921
|
value: getDatabaseUrl(database, projectName),
|
|
4961
4922
|
condition: true
|
|
4962
|
-
}];
|
|
4963
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
4923
|
+
}]);
|
|
4964
4924
|
}
|
|
4965
4925
|
function getDatabaseUrl(database, projectName) {
|
|
4966
4926
|
switch (database) {
|
|
@@ -5030,13 +4990,11 @@ async function initMongoDBAtlas(serverDir) {
|
|
|
5030
4990
|
async function writeEnvFile$3(projectDir, backend, config) {
|
|
5031
4991
|
try {
|
|
5032
4992
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5033
|
-
|
|
5034
|
-
const variables = [{
|
|
4993
|
+
await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
|
|
5035
4994
|
key: "DATABASE_URL",
|
|
5036
4995
|
value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
|
|
5037
4996
|
condition: true
|
|
5038
|
-
}];
|
|
5039
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
4997
|
+
}]);
|
|
5040
4998
|
} catch (_error) {
|
|
5041
4999
|
consola.error("Failed to update environment configuration");
|
|
5042
5000
|
}
|
|
@@ -5159,8 +5117,7 @@ async function executeNeonCommand(packageManager, commandArgsString, spinnerText
|
|
|
5159
5117
|
}
|
|
5160
5118
|
async function createNeonProject(projectName, regionId, packageManager) {
|
|
5161
5119
|
try {
|
|
5162
|
-
const
|
|
5163
|
-
const { stdout } = await executeNeonCommand(packageManager, commandArgsString, `Creating Neon project "${projectName}"...`);
|
|
5120
|
+
const { stdout } = await executeNeonCommand(packageManager, `neonctl@latest projects create --name ${projectName} --region-id ${regionId} --output json`, `Creating Neon project "${projectName}"...`);
|
|
5164
5121
|
const response = JSON.parse(stdout);
|
|
5165
5122
|
if (response.project && response.connection_uris && response.connection_uris.length > 0) {
|
|
5166
5123
|
const projectId = response.project.id;
|
|
@@ -5181,13 +5138,11 @@ async function createNeonProject(projectName, regionId, packageManager) {
|
|
|
5181
5138
|
}
|
|
5182
5139
|
async function writeEnvFile$2(projectDir, backend, config) {
|
|
5183
5140
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5184
|
-
|
|
5185
|
-
const variables = [{
|
|
5141
|
+
await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
|
|
5186
5142
|
key: "DATABASE_URL",
|
|
5187
5143
|
value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
|
|
5188
5144
|
condition: true
|
|
5189
|
-
}];
|
|
5190
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
5145
|
+
}]);
|
|
5191
5146
|
return true;
|
|
5192
5147
|
}
|
|
5193
5148
|
async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
@@ -5197,8 +5152,7 @@ async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
|
5197
5152
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5198
5153
|
const targetDir = path.join(projectDir, targetApp);
|
|
5199
5154
|
await fs.ensureDir(targetDir);
|
|
5200
|
-
|
|
5201
|
-
await execa(packageCmd, {
|
|
5155
|
+
await execa(getPackageExecutionCommand(packageManager, "neondb@latest --yes"), {
|
|
5202
5156
|
shell: true,
|
|
5203
5157
|
cwd: targetDir
|
|
5204
5158
|
});
|
|
@@ -5416,8 +5370,7 @@ async function initPrismaDatabase(serverDir, packageManager) {
|
|
|
5416
5370
|
const prismaDir = path.join(serverDir, "prisma");
|
|
5417
5371
|
await fs.ensureDir(prismaDir);
|
|
5418
5372
|
log.info("Starting Prisma PostgreSQL setup.");
|
|
5419
|
-
|
|
5420
|
-
await execa(prismaInitCommand, {
|
|
5373
|
+
await execa(getPackageExecutionCommand(packageManager, "prisma init --db"), {
|
|
5421
5374
|
cwd: serverDir,
|
|
5422
5375
|
stdio: "inherit",
|
|
5423
5376
|
shell: true
|
|
@@ -5478,10 +5431,9 @@ DATABASE_URL="your_database_url"`);
|
|
|
5478
5431
|
}
|
|
5479
5432
|
async function addPrismaAccelerateExtension(projectDir) {
|
|
5480
5433
|
try {
|
|
5481
|
-
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5482
5434
|
await addPackageDependency({
|
|
5483
5435
|
dependencies: ["@prisma/extension-accelerate"],
|
|
5484
|
-
projectDir:
|
|
5436
|
+
projectDir: path.join(projectDir, "packages/db")
|
|
5485
5437
|
});
|
|
5486
5438
|
return true;
|
|
5487
5439
|
} catch (_error) {
|
|
@@ -5592,8 +5544,7 @@ function extractDbUrl(output) {
|
|
|
5592
5544
|
async function initializeSupabase(serverDir, packageManager) {
|
|
5593
5545
|
log.info("Initializing Supabase project...");
|
|
5594
5546
|
try {
|
|
5595
|
-
|
|
5596
|
-
await execa(supabaseInitCommand, {
|
|
5547
|
+
await execa(getPackageExecutionCommand(packageManager, "supabase init"), {
|
|
5597
5548
|
cwd: serverDir,
|
|
5598
5549
|
stdio: "inherit",
|
|
5599
5550
|
shell: true
|
|
@@ -5784,13 +5735,12 @@ async function selectTursoGroup() {
|
|
|
5784
5735
|
log.info(`Using the only available group: ${pc.blue(groups[0].name)}`);
|
|
5785
5736
|
return groups[0].name;
|
|
5786
5737
|
}
|
|
5787
|
-
const groupOptions = groups.map((group$1) => ({
|
|
5788
|
-
value: group$1.name,
|
|
5789
|
-
label: `${group$1.name} (${group$1.locations})`
|
|
5790
|
-
}));
|
|
5791
5738
|
const selectedGroup = await select({
|
|
5792
5739
|
message: "Select a Turso database group:",
|
|
5793
|
-
options:
|
|
5740
|
+
options: groups.map((group$1) => ({
|
|
5741
|
+
value: group$1.name,
|
|
5742
|
+
label: `${group$1.name} (${group$1.locations})`
|
|
5743
|
+
}))
|
|
5794
5744
|
});
|
|
5795
5745
|
if (isCancel(selectedGroup)) return exitCancelled("Operation cancelled");
|
|
5796
5746
|
return selectedGroup;
|
|
@@ -5821,8 +5771,7 @@ async function createTursoDatabase(dbName, groupName) {
|
|
|
5821
5771
|
}
|
|
5822
5772
|
async function writeEnvFile(projectDir, backend, config) {
|
|
5823
5773
|
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5824
|
-
|
|
5825
|
-
const variables = [{
|
|
5774
|
+
await addEnvVariablesToFile(path.join(projectDir, targetApp, ".env"), [{
|
|
5826
5775
|
key: "DATABASE_URL",
|
|
5827
5776
|
value: config?.dbUrl ?? "",
|
|
5828
5777
|
condition: true
|
|
@@ -5830,8 +5779,7 @@ async function writeEnvFile(projectDir, backend, config) {
|
|
|
5830
5779
|
key: "DATABASE_AUTH_TOKEN",
|
|
5831
5780
|
value: config?.authToken ?? "",
|
|
5832
5781
|
condition: true
|
|
5833
|
-
}];
|
|
5834
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
5782
|
+
}]);
|
|
5835
5783
|
}
|
|
5836
5784
|
function displayManualSetupInstructions() {
|
|
5837
5785
|
log.info(`Manual Turso Setup Instructions:
|
|
@@ -5912,8 +5860,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5912
5860
|
if (isCancel(dbNameResponse)) return exitCancelled("Operation cancelled");
|
|
5913
5861
|
dbName = dbNameResponse;
|
|
5914
5862
|
try {
|
|
5915
|
-
|
|
5916
|
-
await writeEnvFile(projectDir, backend, config$1);
|
|
5863
|
+
await writeEnvFile(projectDir, backend, await createTursoDatabase(dbName, selectedGroup));
|
|
5917
5864
|
success = true;
|
|
5918
5865
|
} catch (error) {
|
|
5919
5866
|
if (error instanceof Error && error.message === "DATABASE_EXISTS") {
|
|
@@ -7083,7 +7030,6 @@ async function createProjectHandler(input) {
|
|
|
7083
7030
|
shouldClearDirectory = result.shouldClearDirectory;
|
|
7084
7031
|
}
|
|
7085
7032
|
} catch (error) {
|
|
7086
|
-
const elapsedTimeMs$1 = Date.now() - startTime;
|
|
7087
7033
|
return {
|
|
7088
7034
|
success: false,
|
|
7089
7035
|
projectConfig: {
|
|
@@ -7109,7 +7055,7 @@ async function createProjectHandler(input) {
|
|
|
7109
7055
|
},
|
|
7110
7056
|
reproducibleCommand: "",
|
|
7111
7057
|
timeScaffolded,
|
|
7112
|
-
elapsedTimeMs:
|
|
7058
|
+
elapsedTimeMs: Date.now() - startTime,
|
|
7113
7059
|
projectDirectory: "",
|
|
7114
7060
|
relativePath: "",
|
|
7115
7061
|
error: error instanceof Error ? error.message : String(error)
|
|
@@ -7361,11 +7307,10 @@ const router = os.router({
|
|
|
7361
7307
|
manualDb: z$1.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
|
|
7362
7308
|
})])).handler(async ({ input }) => {
|
|
7363
7309
|
const [projectName, options] = input;
|
|
7364
|
-
const
|
|
7310
|
+
const result = await createProjectHandler({
|
|
7365
7311
|
projectName,
|
|
7366
7312
|
...options
|
|
7367
|
-
};
|
|
7368
|
-
const result = await createProjectHandler(combinedInput);
|
|
7313
|
+
});
|
|
7369
7314
|
if (options.verbose) return result;
|
|
7370
7315
|
}),
|
|
7371
7316
|
add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z$1.tuple([z$1.object({
|
|
@@ -7383,8 +7328,7 @@ const router = os.router({
|
|
|
7383
7328
|
try {
|
|
7384
7329
|
renderTitle();
|
|
7385
7330
|
intro(pc.magenta("Better-T-Stack Sponsors"));
|
|
7386
|
-
|
|
7387
|
-
displaySponsors(sponsors$1);
|
|
7331
|
+
displaySponsors(await fetchSponsors());
|
|
7388
7332
|
} catch (error) {
|
|
7389
7333
|
handleError(error, "Failed to display sponsors");
|
|
7390
7334
|
}
|
|
@@ -7472,4 +7416,4 @@ async function builder() {
|
|
|
7472
7416
|
}
|
|
7473
7417
|
|
|
7474
7418
|
//#endregion
|
|
7475
|
-
export {
|
|
7419
|
+
export { router as a, init as i, createBtsCli as n, sponsors as o, docs as r, builder as t };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-better-t-stack",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.6",
|
|
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",
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
69
|
"@biomejs/js-api": "^3.0.0",
|
|
70
|
-
"@biomejs/wasm-nodejs": "^2.2.
|
|
71
|
-
"@clack/prompts": "^1.0.0-alpha.
|
|
72
|
-
"@orpc/server": "^1.
|
|
70
|
+
"@biomejs/wasm-nodejs": "^2.2.6",
|
|
71
|
+
"@clack/prompts": "^1.0.0-alpha.6",
|
|
72
|
+
"@orpc/server": "^1.10.0",
|
|
73
73
|
"consola": "^3.4.2",
|
|
74
74
|
"execa": "^9.6.0",
|
|
75
75
|
"fs-extra": "^11.3.2",
|
|
@@ -78,18 +78,18 @@
|
|
|
78
78
|
"jsonc-parser": "^3.3.1",
|
|
79
79
|
"picocolors": "^1.1.1",
|
|
80
80
|
"tinyglobby": "^0.2.15",
|
|
81
|
-
"trpc-cli": "^0.
|
|
82
|
-
"ts-morph": "^27.0.
|
|
83
|
-
"yaml": "^2.
|
|
84
|
-
"zod": "^4.1.
|
|
81
|
+
"trpc-cli": "^0.12.0",
|
|
82
|
+
"ts-morph": "^27.0.2",
|
|
83
|
+
"yaml": "^2.8.1",
|
|
84
|
+
"zod": "^4.1.12"
|
|
85
85
|
},
|
|
86
86
|
"devDependencies": {
|
|
87
87
|
"@types/fs-extra": "^11.0.4",
|
|
88
|
-
"@types/node": "^24.
|
|
88
|
+
"@types/node": "^24.9.1",
|
|
89
89
|
"@vitest/ui": "^3.2.4",
|
|
90
|
-
"publint": "^0.3.
|
|
91
|
-
"tsdown": "^0.15.
|
|
92
|
-
"typescript": "^5.9.
|
|
90
|
+
"publint": "^0.3.15",
|
|
91
|
+
"tsdown": "^0.15.9",
|
|
92
|
+
"typescript": "^5.9.3",
|
|
93
93
|
"vitest": "^3.2.4"
|
|
94
94
|
}
|
|
95
95
|
}
|