create-better-t-stack 2.33.8-canary.98a850ad → 2.33.9-canary.7db03e6d

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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-D0MPVT22.js";
2
+ import { createBtsCli } from "./src-CEmXO3U_.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { builder, createBtsCli, docs, init, router, sponsors } from "./src-D0MPVT22.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-CEmXO3U_.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -5,7 +5,6 @@ import { createCli, trpcServer } from "trpc-cli";
5
5
  import z from "zod";
6
6
  import path from "node:path";
7
7
  import consola, { consola as consola$1 } from "consola";
8
- import * as fs$1 from "fs-extra";
9
8
  import fs from "fs-extra";
10
9
  import { fileURLToPath } from "node:url";
11
10
  import gradient from "gradient-string";
@@ -137,7 +136,8 @@ const ADDON_COMPATIBILITY = {
137
136
  "react-router",
138
137
  "nuxt",
139
138
  "svelte",
140
- "solid"
139
+ "solid",
140
+ "next"
141
141
  ],
142
142
  biome: [],
143
143
  husky: [],
@@ -565,6 +565,20 @@ function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
565
565
  function validateServerDeployRequiresBackend(serverDeploy, backend) {
566
566
  if (serverDeploy && serverDeploy !== "none" && (!backend || backend === "none")) exitWithError("'--server-deploy' requires a backend. Please select a backend or set '--server-deploy none'.");
567
567
  }
568
+ function validateAddonsAgainstFrontends(addons = [], frontends = []) {
569
+ for (const addon of addons) {
570
+ if (addon === "none") continue;
571
+ const { isCompatible, reason } = validateAddonCompatibility(addon, frontends);
572
+ if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
573
+ }
574
+ }
575
+ function validateExamplesCompatibility(examples, backend, database, frontend) {
576
+ const examplesArr = examples ?? [];
577
+ if (examplesArr.length === 0 || examplesArr.includes("none")) return;
578
+ if (examplesArr.includes("todo") && backend !== "convex" && backend !== "none" && database === "none") exitWithError("The 'todo' example requires a database if a backend (other than Convex) is present. Cannot use --examples todo when database is 'none' and a backend is selected.");
579
+ if (examplesArr.includes("ai") && backend === "elysia") exitWithError("The 'ai' example is not compatible with the Elysia backend.");
580
+ if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
581
+ }
568
582
 
569
583
  //#endregion
570
584
  //#region src/prompts/api.ts
@@ -1298,9 +1312,9 @@ async function getProjectName(initialName) {
1298
1312
 
1299
1313
  //#endregion
1300
1314
  //#region src/utils/get-latest-cli-version.ts
1301
- const getLatestCLIVersion = async () => {
1315
+ const getLatestCLIVersion = () => {
1302
1316
  const packageJsonPath = path.join(PKG_ROOT, "package.json");
1303
- const packageJsonContent = await fs.readJSON(packageJsonPath);
1317
+ const packageJsonContent = fs.readJSONSync(packageJsonPath);
1304
1318
  return packageJsonContent.version ?? "1.0.0";
1305
1319
  };
1306
1320
 
@@ -1662,6 +1676,43 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1662
1676
  validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
1663
1677
  return config;
1664
1678
  }
1679
+ function validateConfigCompatibility(config) {
1680
+ const effectiveDatabase = config.database;
1681
+ const effectiveBackend = config.backend;
1682
+ const effectiveFrontend = config.frontend;
1683
+ const effectiveApi = config.api;
1684
+ validateApiFrontendCompatibility(effectiveApi, effectiveFrontend);
1685
+ if (config.addons && config.addons.length > 0) {
1686
+ validateAddonsAgainstFrontends(config.addons, effectiveFrontend);
1687
+ config.addons = [...new Set(config.addons)];
1688
+ }
1689
+ validateExamplesCompatibility(config.examples ?? [], effectiveBackend, effectiveDatabase, effectiveFrontend ?? []);
1690
+ }
1691
+ function processProvidedFlagsWithoutValidation(options, projectName) {
1692
+ const config = {};
1693
+ if (options.api) config.api = options.api;
1694
+ if (options.backend) config.backend = options.backend;
1695
+ if (options.database) config.database = options.database;
1696
+ if (options.orm) config.orm = options.orm;
1697
+ if (options.auth !== void 0) config.auth = options.auth;
1698
+ if (options.git !== void 0) config.git = options.git;
1699
+ if (options.install !== void 0) config.install = options.install;
1700
+ if (options.runtime) config.runtime = options.runtime;
1701
+ if (options.dbSetup) config.dbSetup = options.dbSetup;
1702
+ if (options.packageManager) config.packageManager = options.packageManager;
1703
+ if (options.webDeploy) config.webDeploy = options.webDeploy;
1704
+ const derivedName = deriveProjectName(projectName, options.projectDirectory);
1705
+ if (derivedName) {
1706
+ const nameToValidate = projectName ? path.basename(projectName) : derivedName;
1707
+ const result = ProjectNameSchema.safeParse(nameToValidate);
1708
+ if (!result.success) throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`);
1709
+ config.projectName = projectName || derivedName;
1710
+ }
1711
+ if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
1712
+ if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
1713
+ if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
1714
+ return config;
1715
+ }
1665
1716
  function getProvidedFlags(options) {
1666
1717
  return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
1667
1718
  }
@@ -2216,20 +2267,16 @@ async function setupDeploymentTemplates(projectDir, context) {
2216
2267
  }
2217
2268
  } else {
2218
2269
  if (context.webDeploy === "alchemy") {
2270
+ const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2219
2271
  const webAppDir = path.join(projectDir, "apps/web");
2220
- if (await fs.pathExists(webAppDir)) {
2221
- const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2222
- if (await fs.pathExists(alchemyTemplateSrc)) await processAndCopyFiles("**/*", alchemyTemplateSrc, webAppDir, context);
2223
- }
2272
+ if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(webAppDir)) await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, webAppDir, context);
2224
2273
  }
2225
2274
  if (context.serverDeploy === "alchemy") {
2275
+ const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2226
2276
  const serverAppDir = path.join(projectDir, "apps/server");
2227
- if (await fs.pathExists(serverAppDir)) {
2228
- const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
2229
- if (await fs.pathExists(alchemyTemplateSrc)) {
2230
- await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2231
- await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2232
- }
2277
+ if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
2278
+ await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2279
+ await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2233
2280
  }
2234
2281
  }
2235
2282
  }
@@ -2501,7 +2548,7 @@ async function setupUltracite(config, hasHusky) {
2501
2548
  ];
2502
2549
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
2503
2550
  if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
2504
- if (hasHusky) ultraciteArgs.push("--features", "husky", "lint-staged");
2551
+ if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
2505
2552
  const ultraciteArgsString = ultraciteArgs.join(" ");
2506
2553
  const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
2507
2554
  const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
@@ -3342,15 +3389,15 @@ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
3342
3389
  //#region src/helpers/deployment/workers/workers-next-setup.ts
3343
3390
  async function setupNextWorkersDeploy(projectDir, _packageManager) {
3344
3391
  const webAppDir = path.join(projectDir, "apps/web");
3345
- if (!await fs$1.pathExists(webAppDir)) return;
3392
+ if (!await fs.pathExists(webAppDir)) return;
3346
3393
  await addPackageDependency({
3347
3394
  dependencies: ["@opennextjs/cloudflare"],
3348
3395
  devDependencies: ["wrangler"],
3349
3396
  projectDir: webAppDir
3350
3397
  });
3351
3398
  const packageJsonPath = path.join(webAppDir, "package.json");
3352
- if (await fs$1.pathExists(packageJsonPath)) {
3353
- const pkg = await fs$1.readJson(packageJsonPath);
3399
+ if (await fs.pathExists(packageJsonPath)) {
3400
+ const pkg = await fs.readJson(packageJsonPath);
3354
3401
  pkg.scripts = {
3355
3402
  ...pkg.scripts,
3356
3403
  preview: "opennextjs-cloudflare build && opennextjs-cloudflare preview",
@@ -3358,7 +3405,7 @@ async function setupNextWorkersDeploy(projectDir, _packageManager) {
3358
3405
  upload: "opennextjs-cloudflare build && opennextjs-cloudflare upload",
3359
3406
  "cf-typegen": "wrangler types --env-interface CloudflareEnv cloudflare-env.d.ts"
3360
3407
  };
3361
- await fs$1.writeJson(packageJsonPath, pkg, { spaces: 2 });
3408
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
3362
3409
  }
3363
3410
  }
3364
3411
 
@@ -5471,13 +5518,14 @@ async function displayPostInstallInstructions(config) {
5471
5518
  const runCmd = packageManager === "npm" ? "npm run" : packageManager;
5472
5519
  const cdCmd = `cd ${relativePath}`;
5473
5520
  const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
5474
- const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup) : "";
5521
+ const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy) : "";
5475
5522
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
5476
5523
  const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
5477
5524
  const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
5478
5525
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
5479
5526
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
5480
- const workersDeployInstructions = webDeploy === "wrangler" ? getWorkersDeployInstructions(runCmd) : "";
5527
+ const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy);
5528
+ const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy);
5481
5529
  const hasWeb = frontend?.some((f) => [
5482
5530
  "tanstack-router",
5483
5531
  "react-router",
@@ -5520,7 +5568,8 @@ async function displayPostInstallInstructions(config) {
5520
5568
  if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
5521
5569
  if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
5522
5570
  if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
5523
- if (workersDeployInstructions) output += `\n${workersDeployInstructions.trim()}\n`;
5571
+ if (wranglerDeployInstructions) output += `\n${wranglerDeployInstructions.trim()}\n`;
5572
+ if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
5524
5573
  if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
5525
5574
  if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
5526
5575
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
@@ -5541,7 +5590,7 @@ function getNativeInstructions(isConvex) {
5541
5590
  function getLintingInstructions(runCmd) {
5542
5591
  return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
5543
5592
  }
5544
- async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup) {
5593
+ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, serverDeploy) {
5545
5594
  const instructions = [];
5546
5595
  if (dbSetup === "docker") {
5547
5596
  const dockerStatus = await getDockerStatus(database);
@@ -5550,7 +5599,7 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
5550
5599
  instructions.push("");
5551
5600
  }
5552
5601
  }
5553
- if (runtime === "workers" && dbSetup === "d1") {
5602
+ if (serverDeploy === "wrangler" && dbSetup === "d1") {
5554
5603
  const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
5555
5604
  instructions.push(`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(`${packageManager} wrangler login`)}`);
5556
5605
  instructions.push(`${pc.cyan("2.")} Create D1 database: ${pc.white(`${packageManager} wrangler d1 create your-database-name`)}`);
@@ -5560,6 +5609,10 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
5560
5609
  instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
5561
5610
  instructions.push("");
5562
5611
  }
5612
+ if (dbSetup === "d1" && serverDeploy === "alchemy") {
5613
+ instructions.push(`${pc.cyan("•")} Generate migrations: ${pc.white(`${runCmd} db:generate`)}`);
5614
+ instructions.push("");
5615
+ }
5563
5616
  if (orm === "prisma") {
5564
5617
  if (dbSetup === "turso") instructions.push(`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires\n additional setup. Learn more at:\n https://www.prisma.io/docs/orm/overview/databases/turso`);
5565
5618
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
@@ -5591,8 +5644,18 @@ function getNoOrmWarning() {
5591
5644
  function getBunWebNativeWarning() {
5592
5645
  return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
5593
5646
  }
5594
- function getWorkersDeployInstructions(runCmd) {
5595
- return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`;
5647
+ function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy) {
5648
+ const instructions = [];
5649
+ if (webDeploy === "wrangler") instructions.push(`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`);
5650
+ if (serverDeploy === "wrangler") instructions.push(`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} run deploy`}`);
5651
+ return instructions.length ? `\n${instructions.join("\n")}` : "";
5652
+ }
5653
+ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
5654
+ const instructions = [];
5655
+ if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web to Alchemy:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}`);
5656
+ else if (serverDeploy === "alchemy" && webDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy server to Alchemy:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}`);
5657
+ else if (webDeploy === "alchemy" && serverDeploy === "alchemy") instructions.push(`${pc.bold("Deploy to Alchemy:")}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}`);
5658
+ return instructions.length ? `\n${instructions.join("\n")}` : "";
5596
5659
  }
5597
5660
 
5598
5661
  //#endregion
@@ -5902,15 +5965,9 @@ async function createProjectHandler(input) {
5902
5965
  projectDirectory: input.projectName
5903
5966
  };
5904
5967
  const providedFlags = getProvidedFlags(cliInput);
5905
- const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
5906
- const { projectName: _projectNameFromFlags,...otherFlags } = flagConfig;
5907
- if (!input.yes && Object.keys(otherFlags).length > 0) {
5908
- log.info(pc.yellow("Using these pre-selected options:"));
5909
- log.message(displayConfig(otherFlags));
5910
- log.message("");
5911
- }
5912
5968
  let config;
5913
5969
  if (input.yes) {
5970
+ const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
5914
5971
  config = {
5915
5972
  ...DEFAULT_CONFIG,
5916
5973
  ...flagConfig,
@@ -5918,12 +5975,22 @@ async function createProjectHandler(input) {
5918
5975
  projectDir: finalResolvedPath,
5919
5976
  relativePath: finalPathInput
5920
5977
  };
5978
+ validateConfigCompatibility(config);
5921
5979
  if (config.backend === "convex") log.info("Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo");
5922
5980
  else if (config.backend === "none") log.info("Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none");
5923
5981
  log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
5924
5982
  log.message(displayConfig(config));
5925
5983
  log.message("");
5926
- } else config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
5984
+ } else {
5985
+ const flagConfig = processAndValidateFlags(cliInput, providedFlags, finalBaseName);
5986
+ const { projectName: _projectNameFromFlags,...otherFlags } = flagConfig;
5987
+ if (Object.keys(otherFlags).length > 0) {
5988
+ log.info(pc.yellow("Using these pre-selected options:"));
5989
+ log.message(displayConfig(otherFlags));
5990
+ log.message("");
5991
+ }
5992
+ config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
5993
+ }
5927
5994
  await createProject(config);
5928
5995
  const reproducibleCommand = generateReproducibleCommand(config);
5929
5996
  log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.33.8-canary.98a850ad",
3
+ "version": "2.33.9-canary.7db03e6d",
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",