create-better-fullstack 1.4.15 → 1.5.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.
package/README.md CHANGED
@@ -38,6 +38,7 @@ Configure your stack visually — pick every option from a UI, preview your choi
38
38
  --yolo # Scaffold a random stack — good for exploring
39
39
  --template <name> # Use a preset (t3, mern, pern, uniwind)
40
40
  --ecosystem <lang> # Start in rust, python, or go mode
41
+ --version-channel # Dependency channel: stable, latest, beta
41
42
  --no-git # Skip git initialization
42
43
  --no-install # Skip dependency installation
43
44
  --package-manager # Package manager (bun, pnpm, npm, yarn)
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { s as createBtsCli } from "./src-D-YGZXlq.mjs";
2
+ import { s as createBtsCli } from "./src-DaUU4ThO.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.d.mts CHANGED
@@ -278,6 +278,11 @@ declare const router: {
278
278
  yarn: "yarn";
279
279
  }>>;
280
280
  install: z.ZodOptional<z.ZodBoolean>;
281
+ versionChannel: z.ZodOptional<z.ZodEnum<{
282
+ stable: "stable";
283
+ latest: "latest";
284
+ beta: "beta";
285
+ }>>;
281
286
  dbSetup: z.ZodOptional<z.ZodEnum<{
282
287
  none: "none";
283
288
  turso: "turso";
@@ -543,6 +548,7 @@ declare const router: {
543
548
  payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
544
549
  git: boolean;
545
550
  packageManager: "bun" | "npm" | "pnpm" | "yarn";
551
+ versionChannel: "stable" | "latest" | "beta";
546
552
  install: boolean;
547
553
  dbSetup: "none" | "turso" | "neon" | "prisma-postgres" | "planetscale" | "mongodb-atlas" | "supabase" | "upstash" | "d1" | "docker";
548
554
  api: "none" | "trpc" | "orpc" | "ts-rest" | "garph";
@@ -629,6 +635,7 @@ declare const router: {
629
635
  payments: "none" | "polar" | "stripe" | "lemon-squeezy" | "paddle" | "dodo";
630
636
  git: boolean;
631
637
  packageManager: "bun" | "npm" | "pnpm" | "yarn";
638
+ versionChannel: "stable" | "latest" | "beta";
632
639
  install: boolean;
633
640
  dbSetup: "none" | "turso" | "neon" | "prisma-postgres" | "planetscale" | "mongodb-atlas" | "supabase" | "upstash" | "d1" | "docker";
634
641
  api: "none" | "trpc" | "orpc" | "ts-rest" | "garph";
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { a as builder, c as createVirtual, d as history, f as router, i as add, l as docs, n as TEMPLATE_COUNT, o as create, p as sponsors, r as VirtualFileSystem, s as createBtsCli, t as EMBEDDED_TEMPLATES, u as generateVirtualProject } from "./src-D-YGZXlq.mjs";
2
+ import { a as builder, c as createVirtual, d as history, f as router, i as add, l as docs, n as TEMPLATE_COUNT, o as create, p as sponsors, r as VirtualFileSystem, s as createBtsCli, t as EMBEDDED_TEMPLATES, u as generateVirtualProject } from "./src-DaUU4ThO.mjs";
3
3
 
4
4
  export { EMBEDDED_TEMPLATES, TEMPLATE_COUNT, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generateVirtualProject, history, router, sponsors };
@@ -9,7 +9,7 @@ import envPaths from "env-paths";
9
9
  import fs from "fs-extra";
10
10
  import path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
- import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, checkAllVersions, dependencyVersionMap, generateCliReport, generateVirtualProject, generateVirtualProject as generateVirtualProject$1, listEcosystems, processAddonTemplates, processAddonsDeps } from "@better-fullstack/template-generator";
12
+ import { ECOSYSTEM_GROUPS, EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, checkAllVersions, dependencyVersionMap, generateCliReport, generateVirtualProject, generateVirtualProject as generateVirtualProject$1, listEcosystems, processAddonTemplates, processAddonsDeps, validatePreflightConfig } from "@better-fullstack/template-generator";
13
13
  import gradient from "gradient-string";
14
14
  import path$1 from "path";
15
15
  import { writeTreeToFilesystem } from "@better-fullstack/template-generator/fs-writer";
@@ -67,6 +67,7 @@ const DEFAULT_CONFIG_BASE = {
67
67
  examples: [],
68
68
  git: true,
69
69
  install: true,
70
+ versionChannel: "stable",
70
71
  dbSetup: "none",
71
72
  backend: "hono",
72
73
  runtime: "bun",
@@ -1288,10 +1289,9 @@ const ADDON_GROUPS = {
1288
1289
  "tauri",
1289
1290
  "opentui",
1290
1291
  "wxt",
1291
- "ruler",
1292
- "msw",
1293
- "storybook"
1292
+ "ruler"
1294
1293
  ],
1294
+ Integrations: ["msw", "storybook"],
1295
1295
  "AI Agents": ["mcp", "skills"],
1296
1296
  TanStack: [
1297
1297
  "tanstack-query",
@@ -1308,6 +1308,7 @@ async function getAddonsChoice(addons, frontends, auth) {
1308
1308
  Tooling: [],
1309
1309
  Documentation: [],
1310
1310
  Extensions: [],
1311
+ Integrations: [],
1311
1312
  "AI Agents": [],
1312
1313
  TanStack: []
1313
1314
  };
@@ -1324,6 +1325,7 @@ async function getAddonsChoice(addons, frontends, auth) {
1324
1325
  if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
1325
1326
  else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
1326
1327
  else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
1328
+ else if (ADDON_GROUPS.Integrations.includes(addon)) groupedOptions.Integrations.push(option);
1327
1329
  else if (ADDON_GROUPS["AI Agents"].includes(addon)) groupedOptions["AI Agents"].push(option);
1328
1330
  else if (ADDON_GROUPS.TanStack.includes(addon)) groupedOptions.TanStack.push(option);
1329
1331
  }
@@ -1350,6 +1352,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
1350
1352
  Tooling: [],
1351
1353
  Documentation: [],
1352
1354
  Extensions: [],
1355
+ Integrations: [],
1353
1356
  "AI Agents": [],
1354
1357
  TanStack: []
1355
1358
  };
@@ -1365,6 +1368,7 @@ async function getAddonsToAdd(frontend, existingAddons = [], auth) {
1365
1368
  if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
1366
1369
  else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
1367
1370
  else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
1371
+ else if (ADDON_GROUPS.Integrations.includes(addon)) groupedOptions.Integrations.push(option);
1368
1372
  else if (ADDON_GROUPS["AI Agents"].includes(addon)) groupedOptions["AI Agents"].push(option);
1369
1373
  else if (ADDON_GROUPS.TanStack.includes(addon)) groupedOptions.TanStack.push(option);
1370
1374
  }
@@ -1413,6 +1417,7 @@ async function writeBtsConfig(projectConfig) {
1413
1417
  forms: projectConfig.forms,
1414
1418
  testing: projectConfig.testing,
1415
1419
  packageManager: projectConfig.packageManager,
1420
+ versionChannel: projectConfig.versionChannel,
1416
1421
  dbSetup: projectConfig.dbSetup,
1417
1422
  api: projectConfig.api,
1418
1423
  webDeploy: projectConfig.webDeploy,
@@ -1472,6 +1477,7 @@ async function writeBtsConfig(projectConfig) {
1472
1477
  forms: btsConfig.forms,
1473
1478
  testing: btsConfig.testing,
1474
1479
  packageManager: btsConfig.packageManager,
1480
+ versionChannel: btsConfig.versionChannel,
1475
1481
  dbSetup: btsConfig.dbSetup,
1476
1482
  api: btsConfig.api,
1477
1483
  webDeploy: btsConfig.webDeploy,
@@ -1558,6 +1564,166 @@ async function updateBtsConfig(projectDir, updates) {
1558
1564
  } catch {}
1559
1565
  }
1560
1566
 
1567
+ //#endregion
1568
+ //#region src/utils/dependency-version-channel.ts
1569
+ const VERSION_CACHE = /* @__PURE__ */ new Map();
1570
+ const PRERELEASE_TAG_PRIORITY = [
1571
+ "beta",
1572
+ "next",
1573
+ "rc",
1574
+ "canary",
1575
+ "alpha"
1576
+ ];
1577
+ const REGISTRY_FETCH_TIMEOUT_MS = 1e4;
1578
+ const REGISTRY_CONCURRENCY = 10;
1579
+ function mapWithConcurrency(items, fn, concurrency) {
1580
+ const results = Array.from({ length: items.length });
1581
+ let index = 0;
1582
+ async function worker() {
1583
+ while (index < items.length) {
1584
+ const i = index++;
1585
+ results[i] = await fn(items[i], i);
1586
+ }
1587
+ }
1588
+ return Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker())).then(() => results);
1589
+ }
1590
+ function parseVersion(value) {
1591
+ const normalized = value.replace(/^[^\d]*/, "");
1592
+ const dashIdx = normalized.indexOf("-");
1593
+ const base = dashIdx === -1 ? normalized : normalized.slice(0, dashIdx);
1594
+ const prerelease = dashIdx === -1 ? "" : normalized.slice(dashIdx + 1);
1595
+ const [major = "0", minor = "0", patch = "0"] = base.split(".");
1596
+ return {
1597
+ major: Number(major) || 0,
1598
+ minor: Number(minor) || 0,
1599
+ patch: Number(patch) || 0,
1600
+ prerelease: prerelease ? prerelease.split(/[.-]/).map((part) => /^\d+$/.test(part) ? Number(part) : part) : []
1601
+ };
1602
+ }
1603
+ function compareVersions(a, b) {
1604
+ const left = parseVersion(a);
1605
+ const right = parseVersion(b);
1606
+ if (left.major !== right.major) return left.major - right.major;
1607
+ if (left.minor !== right.minor) return left.minor - right.minor;
1608
+ if (left.patch !== right.patch) return left.patch - right.patch;
1609
+ if (left.prerelease.length === 0 && right.prerelease.length === 0) return 0;
1610
+ if (left.prerelease.length === 0) return 1;
1611
+ if (right.prerelease.length === 0) return -1;
1612
+ const maxLength = Math.max(left.prerelease.length, right.prerelease.length);
1613
+ for (let index = 0; index < maxLength; index++) {
1614
+ const leftPart = left.prerelease[index];
1615
+ const rightPart = right.prerelease[index];
1616
+ if (leftPart === void 0) return -1;
1617
+ if (rightPart === void 0) return 1;
1618
+ if (leftPart === rightPart) continue;
1619
+ if (typeof leftPart === "number" && typeof rightPart === "number") return leftPart - rightPart;
1620
+ if (typeof leftPart === "number") return -1;
1621
+ if (typeof rightPart === "number") return 1;
1622
+ return leftPart.localeCompare(rightPart);
1623
+ }
1624
+ return 0;
1625
+ }
1626
+ function isPrerelease(version) {
1627
+ return /-(alpha|beta|rc|next|canary)/i.test(version);
1628
+ }
1629
+ function getVersionPrefix(version) {
1630
+ return version.match(/^[^\d]*/)?.[0] ?? "";
1631
+ }
1632
+ function applyVersionPrefix(currentVersion, resolvedVersion) {
1633
+ return `${getVersionPrefix(currentVersion)}${resolvedVersion}`;
1634
+ }
1635
+ function isRegistrySemverSpec(version) {
1636
+ return /^[~^]?\d/.test(version);
1637
+ }
1638
+ async function fetchPackageInfo(packageName) {
1639
+ const cached = VERSION_CACHE.get(packageName);
1640
+ if (cached) return cached;
1641
+ const encodedName = encodeURIComponent(packageName).replace("%40", "@");
1642
+ const response = await fetch(`https://registry.npmjs.org/${encodedName}`, {
1643
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
1644
+ signal: AbortSignal.timeout(REGISTRY_FETCH_TIMEOUT_MS)
1645
+ });
1646
+ if (!response.ok) throw new Error(`Package ${packageName} not found (${response.status})`);
1647
+ const raw = await response.json();
1648
+ const data = {
1649
+ "dist-tags": raw["dist-tags"],
1650
+ versions: raw["versions"]
1651
+ };
1652
+ VERSION_CACHE.set(packageName, data);
1653
+ return data;
1654
+ }
1655
+ function selectRegistryVersionForChannel(packageInfo, channel) {
1656
+ const tags = packageInfo["dist-tags"] ?? {};
1657
+ if (channel === "latest") return tags.latest ?? null;
1658
+ for (const tag of PRERELEASE_TAG_PRIORITY) if (tags[tag]) return tags[tag];
1659
+ const prereleases = Object.keys(packageInfo.versions ?? {}).filter(isPrerelease);
1660
+ if (prereleases.length > 0) return prereleases.sort((left, right) => compareVersions(right, left))[0] ?? null;
1661
+ return tags.latest ?? null;
1662
+ }
1663
+ async function resolveRegistryVersion(packageName, channel) {
1664
+ const version = selectRegistryVersionForChannel(await fetchPackageInfo(packageName), channel);
1665
+ if (!version) throw new Error(`No ${channel} version available for ${packageName}`);
1666
+ return version;
1667
+ }
1668
+ async function collectPackageJsonPaths$1(projectDir) {
1669
+ const results = [];
1670
+ async function walk(currentDir) {
1671
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
1672
+ for (const entry of entries) {
1673
+ if (entry.name === "node_modules" || entry.name === ".git" || entry.name === ".turbo") continue;
1674
+ const fullPath = path.join(currentDir, entry.name);
1675
+ if (entry.isDirectory()) {
1676
+ await walk(fullPath);
1677
+ continue;
1678
+ }
1679
+ if (entry.isFile() && entry.name === "package.json") results.push(fullPath);
1680
+ }
1681
+ }
1682
+ await walk(projectDir);
1683
+ return results.sort();
1684
+ }
1685
+ async function applyDependencyVersionChannel(projectDir, channel) {
1686
+ if (channel === "stable") return;
1687
+ const packageJsonPaths = await collectPackageJsonPaths$1(projectDir);
1688
+ if (packageJsonPaths.length === 0) return;
1689
+ const packageNames = /* @__PURE__ */ new Set();
1690
+ for (const packageJsonPath of packageJsonPaths) {
1691
+ const packageJson = await fs.readJson(packageJsonPath);
1692
+ for (const [depName, depVersion] of Object.entries(packageJson.dependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
1693
+ for (const [depName, depVersion] of Object.entries(packageJson.devDependencies ?? {})) if (typeof depVersion === "string" && isRegistrySemverSpec(depVersion)) packageNames.add(depName);
1694
+ }
1695
+ if (packageNames.size === 0) return;
1696
+ const resolvedVersions = /* @__PURE__ */ new Map();
1697
+ await mapWithConcurrency([...packageNames], async (packageName) => {
1698
+ try {
1699
+ const resolvedVersion = await resolveRegistryVersion(packageName, channel);
1700
+ resolvedVersions.set(packageName, resolvedVersion);
1701
+ } catch (error) {
1702
+ log.warn(`Failed to resolve ${channel} version for ${packageName}: ${error instanceof Error ? error.message : String(error)}`);
1703
+ }
1704
+ }, REGISTRY_CONCURRENCY);
1705
+ if (resolvedVersions.size === 0) return;
1706
+ for (const packageJsonPath of packageJsonPaths) {
1707
+ const packageJson = await fs.readJson(packageJsonPath);
1708
+ let changed = false;
1709
+ for (const sectionName of ["dependencies", "devDependencies"]) {
1710
+ const section = packageJson[sectionName];
1711
+ if (!section) continue;
1712
+ for (const [packageName, currentVersion] of Object.entries(section)) {
1713
+ if (!isRegistrySemverSpec(currentVersion)) continue;
1714
+ const resolvedVersion = resolvedVersions.get(packageName);
1715
+ if (!resolvedVersion) continue;
1716
+ const nextVersion = applyVersionPrefix(currentVersion, resolvedVersion);
1717
+ if (nextVersion !== currentVersion) {
1718
+ section[packageName] = nextVersion;
1719
+ changed = true;
1720
+ }
1721
+ }
1722
+ }
1723
+ if (changed) await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1724
+ }
1725
+ }
1726
+
1561
1727
  //#endregion
1562
1728
  //#region src/utils/add-package-deps.ts
1563
1729
  const addPackageDependency = async (opts) => {
@@ -2946,6 +3112,7 @@ async function addHandlerInternal(input) {
2946
3112
  config
2947
3113
  }, projectDir);
2948
3114
  await setupAddons(config);
3115
+ await applyDependencyVersionChannel(projectDir, config.versionChannel);
2949
3116
  const configUpdates = { addons: [...new Set([...existingAddons, ...addonsToAdd])] };
2950
3117
  if (input.webDeploy !== void 0) configUpdates.webDeploy = input.webDeploy;
2951
3118
  if (input.serverDeploy !== void 0) configUpdates.serverDeploy = input.serverDeploy;
@@ -5985,6 +6152,35 @@ async function getProjectName(initialName) {
5985
6152
  return projectPath;
5986
6153
  }
5987
6154
 
6155
+ //#endregion
6156
+ //#region src/prompts/version-channel.ts
6157
+ async function getVersionChannelChoice(versionChannel) {
6158
+ if (versionChannel !== void 0) return versionChannel;
6159
+ const response = await navigableSelect({
6160
+ message: "Choose dependency version channel",
6161
+ options: [
6162
+ {
6163
+ value: "stable",
6164
+ label: "Stable",
6165
+ hint: "Use Better Fullstack's curated pinned versions"
6166
+ },
6167
+ {
6168
+ value: "latest",
6169
+ label: "Latest",
6170
+ hint: "Resolve current npm latest tags during scaffolding"
6171
+ },
6172
+ {
6173
+ value: "beta",
6174
+ label: "Beta",
6175
+ hint: "Prefer npm beta/next prerelease tags when available"
6176
+ }
6177
+ ],
6178
+ initialValue: "stable"
6179
+ });
6180
+ if (isCancel$1(response)) return exitCancelled("Operation cancelled");
6181
+ return response;
6182
+ }
6183
+
5988
6184
  //#endregion
5989
6185
  //#region src/utils/telemetry.ts
5990
6186
  /**
@@ -6077,6 +6273,7 @@ function displayConfig(config) {
6077
6273
  configDisplay.push(`${pc.blue("Git Init:")} ${gitText}`);
6078
6274
  }
6079
6275
  if (config.packageManager !== void 0) configDisplay.push(`${pc.blue("Package Manager:")} ${String(config.packageManager)}`);
6276
+ if (config.versionChannel !== void 0) configDisplay.push(`${pc.blue("Version Channel:")} ${String(config.versionChannel)}`);
6080
6277
  if (config.install !== void 0) {
6081
6278
  const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
6082
6279
  configDisplay.push(`${pc.blue("Install Dependencies:")} ${installText}`);
@@ -6109,6 +6306,7 @@ function appendCommonFlags(flags, config) {
6109
6306
  else flags.push("--ai-docs none");
6110
6307
  flags.push(config.git ? "--git" : "--no-git");
6111
6308
  flags.push(`--package-manager ${config.packageManager}`);
6309
+ if (config.versionChannel !== "stable") flags.push(`--version-channel ${config.versionChannel}`);
6112
6310
  flags.push(config.install ? "--install" : "--no-install");
6113
6311
  }
6114
6312
  function appendSharedNonTypeScriptFlags(flags, config) {
@@ -6432,6 +6630,7 @@ function processFlags(options, projectName) {
6432
6630
  if (options.runtime) config.runtime = options.runtime;
6433
6631
  if (options.dbSetup) config.dbSetup = options.dbSetup;
6434
6632
  if (options.packageManager) config.packageManager = options.packageManager;
6633
+ if (options.versionChannel) config.versionChannel = options.versionChannel;
6435
6634
  if (options.webDeploy) config.webDeploy = options.webDeploy;
6436
6635
  if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
6437
6636
  const derivedName = deriveProjectName(projectName, options.projectDirectory);
@@ -7053,6 +7252,26 @@ function validateConfigCompatibility(config, providedFlags, options) {
7053
7252
  else validateConfigForProgrammaticUse(config);
7054
7253
  }
7055
7254
 
7255
+ //#endregion
7256
+ //#region src/utils/preflight-display.ts
7257
+ function displayPreflightWarnings({ warnings }) {
7258
+ if (warnings.length === 0) return;
7259
+ const count = warnings.length;
7260
+ const lines = [pc.bold(pc.yellow(`${count} feature${count > 1 ? "s" : ""} will not generate templates:`)), ""];
7261
+ warnings.forEach((w, i) => {
7262
+ const selected = Array.isArray(w.selectedValue) ? w.selectedValue.join(", ") : w.selectedValue;
7263
+ lines.push(` ${pc.yellow(`${i + 1}.`)} ${pc.bold(w.featureDisplayName)} ${pc.dim(`(${selected})`)}`);
7264
+ lines.push(` ${w.reason}`);
7265
+ w.suggestions.forEach((s) => lines.push(` ${pc.green("•")} ${s}`));
7266
+ if (i < count - 1) lines.push("");
7267
+ });
7268
+ consola.box({
7269
+ title: pc.yellow("Pre-flight Check"),
7270
+ message: lines.join("\n"),
7271
+ style: { borderColor: "yellow" }
7272
+ });
7273
+ }
7274
+
7056
7275
  //#endregion
7057
7276
  //#region src/utils/file-formatter.ts
7058
7277
  const formatOptions = {
@@ -8342,8 +8561,9 @@ async function displayPostInstallInstructions(config) {
8342
8561
  if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
8343
8562
  if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
8344
8563
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
8345
- output += `\n${pc.bold("Like Better Fullstack?")} Please consider giving us a star\n on GitHub:\n`;
8346
- output += pc.cyan("https://github.com/Marve10s/Better-Fullstack");
8564
+ output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow star the repo!\n`;
8565
+ output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
8566
+ output += pc.dim("Your star helps other developers discover the project.");
8347
8567
  consola$1.box(output);
8348
8568
  }
8349
8569
  function getNativeInstructions(isConvex, isBackendSelf, frontend, runCmd) {
@@ -8481,8 +8701,9 @@ function displayRustInstructions(config) {
8481
8701
  output += `${pc.cyan("•")} Check: cargo check\n`;
8482
8702
  output += `${pc.cyan("•")} Format: cargo fmt\n`;
8483
8703
  output += `${pc.cyan("•")} Lint: cargo clippy\n`;
8484
- output += `\n${pc.bold("Like Better Fullstack?")} Please consider giving us a star\n on GitHub:\n`;
8485
- output += pc.cyan("https://github.com/Marve10s/Better-Fullstack");
8704
+ output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow star the repo!\n`;
8705
+ output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
8706
+ output += pc.dim("Your star helps other developers discover the project.");
8486
8707
  consola$1.box(output);
8487
8708
  }
8488
8709
  function displayGoInstructions(config) {
@@ -8517,8 +8738,9 @@ function displayGoInstructions(config) {
8517
8738
  output += `\n${pc.bold("Your project will be available at:")}\n`;
8518
8739
  output += `${pc.cyan("•")} API: http://localhost:8080\n`;
8519
8740
  if (goApi === "grpc-go") output += `${pc.cyan("•")} gRPC: localhost:50051\n`;
8520
- output += `\n${pc.bold("Like Better Fullstack?")} Please consider giving us a star\n on GitHub:\n`;
8521
- output += pc.cyan("https://github.com/Marve10s/Better-Fullstack");
8741
+ output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow star the repo!\n`;
8742
+ output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
8743
+ output += pc.dim("Your star helps other developers discover the project.");
8522
8744
  consola$1.box(output);
8523
8745
  }
8524
8746
  function displayPythonInstructions(config) {
@@ -8562,8 +8784,9 @@ function displayPythonInstructions(config) {
8562
8784
  output += `${pc.cyan("•")} Lint: uv run ruff check .\n`;
8563
8785
  output += `\n${pc.bold("Your project will be available at:")}\n`;
8564
8786
  output += `${pc.cyan("•")} API: http://localhost:8000\n`;
8565
- output += `\n${pc.bold("Like Better Fullstack?")} Please consider giving us a star\n on GitHub:\n`;
8566
- output += pc.cyan("https://github.com/Marve10s/Better-Fullstack");
8787
+ output += `\n${pc.bold("Enjoying Better Fullstack?")} Help us grow star the repo!\n`;
8788
+ output += `${pc.cyan("https://github.com/Marve10s/Better-Fullstack")}\n`;
8789
+ output += pc.dim("Your star helps other developers discover the project.");
8567
8790
  consola$1.box(output);
8568
8791
  }
8569
8792
 
@@ -8583,6 +8806,7 @@ async function createProject(options, cliInput = {}) {
8583
8806
  await setPackageManagerVersion(projectDir, options.packageManager);
8584
8807
  if (!isConvex && options.database !== "none") await setupDatabase(options, cliInput);
8585
8808
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
8809
+ await applyDependencyVersionChannel(projectDir, options.versionChannel);
8586
8810
  await writeBtsConfig(options);
8587
8811
  await formatProject(projectDir);
8588
8812
  if (!isSilent()) log.success("Project template successfully scaffolded!");
@@ -8627,6 +8851,10 @@ async function setPackageManagerVersion(projectDir, packageManager) {
8627
8851
 
8628
8852
  //#endregion
8629
8853
  //#region src/helpers/core/command-handlers.ts
8854
+ function shouldPromptForVersionChannel(input) {
8855
+ if (input.yes || input.versionChannel !== void 0 || isSilent()) return false;
8856
+ return process.stdin.isTTY === true && process.stdout.isTTY === true && process.env.CI !== "true";
8857
+ }
8630
8858
  async function createProjectHandler(input, options = {}) {
8631
8859
  const { silent = false } = options;
8632
8860
  return runWithContextAsync({ silent }, async () => {
@@ -8648,6 +8876,7 @@ async function createProjectHandler(input, options = {}) {
8648
8876
  }
8649
8877
  currentPathInput = defaultName;
8650
8878
  } else currentPathInput = await getProjectName(input.projectName);
8879
+ const versionChannel = shouldPromptForVersionChannel(input) ? await getVersionChannelChoice() : input.versionChannel ?? "stable";
8651
8880
  let finalPathInput;
8652
8881
  let shouldClearDirectory;
8653
8882
  try {
@@ -8683,6 +8912,7 @@ async function createProjectHandler(input, options = {}) {
8683
8912
  effect: "none",
8684
8913
  git: false,
8685
8914
  packageManager: "npm",
8915
+ versionChannel: "stable",
8686
8916
  install: false,
8687
8917
  dbSetup: "none",
8688
8918
  api: "none",
@@ -8767,9 +8997,12 @@ async function createProjectHandler(input, options = {}) {
8767
8997
  ...flagConfig,
8768
8998
  projectName: finalBaseName,
8769
8999
  projectDir: finalResolvedPath,
8770
- relativePath: finalPathInput
9000
+ relativePath: finalPathInput,
9001
+ versionChannel
8771
9002
  };
8772
9003
  validateConfigCompatibility(config, providedFlags, cliInput);
9004
+ const yesPreflight = validatePreflightConfig(config);
9005
+ if (yesPreflight.hasWarnings && !isSilent()) displayPreflightWarnings(yesPreflight);
8773
9006
  if (!isSilent()) {
8774
9007
  log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
8775
9008
  log.message(displayConfig(config));
@@ -8782,8 +9015,13 @@ async function createProjectHandler(input, options = {}) {
8782
9015
  log.message(displayConfig(otherFlags));
8783
9016
  log.message("");
8784
9017
  }
8785
- config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
9018
+ config = {
9019
+ ...await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput),
9020
+ versionChannel
9021
+ };
8786
9022
  }
9023
+ const preflight = validatePreflightConfig(config);
9024
+ if (preflight.hasWarnings && !isSilent()) displayPreflightWarnings(preflight);
8787
9025
  await createProject(config, { manualDb: cliInput.manualDb ?? input.manualDb });
8788
9026
  const reproducibleCommand = generateReproducibleCommand(config);
8789
9027
  if (!isSilent()) log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
@@ -8974,6 +9212,7 @@ const router = os.router({
8974
9212
  git: z.boolean().optional(),
8975
9213
  packageManager: types_exports.PackageManagerSchema.optional(),
8976
9214
  install: z.boolean().optional(),
9215
+ versionChannel: types_exports.VersionChannelSchema.optional().describe("Dependency version channel (stable, latest, beta)"),
8977
9216
  dbSetup: types_exports.DatabaseSetupSchema.optional(),
8978
9217
  backend: types_exports.BackendSchema.optional(),
8979
9218
  runtime: types_exports.RuntimeSchema.optional(),
@@ -9198,6 +9437,7 @@ async function createVirtual(options) {
9198
9437
  effect: options.effect || "none",
9199
9438
  git: options.git ?? false,
9200
9439
  packageManager: options.packageManager || "bun",
9440
+ versionChannel: options.versionChannel || "stable",
9201
9441
  install: false,
9202
9442
  dbSetup: options.dbSetup || "none",
9203
9443
  api: options.api || "trpc",
package/package.json CHANGED
@@ -1,30 +1,51 @@
1
1
  {
2
2
  "name": "create-better-fullstack",
3
- "version": "1.4.15",
3
+ "version": "1.5.0",
4
4
  "description": "Scaffold production-ready fullstack apps in seconds. Pick your stack from 270+ options — the CLI wires everything together.",
5
5
  "keywords": [
6
+ "angular",
7
+ "astro",
8
+ "authentication",
6
9
  "better-auth",
7
10
  "better-fullstack",
8
11
  "biome",
9
12
  "boilerplate",
13
+ "bun",
10
14
  "cli",
15
+ "create",
16
+ "create-app",
17
+ "desktop",
18
+ "developer-tools",
19
+ "docker",
11
20
  "drizzle",
12
21
  "elysia",
13
22
  "expo",
23
+ "fastapi",
14
24
  "fullstack",
15
25
  "go",
16
26
  "golang",
17
27
  "hono",
28
+ "mobile",
18
29
  "monorepo",
30
+ "nextjs",
31
+ "nuxt",
32
+ "payments",
33
+ "pnpm",
19
34
  "prisma",
35
+ "project-generator",
20
36
  "pwa",
21
37
  "python",
22
38
  "react",
23
39
  "react-native",
24
40
  "rust",
41
+ "scaffold",
25
42
  "scaffolding",
26
43
  "shadcn",
44
+ "solid",
27
45
  "starter",
46
+ "starter-kit",
47
+ "svelte",
48
+ "sveltekit",
28
49
  "tailwind",
29
50
  "tanstack",
30
51
  "tauri",
@@ -33,6 +54,9 @@
33
54
  "turborepo",
34
55
  "type-safety",
35
56
  "typescript",
57
+ "vite",
58
+ "vue",
59
+ "web-app",
36
60
  "yarn"
37
61
  ],
38
62
  "homepage": "https://better-fullstack.dev/",
@@ -75,7 +99,8 @@
75
99
  "test:watch": "bun test --watch",
76
100
  "test:coverage": "bun test --coverage",
77
101
  "test:ci": "CI=1 bun test --bail=5",
78
- "test:e2e": "E2E=1 bun test test/e2e/e2e.e2e.ts",
102
+ "test:e2e": "E2E=1 bun test test/e2e/e2e.e2e.ts --timeout 600000",
103
+ "test:integration": "bun test test/e2e/cli-interaction.test.ts test/e2e/cli-binary.test.ts",
79
104
  "test:astro-combos": "bun run scripts/test-astro-combinations.ts",
80
105
  "test:matrix": "MATRIX_MODE=batched bun test ./test/matrix/matrix-test.test.ts",
81
106
  "test:matrix:fast": "MATRIX_MODE=sample MATRIX_SAMPLE=0.1 bun test ./test/matrix/matrix-test.test.ts",
@@ -83,11 +108,11 @@
83
108
  "prepublishOnly": "npm run build"
84
109
  },
85
110
  "dependencies": {
86
- "@better-fullstack/template-generator": "^1.4.15",
87
- "@better-fullstack/types": "^1.4.15",
111
+ "@better-fullstack/template-generator": "^1.5.0",
112
+ "@better-fullstack/types": "^1.5.0",
88
113
  "@clack/core": "^0.5.0",
89
114
  "@clack/prompts": "^1.1.0",
90
- "@orpc/server": "^1.13.7",
115
+ "@orpc/server": "^1.13.9",
91
116
  "consola": "^3.4.2",
92
117
  "env-paths": "^4.0.0",
93
118
  "execa": "^9.6.1",
@@ -100,11 +125,11 @@
100
125
  "tinyglobby": "^0.2.15",
101
126
  "trpc-cli": "^0.12.1",
102
127
  "ts-morph": "^27.0.2",
103
- "yaml": "^2.8.2",
128
+ "yaml": "^2.8.3",
104
129
  "zod": "^4.3.6"
105
130
  },
106
131
  "devDependencies": {
107
- "@types/bun": "^1.3.10",
132
+ "@types/bun": "^1.3.11",
108
133
  "@types/fs-extra": "^11.0.4",
109
134
  "@types/node": "^25.5.0",
110
135
  "publint": "^0.3.18",