create-better-t-stack 2.33.9-canary.88769d6d → 2.34.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/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-Bm5FOLMd.js";
2
+ import { createBtsCli } from "./src-CqC0HMY7.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-Bm5FOLMd.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-CqC0HMY7.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -29,9 +29,8 @@ const getUserPkgManager = () => {
29
29
  const __filename = fileURLToPath(import.meta.url);
30
30
  const distPath = path.dirname(__filename);
31
31
  const PKG_ROOT = path.join(distPath, "../");
32
- const DEFAULT_CONFIG = {
32
+ const DEFAULT_CONFIG_BASE = {
33
33
  projectName: "my-better-t-app",
34
- projectDir: path.resolve(process.cwd(), "my-better-t-app"),
35
34
  relativePath: "my-better-t-app",
36
35
  frontend: ["tanstack-router"],
37
36
  database: "sqlite",
@@ -40,7 +39,6 @@ const DEFAULT_CONFIG = {
40
39
  addons: ["turborepo"],
41
40
  examples: [],
42
41
  git: true,
43
- packageManager: getUserPkgManager(),
44
42
  install: true,
45
43
  dbSetup: "none",
46
44
  backend: "hono",
@@ -49,6 +47,17 @@ const DEFAULT_CONFIG = {
49
47
  webDeploy: "none",
50
48
  serverDeploy: "none"
51
49
  };
50
+ function getDefaultConfig() {
51
+ return {
52
+ ...DEFAULT_CONFIG_BASE,
53
+ projectDir: path.resolve(process.cwd(), DEFAULT_CONFIG_BASE.projectName),
54
+ packageManager: getUserPkgManager(),
55
+ frontend: [...DEFAULT_CONFIG_BASE.frontend],
56
+ addons: [...DEFAULT_CONFIG_BASE.addons],
57
+ examples: [...DEFAULT_CONFIG_BASE.examples]
58
+ };
59
+ }
60
+ const DEFAULT_CONFIG = getDefaultConfig();
52
61
  const dependencyVersionMap = {
53
62
  "better-auth": "^1.3.4",
54
63
  "@better-auth/expo": "^1.3.4",
@@ -579,6 +588,17 @@ function validateExamplesCompatibility(examples, backend, database, frontend) {
579
588
  if (examplesArr.includes("ai") && backend === "elysia") exitWithError("The 'ai' example is not compatible with the Elysia backend.");
580
589
  if (examplesArr.includes("ai") && (frontend ?? []).includes("solid")) exitWithError("The 'ai' example is not compatible with the Solid frontend.");
581
590
  }
591
+ function validateAlchemyCompatibility(webDeploy, serverDeploy, frontends = []) {
592
+ const isAlchemyWebDeploy = webDeploy === "alchemy";
593
+ const isAlchemyServerDeploy = serverDeploy === "alchemy";
594
+ if (isAlchemyWebDeploy || isAlchemyServerDeploy) {
595
+ const incompatibleFrontends = frontends.filter((f) => f === "next" || f === "react-router");
596
+ if (incompatibleFrontends.length > 0) {
597
+ const deployType = isAlchemyWebDeploy && isAlchemyServerDeploy ? "web and server deployment" : isAlchemyWebDeploy ? "web deployment" : "server deployment";
598
+ exitWithError(`Alchemy ${deployType} is temporarily not compatible with ${incompatibleFrontends.join(" and ")} frontend(s). Please choose a different frontend or deployment option.`);
599
+ }
600
+ }
601
+ }
582
602
 
583
603
  //#endregion
584
604
  //#region src/prompts/api.ts
@@ -1576,7 +1596,7 @@ const renderTitle = () => {
1576
1596
  };
1577
1597
 
1578
1598
  //#endregion
1579
- //#region src/validation.ts
1599
+ //#region src/utils/config-processing.ts
1580
1600
  function processArrayOption(options) {
1581
1601
  if (!options || options.length === 0) return [];
1582
1602
  if (options.includes("none")) return [];
@@ -1587,22 +1607,10 @@ function deriveProjectName(projectName, projectDirectory) {
1587
1607
  if (projectDirectory) return path.basename(path.resolve(process.cwd(), projectDirectory));
1588
1608
  return "";
1589
1609
  }
1590
- function validateProjectName(name) {
1591
- const result = ProjectNameSchema.safeParse(name);
1592
- if (!result.success) exitWithError(`Invalid project name: ${result.error.issues[0]?.message || "Invalid project name"}`);
1593
- }
1594
- function processAndValidateFlags(options, providedFlags, projectName) {
1610
+ function processFlags(options, projectName) {
1595
1611
  const config = {};
1596
- if (options.api) {
1597
- config.api = options.api;
1598
- if (options.api === "none") {
1599
- if (options.examples && !(options.examples.length === 1 && options.examples[0] === "none") && options.backend !== "convex") exitWithError("Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.");
1600
- }
1601
- }
1612
+ if (options.api) config.api = options.api;
1602
1613
  if (options.backend) config.backend = options.backend;
1603
- if (providedFlags.has("backend") && config.backend && config.backend !== "convex" && config.backend !== "none") {
1604
- if (providedFlags.has("runtime") && options.runtime === "none") exitWithError(`'--runtime none' is only supported with '--backend convex' or '--backend none'. Please choose 'bun', 'node', or remove the --runtime flag.`);
1605
- }
1606
1614
  if (options.database) config.database = options.database;
1607
1615
  if (options.orm) config.orm = options.orm;
1608
1616
  if (options.auth !== void 0) config.auth = options.auth;
@@ -1614,107 +1622,188 @@ function processAndValidateFlags(options, providedFlags, projectName) {
1614
1622
  if (options.webDeploy) config.webDeploy = options.webDeploy;
1615
1623
  if (options.serverDeploy) config.serverDeploy = options.serverDeploy;
1616
1624
  const derivedName = deriveProjectName(projectName, options.projectDirectory);
1617
- if (derivedName) {
1618
- const nameToValidate = projectName ? path.basename(projectName) : derivedName;
1619
- validateProjectName(nameToValidate);
1620
- config.projectName = projectName || derivedName;
1621
- }
1622
- if (options.frontend && options.frontend.length > 0) if (options.frontend.includes("none")) {
1623
- if (options.frontend.length > 1) exitWithError(`Cannot combine 'none' with other frontend options.`);
1624
- config.frontend = [];
1625
- } else {
1626
- const validOptions = processArrayOption(options.frontend);
1627
- ensureSingleWebAndNative(validOptions);
1628
- config.frontend = validOptions;
1629
- }
1630
- if (providedFlags.has("api") && providedFlags.has("frontend") && config.api && config.frontend && config.frontend.length > 0) validateApiFrontendCompatibility(config.api, config.frontend);
1631
- if (options.addons && options.addons.length > 0) if (options.addons.includes("none")) {
1632
- if (options.addons.length > 1) exitWithError(`Cannot combine 'none' with other addons.`);
1633
- config.addons = [];
1634
- } else config.addons = processArrayOption(options.addons);
1635
- if (options.examples && options.examples.length > 0) if (options.examples.includes("none")) {
1636
- if (options.examples.length > 1) exitWithError("Cannot combine 'none' with other examples.");
1637
- config.examples = [];
1638
- } else {
1639
- config.examples = processArrayOption(options.examples);
1640
- if (options.examples.includes("none") && config.backend !== "convex") config.examples = [];
1625
+ if (derivedName) config.projectName = projectName || derivedName;
1626
+ if (options.frontend && options.frontend.length > 0) config.frontend = processArrayOption(options.frontend);
1627
+ if (options.addons && options.addons.length > 0) config.addons = processArrayOption(options.addons);
1628
+ if (options.examples && options.examples.length > 0) config.examples = processArrayOption(options.examples);
1629
+ return config;
1630
+ }
1631
+ function getProvidedFlags(options) {
1632
+ return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
1633
+ }
1634
+ function validateNoneExclusivity(options, optionName) {
1635
+ if (!options || options.length === 0) return;
1636
+ if (options.includes("none") && options.length > 1) throw new Error(`Cannot combine 'none' with other ${optionName}.`);
1637
+ }
1638
+ function validateArrayOptions(options) {
1639
+ validateNoneExclusivity(options.frontend, "frontend options");
1640
+ validateNoneExclusivity(options.addons, "addons");
1641
+ validateNoneExclusivity(options.examples, "examples");
1642
+ }
1643
+
1644
+ //#endregion
1645
+ //#region src/utils/config-validation.ts
1646
+ function validateDatabaseOrmAuth(cfg, flags) {
1647
+ const db = cfg.database;
1648
+ const orm = cfg.orm;
1649
+ const has = (k) => flags ? flags.has(k) : true;
1650
+ if (has("orm") && has("database") && orm === "mongoose" && db !== "mongodb") exitWithError("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
1651
+ if (has("orm") && has("database") && orm === "drizzle" && db === "mongodb") exitWithError("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
1652
+ if (has("database") && has("orm") && db === "mongodb" && orm && orm !== "mongoose" && orm !== "prisma" && orm !== "none") exitWithError("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
1653
+ if (has("database") && has("orm") && db && db !== "none" && orm === "none") exitWithError("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
1654
+ if (has("orm") && has("database") && orm && orm !== "none" && db === "none") exitWithError("ORM selection requires a database. Please choose a database or set '--orm none'.");
1655
+ if (has("auth") && has("database") && cfg.auth && db === "none") exitWithError("Authentication requires a database. Please choose a database or set '--no-auth'.");
1656
+ if (cfg.auth && db === "none") exitWithError("Authentication requires a database. Please choose a database or set '--no-auth'.");
1657
+ if (orm && orm !== "none" && db === "none") exitWithError("ORM selection requires a database. Please choose a database or set '--orm none'.");
1658
+ }
1659
+ function validateDatabaseSetup(config, providedFlags) {
1660
+ const { dbSetup, database, runtime } = config;
1661
+ if (providedFlags.has("dbSetup") && providedFlags.has("database") && dbSetup && dbSetup !== "none" && database === "none") exitWithError("Database setup requires a database. Please choose a database or set '--db-setup none'.");
1662
+ const setupValidations = {
1663
+ turso: {
1664
+ database: "sqlite",
1665
+ errorMessage: "Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup."
1666
+ },
1667
+ neon: {
1668
+ database: "postgres",
1669
+ errorMessage: "Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
1670
+ },
1671
+ "prisma-postgres": {
1672
+ database: "postgres",
1673
+ errorMessage: "Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
1674
+ },
1675
+ "mongodb-atlas": {
1676
+ database: "mongodb",
1677
+ errorMessage: "MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup."
1678
+ },
1679
+ supabase: {
1680
+ database: "postgres",
1681
+ errorMessage: "Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup."
1682
+ },
1683
+ d1: {
1684
+ database: "sqlite",
1685
+ runtime: "workers",
1686
+ errorMessage: "Cloudflare D1 setup requires SQLite database and Cloudflare Workers runtime."
1687
+ },
1688
+ docker: { errorMessage: "Docker setup is not compatible with SQLite database or Cloudflare Workers runtime." },
1689
+ none: { errorMessage: "" }
1690
+ };
1691
+ if (dbSetup && dbSetup !== "none") {
1692
+ const validation = setupValidations[dbSetup];
1693
+ if (validation.database && database !== validation.database) exitWithError(validation.errorMessage);
1694
+ if (validation.runtime && runtime !== validation.runtime) exitWithError(validation.errorMessage);
1695
+ if (dbSetup === "docker") {
1696
+ if (database === "sqlite") exitWithError("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
1697
+ if (runtime === "workers") exitWithError("Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
1698
+ }
1641
1699
  }
1642
- if (config.backend === "convex" || config.backend === "none") {
1643
- const incompatibleFlags = incompatibleFlagsForBackend(config.backend, providedFlags, options);
1644
- if (incompatibleFlags.length > 0) exitWithError(`The following flags are incompatible with '--backend ${config.backend}': ${incompatibleFlags.join(", ")}. Please remove them.`);
1645
- if (config.backend === "convex" && providedFlags.has("frontend") && options.frontend) {
1700
+ }
1701
+ function validateBackendConstraints(config, providedFlags, options) {
1702
+ const { backend } = config;
1703
+ if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none") {
1704
+ if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex' or '--backend none'. Please choose 'bun', 'node', or remove the --runtime flag.");
1705
+ }
1706
+ if (backend === "convex" || backend === "none") {
1707
+ const incompatibleFlags = incompatibleFlagsForBackend(backend, providedFlags, options);
1708
+ if (incompatibleFlags.length > 0) exitWithError(`The following flags are incompatible with '--backend ${backend}': ${incompatibleFlags.join(", ")}. Please remove them.`);
1709
+ if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
1646
1710
  const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
1647
1711
  if (incompatibleFrontends.length > 0) exitWithError(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
1648
1712
  }
1649
1713
  coerceBackendPresets(config);
1650
1714
  }
1651
- if (providedFlags.has("orm") && providedFlags.has("database") && config.orm === "mongoose" && config.database !== "mongodb") exitWithError("Mongoose ORM requires MongoDB database. Please use '--database mongodb' or choose a different ORM.");
1652
- if (providedFlags.has("database") && providedFlags.has("orm") && config.database === "mongodb" && config.orm && config.orm !== "mongoose" && config.orm !== "prisma") exitWithError("MongoDB database requires Mongoose or Prisma ORM. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
1653
- if (providedFlags.has("orm") && providedFlags.has("database") && config.orm === "drizzle" && config.database === "mongodb") exitWithError("Drizzle ORM does not support MongoDB. Please use '--orm mongoose' or '--orm prisma' or choose a different database.");
1654
- if (providedFlags.has("database") && providedFlags.has("orm") && config.database && config.database !== "none" && config.orm === "none") exitWithError("Database selection requires an ORM. Please choose '--orm drizzle', '--orm prisma', or '--orm mongoose'.");
1655
- if (providedFlags.has("orm") && providedFlags.has("database") && config.orm && config.orm !== "none" && config.database === "none") exitWithError("ORM selection requires a database. Please choose a database or set '--orm none'.");
1656
- if (providedFlags.has("auth") && providedFlags.has("database") && config.auth && config.database === "none") exitWithError("Authentication requires a database. Please choose a database or set '--no-auth'.");
1657
- if (providedFlags.has("dbSetup") && providedFlags.has("database") && config.dbSetup && config.dbSetup !== "none" && config.database === "none") exitWithError("Database setup requires a database. Please choose a database or set '--db-setup none'.");
1658
- if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "turso" && config.database !== "sqlite") exitWithError("Turso setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
1659
- if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "neon" && config.database !== "postgres") exitWithError("Neon setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
1660
- if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "prisma-postgres" && config.database !== "postgres") exitWithError("Prisma PostgreSQL setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
1661
- if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "mongodb-atlas" && config.database !== "mongodb") exitWithError("MongoDB Atlas setup requires MongoDB database. Please use '--database mongodb' or choose a different setup.");
1662
- if (providedFlags.has("dbSetup") && (config.database ? providedFlags.has("database") : true) && config.dbSetup === "supabase" && config.database !== "postgres") exitWithError("Supabase setup requires PostgreSQL database. Please use '--database postgres' or choose a different setup.");
1663
- if (config.dbSetup === "d1") {
1664
- if (providedFlags.has("dbSetup") && providedFlags.has("database") || providedFlags.has("dbSetup") && !config.database) {
1665
- if (config.database !== "sqlite") exitWithError("Cloudflare D1 setup requires SQLite database. Please use '--database sqlite' or choose a different setup.");
1666
- }
1667
- if (providedFlags.has("dbSetup") && providedFlags.has("runtime") || providedFlags.has("dbSetup") && !config.runtime) {
1668
- if (config.runtime !== "workers") exitWithError("Cloudflare D1 setup requires the Cloudflare Workers runtime. Please use '--runtime workers' or choose a different setup.");
1669
- }
1715
+ }
1716
+ function validateFrontendConstraints(config, providedFlags) {
1717
+ const { frontend } = config;
1718
+ if (frontend && frontend.length > 0) {
1719
+ ensureSingleWebAndNative(frontend);
1720
+ if (providedFlags.has("api") && providedFlags.has("frontend") && config.api) validateApiFrontendCompatibility(config.api, frontend);
1670
1721
  }
1671
- if (providedFlags.has("dbSetup") && providedFlags.has("database") && config.dbSetup === "docker" && config.database === "sqlite") exitWithError("Docker setup is not compatible with SQLite database. SQLite is file-based and doesn't require Docker. Please use '--database postgres', '--database mysql', '--database mongodb', or choose a different setup.");
1672
- if (providedFlags.has("dbSetup") && providedFlags.has("runtime") && config.dbSetup === "docker" && config.runtime === "workers") exitWithError("Docker setup is not compatible with Cloudflare Workers runtime. Workers runtime uses serverless databases (D1) and doesn't support local Docker containers. Please use '--db-setup d1' for SQLite or choose a different runtime.");
1673
- validateWorkersCompatibility(providedFlags, options, config);
1674
- const hasWebFrontendFlag = (config.frontend ?? []).some((f) => isWebFrontend(f));
1722
+ const hasWebFrontendFlag = (frontend ?? []).some((f) => isWebFrontend(f));
1675
1723
  validateWebDeployRequiresWebFrontend(config.webDeploy, hasWebFrontendFlag);
1676
- validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
1677
- return config;
1678
1724
  }
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);
1725
+ function validateApiConstraints(config, options) {
1726
+ if (config.api === "none") {
1727
+ if (options.examples && !(options.examples.length === 1 && options.examples[0] === "none") && options.backend !== "convex") exitWithError("Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.");
1728
+ }
1729
+ }
1730
+ function validateFullConfig(config, providedFlags, options) {
1731
+ validateDatabaseOrmAuth(config, providedFlags);
1732
+ validateDatabaseSetup(config, providedFlags);
1733
+ validateBackendConstraints(config, providedFlags, options);
1734
+ validateFrontendConstraints(config, providedFlags);
1735
+ validateApiConstraints(config, options);
1736
+ validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
1737
+ validateWorkersCompatibility(providedFlags, options, config);
1685
1738
  if (config.addons && config.addons.length > 0) {
1686
- validateAddonsAgainstFrontends(config.addons, effectiveFrontend);
1739
+ validateAddonsAgainstFrontends(config.addons, config.frontend);
1687
1740
  config.addons = [...new Set(config.addons)];
1688
1741
  }
1689
- validateExamplesCompatibility(config.examples ?? [], effectiveBackend, effectiveDatabase, effectiveFrontend ?? []);
1742
+ validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1743
+ validateAlchemyCompatibility(config.webDeploy, config.serverDeploy, config.frontend ?? []);
1690
1744
  }
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;
1745
+ function validateConfigForProgrammaticUse(config) {
1746
+ try {
1747
+ validateDatabaseOrmAuth(config);
1748
+ if (config.frontend && config.frontend.length > 0) ensureSingleWebAndNative(config.frontend);
1749
+ validateApiFrontendCompatibility(config.api, config.frontend);
1750
+ if (config.addons && config.addons.length > 0) validateAddonsAgainstFrontends(config.addons, config.frontend);
1751
+ validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
1752
+ } catch (error) {
1753
+ if (error instanceof Error) throw error;
1754
+ throw new Error(String(error));
1710
1755
  }
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);
1756
+ }
1757
+
1758
+ //#endregion
1759
+ //#region src/utils/project-name-validation.ts
1760
+ function validateProjectName(name) {
1761
+ const result = ProjectNameSchema.safeParse(name);
1762
+ if (!result.success) exitWithError(`Invalid project name: ${result.error.issues[0]?.message || "Invalid project name"}`);
1763
+ }
1764
+ function validateProjectNameThrow(name) {
1765
+ const result = ProjectNameSchema.safeParse(name);
1766
+ if (!result.success) throw new Error(`Invalid project name: ${result.error.issues[0]?.message}`);
1767
+ }
1768
+ function extractAndValidateProjectName(projectName, projectDirectory, throwOnError = false) {
1769
+ const derivedName = projectName || (projectDirectory ? path.basename(path.resolve(process.cwd(), projectDirectory)) : "");
1770
+ if (!derivedName) return "";
1771
+ const nameToValidate = projectName ? path.basename(projectName) : derivedName;
1772
+ if (throwOnError) validateProjectNameThrow(nameToValidate);
1773
+ else validateProjectName(nameToValidate);
1774
+ return projectName || derivedName;
1775
+ }
1776
+
1777
+ //#endregion
1778
+ //#region src/validation.ts
1779
+ function processAndValidateFlags(options, providedFlags, projectName) {
1780
+ if (options.yolo) {
1781
+ const cfg = processFlags(options, projectName);
1782
+ const validatedProjectName$1 = extractAndValidateProjectName(projectName, options.projectDirectory, true);
1783
+ if (validatedProjectName$1) cfg.projectName = validatedProjectName$1;
1784
+ return cfg;
1785
+ }
1786
+ try {
1787
+ validateArrayOptions(options);
1788
+ } catch (error) {
1789
+ exitWithError(error instanceof Error ? error.message : String(error));
1790
+ }
1791
+ const config = processFlags(options, projectName);
1792
+ const validatedProjectName = extractAndValidateProjectName(projectName, options.projectDirectory, false);
1793
+ if (validatedProjectName) config.projectName = validatedProjectName;
1794
+ validateFullConfig(config, providedFlags, options);
1714
1795
  return config;
1715
1796
  }
1716
- function getProvidedFlags(options) {
1717
- return new Set(Object.keys(options).filter((key) => options[key] !== void 0));
1797
+ function processProvidedFlagsWithoutValidation(options, projectName) {
1798
+ const config = processFlags(options, projectName);
1799
+ const validatedProjectName = extractAndValidateProjectName(projectName, options.projectDirectory, true);
1800
+ if (validatedProjectName) config.projectName = validatedProjectName;
1801
+ return config;
1802
+ }
1803
+ function validateConfigCompatibility(config, providedFlags, options) {
1804
+ if (options?.yolo) return;
1805
+ if (options && providedFlags) validateFullConfig(config, providedFlags, options);
1806
+ else validateConfigForProgrammaticUse(config);
1718
1807
  }
1719
1808
 
1720
1809
  //#endregion
@@ -2263,7 +2352,10 @@ async function setupDeploymentTemplates(projectDir, context) {
2263
2352
  if (await fs.pathExists(alchemyTemplateSrc)) {
2264
2353
  await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, projectDir, context);
2265
2354
  const serverAppDir = path.join(projectDir, "apps/server");
2266
- if (await fs.pathExists(serverAppDir)) await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2355
+ if (await fs.pathExists(serverAppDir)) {
2356
+ await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2357
+ await processAndCopyFiles("wrangler.jsonc.hbs", alchemyTemplateSrc, serverAppDir, context);
2358
+ }
2267
2359
  }
2268
2360
  } else {
2269
2361
  if (context.webDeploy === "alchemy") {
@@ -2277,6 +2369,7 @@ async function setupDeploymentTemplates(projectDir, context) {
2277
2369
  if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
2278
2370
  await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2279
2371
  await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
2372
+ await processAndCopyFiles("wrangler.jsonc.hbs", alchemyTemplateSrc, serverAppDir, context);
2280
2373
  }
2281
2374
  }
2282
2375
  }
@@ -4687,8 +4780,10 @@ async function setupPrismaPostgres(config) {
4687
4780
  else prismaConfig = await initPrismaDatabase(serverDir, packageManager);
4688
4781
  if (prismaConfig) {
4689
4782
  await writeEnvFile$1(projectDir, prismaConfig);
4690
- await addDotenvImportToPrismaConfig(projectDir);
4691
- if (orm === "prisma") await addPrismaAccelerateExtension(serverDir);
4783
+ if (orm === "prisma") {
4784
+ await addDotenvImportToPrismaConfig(projectDir);
4785
+ await addPrismaAccelerateExtension(serverDir);
4786
+ }
4692
4787
  log.success(pc.green("Prisma Postgres database configured successfully!"));
4693
4788
  } else {
4694
4789
  await writeEnvFile$1(projectDir);
@@ -5518,13 +5613,14 @@ async function displayPostInstallInstructions(config) {
5518
5613
  const runCmd = packageManager === "npm" ? "npm run" : packageManager;
5519
5614
  const cdCmd = `cd ${relativePath}`;
5520
5615
  const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
5521
- const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup) : "";
5616
+ const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy) : "";
5522
5617
  const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
5523
5618
  const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
5524
5619
  const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
5525
5620
  const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
5526
5621
  const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
5527
- const workersDeployInstructions = webDeploy === "wrangler" ? getWorkersDeployInstructions(runCmd) : "";
5622
+ const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy);
5623
+ const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy);
5528
5624
  const hasWeb = frontend?.some((f) => [
5529
5625
  "tanstack-router",
5530
5626
  "react-router",
@@ -5553,8 +5649,8 @@ async function displayPostInstallInstructions(config) {
5553
5649
  if (runtime === "workers") {
5554
5650
  if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
5555
5651
  output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
5556
- if (serverDeploy === "wrangler") output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} cf-typegen\n\n`;
5557
- } else output += "\n";
5652
+ if (serverDeploy === "wrangler") output += `${pc.cyan(`${stepCounter++}.`)} cd apps/server && ${runCmd} cf-typegen\n`;
5653
+ }
5558
5654
  }
5559
5655
  output += `${pc.bold("Your project will be available at:")}\n`;
5560
5656
  if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
@@ -5567,7 +5663,8 @@ async function displayPostInstallInstructions(config) {
5567
5663
  if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
5568
5664
  if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
5569
5665
  if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
5570
- if (workersDeployInstructions) output += `\n${workersDeployInstructions.trim()}\n`;
5666
+ if (wranglerDeployInstructions) output += `\n${wranglerDeployInstructions.trim()}\n`;
5667
+ if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
5571
5668
  if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
5572
5669
  if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
5573
5670
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
@@ -5588,7 +5685,7 @@ function getNativeInstructions(isConvex) {
5588
5685
  function getLintingInstructions(runCmd) {
5589
5686
  return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
5590
5687
  }
5591
- async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup) {
5688
+ async function getDatabaseInstructions(database, orm, runCmd, _runtime, dbSetup, serverDeploy) {
5592
5689
  const instructions = [];
5593
5690
  if (dbSetup === "docker") {
5594
5691
  const dockerStatus = await getDockerStatus(database);
@@ -5597,7 +5694,7 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
5597
5694
  instructions.push("");
5598
5695
  }
5599
5696
  }
5600
- if (runtime === "workers" && dbSetup === "d1") {
5697
+ if (serverDeploy === "wrangler" && dbSetup === "d1") {
5601
5698
  const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
5602
5699
  instructions.push(`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(`${packageManager} wrangler login`)}`);
5603
5700
  instructions.push(`${pc.cyan("2.")} Create D1 database: ${pc.white(`${packageManager} wrangler d1 create your-database-name`)}`);
@@ -5605,8 +5702,8 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
5605
5702
  instructions.push(`${pc.cyan("4.")} Generate migrations: ${pc.white(`cd apps/server && ${packageManager} db:generate`)}`);
5606
5703
  instructions.push(`${pc.cyan("5.")} Apply migrations locally: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`)}`);
5607
5704
  instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
5608
- instructions.push("");
5609
5705
  }
5706
+ if (dbSetup === "d1" && serverDeploy === "alchemy") {}
5610
5707
  if (orm === "prisma") {
5611
5708
  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`);
5612
5709
  if (database === "mongodb" && dbSetup === "docker") instructions.push(`${pc.yellow("WARNING:")} Prisma + MongoDB + Docker combination\n may not work.`);
@@ -5615,7 +5712,7 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup)
5615
5712
  instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
5616
5713
  } else if (orm === "drizzle") {
5617
5714
  if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
5618
- instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
5715
+ if (dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
5619
5716
  instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
5620
5717
  if (database === "sqlite" && dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`);
5621
5718
  } else if (orm === "mongoose") {
@@ -5638,8 +5735,18 @@ function getNoOrmWarning() {
5638
5735
  function getBunWebNativeWarning() {
5639
5736
  return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
5640
5737
  }
5641
- function getWorkersDeployInstructions(runCmd) {
5642
- return `\n${pc.bold("Deploy frontend to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`;
5738
+ function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy) {
5739
+ const instructions = [];
5740
+ if (webDeploy === "wrangler") instructions.push(`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} run deploy`}`);
5741
+ if (serverDeploy === "wrangler") instructions.push(`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} run deploy`}`);
5742
+ return instructions.length ? `\n${instructions.join("\n")}` : "";
5743
+ }
5744
+ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
5745
+ const instructions = [];
5746
+ if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web to Alchemy:")}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}`);
5747
+ else if (serverDeploy === "alchemy" && webDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy server to Alchemy:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}`);
5748
+ else if (webDeploy === "alchemy" && serverDeploy === "alchemy") instructions.push(`${pc.bold("Deploy to Alchemy:")}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}`);
5749
+ return instructions.length ? `\n${instructions.join("\n")}` : "";
5643
5750
  }
5644
5751
 
5645
5752
  //#endregion
@@ -5852,6 +5959,7 @@ async function createProject(options) {
5852
5959
  if (!isConvex && options.auth) await setupAuth(options);
5853
5960
  await handleExtras(projectDir, options);
5854
5961
  await setupWebDeploy(options);
5962
+ await setupServerDeploy(options);
5855
5963
  await setupEnvironmentVariables(options);
5856
5964
  await updatePackageConfigurations(projectDir, options);
5857
5965
  await createReadme(projectDir, options);
@@ -5891,10 +5999,11 @@ async function createProjectHandler(input) {
5891
5999
  let currentPathInput;
5892
6000
  if (input.yes && input.projectName) currentPathInput = input.projectName;
5893
6001
  else if (input.yes) {
5894
- let defaultName = DEFAULT_CONFIG.relativePath;
6002
+ const defaultConfig = getDefaultConfig();
6003
+ let defaultName = defaultConfig.relativePath;
5895
6004
  let counter = 1;
5896
6005
  while (await fs.pathExists(path.resolve(process.cwd(), defaultName)) && (await fs.readdir(path.resolve(process.cwd(), defaultName))).length > 0) {
5897
- defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
6006
+ defaultName = `${defaultConfig.projectName}-${counter}`;
5898
6007
  counter++;
5899
6008
  }
5900
6009
  currentPathInput = defaultName;
@@ -5953,13 +6062,14 @@ async function createProjectHandler(input) {
5953
6062
  if (input.yes) {
5954
6063
  const flagConfig = processProvidedFlagsWithoutValidation(cliInput, finalBaseName);
5955
6064
  config = {
5956
- ...DEFAULT_CONFIG,
6065
+ ...getDefaultConfig(),
5957
6066
  ...flagConfig,
5958
6067
  projectName: finalBaseName,
5959
6068
  projectDir: finalResolvedPath,
5960
6069
  relativePath: finalPathInput
5961
6070
  };
5962
- validateConfigCompatibility(config);
6071
+ coerceBackendPresets(config);
6072
+ validateConfigCompatibility(config, providedFlags, cliInput);
5963
6073
  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");
5964
6074
  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");
5965
6075
  log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.33.9-canary.88769d6d",
3
+ "version": "2.34.0",
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",
@@ -7,11 +7,13 @@ export default defineConfig({
7
7
  // DOCS: https://orm.drizzle.team/docs/guides/d1-http-with-drizzle-kit
8
8
  dialect: "sqlite",
9
9
  driver: "d1-http",
10
+ {{#if (eq serverDeploy "wrangler")}}
10
11
  dbCredentials: {
11
12
  accountId: process.env.CLOUDFLARE_ACCOUNT_ID!,
12
13
  databaseId: process.env.CLOUDFLARE_DATABASE_ID!,
13
14
  token: process.env.CLOUDFLARE_D1_TOKEN!,
14
15
  },
16
+ {{/if}}
15
17
  {{else}}
16
18
  dialect: "turso",
17
19
  dbCredentials: {
@@ -22,6 +22,9 @@ import { Worker, WranglerJson } from "alchemy/cloudflare";
22
22
  import { D1Database } from "alchemy/cloudflare";
23
23
  {{/if}}
24
24
  {{/if}}
25
+ {{#if (and (eq serverDeploy "alchemy") (eq dbSetup "d1"))}}
26
+ import { Exec } from "alchemy/os";
27
+ {{/if}}
25
28
  import { config } from "dotenv";
26
29
 
27
30
  {{#if (and (eq webDeploy "alchemy") (eq serverDeploy "alchemy"))}}
@@ -35,6 +38,11 @@ config({ path: "./.env" });
35
38
  const app = await alchemy("{{projectName}}");
36
39
 
37
40
  {{#if (and (eq serverDeploy "alchemy") (eq dbSetup "d1"))}}
41
+ await Exec("db-generate", {
42
+ {{#if (and (eq webDeploy "alchemy") (eq serverDeploy "alchemy"))}}cwd: "apps/server",{{/if}}
43
+ command: "{{packageManager}} run db:generate",
44
+ });
45
+
38
46
  const db = await D1Database("database", {
39
47
  name: `${app.name}-${app.stage}-db`,
40
48
  migrationsDir: "apps/server/src/db/migrations",
@@ -0,0 +1,11 @@
1
+ // This is a temporary wrangler.jsonc file that will be overwritten by alchemy
2
+ // It's only here so that `wrangler dev` can work or use alchemy dev instead
3
+ {
4
+ "name": "{{projectName}}",
5
+ "main": "src/index.ts",
6
+ "compatibility_date": "2025-08-16",
7
+ "compatibility_flags": [
8
+ "nodejs_compat",
9
+ "nodejs_compat_populate_process_env"
10
+ ]
11
+ }
@@ -17,7 +17,5 @@
17
17
  "path": "../server"
18
18
  }
19
19
  {{/unless}}
20
- ]{{#if (eq webDeploy "alchemy")}},
21
- "include": ["alchemy.run.ts"]
22
- {{/if}}
20
+ ]
23
21
  }