create-better-fullstack 2.0.2 → 2.1.0

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.
@@ -1,46 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import { f as types_exports, m as DEFAULT_CONFIG } from "./bts-config-Bg1Qea9Y.mjs";
3
- import { a as CLIError, c as exitWithError, d as isFirstPrompt, f as isSilent, h as setLastPromptShownUI, m as setIsFirstPrompt$1, s as exitCancelled } from "./addons-setup-HSghQS7c.mjs";
2
+ import { c as DEFAULT_CONFIG, y as types_exports } from "./bts-config-InNcw1aJ.mjs";
3
+ import { c as isSilent, d as setLastPromptShownUI, i as exitWithError, r as exitCancelled, s as isFirstPrompt, t as CLIError, u as setIsFirstPrompt$1 } from "./errors-ns_o2OKg.mjs";
4
4
  import { log, spinner } from "@clack/prompts";
5
5
  import pc from "picocolors";
6
6
  import fs from "fs-extra";
7
7
  import path from "node:path";
8
8
  import { allowedApisForFrontends, getAIFrontendCompatibilityIssue, getApiFrontendCompatibilityIssue, getCompatibleAddons, getCompatibleCSSFrameworks, getCompatibleUILibraries, getUnsupportedWebDeployFrontend, hasDockerComposeCompatibleFrontend, hasWebStyling, isBackendUtilsCompatibleBackend, isExampleAIAllowed, isExampleChatSdkAllowed, isFrontendAllowedWithBackend, isWebFrontend, requiresChatSdkVercelAIForSelection, splitFrontends, validateAddonCompatibility } from "@better-fullstack/types";
9
- import gradient from "gradient-string";
10
9
  import consola from "consola";
11
10
  import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
12
11
  import { $ } from "execa";
13
12
 
14
- //#region src/utils/render-title.ts
15
- const TITLE_TEXT = `
16
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
17
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
18
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
19
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
20
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
21
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
22
-
23
- ███████╗██╗ ██╗██╗ ██╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
24
- ██╔════╝██║ ██║██║ ██║ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
25
- █████╗ ██║ ██║██║ ██║ ███████╗ ██║ ███████║██║ █████╔╝
26
- ██╔══╝ ██║ ██║██║ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
27
- ██║ ╚██████╔╝███████╗███████╗███████║ ██║ ██║ ██║╚██████╗██║ ██╗
28
- ╚═╝ ╚═════╝ ╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
29
- `;
30
- const monochromeTheme = {
31
- white: "#FFFFFF",
32
- lightGray: "#E5E5E5",
33
- gray: "#A3A3A3",
34
- darkGray: "#737373"
35
- };
36
- const renderTitle = () => {
37
- const terminalWidth = process.stdout.columns || 80;
38
- const titleLines = TITLE_TEXT.split("\n");
39
- if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(monochromeTheme)).multiline(`Better Fullstack`));
40
- else console.log(gradient(Object.values(monochromeTheme)).multiline(TITLE_TEXT));
41
- };
42
-
43
- //#endregion
44
13
  //#region src/utils/error-formatter.ts
45
14
  function getCategoryTitle(category) {
46
15
  return {
@@ -241,34 +210,46 @@ function validateAddonCompatibility$1(addon, frontend, _auth, backend, runtime,
241
210
  reason: "Backend Utils requires a Hono, Express, Fastify, Elysia, feTS, or NestJS backend"
242
211
  };
243
212
  }
244
- if (addon === "docker-compose") {
213
+ if (addon === "docker-compose" || addon === "devcontainer") {
214
+ const label = addon === "devcontainer" ? "DevContainer" : "docker-compose";
215
+ const title = addon === "devcontainer" ? "DevContainer" : "Docker Compose";
245
216
  if (backend === "convex") return {
246
217
  isCompatible: false,
247
- reason: "docker-compose is not compatible with Convex backend (managed service)"
218
+ reason: `${label} is not compatible with Convex backend (managed service)`
248
219
  };
249
220
  if (runtime === "workers") return {
250
221
  isCompatible: false,
251
- reason: "docker-compose is not compatible with Cloudflare Workers runtime"
222
+ reason: `${label} is not compatible with Cloudflare Workers runtime`
223
+ };
224
+ if (ecosystem !== void 0 && ![
225
+ "typescript",
226
+ "python",
227
+ "go",
228
+ "rust",
229
+ "java"
230
+ ].includes(ecosystem)) return {
231
+ isCompatible: false,
232
+ reason: `${title} currently supports TypeScript, Python, Go, Rust, or Java projects`
252
233
  };
253
234
  if (ecosystem === "typescript" && !hasDockerComposeCompatibleFrontend(frontend)) return {
254
235
  isCompatible: false,
255
- reason: "Docker Compose currently supports Next.js, Vinext, TanStack Router, React Router, React Vite, Solid, or Astro"
236
+ reason: `${title} currently supports Next.js, Vinext, TanStack Router, React Router, React Vite, Solid, or Astro`
256
237
  };
257
238
  if (ecosystem === "typescript" && backend === "self" && !frontend.includes("next") && !frontend.includes("vinext")) return {
258
239
  isCompatible: false,
259
- reason: "Docker Compose self-backend support currently requires Next.js or Vinext"
240
+ reason: `${title} self-backend support currently requires Next.js or Vinext`
260
241
  };
261
242
  if (ecosystem === "rust" && rustFrontend && rustFrontend !== "none") return {
262
243
  isCompatible: false,
263
- reason: "Docker Compose for Rust currently supports server-only projects"
244
+ reason: `${title} for Rust currently supports server-only projects`
264
245
  };
265
246
  if (ecosystem === "java" && javaWebFramework && javaWebFramework !== "spring-boot") return {
266
247
  isCompatible: false,
267
- reason: "Docker Compose for Java currently requires Spring Boot"
248
+ reason: `${title} for Java currently requires Spring Boot`
268
249
  };
269
250
  if (ecosystem === "python" && database && database !== "none" && database !== "sqlite" && database !== "postgres") return {
270
251
  isCompatible: false,
271
- reason: "Docker Compose for Python ORM projects currently supports SQLite defaults or Postgres"
252
+ reason: `${title} for Python ORM projects currently supports SQLite defaults or Postgres`
272
253
  };
273
254
  }
274
255
  return { isCompatible: true };
@@ -280,6 +261,7 @@ function getCompatibleAddons$1(allAddons, frontend, existingAddons = [], auth, b
280
261
  });
281
262
  }
282
263
  function validateAddonsAgainstFrontends(addons = [], frontends = [], auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database) {
264
+ if (addons.includes("nx") && addons.includes("turborepo")) exitWithError("Nx and Turborepo are alternative workspace runners. Choose one addon.");
283
265
  for (const addon of addons) {
284
266
  if (addon === "none") continue;
285
267
  const { isCompatible, reason } = validateAddonCompatibility$1(addon, frontends, auth, backend, runtime, ecosystem, rustFrontend, javaWebFramework, database);
@@ -635,6 +617,10 @@ function getAddonDisplay(addon) {
635
617
  label = "Turborepo";
636
618
  hint = "High-performance build system";
637
619
  break;
620
+ case "nx":
621
+ label = "Nx";
622
+ hint = "Smart monorepo task runner and cache";
623
+ break;
638
624
  case "pwa":
639
625
  label = "PWA";
640
626
  hint = "Make your app installable and work offline";
@@ -727,10 +713,18 @@ function getAddonDisplay(addon) {
727
713
  label = "Backend Utils";
728
714
  hint = "asyncHandler, ApiResponse & global error handler for your server";
729
715
  break;
716
+ case "devcontainer":
717
+ label = "DevContainer";
718
+ hint = "VS Code container config with stack-aware ports and extensions";
719
+ break;
730
720
  case "docker-compose":
731
721
  label = "Docker Compose";
732
722
  hint = "Containerize your app for deployment";
733
723
  break;
724
+ case "github-actions":
725
+ label = "GitHub Actions";
726
+ hint = "Ship a CI workflow (install, lint, type-check, build)";
727
+ break;
734
728
  default:
735
729
  label = addon;
736
730
  hint = `Add ${addon}`;
@@ -743,6 +737,8 @@ function getAddonDisplay(addon) {
743
737
  const ADDON_GROUPS = {
744
738
  Tooling: [
745
739
  "turborepo",
740
+ "nx",
741
+ "github-actions",
746
742
  "biome",
747
743
  "oxlint",
748
744
  "ultracite",
@@ -756,6 +752,7 @@ const ADDON_GROUPS = {
756
752
  "opentui",
757
753
  "wxt",
758
754
  "ruler",
755
+ "devcontainer",
759
756
  "docker-compose"
760
757
  ],
761
758
  Integrations: [
@@ -1015,6 +1012,9 @@ async function applyDependencyVersionChannel(projectDir, channel) {
1015
1012
 
1016
1013
  //#endregion
1017
1014
  //#region src/helpers/core/install-dependencies.ts
1015
+ function toErrorMessage(error) {
1016
+ return error instanceof Error ? error.message : String(error);
1017
+ }
1018
1018
  function getInstallEnvironment(packageManager) {
1019
1019
  if (packageManager === "yarn") return {
1020
1020
  YARN_ENABLE_HARDENED_MODE: "0",
@@ -1027,6 +1027,7 @@ function getInstallArgs(packageManager) {
1027
1027
  }
1028
1028
  async function installDependencies({ projectDir, packageManager }) {
1029
1029
  const s = spinner();
1030
+ const step = "Install dependencies";
1030
1031
  try {
1031
1032
  s.start(`Running ${packageManager} install...`);
1032
1033
  const installArgs = getInstallArgs(packageManager);
@@ -1039,13 +1040,24 @@ async function installDependencies({ projectDir, packageManager }) {
1039
1040
  stderr: "inherit"
1040
1041
  })`${packageManager} ${installArgs}`;
1041
1042
  s.stop("Dependencies installed successfully");
1043
+ return {
1044
+ step,
1045
+ success: true
1046
+ };
1042
1047
  } catch (error) {
1043
1048
  s.stop(pc.red("Failed to install dependencies"));
1044
- if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
1049
+ const errorMessage = toErrorMessage(error);
1050
+ consola.error(pc.red(`Installation error: ${errorMessage}`));
1051
+ return {
1052
+ step,
1053
+ success: false,
1054
+ errorMessage
1055
+ };
1045
1056
  }
1046
1057
  }
1047
1058
  async function runCargoBuild({ projectDir }) {
1048
1059
  const s = spinner();
1060
+ const step = "Cargo build";
1049
1061
  try {
1050
1062
  s.start("Running cargo build...");
1051
1063
  await $({
@@ -1053,13 +1065,24 @@ async function runCargoBuild({ projectDir }) {
1053
1065
  stderr: "inherit"
1054
1066
  })`cargo build`;
1055
1067
  s.stop("Cargo build completed");
1068
+ return {
1069
+ step,
1070
+ success: true
1071
+ };
1056
1072
  } catch (error) {
1057
1073
  s.stop(pc.red("Cargo build failed"));
1058
- if (error instanceof Error) consola.error(pc.red(`Cargo build error: ${error.message}`));
1074
+ const errorMessage = toErrorMessage(error);
1075
+ consola.error(pc.red(`Cargo build error: ${errorMessage}`));
1076
+ return {
1077
+ step,
1078
+ success: false,
1079
+ errorMessage
1080
+ };
1059
1081
  }
1060
1082
  }
1061
1083
  async function runUvSync({ projectDir }) {
1062
1084
  const s = spinner();
1085
+ const step = "uv sync (Python dependencies)";
1063
1086
  try {
1064
1087
  s.start("Running uv sync...");
1065
1088
  await $({
@@ -1067,13 +1090,24 @@ async function runUvSync({ projectDir }) {
1067
1090
  stderr: "inherit"
1068
1091
  })`uv sync`;
1069
1092
  s.stop("Python dependencies installed successfully");
1093
+ return {
1094
+ step,
1095
+ success: true
1096
+ };
1070
1097
  } catch (error) {
1071
1098
  s.stop(pc.red("uv sync failed"));
1072
- if (error instanceof Error) consola.error(pc.red(`uv sync error: ${error.message}`));
1099
+ const errorMessage = toErrorMessage(error);
1100
+ consola.error(pc.red(`uv sync error: ${errorMessage}`));
1101
+ return {
1102
+ step,
1103
+ success: false,
1104
+ errorMessage
1105
+ };
1073
1106
  }
1074
1107
  }
1075
1108
  async function runGoModTidy({ projectDir }) {
1076
1109
  const s = spinner();
1110
+ const step = "go mod tidy";
1077
1111
  try {
1078
1112
  s.start("Running go mod tidy...");
1079
1113
  await $({
@@ -1081,14 +1115,25 @@ async function runGoModTidy({ projectDir }) {
1081
1115
  stderr: "inherit"
1082
1116
  })`go mod tidy`;
1083
1117
  s.stop("Go dependencies installed successfully");
1118
+ return {
1119
+ step,
1120
+ success: true
1121
+ };
1084
1122
  } catch (error) {
1085
1123
  s.stop(pc.red("go mod tidy failed"));
1086
- if (error instanceof Error) consola.error(pc.red(`go mod tidy error: ${error.message}`));
1124
+ const errorMessage = toErrorMessage(error);
1125
+ consola.error(pc.red(`go mod tidy error: ${errorMessage}`));
1126
+ return {
1127
+ step,
1128
+ success: false,
1129
+ errorMessage
1130
+ };
1087
1131
  }
1088
1132
  }
1089
1133
  async function runMavenTests({ projectDir }) {
1090
1134
  const s = spinner();
1091
1135
  const mvnw = process.platform === "win32" ? "mvnw.cmd" : "./mvnw";
1136
+ const step = "Maven tests";
1092
1137
  try {
1093
1138
  s.start("Running Maven tests...");
1094
1139
  await $({
@@ -1096,14 +1141,25 @@ async function runMavenTests({ projectDir }) {
1096
1141
  stderr: "inherit"
1097
1142
  })`${mvnw} test`;
1098
1143
  s.stop("Maven tests completed");
1144
+ return {
1145
+ step,
1146
+ success: true
1147
+ };
1099
1148
  } catch (error) {
1100
1149
  s.stop(pc.red("Maven tests failed"));
1101
- if (error instanceof Error) consola.error(pc.red(`Maven test error: ${error.message}`));
1150
+ const errorMessage = toErrorMessage(error);
1151
+ consola.error(pc.red(`Maven test error: ${errorMessage}`));
1152
+ return {
1153
+ step,
1154
+ success: false,
1155
+ errorMessage
1156
+ };
1102
1157
  }
1103
1158
  }
1104
1159
  async function runGradleTests({ projectDir }) {
1105
1160
  const s = spinner();
1106
1161
  const gradlew = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
1162
+ const step = "Gradle tests";
1107
1163
  try {
1108
1164
  s.start("Running Gradle tests...");
1109
1165
  await $({
@@ -1111,13 +1167,24 @@ async function runGradleTests({ projectDir }) {
1111
1167
  stderr: "inherit"
1112
1168
  })`${gradlew} test`;
1113
1169
  s.stop("Gradle tests completed");
1170
+ return {
1171
+ step,
1172
+ success: true
1173
+ };
1114
1174
  } catch (error) {
1115
1175
  s.stop(pc.red("Gradle tests failed"));
1116
- if (error instanceof Error) consola.error(pc.red(`Gradle test error: ${error.message}`));
1176
+ const errorMessage = toErrorMessage(error);
1177
+ consola.error(pc.red(`Gradle test error: ${errorMessage}`));
1178
+ return {
1179
+ step,
1180
+ success: false,
1181
+ errorMessage
1182
+ };
1117
1183
  }
1118
1184
  }
1119
1185
  async function runMixCompile({ projectDir }) {
1120
1186
  const s = spinner();
1187
+ const step = "mix deps.get / compile";
1121
1188
  try {
1122
1189
  s.start("Running mix deps.get and mix compile...");
1123
1190
  await $({
@@ -1129,11 +1196,21 @@ async function runMixCompile({ projectDir }) {
1129
1196
  stderr: "inherit"
1130
1197
  })`mix compile`;
1131
1198
  s.stop("Elixir dependencies installed and project compiled");
1199
+ return {
1200
+ step,
1201
+ success: true
1202
+ };
1132
1203
  } catch (error) {
1133
1204
  s.stop(pc.red("mix compile failed"));
1134
- if (error instanceof Error) consola.error(pc.red(`Mix error: ${error.message}`));
1205
+ const errorMessage = toErrorMessage(error);
1206
+ consola.error(pc.red(`Mix error: ${errorMessage}`));
1207
+ return {
1208
+ step,
1209
+ success: false,
1210
+ errorMessage
1211
+ };
1135
1212
  }
1136
1213
  }
1137
1214
 
1138
1215
  //#endregion
1139
- export { validateAddonsAgainstFrontends as A, validateWorkersCompatibility as B, isExampleAIAllowed$1 as C, requiresChatSdkVercelAI as D, isWebFrontend$1 as E, validateServerDeployRequiresBackend as F, incompatibilityError as H, validateUILibraryCSSFrameworkCompatibility as I, validateUILibraryFrontendCompatibility as L, validateExamplesCompatibility as M, validatePaymentsCompatibility as N, splitFrontends$1 as O, validateSelfBackendCompatibility as P, validateWebDeployFrontendTemplates as R, hasWebStyling$1 as S, isFrontendAllowedWithBackend$1 as T, missingRequirementError as U, constraintError as V, renderTitle as W, isGoBack as _, runMavenTests as a, getCompatibleCSSFrameworks$1 as b, applyDependencyVersionChannel as c, isCancel$1 as d, navigableConfirm as f, GO_BACK_SYMBOL as g, setIsFirstPrompt as h, runGradleTests as i, validateApiFrontendCompatibility as j, validateAIFrontendCompatibility as k, getAddonsChoice as l, navigableSelect as m, runCargoBuild as n, runMixCompile as o, navigableMultiselect as p, runGoModTidy as r, runUvSync as s, installDependencies as t, getAddonsToAdd as u, allowedApisForFrontends$1 as v, isExampleChatSdkAllowed$1 as w, getCompatibleUILibraries$1 as x, ensureSingleWebAndNative as y, validateWebDeployRequiresWebFrontend as z };
1216
+ export { validateAddonsAgainstFrontends as A, validateWorkersCompatibility as B, isExampleAIAllowed$1 as C, requiresChatSdkVercelAI as D, isWebFrontend$1 as E, validateServerDeployRequiresBackend as F, incompatibilityError as H, validateUILibraryCSSFrameworkCompatibility as I, validateUILibraryFrontendCompatibility as L, validateExamplesCompatibility as M, validatePaymentsCompatibility as N, splitFrontends$1 as O, validateSelfBackendCompatibility as P, validateWebDeployFrontendTemplates as R, hasWebStyling$1 as S, isFrontendAllowedWithBackend$1 as T, missingRequirementError as U, constraintError as V, isGoBack as _, runMavenTests as a, getCompatibleCSSFrameworks$1 as b, applyDependencyVersionChannel as c, isCancel$1 as d, navigableConfirm as f, GO_BACK_SYMBOL as g, setIsFirstPrompt as h, runGradleTests as i, validateApiFrontendCompatibility as j, validateAIFrontendCompatibility as k, getAddonsChoice as l, navigableSelect as m, runCargoBuild as n, runMixCompile as o, navigableMultiselect as p, runGoModTidy as r, runUvSync as s, installDependencies as t, getAddonsToAdd as u, allowedApisForFrontends$1 as v, isExampleChatSdkAllowed$1 as w, getCompatibleUILibraries$1 as x, ensureSingleWebAndNative as y, validateWebDeployRequiresWebFrontend as z };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./bts-config-Bg1Qea9Y.mjs";
2
+ import "./bts-config-InNcw1aJ.mjs";
3
3
  import { n as startMcpServer, t as getMcpGraphPreview } from "./mcp-entry.mjs";
4
4
 
5
5
  export { startMcpServer };