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 +1 -1
- package/dist/index.js +1 -1
- package/dist/{src-Bm5FOLMd.js → src-CqC0HMY7.js} +231 -121
- package/package.json +1 -1
- package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +2 -0
- package/templates/deploy/alchemy/alchemy.run.ts.hbs +8 -0
- package/templates/deploy/alchemy/wrangler.jsonc.hbs +11 -0
- package/templates/frontend/nuxt/tsconfig.json.hbs +1 -3
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -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
|
|
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/
|
|
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
|
|
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
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
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
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
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
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
if (
|
|
1655
|
-
|
|
1656
|
-
|
|
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
|
-
|
|
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
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
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,
|
|
1739
|
+
validateAddonsAgainstFrontends(config.addons, config.frontend);
|
|
1687
1740
|
config.addons = [...new Set(config.addons)];
|
|
1688
1741
|
}
|
|
1689
|
-
validateExamplesCompatibility(config.examples ?? [],
|
|
1742
|
+
validateExamplesCompatibility(config.examples ?? [], config.backend, config.database, config.frontend ?? []);
|
|
1743
|
+
validateAlchemyCompatibility(config.webDeploy, config.serverDeploy, config.frontend ?? []);
|
|
1690
1744
|
}
|
|
1691
|
-
function
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
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
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
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
|
|
1717
|
-
|
|
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))
|
|
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
|
-
|
|
4691
|
-
|
|
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
|
|
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
|
|
5557
|
-
}
|
|
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 (
|
|
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,
|
|
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 (
|
|
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
|
|
5642
|
-
|
|
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
|
-
|
|
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 = `${
|
|
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
|
-
...
|
|
6065
|
+
...getDefaultConfig(),
|
|
5957
6066
|
...flagConfig,
|
|
5958
6067
|
projectName: finalBaseName,
|
|
5959
6068
|
projectDir: finalResolvedPath,
|
|
5960
6069
|
relativePath: finalPathInput
|
|
5961
6070
|
};
|
|
5962
|
-
|
|
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.
|
|
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
|
+
}
|