create-better-t-stack 2.48.3 → 2.49.1-canary.80158905
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.d.ts +1 -2
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +228 -101
- package/dist/index.js +1 -1
- package/dist/{src-BwIapwZk.js → src-CyG8-I-3.js} +451 -414
- package/package.json +9 -8
- package/templates/api/orpc/server/base/_gitignore +34 -0
- package/templates/api/orpc/server/base/package.json.hbs +24 -0
- package/templates/{backend/server/server-base → api/orpc/server/base}/src/routers/index.ts.hbs +2 -2
- package/templates/api/orpc/server/base/tsconfig.json.hbs +10 -0
- package/templates/api/orpc/server/base/tsdown.config.ts.hbs +7 -0
- package/templates/api/orpc/server/{base/src/lib → rest/src}/context.ts.hbs +5 -5
- package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +1 -1
- package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +1 -1
- package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +1 -1
- package/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs +1 -1
- package/templates/api/trpc/server/base/_gitignore +34 -0
- package/templates/api/trpc/server/base/package.json.hbs +23 -0
- package/templates/api/trpc/server/base/src/routers/index.ts.hbs +55 -0
- package/templates/api/trpc/server/base/tsconfig.json.hbs +13 -0
- package/templates/api/trpc/server/base/tsdown.config.ts.hbs +7 -0
- package/templates/api/trpc/server/{base/src/lib → rest/src}/context.ts.hbs +5 -5
- package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +2 -2
- package/templates/auth/better-auth/server/base/_gitignore +34 -0
- package/templates/auth/better-auth/server/base/package.json.hbs +24 -0
- package/templates/auth/better-auth/server/base/src/{lib/auth.ts.hbs → index.ts.hbs} +6 -6
- package/templates/auth/better-auth/server/base/tsconfig.json.hbs +13 -0
- package/templates/auth/better-auth/server/base/tsdown.config.ts.hbs +7 -0
- package/templates/backend/server/{server-base → base}/package.json.hbs +0 -1
- package/templates/backend/server/{server-base → base}/tsconfig.json.hbs +5 -10
- package/templates/backend/server/base/tsdown.config.ts.hbs +14 -0
- package/templates/backend/server/elysia/src/index.ts.hbs +5 -5
- package/templates/backend/server/express/src/index.ts.hbs +5 -5
- package/templates/backend/server/fastify/src/index.ts.hbs +5 -5
- package/templates/backend/server/hono/src/index.ts.hbs +5 -5
- package/templates/base/_gitignore +47 -1
- package/templates/base/package.json.hbs +1 -3
- package/templates/base/tsconfig.base.json +23 -0
- package/templates/db/base/_gitignore +34 -0
- package/templates/db/base/package.json.hbs +23 -0
- package/templates/db/base/tsconfig.json.hbs +13 -0
- package/templates/db/base/tsdown.config.ts.hbs +7 -0
- package/templates/db/drizzle/mysql/drizzle.config.ts.hbs +7 -2
- package/templates/db/drizzle/postgres/drizzle.config.ts.hbs +7 -2
- package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +7 -2
- package/templates/db/prisma/mongodb/prisma.config.ts.hbs +5 -1
- package/templates/db/prisma/mongodb/src/index.ts.hbs +5 -0
- package/templates/db/prisma/mysql/prisma.config.ts.hbs +5 -1
- package/templates/db/prisma/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
- package/templates/db/prisma/postgres/prisma.config.ts.hbs +7 -3
- package/templates/db/prisma/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
- package/templates/db/prisma/sqlite/prisma.config.ts.hbs +5 -1
- package/templates/db/prisma/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +3 -3
- package/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs +6 -6
- package/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +4 -4
- package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +4 -4
- package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +1 -1
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +1 -1
- package/templates/db/prisma/mongodb/src/db/index.ts.hbs +0 -5
- /package/templates/api/orpc/server/{base/src/lib/orpc.ts.hbs → rest/src/index.ts.hbs} +0 -0
- /package/templates/api/trpc/server/{base/src/lib/trpc.ts.hbs → rest/src/index.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/mysql/src/{db/schema → schema}/auth.ts +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/postgres/src/{db/schema → schema}/auth.ts +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/{db/schema → schema}/auth.ts +0 -0
- /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/{db/models → models}/auth.model.ts +0 -0
- /package/templates/backend/server/{server-base → base}/_gitignore +0 -0
- /package/templates/db/drizzle/mysql/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
- /package/templates/db/drizzle/postgres/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
- /package/templates/db/drizzle/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
- /package/templates/db/mongoose/mongodb/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
- /package/templates/examples/todo/server/mongoose/mongodb/src/db/models/{todo.model.ts → todo.model.ts.hbs} +0 -0
- /package/templates/examples/todo/server/prisma/mongodb/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
- /package/templates/examples/todo/server/prisma/mysql/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
- /package/templates/examples/todo/server/prisma/postgres/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
- /package/templates/examples/todo/server/prisma/sqlite/prisma/schema/{todo.prisma → todo.prisma.hbs} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { autocompleteMultiselect, cancel, confirm, group, groupMultiselect, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
|
|
3
|
+
import { createRouterClient, os } from "@orpc/server";
|
|
3
4
|
import pc from "picocolors";
|
|
4
|
-
import { createCli
|
|
5
|
-
import z from "zod";
|
|
5
|
+
import { createCli } from "trpc-cli";
|
|
6
|
+
import z$1, { z } from "zod";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import consola, { consola as consola$1 } from "consola";
|
|
8
9
|
import fs from "fs-extra";
|
|
@@ -14,7 +15,7 @@ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph"
|
|
|
14
15
|
import { glob } from "tinyglobby";
|
|
15
16
|
import handlebars from "handlebars";
|
|
16
17
|
import { Biome } from "@biomejs/js-api/nodejs";
|
|
17
|
-
import os from "node:os";
|
|
18
|
+
import os$1 from "node:os";
|
|
18
19
|
|
|
19
20
|
//#region src/utils/get-package-manager.ts
|
|
20
21
|
const getUserPkgManager = () => {
|
|
@@ -70,6 +71,7 @@ const dependencyVersionMap = {
|
|
|
70
71
|
"drizzle-kit": "^0.31.2",
|
|
71
72
|
"@planetscale/database": "^1.19.0",
|
|
72
73
|
"@libsql/client": "^0.15.9",
|
|
74
|
+
libsql: "^0.5.22",
|
|
73
75
|
"@neondatabase/serverless": "^1.0.1",
|
|
74
76
|
pg: "^8.14.1",
|
|
75
77
|
"@types/pg": "^8.11.11",
|
|
@@ -145,7 +147,9 @@ const dependencyVersionMap = {
|
|
|
145
147
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
146
148
|
alchemy: "^0.67.0",
|
|
147
149
|
nitropack: "^2.12.4",
|
|
148
|
-
dotenv: "^17.2.
|
|
150
|
+
dotenv: "^17.2.2",
|
|
151
|
+
tsdown: "^0.15.5",
|
|
152
|
+
zod: "^4.1.11",
|
|
149
153
|
"@polar-sh/better-auth": "^1.1.3",
|
|
150
154
|
"@polar-sh/sdk": "^0.34.16"
|
|
151
155
|
};
|
|
@@ -263,7 +267,7 @@ const AuthSchema = z.enum([
|
|
|
263
267
|
]).describe("Authentication provider");
|
|
264
268
|
const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
|
|
265
269
|
const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(255, "Project name must be less than 255 characters").refine((name) => name === "." || !name.startsWith("."), "Project name cannot start with a dot (except for '.')").refine((name) => name === "." || !name.startsWith("-"), "Project name cannot start with a dash").refine((name) => {
|
|
266
|
-
|
|
270
|
+
return ![
|
|
267
271
|
"<",
|
|
268
272
|
">",
|
|
269
273
|
":",
|
|
@@ -271,8 +275,7 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
|
|
|
271
275
|
"|",
|
|
272
276
|
"?",
|
|
273
277
|
"*"
|
|
274
|
-
];
|
|
275
|
-
return !invalidChars.some((char) => name.includes(char));
|
|
278
|
+
].some((char) => name.includes(char));
|
|
276
279
|
}, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
|
|
277
280
|
const WebDeploySchema = z.enum([
|
|
278
281
|
"wrangler",
|
|
@@ -360,12 +363,11 @@ function validateApiFrontendCompatibility(api, frontends = []) {
|
|
|
360
363
|
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
361
364
|
if (backend === "convex" && frontend === "solid") return false;
|
|
362
365
|
if (auth === "clerk" && backend === "convex") {
|
|
363
|
-
|
|
366
|
+
if ([
|
|
364
367
|
"nuxt",
|
|
365
368
|
"svelte",
|
|
366
369
|
"solid"
|
|
367
|
-
];
|
|
368
|
-
if (incompatibleFrontends.includes(frontend)) return false;
|
|
370
|
+
].includes(frontend)) return false;
|
|
369
371
|
}
|
|
370
372
|
return true;
|
|
371
373
|
}
|
|
@@ -385,8 +387,7 @@ function isExampleTodoAllowed(backend, database) {
|
|
|
385
387
|
return !(backend !== "convex" && backend !== "none" && database === "none");
|
|
386
388
|
}
|
|
387
389
|
function isExampleAIAllowed(_backend, frontends = []) {
|
|
388
|
-
|
|
389
|
-
if (includesSolid) return false;
|
|
390
|
+
if (frontends.includes("solid")) return false;
|
|
390
391
|
return true;
|
|
391
392
|
}
|
|
392
393
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
@@ -398,8 +399,7 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
|
398
399
|
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
399
400
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
400
401
|
if (compatibleFrontends.length > 0) {
|
|
401
|
-
|
|
402
|
-
if (!hasCompatibleFrontend) {
|
|
402
|
+
if (!frontend.some((f) => compatibleFrontends.includes(f))) {
|
|
403
403
|
const frontendList = compatibleFrontends.join(", ");
|
|
404
404
|
return {
|
|
405
405
|
isCompatible: false,
|
|
@@ -912,7 +912,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
912
912
|
if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
|
|
913
913
|
const result = [];
|
|
914
914
|
if (frontendTypes.includes("web")) {
|
|
915
|
-
const
|
|
915
|
+
const webOptions = [
|
|
916
916
|
{
|
|
917
917
|
value: "tanstack-router",
|
|
918
918
|
label: "TanStack Router",
|
|
@@ -948,8 +948,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
948
948
|
label: "TanStack Start",
|
|
949
949
|
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
950
950
|
}
|
|
951
|
-
];
|
|
952
|
-
const webOptions = allWebOptions.filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
951
|
+
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
953
952
|
const webFramework = await select({
|
|
954
953
|
message: "Choose web",
|
|
955
954
|
options: webOptions,
|
|
@@ -1069,20 +1068,18 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
1069
1068
|
//#region src/prompts/payments.ts
|
|
1070
1069
|
async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
1071
1070
|
if (payments !== void 0) return payments;
|
|
1072
|
-
|
|
1073
|
-
if (!isPolarCompatible) return "none";
|
|
1074
|
-
const options = [{
|
|
1075
|
-
value: "polar",
|
|
1076
|
-
label: "Polar",
|
|
1077
|
-
hint: "Turn your software into a business. 6 lines of code."
|
|
1078
|
-
}, {
|
|
1079
|
-
value: "none",
|
|
1080
|
-
label: "None",
|
|
1081
|
-
hint: "No payments integration"
|
|
1082
|
-
}];
|
|
1071
|
+
if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
|
|
1083
1072
|
const response = await select({
|
|
1084
1073
|
message: "Select payments provider",
|
|
1085
|
-
options
|
|
1074
|
+
options: [{
|
|
1075
|
+
value: "polar",
|
|
1076
|
+
label: "Polar",
|
|
1077
|
+
hint: "Turn your software into a business. 6 lines of code."
|
|
1078
|
+
}, {
|
|
1079
|
+
value: "none",
|
|
1080
|
+
label: "None",
|
|
1081
|
+
hint: "No payments integration"
|
|
1082
|
+
}],
|
|
1086
1083
|
initialValue: DEFAULT_CONFIG.payments
|
|
1087
1084
|
});
|
|
1088
1085
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
@@ -1211,12 +1208,11 @@ function getDeploymentDisplay(deployment) {
|
|
|
1211
1208
|
async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
|
|
1212
1209
|
if (deployment !== void 0) return deployment;
|
|
1213
1210
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1214
|
-
const
|
|
1211
|
+
const options = [
|
|
1215
1212
|
"wrangler",
|
|
1216
1213
|
"alchemy",
|
|
1217
1214
|
"none"
|
|
1218
|
-
]
|
|
1219
|
-
const options = availableDeployments.map((deploy) => {
|
|
1215
|
+
].map((deploy) => {
|
|
1220
1216
|
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1221
1217
|
return {
|
|
1222
1218
|
value: deploy,
|
|
@@ -1322,14 +1318,12 @@ function validateDirectoryName(name) {
|
|
|
1322
1318
|
if (name === ".") return void 0;
|
|
1323
1319
|
const result = ProjectNameSchema.safeParse(name);
|
|
1324
1320
|
if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
|
|
1325
|
-
return void 0;
|
|
1326
1321
|
}
|
|
1327
1322
|
async function getProjectName(initialName) {
|
|
1328
1323
|
if (initialName) {
|
|
1329
1324
|
if (initialName === ".") return initialName;
|
|
1330
1325
|
const finalDirName = path.basename(initialName);
|
|
1331
|
-
|
|
1332
|
-
if (!validationError) {
|
|
1326
|
+
if (!validateDirectoryName(finalDirName)) {
|
|
1333
1327
|
const projectDir = path.resolve(process.cwd(), initialName);
|
|
1334
1328
|
if (isPathWithinCwd(projectDir)) return initialName;
|
|
1335
1329
|
consola.error(pc.red("Project path must be within current directory"));
|
|
@@ -1358,7 +1352,6 @@ async function getProjectName(initialName) {
|
|
|
1358
1352
|
const projectDir = path.resolve(process.cwd(), nameToUse);
|
|
1359
1353
|
if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
|
|
1360
1354
|
}
|
|
1361
|
-
return void 0;
|
|
1362
1355
|
}
|
|
1363
1356
|
});
|
|
1364
1357
|
if (isCancel(response)) return exitCancelled("Operation cancelled.");
|
|
@@ -1372,8 +1365,7 @@ async function getProjectName(initialName) {
|
|
|
1372
1365
|
//#region src/utils/get-latest-cli-version.ts
|
|
1373
1366
|
const getLatestCLIVersion = () => {
|
|
1374
1367
|
const packageJsonPath = path.join(PKG_ROOT, "package.json");
|
|
1375
|
-
|
|
1376
|
-
return packageJsonContent.version ?? "1.0.0";
|
|
1368
|
+
return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
|
|
1377
1369
|
};
|
|
1378
1370
|
|
|
1379
1371
|
//#endregion
|
|
@@ -1386,7 +1378,7 @@ const getLatestCLIVersion = () => {
|
|
|
1386
1378
|
*/
|
|
1387
1379
|
function isTelemetryEnabled() {
|
|
1388
1380
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
1389
|
-
const BTS_TELEMETRY = "
|
|
1381
|
+
const BTS_TELEMETRY = "0";
|
|
1390
1382
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
1391
1383
|
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
1392
1384
|
return true;
|
|
@@ -1394,12 +1386,11 @@ function isTelemetryEnabled() {
|
|
|
1394
1386
|
|
|
1395
1387
|
//#endregion
|
|
1396
1388
|
//#region src/utils/analytics.ts
|
|
1397
|
-
const POSTHOG_API_KEY = "
|
|
1398
|
-
const POSTHOG_HOST = "
|
|
1389
|
+
const POSTHOG_API_KEY = "random";
|
|
1390
|
+
const POSTHOG_HOST = "random";
|
|
1399
1391
|
function generateSessionId() {
|
|
1400
1392
|
const rand = Math.random().toString(36).slice(2);
|
|
1401
|
-
|
|
1402
|
-
return `cli_${now}${rand}`;
|
|
1393
|
+
return `cli_${Date.now().toString(36)}${rand}`;
|
|
1403
1394
|
}
|
|
1404
1395
|
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
1405
1396
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
@@ -1506,9 +1497,7 @@ function generateReproducibleCommand(config) {
|
|
|
1506
1497
|
async function handleDirectoryConflict(currentPathInput, silent = false) {
|
|
1507
1498
|
while (true) {
|
|
1508
1499
|
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
|
|
1509
|
-
|
|
1510
|
-
const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
|
|
1511
|
-
if (!dirIsNotEmpty) return {
|
|
1500
|
+
if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
|
|
1512
1501
|
finalPathInput: currentPathInput,
|
|
1513
1502
|
shouldClearDirectory: false
|
|
1514
1503
|
};
|
|
@@ -1622,14 +1611,12 @@ const renderTitle = () => {
|
|
|
1622
1611
|
const terminalWidth = process.stdout.columns || 80;
|
|
1623
1612
|
const titleLines = TITLE_TEXT.split("\n");
|
|
1624
1613
|
const titleWidth = Math.max(...titleLines.map((line) => line.length));
|
|
1625
|
-
if (terminalWidth < titleWidth)
|
|
1626
|
-
const simplifiedTitle = `
|
|
1614
|
+
if (terminalWidth < titleWidth) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
|
|
1627
1615
|
╔══════════════════╗
|
|
1628
1616
|
║ Better T Stack ║
|
|
1629
1617
|
╚══════════════════╝
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
} else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1618
|
+
`));
|
|
1619
|
+
else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1633
1620
|
};
|
|
1634
1621
|
|
|
1635
1622
|
//#endregion
|
|
@@ -1755,8 +1742,7 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
1755
1742
|
"tanstack-start",
|
|
1756
1743
|
"next"
|
|
1757
1744
|
];
|
|
1758
|
-
|
|
1759
|
-
if (!hasSupportedFrontend) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
|
|
1745
|
+
if (!config.frontend?.some((f) => supportedFrontends.includes(f))) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
|
|
1760
1746
|
}
|
|
1761
1747
|
}
|
|
1762
1748
|
function validateBackendNoneConstraints(config, providedFlags) {
|
|
@@ -1987,8 +1973,7 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1987
1973
|
try {
|
|
1988
1974
|
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
|
1989
1975
|
if (!await fs.pathExists(configPath)) return;
|
|
1990
|
-
|
|
1991
|
-
let modifiedContent = configContent;
|
|
1976
|
+
let modifiedContent = await fs.readFile(configPath, "utf-8");
|
|
1992
1977
|
for (const [key, value] of Object.entries(updates)) {
|
|
1993
1978
|
const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
|
|
1994
1979
|
tabSize: 2,
|
|
@@ -2010,15 +1995,17 @@ const addPackageDependency = async (opts) => {
|
|
|
2010
1995
|
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
2011
1996
|
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
2012
1997
|
for (const pkgName of dependencies) {
|
|
2013
|
-
const version =
|
|
1998
|
+
const version = dependencyVersionMap[pkgName];
|
|
2014
1999
|
if (version) pkgJson.dependencies[pkgName] = version;
|
|
2015
2000
|
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
2016
2001
|
}
|
|
2017
2002
|
for (const pkgName of devDependencies) {
|
|
2018
|
-
const version =
|
|
2003
|
+
const version = dependencyVersionMap[pkgName];
|
|
2019
2004
|
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
2020
2005
|
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
2021
2006
|
}
|
|
2007
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2008
|
+
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2022
2009
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2023
2010
|
};
|
|
2024
2011
|
|
|
@@ -2078,8 +2065,7 @@ async function setupFumadocs(config) {
|
|
|
2078
2065
|
initialValue: "next-mdx"
|
|
2079
2066
|
});
|
|
2080
2067
|
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2081
|
-
const
|
|
2082
|
-
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${templateArg} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
|
|
2068
|
+
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
|
|
2083
2069
|
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2084
2070
|
await execa(fumadocsInitCommand, {
|
|
2085
2071
|
cwd: path.join(projectDir, "apps"),
|
|
@@ -2112,27 +2098,26 @@ async function setupRuler(config) {
|
|
|
2112
2098
|
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
2113
2099
|
return;
|
|
2114
2100
|
}
|
|
2115
|
-
const EDITORS$1 = {
|
|
2116
|
-
amp: { label: "AMP" },
|
|
2117
|
-
copilot: { label: "GitHub Copilot" },
|
|
2118
|
-
claude: { label: "Claude Code" },
|
|
2119
|
-
codex: { label: "OpenAI Codex CLI" },
|
|
2120
|
-
cursor: { label: "Cursor" },
|
|
2121
|
-
windsurf: { label: "Windsurf" },
|
|
2122
|
-
cline: { label: "Cline" },
|
|
2123
|
-
aider: { label: "Aider" },
|
|
2124
|
-
firebase: { label: "Firebase Studio" },
|
|
2125
|
-
"gemini-cli": { label: "Gemini CLI" },
|
|
2126
|
-
junie: { label: "Junie" },
|
|
2127
|
-
kilocode: { label: "Kilo Code" },
|
|
2128
|
-
opencode: { label: "OpenCode" },
|
|
2129
|
-
crush: { label: "Crush" },
|
|
2130
|
-
zed: { label: "Zed" },
|
|
2131
|
-
qwen: { label: "Qwen" }
|
|
2132
|
-
};
|
|
2133
2101
|
const selectedEditors = await autocompleteMultiselect({
|
|
2134
2102
|
message: "Select AI assistants for Ruler",
|
|
2135
|
-
options: Object.entries(
|
|
2103
|
+
options: Object.entries({
|
|
2104
|
+
amp: { label: "AMP" },
|
|
2105
|
+
copilot: { label: "GitHub Copilot" },
|
|
2106
|
+
claude: { label: "Claude Code" },
|
|
2107
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
2108
|
+
cursor: { label: "Cursor" },
|
|
2109
|
+
windsurf: { label: "Windsurf" },
|
|
2110
|
+
cline: { label: "Cline" },
|
|
2111
|
+
aider: { label: "Aider" },
|
|
2112
|
+
firebase: { label: "Firebase Studio" },
|
|
2113
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
2114
|
+
junie: { label: "Junie" },
|
|
2115
|
+
kilocode: { label: "Kilo Code" },
|
|
2116
|
+
opencode: { label: "OpenCode" },
|
|
2117
|
+
crush: { label: "Crush" },
|
|
2118
|
+
zed: { label: "Zed" },
|
|
2119
|
+
qwen: { label: "Qwen" }
|
|
2120
|
+
}).map(([key, v]) => ({
|
|
2136
2121
|
value: key,
|
|
2137
2122
|
label: v.label
|
|
2138
2123
|
})),
|
|
@@ -2145,8 +2130,7 @@ async function setupRuler(config) {
|
|
|
2145
2130
|
return;
|
|
2146
2131
|
}
|
|
2147
2132
|
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2148
|
-
|
|
2149
|
-
let updatedConfig = currentConfig;
|
|
2133
|
+
let updatedConfig = await fs.readFile(configFile, "utf-8");
|
|
2150
2134
|
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2151
2135
|
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2152
2136
|
await fs.writeFile(configFile, updatedConfig);
|
|
@@ -2189,7 +2173,7 @@ async function setupStarlight(config) {
|
|
|
2189
2173
|
const s = spinner();
|
|
2190
2174
|
try {
|
|
2191
2175
|
s.start("Setting up Starlight docs...");
|
|
2192
|
-
const
|
|
2176
|
+
const commandWithArgs = `create-astro@latest ${[
|
|
2193
2177
|
"docs",
|
|
2194
2178
|
"--template",
|
|
2195
2179
|
"starlight",
|
|
@@ -2198,9 +2182,7 @@ async function setupStarlight(config) {
|
|
|
2198
2182
|
"tailwind",
|
|
2199
2183
|
"--no-git",
|
|
2200
2184
|
"--skip-houston"
|
|
2201
|
-
]
|
|
2202
|
-
const starlightArgsString = starlightArgs.join(" ");
|
|
2203
|
-
const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
|
|
2185
|
+
].join(" ")}`;
|
|
2204
2186
|
const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2205
2187
|
await execa(starlightInitCommand, {
|
|
2206
2188
|
cwd: path.join(projectDir, "apps"),
|
|
@@ -2246,7 +2228,7 @@ async function setupTauri(config) {
|
|
|
2246
2228
|
const hasNext = frontend.includes("next");
|
|
2247
2229
|
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
|
|
2248
2230
|
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
|
|
2249
|
-
const
|
|
2231
|
+
const commandWithArgs = `@tauri-apps/cli@latest ${[
|
|
2250
2232
|
"init",
|
|
2251
2233
|
`--app-name=${path.basename(projectDir)}`,
|
|
2252
2234
|
`--window-title=${path.basename(projectDir)}`,
|
|
@@ -2254,9 +2236,7 @@ async function setupTauri(config) {
|
|
|
2254
2236
|
`--dev-url=${devUrl}`,
|
|
2255
2237
|
`--before-dev-command="${packageManager} run dev"`,
|
|
2256
2238
|
`--before-build-command="${packageManager} run build"`
|
|
2257
|
-
]
|
|
2258
|
-
const tauriArgsString = tauriArgs.join(" ");
|
|
2259
|
-
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
|
|
2239
|
+
].join(" ")}`;
|
|
2260
2240
|
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2261
2241
|
await execa(tauriInitCommand, {
|
|
2262
2242
|
cwd: clientPackageDir,
|
|
@@ -2330,8 +2310,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2330
2310
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2331
2311
|
if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
|
|
2332
2312
|
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2333
|
-
const
|
|
2334
|
-
const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
|
|
2313
|
+
const commandWithArgs = `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`;
|
|
2335
2314
|
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2336
2315
|
await execa(ultraciteInitCommand, {
|
|
2337
2316
|
cwd: projectDir,
|
|
@@ -2371,8 +2350,7 @@ function ensureArrayProperty(obj, name) {
|
|
|
2371
2350
|
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2372
2351
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2373
2352
|
if (!sourceFile) throw new Error("vite config not found");
|
|
2374
|
-
|
|
2375
|
-
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2353
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
|
|
2376
2354
|
namedImports: ["VitePWA"],
|
|
2377
2355
|
moduleSpecifier: "vite-plugin-pwa"
|
|
2378
2356
|
});
|
|
@@ -2381,12 +2359,10 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
|
2381
2359
|
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2382
2360
|
});
|
|
2383
2361
|
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2384
|
-
const
|
|
2385
|
-
const configObject = callExpr.getArguments()[0];
|
|
2362
|
+
const configObject = defineCall.getArguments()[0];
|
|
2386
2363
|
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2387
2364
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2388
|
-
|
|
2389
|
-
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2365
|
+
if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
|
|
2390
2366
|
registerType: "autoUpdate",
|
|
2391
2367
|
manifest: {
|
|
2392
2368
|
name: "${projectName}",
|
|
@@ -2487,12 +2463,11 @@ async function setupHusky(projectDir, linter) {
|
|
|
2487
2463
|
}
|
|
2488
2464
|
}
|
|
2489
2465
|
async function setupPwa(projectDir, frontends) {
|
|
2490
|
-
|
|
2466
|
+
if (!frontends.some((f) => [
|
|
2491
2467
|
"react-router",
|
|
2492
2468
|
"tanstack-router",
|
|
2493
2469
|
"solid"
|
|
2494
|
-
].includes(f));
|
|
2495
|
-
if (!isCompatibleFrontend) return;
|
|
2470
|
+
].includes(f))) return;
|
|
2496
2471
|
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
2497
2472
|
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2498
2473
|
await addPackageDependency({
|
|
@@ -2592,8 +2567,7 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
2592
2567
|
function initializeBiome() {
|
|
2593
2568
|
try {
|
|
2594
2569
|
const biome = new Biome();
|
|
2595
|
-
const
|
|
2596
|
-
const projectKey = result.projectKey;
|
|
2570
|
+
const projectKey = biome.openProject("./").projectKey;
|
|
2597
2571
|
biome.applyConfiguration(projectKey, {
|
|
2598
2572
|
formatter: {
|
|
2599
2573
|
enabled: true,
|
|
@@ -2615,27 +2589,25 @@ function initializeBiome() {
|
|
|
2615
2589
|
}
|
|
2616
2590
|
function isSupportedFile(filePath) {
|
|
2617
2591
|
const ext = path.extname(filePath).toLowerCase();
|
|
2618
|
-
|
|
2592
|
+
return [
|
|
2619
2593
|
".js",
|
|
2620
2594
|
".jsx",
|
|
2621
2595
|
".ts",
|
|
2622
2596
|
".tsx",
|
|
2623
2597
|
".json",
|
|
2624
2598
|
".jsonc"
|
|
2625
|
-
];
|
|
2626
|
-
return supportedExtensions.includes(ext);
|
|
2599
|
+
].includes(ext);
|
|
2627
2600
|
}
|
|
2628
2601
|
function shouldSkipFile(filePath) {
|
|
2629
2602
|
const basename = path.basename(filePath);
|
|
2630
|
-
|
|
2603
|
+
return [
|
|
2631
2604
|
".hbs",
|
|
2632
2605
|
"package-lock.json",
|
|
2633
2606
|
"yarn.lock",
|
|
2634
2607
|
"pnpm-lock.yaml",
|
|
2635
2608
|
"bun.lock",
|
|
2636
2609
|
".d.ts"
|
|
2637
|
-
];
|
|
2638
|
-
return skipPatterns.some((pattern) => basename.includes(pattern));
|
|
2610
|
+
].some((pattern) => basename.includes(pattern));
|
|
2639
2611
|
}
|
|
2640
2612
|
function formatFileWithBiome(filePath, content) {
|
|
2641
2613
|
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
|
|
@@ -2672,8 +2644,7 @@ async function processTemplate(srcPath, destPath, context) {
|
|
|
2672
2644
|
let content;
|
|
2673
2645
|
if (srcPath.endsWith(".hbs")) {
|
|
2674
2646
|
const templateContent = await fs.readFile(srcPath, "utf-8");
|
|
2675
|
-
|
|
2676
|
-
content = template(context);
|
|
2647
|
+
content = handlebars.compile(templateContent)(context);
|
|
2677
2648
|
} else content = await fs.readFile(srcPath, "utf-8");
|
|
2678
2649
|
try {
|
|
2679
2650
|
const formattedContent = await formatFileWithBiome(destPath, content);
|
|
@@ -2804,23 +2775,28 @@ async function setupBackendFramework(projectDir, context) {
|
|
|
2804
2775
|
return;
|
|
2805
2776
|
}
|
|
2806
2777
|
await fs.ensureDir(serverAppDir);
|
|
2807
|
-
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/
|
|
2778
|
+
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
|
|
2808
2779
|
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2809
2780
|
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2810
2781
|
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2811
2782
|
if (context.api !== "none") {
|
|
2783
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
2784
|
+
await fs.ensureDir(apiPackageDir);
|
|
2812
2785
|
const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
|
|
2813
|
-
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir,
|
|
2814
|
-
|
|
2815
|
-
if (
|
|
2786
|
+
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, apiPackageDir, context);
|
|
2787
|
+
let apiServerFrameworkDir = "";
|
|
2788
|
+
if (context.backend === "next") apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
|
|
2789
|
+
else apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/rest`);
|
|
2790
|
+
if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, apiPackageDir, context, true);
|
|
2791
|
+
}
|
|
2792
|
+
if (context.database !== "none" && context.orm !== "none") {
|
|
2793
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2794
|
+
await fs.ensureDir(dbPackageDir);
|
|
2795
|
+
const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
|
|
2796
|
+
if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
|
|
2797
|
+
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2798
|
+
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
|
|
2816
2799
|
}
|
|
2817
|
-
}
|
|
2818
|
-
async function setupDbOrmTemplates(projectDir, context) {
|
|
2819
|
-
if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
|
|
2820
|
-
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2821
|
-
await fs.ensureDir(serverAppDir);
|
|
2822
|
-
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2823
|
-
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
|
|
2824
2800
|
}
|
|
2825
2801
|
async function setupAuthTemplate(projectDir, context) {
|
|
2826
2802
|
if (!context.auth || context.auth === "none") return;
|
|
@@ -2901,20 +2877,24 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2901
2877
|
return;
|
|
2902
2878
|
}
|
|
2903
2879
|
if (serverAppDirExists && context.backend !== "convex") {
|
|
2880
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2881
|
+
await fs.ensureDir(authPackageDir);
|
|
2904
2882
|
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2905
|
-
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc,
|
|
2883
|
+
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
|
|
2906
2884
|
if (context.backend === "next") {
|
|
2907
2885
|
const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
|
|
2908
|
-
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc,
|
|
2886
|
+
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, authPackageDir, context);
|
|
2909
2887
|
}
|
|
2910
2888
|
if (context.orm !== "none" && context.database !== "none") {
|
|
2889
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2890
|
+
await fs.ensureDir(dbPackageDir);
|
|
2911
2891
|
const orm = context.orm;
|
|
2912
2892
|
const db = context.database;
|
|
2913
2893
|
let authDbSrc = "";
|
|
2914
2894
|
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
|
|
2915
2895
|
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
|
|
2916
2896
|
else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
|
|
2917
|
-
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc,
|
|
2897
|
+
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
|
|
2918
2898
|
}
|
|
2919
2899
|
}
|
|
2920
2900
|
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
@@ -2961,8 +2941,10 @@ async function setupPaymentsTemplate(projectDir, context) {
|
|
|
2961
2941
|
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2962
2942
|
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2963
2943
|
if (serverAppDirExists && context.backend !== "convex") {
|
|
2944
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2945
|
+
await fs.ensureDir(authPackageDir);
|
|
2964
2946
|
const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
|
|
2965
|
-
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc,
|
|
2947
|
+
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
|
|
2966
2948
|
}
|
|
2967
2949
|
const hasReactWeb = context.frontend.some((f) => [
|
|
2968
2950
|
"tanstack-router",
|
|
@@ -3040,15 +3022,21 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3040
3022
|
const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
|
|
3041
3023
|
if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
|
|
3042
3024
|
const exampleServerSrc = path.join(exampleBaseDir, "server");
|
|
3043
|
-
if (
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3025
|
+
if (context.api !== "none") {
|
|
3026
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
3027
|
+
await fs.ensureDir(apiPackageDir);
|
|
3028
|
+
const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
|
|
3029
|
+
if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
|
|
3046
3030
|
}
|
|
3047
3031
|
if (context.orm !== "none" && context.database !== "none") {
|
|
3048
|
-
const
|
|
3049
|
-
|
|
3032
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
3033
|
+
await fs.ensureDir(dbPackageDir);
|
|
3050
3034
|
const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
|
|
3051
|
-
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc,
|
|
3035
|
+
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
|
|
3036
|
+
}
|
|
3037
|
+
if (example === "ai" && context.backend === "next") {
|
|
3038
|
+
const aiNextServerSrc = path.join(exampleServerSrc, "next");
|
|
3039
|
+
if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
|
|
3052
3040
|
}
|
|
3053
3041
|
}
|
|
3054
3042
|
if (webAppDirExists) {
|
|
@@ -3115,9 +3103,9 @@ async function handleExtras(projectDir, context) {
|
|
|
3115
3103
|
}
|
|
3116
3104
|
async function setupDockerComposeTemplates(projectDir, context) {
|
|
3117
3105
|
if (context.dbSetup !== "docker" || context.database === "none") return;
|
|
3118
|
-
const
|
|
3106
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
3119
3107
|
const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
|
|
3120
|
-
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir,
|
|
3108
|
+
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
|
|
3121
3109
|
}
|
|
3122
3110
|
async function setupDeploymentTemplates(projectDir, context) {
|
|
3123
3111
|
if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
|
|
@@ -3175,8 +3163,7 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3175
3163
|
async function addAddonsToProject(input) {
|
|
3176
3164
|
try {
|
|
3177
3165
|
const projectDir = input.projectDir || process.cwd();
|
|
3178
|
-
|
|
3179
|
-
if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
|
|
3166
|
+
if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
|
|
3180
3167
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
3181
3168
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
3182
3169
|
const config = {
|
|
@@ -3323,15 +3310,13 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3323
3310
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3324
3311
|
}
|
|
3325
3312
|
const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
|
|
3326
|
-
|
|
3313
|
+
await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
3327
3314
|
|
|
3328
3315
|
export default defineCloudflareConfig({});
|
|
3329
|
-
|
|
3330
|
-
await fs.writeFile(openNextConfigPath, openNextConfigContent);
|
|
3316
|
+
`);
|
|
3331
3317
|
const gitignorePath = path.join(webAppDir, ".gitignore");
|
|
3332
3318
|
if (await fs.pathExists(gitignorePath)) {
|
|
3333
|
-
|
|
3334
|
-
if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
3319
|
+
if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
3335
3320
|
} else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
|
|
3336
3321
|
}
|
|
3337
3322
|
|
|
@@ -3367,8 +3352,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3367
3352
|
quoteKind: QuoteKind.Double
|
|
3368
3353
|
} });
|
|
3369
3354
|
project.addSourceFileAtPath(nuxtConfigPath);
|
|
3370
|
-
const
|
|
3371
|
-
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3355
|
+
const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
|
|
3372
3356
|
if (!exportAssignment) return;
|
|
3373
3357
|
const defineConfigCall = exportAssignment.getExpression();
|
|
3374
3358
|
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
|
|
@@ -3389,8 +3373,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3389
3373
|
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
|
3390
3374
|
const initializer = modulesProperty.getInitializer();
|
|
3391
3375
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3392
|
-
|
|
3393
|
-
if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
3376
|
+
if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
3394
3377
|
}
|
|
3395
3378
|
} else if (!modulesProperty) configObject.addPropertyAssignment({
|
|
3396
3379
|
name: "modules",
|
|
@@ -3477,8 +3460,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3477
3460
|
} });
|
|
3478
3461
|
project.addSourceFileAtPath(svelteConfigPath);
|
|
3479
3462
|
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
|
3480
|
-
const
|
|
3481
|
-
const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3463
|
+
const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3482
3464
|
if (adapterImport) {
|
|
3483
3465
|
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
|
3484
3466
|
adapterImport.removeDefaultImport();
|
|
@@ -3604,8 +3586,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3604
3586
|
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
3605
3587
|
const initializer = pluginsProperty.getInitializer();
|
|
3606
3588
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3607
|
-
|
|
3608
|
-
if (!hasShim) initializer.addElement("alchemy()");
|
|
3589
|
+
if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
|
|
3609
3590
|
const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
|
|
3610
3591
|
let needsReactPlugin = false;
|
|
3611
3592
|
tanstackElements.forEach((element) => {
|
|
@@ -3623,8 +3604,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3623
3604
|
name: "target",
|
|
3624
3605
|
initializer: "\"cloudflare-module\""
|
|
3625
3606
|
});
|
|
3626
|
-
|
|
3627
|
-
if (!hasCustomViteReactPlugin) configObj.addPropertyAssignment({
|
|
3607
|
+
if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
|
|
3628
3608
|
name: "customViteReactPlugin",
|
|
3629
3609
|
initializer: "true"
|
|
3630
3610
|
});
|
|
@@ -3645,7 +3625,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3645
3625
|
console.warn("Failed to update vite.config.ts:", error);
|
|
3646
3626
|
}
|
|
3647
3627
|
const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
|
|
3648
|
-
|
|
3628
|
+
await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
|
|
3649
3629
|
|
|
3650
3630
|
export default defineNitroConfig({
|
|
3651
3631
|
preset: "cloudflare-module",
|
|
@@ -3653,8 +3633,7 @@ export default defineNitroConfig({
|
|
|
3653
3633
|
nodeCompat: true,
|
|
3654
3634
|
},
|
|
3655
3635
|
});
|
|
3656
|
-
|
|
3657
|
-
await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
|
|
3636
|
+
`, "utf-8");
|
|
3658
3637
|
}
|
|
3659
3638
|
|
|
3660
3639
|
//#endregion
|
|
@@ -3772,8 +3751,7 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
|
|
|
3772
3751
|
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
|
3773
3752
|
const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
3774
3753
|
if (arrayExpr) {
|
|
3775
|
-
|
|
3776
|
-
if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3754
|
+
if (!arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev")) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3777
3755
|
}
|
|
3778
3756
|
} else configObj.addPropertyAssignment({
|
|
3779
3757
|
name: "modules",
|
|
@@ -3808,13 +3786,10 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
|
|
|
3808
3786
|
if (!sourceFile) return;
|
|
3809
3787
|
const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
|
|
3810
3788
|
if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
|
3811
|
-
else {
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3789
|
+
else if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare")) sourceFile.insertImportDeclaration(0, {
|
|
3790
|
+
defaultImport: "adapter",
|
|
3791
|
+
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3792
|
+
});
|
|
3818
3793
|
await tsProject.save();
|
|
3819
3794
|
}
|
|
3820
3795
|
}
|
|
@@ -3865,8 +3840,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
3865
3840
|
const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
|
|
3866
3841
|
if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
|
|
3867
3842
|
else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
|
3868
|
-
|
|
3869
|
-
if (!hasReactPlugin) {
|
|
3843
|
+
if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
|
|
3870
3844
|
const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
|
|
3871
3845
|
if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
|
|
3872
3846
|
else pluginsArray.addElement(`${reactPluginIdentifier}()`);
|
|
@@ -3886,8 +3860,7 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
3886
3860
|
});
|
|
3887
3861
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3888
3862
|
if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
|
|
3889
|
-
|
|
3890
|
-
if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
|
|
3863
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin")) sourceFile.insertImportDeclaration(0, {
|
|
3891
3864
|
namedImports: ["cloudflare"],
|
|
3892
3865
|
moduleSpecifier: "@cloudflare/vite-plugin"
|
|
3893
3866
|
});
|
|
@@ -3896,12 +3869,10 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
3896
3869
|
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
3897
3870
|
});
|
|
3898
3871
|
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
3899
|
-
const
|
|
3900
|
-
const configObject = callExpr.getArguments()[0];
|
|
3872
|
+
const configObject = defineCall.getArguments()[0];
|
|
3901
3873
|
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
3902
3874
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
3903
|
-
|
|
3904
|
-
if (!hasCloudflarePlugin) pluginsArray.addElement("cloudflare()");
|
|
3875
|
+
if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.addElement("cloudflare()");
|
|
3905
3876
|
await tsProject.save();
|
|
3906
3877
|
}
|
|
3907
3878
|
|
|
@@ -3960,8 +3931,7 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
|
3960
3931
|
async function addDeploymentToProject(input) {
|
|
3961
3932
|
try {
|
|
3962
3933
|
const projectDir = input.projectDir || process.cwd();
|
|
3963
|
-
|
|
3964
|
-
if (!isBetterTStack) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
|
|
3934
|
+
if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
|
|
3965
3935
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
3966
3936
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
3967
3937
|
if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
|
|
@@ -4015,10 +3985,10 @@ async function setupExamples(config) {
|
|
|
4015
3985
|
if (examples.includes("ai")) {
|
|
4016
3986
|
const webClientDir = path.join(projectDir, "apps/web");
|
|
4017
3987
|
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
4018
|
-
const
|
|
3988
|
+
const apiDir = path.join(projectDir, "packages/api");
|
|
4019
3989
|
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
4020
3990
|
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
4021
|
-
const
|
|
3991
|
+
const apiDirExists = await fs.pathExists(apiDir);
|
|
4022
3992
|
const hasNuxt = frontend.includes("nuxt");
|
|
4023
3993
|
const hasSvelte = frontend.includes("svelte");
|
|
4024
3994
|
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
@@ -4039,9 +4009,9 @@ async function setupExamples(config) {
|
|
|
4039
4009
|
dependencies: ["ai", "@ai-sdk/react"],
|
|
4040
4010
|
projectDir: nativeClientDir
|
|
4041
4011
|
});
|
|
4042
|
-
if (
|
|
4012
|
+
if (apiDirExists && backend !== "none") await addPackageDependency({
|
|
4043
4013
|
dependencies: ["ai", "@ai-sdk/google"],
|
|
4044
|
-
projectDir:
|
|
4014
|
+
projectDir: apiDir
|
|
4045
4015
|
});
|
|
4046
4016
|
}
|
|
4047
4017
|
}
|
|
@@ -4131,8 +4101,7 @@ function getQueryDependencies(frontend) {
|
|
|
4131
4101
|
"native-unistyles"
|
|
4132
4102
|
];
|
|
4133
4103
|
const deps = {};
|
|
4134
|
-
|
|
4135
|
-
if (needsReactQuery) {
|
|
4104
|
+
if (frontend.some((f) => reactBasedFrontends.includes(f))) {
|
|
4136
4105
|
const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
|
|
4137
4106
|
const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
4138
4107
|
if (hasReactWeb) deps.web = {
|
|
@@ -4158,32 +4127,54 @@ function getConvexDependencies(frontend) {
|
|
|
4158
4127
|
return deps;
|
|
4159
4128
|
}
|
|
4160
4129
|
async function setupApi(config) {
|
|
4161
|
-
const { api, projectName, frontend, backend, packageManager, projectDir } = config;
|
|
4130
|
+
const { api, projectName, frontend, backend, packageManager, projectDir, auth } = config;
|
|
4162
4131
|
const isConvex = backend === "convex";
|
|
4163
4132
|
const webDir = path.join(projectDir, "apps/web");
|
|
4164
4133
|
const nativeDir = path.join(projectDir, "apps/native");
|
|
4165
4134
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4166
4135
|
const webDirExists = await fs.pathExists(webDir);
|
|
4167
4136
|
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
4168
|
-
|
|
4137
|
+
await fs.pathExists(serverDir);
|
|
4169
4138
|
const frontendType = getFrontendType(frontend);
|
|
4170
4139
|
if (!isConvex && api !== "none") {
|
|
4171
4140
|
const apiDeps = getApiDependencies(api, frontendType);
|
|
4172
|
-
|
|
4141
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
4142
|
+
if (apiDeps.server) {
|
|
4173
4143
|
await addPackageDependency({
|
|
4174
4144
|
dependencies: apiDeps.server.dependencies,
|
|
4175
|
-
projectDir:
|
|
4145
|
+
projectDir: apiPackageDir
|
|
4146
|
+
});
|
|
4147
|
+
const frameworkDeps = [];
|
|
4148
|
+
if (backend === "hono") frameworkDeps.push("hono");
|
|
4149
|
+
else if (backend === "elysia") frameworkDeps.push("elysia");
|
|
4150
|
+
else if (backend === "express") frameworkDeps.push("express", "@types/express");
|
|
4151
|
+
else if (backend === "fastify") frameworkDeps.push("fastify");
|
|
4152
|
+
if (frameworkDeps.length > 0) await addPackageDependency({
|
|
4153
|
+
dependencies: frameworkDeps,
|
|
4154
|
+
projectDir: apiPackageDir
|
|
4176
4155
|
});
|
|
4177
4156
|
if (api === "trpc") {
|
|
4178
4157
|
if (backend === "hono") await addPackageDependency({
|
|
4179
4158
|
dependencies: ["@hono/trpc-server"],
|
|
4180
|
-
projectDir:
|
|
4159
|
+
projectDir: apiPackageDir
|
|
4181
4160
|
});
|
|
4182
4161
|
else if (backend === "elysia") await addPackageDependency({
|
|
4183
4162
|
dependencies: ["@elysiajs/trpc"],
|
|
4184
|
-
projectDir:
|
|
4163
|
+
projectDir: apiPackageDir
|
|
4164
|
+
});
|
|
4165
|
+
else if (backend === "express") await addPackageDependency({
|
|
4166
|
+
dependencies: ["@trpc/server"],
|
|
4167
|
+
projectDir: apiPackageDir
|
|
4168
|
+
});
|
|
4169
|
+
else if (backend === "fastify") await addPackageDependency({
|
|
4170
|
+
dependencies: ["@trpc/server"],
|
|
4171
|
+
projectDir: apiPackageDir
|
|
4185
4172
|
});
|
|
4186
4173
|
}
|
|
4174
|
+
if (auth === "better-auth") await addPackageDependency({
|
|
4175
|
+
dependencies: ["better-auth"],
|
|
4176
|
+
projectDir: apiPackageDir
|
|
4177
|
+
});
|
|
4187
4178
|
}
|
|
4188
4179
|
if (webDirExists && apiDeps.web) await addPackageDependency({
|
|
4189
4180
|
dependencies: apiDeps.web.dependencies,
|
|
@@ -4227,7 +4218,7 @@ async function setupApi(config) {
|
|
|
4227
4218
|
//#endregion
|
|
4228
4219
|
//#region src/helpers/core/backend-setup.ts
|
|
4229
4220
|
async function setupBackendDependencies(config) {
|
|
4230
|
-
const { backend, runtime, api, projectDir } = config;
|
|
4221
|
+
const { backend, runtime, api, auth, examples, projectDir } = config;
|
|
4231
4222
|
if (backend === "convex") return;
|
|
4232
4223
|
const framework = backend;
|
|
4233
4224
|
const serverDir = path.join(projectDir, "apps/server");
|
|
@@ -4255,6 +4246,13 @@ async function setupBackendDependencies(config) {
|
|
|
4255
4246
|
dependencies.push("fastify", "@fastify/cors");
|
|
4256
4247
|
if (runtime === "node") devDependencies.push("tsx", "@types/node");
|
|
4257
4248
|
}
|
|
4249
|
+
if (api === "trpc") {
|
|
4250
|
+
if (framework === "express") dependencies.push("@trpc/server");
|
|
4251
|
+
else if (framework === "fastify") dependencies.push("@trpc/server");
|
|
4252
|
+
else if (runtime === "workers") dependencies.push("@trpc/server");
|
|
4253
|
+
} else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
|
|
4254
|
+
if (auth === "better-auth") dependencies.push("better-auth");
|
|
4255
|
+
if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
|
|
4258
4256
|
if (runtime === "bun") devDependencies.push("@types/bun");
|
|
4259
4257
|
if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
|
|
4260
4258
|
dependencies,
|
|
@@ -4273,7 +4271,7 @@ async function setupAuth(config) {
|
|
|
4273
4271
|
const nativeDir = path.join(projectDir, "apps/native");
|
|
4274
4272
|
const clientDirExists = await fs.pathExists(clientDir);
|
|
4275
4273
|
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
4276
|
-
|
|
4274
|
+
await fs.pathExists(serverDir);
|
|
4277
4275
|
try {
|
|
4278
4276
|
if (backend === "convex") {
|
|
4279
4277
|
if (auth === "clerk" && clientDirExists) {
|
|
@@ -4295,8 +4293,7 @@ async function setupAuth(config) {
|
|
|
4295
4293
|
}
|
|
4296
4294
|
if (auth === "better-auth") {
|
|
4297
4295
|
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4298
|
-
|
|
4299
|
-
if (convexBackendDirExists) await addPackageDependency({
|
|
4296
|
+
if (await fs.pathExists(convexBackendDir)) await addPackageDependency({
|
|
4300
4297
|
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4301
4298
|
customDependencies: { "better-auth": "1.3.8" },
|
|
4302
4299
|
projectDir: convexBackendDir
|
|
@@ -4330,11 +4327,13 @@ async function setupAuth(config) {
|
|
|
4330
4327
|
});
|
|
4331
4328
|
return;
|
|
4332
4329
|
}
|
|
4333
|
-
|
|
4330
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
4331
|
+
const authPackageDirExists = await fs.pathExists(authPackageDir);
|
|
4332
|
+
if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
|
|
4334
4333
|
dependencies: ["better-auth"],
|
|
4335
|
-
projectDir:
|
|
4334
|
+
projectDir: authPackageDir
|
|
4336
4335
|
});
|
|
4337
|
-
|
|
4336
|
+
if (frontend.some((f) => [
|
|
4338
4337
|
"react-router",
|
|
4339
4338
|
"tanstack-router",
|
|
4340
4339
|
"tanstack-start",
|
|
@@ -4342,8 +4341,7 @@ async function setupAuth(config) {
|
|
|
4342
4341
|
"nuxt",
|
|
4343
4342
|
"svelte",
|
|
4344
4343
|
"solid"
|
|
4345
|
-
].includes(f))
|
|
4346
|
-
if (hasWebFrontend$1 && clientDirExists) {
|
|
4344
|
+
].includes(f)) && clientDirExists) {
|
|
4347
4345
|
if (auth === "better-auth") await addPackageDependency({
|
|
4348
4346
|
dependencies: ["better-auth"],
|
|
4349
4347
|
projectDir: clientDir
|
|
@@ -4355,9 +4353,9 @@ async function setupAuth(config) {
|
|
|
4355
4353
|
dependencies: ["better-auth", "@better-auth/expo"],
|
|
4356
4354
|
projectDir: nativeDir
|
|
4357
4355
|
});
|
|
4358
|
-
if (
|
|
4356
|
+
if (authPackageDirExists) await addPackageDependency({
|
|
4359
4357
|
dependencies: ["@better-auth/expo"],
|
|
4360
|
-
projectDir:
|
|
4358
|
+
projectDir: authPackageDir
|
|
4361
4359
|
});
|
|
4362
4360
|
}
|
|
4363
4361
|
}
|
|
@@ -4411,8 +4409,7 @@ async function addEnvVariablesToFile(filePath, variables) {
|
|
|
4411
4409
|
let exampleContentToAdd = "";
|
|
4412
4410
|
for (const exampleVar of exampleVariables) {
|
|
4413
4411
|
const key = exampleVar.split("=")[0];
|
|
4414
|
-
|
|
4415
|
-
if (!regex.test(exampleEnvContent)) {
|
|
4412
|
+
if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
|
|
4416
4413
|
exampleContentToAdd += `${exampleVar}\n`;
|
|
4417
4414
|
exampleModified = true;
|
|
4418
4415
|
}
|
|
@@ -4432,8 +4429,7 @@ async function setupEnvironmentVariables(config) {
|
|
|
4432
4429
|
const hasNuxt = frontend.includes("nuxt");
|
|
4433
4430
|
const hasSvelte = frontend.includes("svelte");
|
|
4434
4431
|
const hasSolid = frontend.includes("solid");
|
|
4435
|
-
|
|
4436
|
-
if (hasWebFrontend$1) {
|
|
4432
|
+
if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
|
|
4437
4433
|
const clientDir = path.join(projectDir, "apps/web");
|
|
4438
4434
|
if (await fs.pathExists(clientDir)) {
|
|
4439
4435
|
let envVarName = "VITE_SERVER_URL";
|
|
@@ -4522,15 +4518,12 @@ async function setupEnvironmentVariables(config) {
|
|
|
4522
4518
|
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4523
4519
|
if (await fs.pathExists(convexBackendDir)) {
|
|
4524
4520
|
const envLocalPath = path.join(convexBackendDir, ".env.local");
|
|
4525
|
-
if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set"))
|
|
4526
|
-
const convexCommands = `# Set Convex environment variables
|
|
4521
|
+
if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) await fs.appendFile(envLocalPath, `# Set Convex environment variables
|
|
4527
4522
|
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
4528
4523
|
# npx convex env set SITE_URL http://localhost:3001
|
|
4529
4524
|
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
}
|
|
4533
|
-
const convexBackendVars = [{
|
|
4525
|
+
`);
|
|
4526
|
+
await addEnvVariablesToFile(envLocalPath, [{
|
|
4534
4527
|
key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
|
|
4535
4528
|
value: "",
|
|
4536
4529
|
condition: true,
|
|
@@ -4539,15 +4532,12 @@ async function setupEnvironmentVariables(config) {
|
|
|
4539
4532
|
key: "SITE_URL",
|
|
4540
4533
|
value: "http://localhost:3001",
|
|
4541
4534
|
condition: true
|
|
4542
|
-
}];
|
|
4543
|
-
await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
4535
|
+
}]);
|
|
4544
4536
|
}
|
|
4545
4537
|
}
|
|
4546
4538
|
return;
|
|
4547
4539
|
}
|
|
4548
4540
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4549
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
4550
|
-
const envPath = path.join(serverDir, ".env");
|
|
4551
4541
|
let corsOrigin = "http://localhost:3001";
|
|
4552
4542
|
if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
|
|
4553
4543
|
let databaseUrl = null;
|
|
@@ -4563,80 +4553,73 @@ async function setupEnvironmentVariables(config) {
|
|
|
4563
4553
|
break;
|
|
4564
4554
|
case "sqlite":
|
|
4565
4555
|
if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
|
|
4566
|
-
else databaseUrl = "
|
|
4556
|
+
else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
|
|
4567
4557
|
break;
|
|
4568
4558
|
}
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4559
|
+
if (await fs.pathExists(serverDir)) {
|
|
4560
|
+
const serverEnvPath = path.join(serverDir, ".env");
|
|
4561
|
+
const serverVars = [
|
|
4562
|
+
{
|
|
4563
|
+
key: "BETTER_AUTH_SECRET",
|
|
4564
|
+
value: generateAuthSecret(),
|
|
4565
|
+
condition: !!auth
|
|
4566
|
+
},
|
|
4567
|
+
{
|
|
4568
|
+
key: "BETTER_AUTH_URL",
|
|
4569
|
+
value: "http://localhost:3000",
|
|
4570
|
+
condition: !!auth
|
|
4571
|
+
},
|
|
4572
|
+
{
|
|
4573
|
+
key: "POLAR_ACCESS_TOKEN",
|
|
4574
|
+
value: "",
|
|
4575
|
+
condition: config.payments === "polar"
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
key: "POLAR_SUCCESS_URL",
|
|
4579
|
+
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
4580
|
+
condition: config.payments === "polar"
|
|
4581
|
+
},
|
|
4582
|
+
{
|
|
4583
|
+
key: "CORS_ORIGIN",
|
|
4584
|
+
value: corsOrigin,
|
|
4585
|
+
condition: true
|
|
4586
|
+
},
|
|
4587
|
+
{
|
|
4588
|
+
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4589
|
+
value: "",
|
|
4590
|
+
condition: examples?.includes("ai") || false
|
|
4591
|
+
},
|
|
4592
|
+
{
|
|
4593
|
+
key: "DATABASE_URL",
|
|
4594
|
+
value: databaseUrl,
|
|
4595
|
+
condition: database !== "none" && dbSetup === "none"
|
|
4596
|
+
}
|
|
4597
|
+
];
|
|
4598
|
+
await addEnvVariablesToFile(serverEnvPath, serverVars);
|
|
4599
|
+
}
|
|
4607
4600
|
const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
|
|
4608
4601
|
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4609
4602
|
if (isUnifiedAlchemy) {
|
|
4610
4603
|
const rootEnvPath = path.join(projectDir, ".env");
|
|
4611
|
-
|
|
4604
|
+
await addEnvVariablesToFile(rootEnvPath, [{
|
|
4612
4605
|
key: "ALCHEMY_PASSWORD",
|
|
4613
4606
|
value: "please-change-this",
|
|
4614
4607
|
condition: true
|
|
4615
|
-
}];
|
|
4616
|
-
await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
|
|
4608
|
+
}]);
|
|
4617
4609
|
} else if (isIndividualAlchemy) {
|
|
4618
4610
|
if (webDeploy === "alchemy") {
|
|
4619
4611
|
const webDir = path.join(projectDir, "apps/web");
|
|
4620
|
-
if (await fs.pathExists(webDir)) {
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
}];
|
|
4626
|
-
await addEnvVariablesToFile(path.join(webDir, ".env"), webAlchemyVars);
|
|
4627
|
-
}
|
|
4628
|
-
}
|
|
4629
|
-
if (serverDeploy === "alchemy") {
|
|
4630
|
-
const serverDir$1 = path.join(projectDir, "apps/server");
|
|
4631
|
-
if (await fs.pathExists(serverDir$1)) {
|
|
4632
|
-
const serverAlchemyVars = [{
|
|
4633
|
-
key: "ALCHEMY_PASSWORD",
|
|
4634
|
-
value: "please-change-this",
|
|
4635
|
-
condition: true
|
|
4636
|
-
}];
|
|
4637
|
-
await addEnvVariablesToFile(path.join(serverDir$1, ".env"), serverAlchemyVars);
|
|
4638
|
-
}
|
|
4612
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
|
|
4613
|
+
key: "ALCHEMY_PASSWORD",
|
|
4614
|
+
value: "please-change-this",
|
|
4615
|
+
condition: true
|
|
4616
|
+
}]);
|
|
4639
4617
|
}
|
|
4618
|
+
if (serverDeploy === "alchemy") await addEnvVariablesToFile(path.join(serverDir, ".env"), [{
|
|
4619
|
+
key: "ALCHEMY_PASSWORD",
|
|
4620
|
+
value: "please-change-this",
|
|
4621
|
+
condition: true
|
|
4622
|
+
}]);
|
|
4640
4623
|
}
|
|
4641
4624
|
}
|
|
4642
4625
|
|
|
@@ -4671,7 +4654,7 @@ async function setupCloudflareD1(config) {
|
|
|
4671
4654
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
|
4672
4655
|
const variables = [{
|
|
4673
4656
|
key: "DATABASE_URL",
|
|
4674
|
-
value: "
|
|
4657
|
+
value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
|
|
4675
4658
|
condition: true
|
|
4676
4659
|
}];
|
|
4677
4660
|
try {
|
|
@@ -4718,13 +4701,8 @@ function getDatabaseUrl(database, projectName) {
|
|
|
4718
4701
|
//#region src/utils/command-exists.ts
|
|
4719
4702
|
async function commandExists(command) {
|
|
4720
4703
|
try {
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
const result$1 = await execa("where", [command]);
|
|
4724
|
-
return result$1.exitCode === 0;
|
|
4725
|
-
}
|
|
4726
|
-
const result = await execa("which", [command]);
|
|
4727
|
-
return result.exitCode === 0;
|
|
4704
|
+
if (process.platform === "win32") return (await execa("where", [command])).exitCode === 0;
|
|
4705
|
+
return (await execa("which", [command])).exitCode === 0;
|
|
4728
4706
|
} catch {
|
|
4729
4707
|
return false;
|
|
4730
4708
|
}
|
|
@@ -4746,8 +4724,7 @@ async function checkAtlasCLI() {
|
|
|
4746
4724
|
}
|
|
4747
4725
|
async function initMongoDBAtlas(serverDir) {
|
|
4748
4726
|
try {
|
|
4749
|
-
|
|
4750
|
-
if (!hasAtlas) {
|
|
4727
|
+
if (!await checkAtlasCLI()) {
|
|
4751
4728
|
consola.error(pc.red("MongoDB Atlas CLI not found."));
|
|
4752
4729
|
log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
|
|
4753
4730
|
return null;
|
|
@@ -5152,9 +5129,8 @@ async function setupWithCreateDb(serverDir, packageManager, orm) {
|
|
|
5152
5129
|
consola$1.error("Failed to parse create-db response");
|
|
5153
5130
|
return null;
|
|
5154
5131
|
}
|
|
5155
|
-
const databaseUrl = orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString;
|
|
5156
5132
|
return {
|
|
5157
|
-
databaseUrl,
|
|
5133
|
+
databaseUrl: orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString,
|
|
5158
5134
|
claimUrl: createDbResponse.claimUrl
|
|
5159
5135
|
};
|
|
5160
5136
|
} catch (error) {
|
|
@@ -5208,9 +5184,9 @@ async function writeEnvFile$1(projectDir, config) {
|
|
|
5208
5184
|
}
|
|
5209
5185
|
async function addDotenvImportToPrismaConfig(projectDir) {
|
|
5210
5186
|
try {
|
|
5211
|
-
const prismaConfigPath = path.join(projectDir, "
|
|
5187
|
+
const prismaConfigPath = path.join(projectDir, "packages/db/prisma.config.ts");
|
|
5212
5188
|
let content = await fs.readFile(prismaConfigPath, "utf8");
|
|
5213
|
-
content = `import "dotenv
|
|
5189
|
+
content = `import dotenv from "dotenv";\ndotenv.config({ path: "../../apps/server/.env" });\n${content}`;
|
|
5214
5190
|
await fs.writeFile(prismaConfigPath, content);
|
|
5215
5191
|
} catch (_error) {
|
|
5216
5192
|
consola$1.error("Failed to update prisma.config.ts");
|
|
@@ -5226,11 +5202,12 @@ function displayManualSetupInstructions$1() {
|
|
|
5226
5202
|
|
|
5227
5203
|
DATABASE_URL="your_database_url"`);
|
|
5228
5204
|
}
|
|
5229
|
-
async function addPrismaAccelerateExtension(
|
|
5205
|
+
async function addPrismaAccelerateExtension(projectDir) {
|
|
5230
5206
|
try {
|
|
5207
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5231
5208
|
await addPackageDependency({
|
|
5232
5209
|
dependencies: ["@prisma/extension-accelerate"],
|
|
5233
|
-
projectDir:
|
|
5210
|
+
projectDir: dbPackageDir
|
|
5234
5211
|
});
|
|
5235
5212
|
return true;
|
|
5236
5213
|
} catch (_error) {
|
|
@@ -5291,7 +5268,7 @@ async function setupPrismaPostgres(config, cliInput) {
|
|
|
5291
5268
|
await writeEnvFile$1(projectDir, prismaConfig);
|
|
5292
5269
|
if (orm === "prisma") {
|
|
5293
5270
|
await addDotenvImportToPrismaConfig(projectDir);
|
|
5294
|
-
await addPrismaAccelerateExtension(
|
|
5271
|
+
await addPrismaAccelerateExtension(projectDir);
|
|
5295
5272
|
}
|
|
5296
5273
|
const connectionType = orm === "drizzle" ? "direct connection" : "Prisma Accelerate";
|
|
5297
5274
|
log.success(pc.green(`Prisma Postgres database configured successfully with ${connectionType}!`));
|
|
@@ -5316,7 +5293,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5316
5293
|
try {
|
|
5317
5294
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
|
5318
5295
|
const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
|
5319
|
-
|
|
5296
|
+
await addEnvVariablesToFile(envPath, [{
|
|
5320
5297
|
key: "DATABASE_URL",
|
|
5321
5298
|
value: dbUrlToUse,
|
|
5322
5299
|
condition: true
|
|
@@ -5324,8 +5301,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5324
5301
|
key: "DIRECT_URL",
|
|
5325
5302
|
value: dbUrlToUse,
|
|
5326
5303
|
condition: true
|
|
5327
|
-
}];
|
|
5328
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
5304
|
+
}]);
|
|
5329
5305
|
return true;
|
|
5330
5306
|
} catch (error) {
|
|
5331
5307
|
consola$1.error(pc.red("Failed to update .env file for Supabase."));
|
|
@@ -5334,8 +5310,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5334
5310
|
}
|
|
5335
5311
|
}
|
|
5336
5312
|
function extractDbUrl(output) {
|
|
5337
|
-
const
|
|
5338
|
-
const url = dbUrlMatch?.[1];
|
|
5313
|
+
const url = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/)?.[1];
|
|
5339
5314
|
if (url) return url;
|
|
5340
5315
|
return null;
|
|
5341
5316
|
}
|
|
@@ -5393,18 +5368,18 @@ function displayManualSupabaseInstructions(output) {
|
|
|
5393
5368
|
log.info(`"Manual Supabase Setup Instructions:"
|
|
5394
5369
|
1. Ensure Docker is installed and running.
|
|
5395
5370
|
2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
|
|
5396
|
-
3. Run \`supabase init\` in your project's \`
|
|
5397
|
-
4. Run \`supabase start\` in your project's \`
|
|
5371
|
+
3. Run \`supabase init\` in your project's \`packages/db\` directory.
|
|
5372
|
+
4. Run \`supabase start\` in your project's \`packages/db\` directory.
|
|
5398
5373
|
5. Copy the 'DB URL' from the output.${output ? `
|
|
5399
5374
|
${pc.bold("Relevant output from `supabase start`:")}
|
|
5400
5375
|
${pc.dim(output)}` : ""}
|
|
5401
|
-
6. Add the DB URL to the .env file in \`
|
|
5376
|
+
6. Add the DB URL to the .env file in \`packages/db/.env\` as \`DATABASE_URL\`:
|
|
5402
5377
|
${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
|
|
5403
5378
|
}
|
|
5404
5379
|
async function setupSupabase(config, cliInput) {
|
|
5405
5380
|
const { projectDir, packageManager } = config;
|
|
5406
5381
|
const manualDb = cliInput?.manualDb ?? false;
|
|
5407
|
-
const serverDir = path.join(projectDir, "
|
|
5382
|
+
const serverDir = path.join(projectDir, "packages", "db");
|
|
5408
5383
|
try {
|
|
5409
5384
|
await fs.ensureDir(serverDir);
|
|
5410
5385
|
if (manualDb) {
|
|
@@ -5431,8 +5406,7 @@ async function setupSupabase(config, cliInput) {
|
|
|
5431
5406
|
await writeSupabaseEnvFile(projectDir, "");
|
|
5432
5407
|
return;
|
|
5433
5408
|
}
|
|
5434
|
-
|
|
5435
|
-
if (!initialized) {
|
|
5409
|
+
if (!await initializeSupabase(serverDir, packageManager)) {
|
|
5436
5410
|
displayManualSupabaseInstructions();
|
|
5437
5411
|
return;
|
|
5438
5412
|
}
|
|
@@ -5442,14 +5416,12 @@ async function setupSupabase(config, cliInput) {
|
|
|
5442
5416
|
return;
|
|
5443
5417
|
}
|
|
5444
5418
|
const dbUrl = extractDbUrl(supabaseOutput);
|
|
5445
|
-
if (dbUrl)
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
}
|
|
5452
|
-
} else {
|
|
5419
|
+
if (dbUrl) if (await writeSupabaseEnvFile(projectDir, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
|
|
5420
|
+
else {
|
|
5421
|
+
log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
|
|
5422
|
+
displayManualSupabaseInstructions(supabaseOutput);
|
|
5423
|
+
}
|
|
5424
|
+
else {
|
|
5453
5425
|
log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
|
|
5454
5426
|
displayManualSupabaseInstructions(supabaseOutput);
|
|
5455
5427
|
}
|
|
@@ -5467,8 +5439,7 @@ async function isTursoInstalled() {
|
|
|
5467
5439
|
}
|
|
5468
5440
|
async function isTursoLoggedIn() {
|
|
5469
5441
|
try {
|
|
5470
|
-
|
|
5471
|
-
return !output.stdout.includes("You are not logged in");
|
|
5442
|
+
return !(await $`turso auth whoami`).stdout.includes("You are not logged in");
|
|
5472
5443
|
} catch {
|
|
5473
5444
|
return false;
|
|
5474
5445
|
}
|
|
@@ -5627,10 +5598,9 @@ async function setupTurso(config, cliInput) {
|
|
|
5627
5598
|
return;
|
|
5628
5599
|
}
|
|
5629
5600
|
setupSpinner.start("Checking Turso CLI availability...");
|
|
5630
|
-
const platform = os.platform();
|
|
5601
|
+
const platform = os$1.platform();
|
|
5631
5602
|
const isMac = platform === "darwin";
|
|
5632
|
-
|
|
5633
|
-
if (isWindows) {
|
|
5603
|
+
if (platform === "win32") {
|
|
5634
5604
|
if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
|
5635
5605
|
log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
|
|
5636
5606
|
await writeEnvFile(projectDir);
|
|
@@ -5638,8 +5608,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5638
5608
|
return;
|
|
5639
5609
|
}
|
|
5640
5610
|
if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
|
|
5641
|
-
|
|
5642
|
-
if (!isCliInstalled) {
|
|
5611
|
+
if (!await isTursoInstalled()) {
|
|
5643
5612
|
const shouldInstall = await confirm({
|
|
5644
5613
|
message: "Would you like to install Turso CLI?",
|
|
5645
5614
|
initialValue: true
|
|
@@ -5652,8 +5621,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5652
5621
|
}
|
|
5653
5622
|
await installTursoCLI(isMac);
|
|
5654
5623
|
}
|
|
5655
|
-
|
|
5656
|
-
if (!isLoggedIn) await loginToTurso();
|
|
5624
|
+
if (!await isTursoLoggedIn()) await loginToTurso();
|
|
5657
5625
|
const selectedGroup = await selectTursoGroup();
|
|
5658
5626
|
let success = false;
|
|
5659
5627
|
let dbName = "";
|
|
@@ -5694,15 +5662,15 @@ async function setupDatabase(config, cliInput) {
|
|
|
5694
5662
|
const { database, orm, dbSetup, backend, projectDir } = config;
|
|
5695
5663
|
if (backend === "convex" || database === "none") {
|
|
5696
5664
|
if (backend !== "convex") {
|
|
5697
|
-
const serverDir
|
|
5698
|
-
const serverDbDir = path.join(serverDir
|
|
5665
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
5666
|
+
const serverDbDir = path.join(serverDir, "src/db");
|
|
5699
5667
|
if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
|
|
5700
5668
|
}
|
|
5701
5669
|
return;
|
|
5702
5670
|
}
|
|
5703
5671
|
const s = spinner();
|
|
5704
|
-
const
|
|
5705
|
-
if (!await fs.pathExists(
|
|
5672
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5673
|
+
if (!await fs.pathExists(dbPackageDir)) return;
|
|
5706
5674
|
try {
|
|
5707
5675
|
if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
|
|
5708
5676
|
dependencies: [
|
|
@@ -5711,23 +5679,27 @@ async function setupDatabase(config, cliInput) {
|
|
|
5711
5679
|
"@planetscale/database"
|
|
5712
5680
|
],
|
|
5713
5681
|
devDependencies: ["prisma"],
|
|
5714
|
-
projectDir:
|
|
5682
|
+
projectDir: dbPackageDir
|
|
5715
5683
|
});
|
|
5716
5684
|
else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
|
|
5717
5685
|
dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
|
|
5718
5686
|
devDependencies: ["prisma"],
|
|
5719
|
-
projectDir:
|
|
5687
|
+
projectDir: dbPackageDir
|
|
5720
5688
|
});
|
|
5721
5689
|
else await addPackageDependency({
|
|
5722
5690
|
dependencies: ["@prisma/client"],
|
|
5723
5691
|
devDependencies: ["prisma"],
|
|
5724
|
-
projectDir:
|
|
5692
|
+
projectDir: dbPackageDir
|
|
5725
5693
|
});
|
|
5726
5694
|
else if (orm === "drizzle") {
|
|
5727
5695
|
if (database === "sqlite") await addPackageDependency({
|
|
5728
|
-
dependencies: [
|
|
5696
|
+
dependencies: [
|
|
5697
|
+
"drizzle-orm",
|
|
5698
|
+
"@libsql/client",
|
|
5699
|
+
"libsql"
|
|
5700
|
+
],
|
|
5729
5701
|
devDependencies: ["drizzle-kit"],
|
|
5730
|
-
projectDir:
|
|
5702
|
+
projectDir: dbPackageDir
|
|
5731
5703
|
});
|
|
5732
5704
|
else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
|
|
5733
5705
|
dependencies: [
|
|
@@ -5736,32 +5708,32 @@ async function setupDatabase(config, cliInput) {
|
|
|
5736
5708
|
"ws"
|
|
5737
5709
|
],
|
|
5738
5710
|
devDependencies: ["drizzle-kit", "@types/ws"],
|
|
5739
|
-
projectDir:
|
|
5711
|
+
projectDir: dbPackageDir
|
|
5740
5712
|
});
|
|
5741
5713
|
else if (dbSetup === "planetscale") await addPackageDependency({
|
|
5742
5714
|
dependencies: ["drizzle-orm", "pg"],
|
|
5743
5715
|
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
5744
|
-
projectDir:
|
|
5716
|
+
projectDir: dbPackageDir
|
|
5745
5717
|
});
|
|
5746
5718
|
else await addPackageDependency({
|
|
5747
5719
|
dependencies: ["drizzle-orm", "pg"],
|
|
5748
5720
|
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
5749
|
-
projectDir:
|
|
5721
|
+
projectDir: dbPackageDir
|
|
5750
5722
|
});
|
|
5751
5723
|
else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
|
|
5752
5724
|
dependencies: ["drizzle-orm", "@planetscale/database"],
|
|
5753
5725
|
devDependencies: ["drizzle-kit"],
|
|
5754
|
-
projectDir:
|
|
5726
|
+
projectDir: dbPackageDir
|
|
5755
5727
|
});
|
|
5756
5728
|
else await addPackageDependency({
|
|
5757
5729
|
dependencies: ["drizzle-orm", "mysql2"],
|
|
5758
5730
|
devDependencies: ["drizzle-kit"],
|
|
5759
|
-
projectDir:
|
|
5731
|
+
projectDir: dbPackageDir
|
|
5760
5732
|
});
|
|
5761
5733
|
} else if (orm === "mongoose") await addPackageDependency({
|
|
5762
5734
|
dependencies: ["mongoose"],
|
|
5763
5735
|
devDependencies: [],
|
|
5764
|
-
projectDir:
|
|
5736
|
+
projectDir: dbPackageDir
|
|
5765
5737
|
});
|
|
5766
5738
|
if (dbSetup === "docker") await setupDockerCompose(config);
|
|
5767
5739
|
else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
|
|
@@ -5910,8 +5882,7 @@ function generateStackDescription(frontend, backend, api, isConvex) {
|
|
|
5910
5882
|
const hasSvelte = frontend.includes("svelte");
|
|
5911
5883
|
const hasNuxt = frontend.includes("nuxt");
|
|
5912
5884
|
const hasSolid = frontend.includes("solid");
|
|
5913
|
-
|
|
5914
|
-
if (!hasFrontendNone) {
|
|
5885
|
+
if (!(frontend.length === 0 || frontend.includes("none"))) {
|
|
5915
5886
|
if (hasTanstackRouter) parts.push("React, TanStack Router");
|
|
5916
5887
|
else if (hasReactRouter) parts.push("React, React Router");
|
|
5917
5888
|
else if (hasNext) parts.push("Next.js");
|
|
@@ -5967,8 +5938,7 @@ function generateProjectStructure(projectName, frontend, backend, addons, isConv
|
|
|
5967
5938
|
structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
|
|
5968
5939
|
}
|
|
5969
5940
|
}
|
|
5970
|
-
|
|
5971
|
-
if (hasNative) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
5941
|
+
if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
5972
5942
|
if (addons.includes("starlight")) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
|
|
5973
5943
|
if (isConvex) {
|
|
5974
5944
|
structure.push("├── packages/");
|
|
@@ -6129,7 +6099,7 @@ SERVER_URL={your-production-server-domain}
|
|
|
6129
6099
|
CORS_ORIGIN={your-production-web-domain}
|
|
6130
6100
|
BETTER_AUTH_URL={your-production-server-domain}
|
|
6131
6101
|
\`\`\`
|
|
6132
|
-
- In \`apps/server/lib/auth.ts\`, uncomment the \`session.cookieCache\` and \`advanced.crossSubDomainCookies\` sections and replace \`<your-workers-subdomain>\` with your actual workers subdomain. These settings are required to ensure cookies are transferred properly between your web and server domains.
|
|
6102
|
+
- In \`apps/server/src/lib/auth.ts\`, uncomment the \`session.cookieCache\` and \`advanced.crossSubDomainCookies\` sections and replace \`<your-workers-subdomain>\` with your actual workers subdomain. These settings are required to ensure cookies are transferred properly between your web and server domains.
|
|
6133
6103
|
`;
|
|
6134
6104
|
}
|
|
6135
6105
|
function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy) {
|
|
@@ -6152,12 +6122,11 @@ function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeplo
|
|
|
6152
6122
|
//#region src/helpers/core/git.ts
|
|
6153
6123
|
async function initializeGit(projectDir, useGit) {
|
|
6154
6124
|
if (!useGit) return;
|
|
6155
|
-
|
|
6125
|
+
if ((await $({
|
|
6156
6126
|
cwd: projectDir,
|
|
6157
6127
|
reject: false,
|
|
6158
6128
|
stderr: "pipe"
|
|
6159
|
-
})`git --version
|
|
6160
|
-
if (gitVersionResult.exitCode !== 0) {
|
|
6129
|
+
})`git --version`).exitCode !== 0) {
|
|
6161
6130
|
log.warn(pc.yellow("Git is not installed"));
|
|
6162
6131
|
return;
|
|
6163
6132
|
}
|
|
@@ -6187,7 +6156,7 @@ async function setupPayments(config) {
|
|
|
6187
6156
|
projectDir: serverDir
|
|
6188
6157
|
});
|
|
6189
6158
|
if (clientDirExists) {
|
|
6190
|
-
|
|
6159
|
+
if (frontend.some((f) => [
|
|
6191
6160
|
"react-router",
|
|
6192
6161
|
"tanstack-router",
|
|
6193
6162
|
"tanstack-start",
|
|
@@ -6195,8 +6164,7 @@ async function setupPayments(config) {
|
|
|
6195
6164
|
"nuxt",
|
|
6196
6165
|
"svelte",
|
|
6197
6166
|
"solid"
|
|
6198
|
-
].includes(f))
|
|
6199
|
-
if (hasWebFrontend$1) await addPackageDependency({
|
|
6167
|
+
].includes(f))) await addPackageDependency({
|
|
6200
6168
|
dependencies: ["@polar-sh/better-auth"],
|
|
6201
6169
|
projectDir: clientDir
|
|
6202
6170
|
});
|
|
@@ -6237,15 +6205,13 @@ function getDockerInstallInstructions(platform, database) {
|
|
|
6237
6205
|
return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
|
|
6238
6206
|
}
|
|
6239
6207
|
async function getDockerStatus(database) {
|
|
6240
|
-
const platform = os.platform();
|
|
6241
|
-
|
|
6242
|
-
if (!installed) return {
|
|
6208
|
+
const platform = os$1.platform();
|
|
6209
|
+
if (!await isDockerInstalled()) return {
|
|
6243
6210
|
installed: false,
|
|
6244
6211
|
running: false,
|
|
6245
6212
|
message: getDockerInstallInstructions(platform, database)
|
|
6246
6213
|
};
|
|
6247
|
-
|
|
6248
|
-
if (!running) return {
|
|
6214
|
+
if (!await isDockerRunning()) return {
|
|
6249
6215
|
installed: true,
|
|
6250
6216
|
running: false,
|
|
6251
6217
|
message: `${pc.yellow("IMPORTANT:")} Docker is installed but not running.`
|
|
@@ -6424,12 +6390,66 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
|
|
|
6424
6390
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
6425
6391
|
}
|
|
6426
6392
|
|
|
6393
|
+
//#endregion
|
|
6394
|
+
//#region src/helpers/core/workspace-setup.ts
|
|
6395
|
+
async function setupWorkspaceDependencies(projectDir, options) {
|
|
6396
|
+
const projectName = options.projectName;
|
|
6397
|
+
const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
|
|
6398
|
+
const commonDeps = ["dotenv", "zod"];
|
|
6399
|
+
const commonDevDeps = ["tsdown"];
|
|
6400
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
6401
|
+
if (await fs.pathExists(dbPackageDir)) await addPackageDependency({
|
|
6402
|
+
dependencies: commonDeps,
|
|
6403
|
+
devDependencies: commonDevDeps,
|
|
6404
|
+
projectDir: dbPackageDir
|
|
6405
|
+
});
|
|
6406
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
6407
|
+
if (await fs.pathExists(authPackageDir)) await addPackageDependency({
|
|
6408
|
+
dependencies: commonDeps,
|
|
6409
|
+
devDependencies: commonDevDeps,
|
|
6410
|
+
customDependencies: { [`@${projectName}/db`]: workspaceVersion },
|
|
6411
|
+
projectDir: authPackageDir
|
|
6412
|
+
});
|
|
6413
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
6414
|
+
if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
|
|
6415
|
+
dependencies: commonDeps,
|
|
6416
|
+
devDependencies: commonDevDeps,
|
|
6417
|
+
customDependencies: {
|
|
6418
|
+
[`@${projectName}/auth`]: workspaceVersion,
|
|
6419
|
+
[`@${projectName}/db`]: workspaceVersion
|
|
6420
|
+
},
|
|
6421
|
+
projectDir: apiPackageDir
|
|
6422
|
+
});
|
|
6423
|
+
const serverPackageDir = path.join(projectDir, "apps/server");
|
|
6424
|
+
if (await fs.pathExists(serverPackageDir)) await addPackageDependency({
|
|
6425
|
+
dependencies: commonDeps,
|
|
6426
|
+
devDependencies: commonDevDeps,
|
|
6427
|
+
customDependencies: {
|
|
6428
|
+
[`@${projectName}/api`]: workspaceVersion,
|
|
6429
|
+
[`@${projectName}/auth`]: workspaceVersion,
|
|
6430
|
+
[`@${projectName}/db`]: workspaceVersion
|
|
6431
|
+
},
|
|
6432
|
+
projectDir: serverPackageDir
|
|
6433
|
+
});
|
|
6434
|
+
if (options.api && options.api !== "none") {
|
|
6435
|
+
const webPackageDir = path.join(projectDir, "apps/web");
|
|
6436
|
+
if (await fs.pathExists(webPackageDir)) await addPackageDependency({
|
|
6437
|
+
customDependencies: { [`@${projectName}/api`]: workspaceVersion },
|
|
6438
|
+
projectDir: webPackageDir
|
|
6439
|
+
});
|
|
6440
|
+
}
|
|
6441
|
+
}
|
|
6442
|
+
|
|
6427
6443
|
//#endregion
|
|
6428
6444
|
//#region src/helpers/core/project-config.ts
|
|
6429
6445
|
async function updatePackageConfigurations(projectDir, options) {
|
|
6430
6446
|
await updateRootPackageJson(projectDir, options);
|
|
6431
|
-
if (options.backend !== "convex")
|
|
6432
|
-
|
|
6447
|
+
if (options.backend !== "convex") {
|
|
6448
|
+
await updateServerPackageJson(projectDir, options);
|
|
6449
|
+
await updateAuthPackageJson(projectDir, options);
|
|
6450
|
+
await updateApiPackageJson(projectDir, options);
|
|
6451
|
+
await setupWorkspaceDependencies(projectDir, options);
|
|
6452
|
+
} else await updateConvexPackageJson(projectDir, options);
|
|
6433
6453
|
}
|
|
6434
6454
|
async function updateRootPackageJson(projectDir, options) {
|
|
6435
6455
|
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
@@ -6439,6 +6459,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6439
6459
|
if (!packageJson.scripts) packageJson.scripts = {};
|
|
6440
6460
|
const scripts = packageJson.scripts;
|
|
6441
6461
|
const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
|
|
6462
|
+
const dbPackageName = `@${options.projectName}/db`;
|
|
6442
6463
|
let serverDevScript = "";
|
|
6443
6464
|
if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
|
|
6444
6465
|
else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
|
|
@@ -6458,14 +6479,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6458
6479
|
scripts["dev:server"] = serverDevScript;
|
|
6459
6480
|
if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
|
|
6460
6481
|
if (needsDbScripts) {
|
|
6461
|
-
scripts["db:push"] = `turbo -F ${
|
|
6462
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${
|
|
6482
|
+
scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
|
|
6483
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${dbPackageName} db:studio`;
|
|
6463
6484
|
if (options.orm === "prisma") {
|
|
6464
|
-
scripts["db:generate"] = `turbo -F ${
|
|
6465
|
-
scripts["db:migrate"] = `turbo -F ${
|
|
6485
|
+
scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
|
|
6486
|
+
scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
|
|
6466
6487
|
} else if (options.orm === "drizzle") {
|
|
6467
|
-
scripts["db:generate"] = `turbo -F ${
|
|
6468
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${
|
|
6488
|
+
scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
|
|
6489
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
|
|
6469
6490
|
}
|
|
6470
6491
|
}
|
|
6471
6492
|
if (options.dbSetup === "docker") {
|
|
@@ -6483,14 +6504,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6483
6504
|
scripts["dev:server"] = serverDevScript;
|
|
6484
6505
|
if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
|
|
6485
6506
|
if (needsDbScripts) {
|
|
6486
|
-
scripts["db:push"] = `pnpm --filter ${
|
|
6487
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${
|
|
6507
|
+
scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
|
|
6508
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${dbPackageName} db:studio`;
|
|
6488
6509
|
if (options.orm === "prisma") {
|
|
6489
|
-
scripts["db:generate"] = `pnpm --filter ${
|
|
6490
|
-
scripts["db:migrate"] = `pnpm --filter ${
|
|
6510
|
+
scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
|
|
6511
|
+
scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
|
|
6491
6512
|
} else if (options.orm === "drizzle") {
|
|
6492
|
-
scripts["db:generate"] = `pnpm --filter ${
|
|
6493
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${
|
|
6513
|
+
scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
|
|
6514
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
|
|
6494
6515
|
}
|
|
6495
6516
|
}
|
|
6496
6517
|
if (options.dbSetup === "docker") {
|
|
@@ -6508,14 +6529,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6508
6529
|
scripts["dev:server"] = serverDevScript;
|
|
6509
6530
|
if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
|
|
6510
6531
|
if (needsDbScripts) {
|
|
6511
|
-
scripts["db:push"] = `npm run db:push --workspace ${
|
|
6512
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${
|
|
6532
|
+
scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
|
|
6533
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${dbPackageName}`;
|
|
6513
6534
|
if (options.orm === "prisma") {
|
|
6514
|
-
scripts["db:generate"] = `npm run db:generate --workspace ${
|
|
6515
|
-
scripts["db:migrate"] = `npm run db:migrate --workspace ${
|
|
6535
|
+
scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
|
|
6536
|
+
scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
|
|
6516
6537
|
} else if (options.orm === "drizzle") {
|
|
6517
|
-
scripts["db:generate"] = `npm run db:generate --workspace ${
|
|
6518
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${
|
|
6538
|
+
scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
|
|
6539
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
|
|
6519
6540
|
}
|
|
6520
6541
|
}
|
|
6521
6542
|
if (options.dbSetup === "docker") {
|
|
@@ -6533,14 +6554,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6533
6554
|
scripts["dev:server"] = serverDevScript;
|
|
6534
6555
|
if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
|
|
6535
6556
|
if (needsDbScripts) {
|
|
6536
|
-
scripts["db:push"] = `bun run --filter ${
|
|
6537
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${
|
|
6557
|
+
scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
|
|
6558
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${dbPackageName} db:studio`;
|
|
6538
6559
|
if (options.orm === "prisma") {
|
|
6539
|
-
scripts["db:generate"] = `bun run --filter ${
|
|
6540
|
-
scripts["db:migrate"] = `bun run --filter ${
|
|
6560
|
+
scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
|
|
6561
|
+
scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
|
|
6541
6562
|
} else if (options.orm === "drizzle") {
|
|
6542
|
-
scripts["db:generate"] = `bun run --filter ${
|
|
6543
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${
|
|
6563
|
+
scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
|
|
6564
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
|
|
6544
6565
|
}
|
|
6545
6566
|
}
|
|
6546
6567
|
if (options.dbSetup === "docker") {
|
|
@@ -6560,8 +6581,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6560
6581
|
const workspaces = packageJson.workspaces;
|
|
6561
6582
|
if (options.backend === "convex") {
|
|
6562
6583
|
if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
|
|
6563
|
-
|
|
6564
|
-
if (needsAppsDir && !workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6584
|
+
if ((options.frontend.length > 0 || options.addons.includes("starlight")) && !workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6565
6585
|
} else {
|
|
6566
6586
|
if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6567
6587
|
if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
|
|
@@ -6574,6 +6594,22 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
6574
6594
|
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
|
6575
6595
|
if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
|
|
6576
6596
|
const scripts = serverPackageJson.scripts;
|
|
6597
|
+
if (options.dbSetup === "docker") {
|
|
6598
|
+
scripts["db:start"] = "docker compose up -d";
|
|
6599
|
+
scripts["db:watch"] = "docker compose up";
|
|
6600
|
+
scripts["db:stop"] = "docker compose stop";
|
|
6601
|
+
scripts["db:down"] = "docker compose down";
|
|
6602
|
+
}
|
|
6603
|
+
await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
|
|
6604
|
+
await updateDbPackageJson(projectDir, options);
|
|
6605
|
+
}
|
|
6606
|
+
async function updateDbPackageJson(projectDir, options) {
|
|
6607
|
+
const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
|
|
6608
|
+
if (!await fs.pathExists(dbPackageJsonPath)) return;
|
|
6609
|
+
const dbPackageJson = await fs.readJson(dbPackageJsonPath);
|
|
6610
|
+
dbPackageJson.name = `@${options.projectName}/db`;
|
|
6611
|
+
if (!dbPackageJson.scripts) dbPackageJson.scripts = {};
|
|
6612
|
+
const scripts = dbPackageJson.scripts;
|
|
6577
6613
|
if (options.database !== "none") {
|
|
6578
6614
|
if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
|
|
6579
6615
|
if (options.orm === "prisma") {
|
|
@@ -6588,13 +6624,21 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
6588
6624
|
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "drizzle-kit migrate";
|
|
6589
6625
|
}
|
|
6590
6626
|
}
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6627
|
+
await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
|
|
6628
|
+
}
|
|
6629
|
+
async function updateAuthPackageJson(projectDir, options) {
|
|
6630
|
+
const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
|
|
6631
|
+
if (!await fs.pathExists(authPackageJsonPath)) return;
|
|
6632
|
+
const authPackageJson = await fs.readJson(authPackageJsonPath);
|
|
6633
|
+
authPackageJson.name = `@${options.projectName}/auth`;
|
|
6634
|
+
await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
|
|
6635
|
+
}
|
|
6636
|
+
async function updateApiPackageJson(projectDir, options) {
|
|
6637
|
+
const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
|
|
6638
|
+
if (!await fs.pathExists(apiPackageJsonPath)) return;
|
|
6639
|
+
const apiPackageJson = await fs.readJson(apiPackageJsonPath);
|
|
6640
|
+
apiPackageJson.name = `@${options.projectName}/api`;
|
|
6641
|
+
await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
|
|
6598
6642
|
}
|
|
6599
6643
|
async function updateConvexPackageJson(projectDir, options) {
|
|
6600
6644
|
const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
|
|
@@ -6615,10 +6659,7 @@ async function createProject(options, cliInput) {
|
|
|
6615
6659
|
await copyBaseTemplate(projectDir, options);
|
|
6616
6660
|
await setupFrontendTemplates(projectDir, options);
|
|
6617
6661
|
await setupBackendFramework(projectDir, options);
|
|
6618
|
-
if (!isConvex)
|
|
6619
|
-
await setupDbOrmTemplates(projectDir, options);
|
|
6620
|
-
await setupDockerComposeTemplates(projectDir, options);
|
|
6621
|
-
}
|
|
6662
|
+
if (!isConvex) await setupDockerComposeTemplates(projectDir, options);
|
|
6622
6663
|
await setupAuthTemplate(projectDir, options);
|
|
6623
6664
|
if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
|
|
6624
6665
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
@@ -6781,9 +6822,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
|
|
|
6781
6822
|
finalPathInput: currentPathInput,
|
|
6782
6823
|
shouldClearDirectory: false
|
|
6783
6824
|
};
|
|
6784
|
-
|
|
6785
|
-
const isNotEmpty = dirContents.length > 0;
|
|
6786
|
-
if (!isNotEmpty) return {
|
|
6825
|
+
if (!((await fs.readdir(currentPath)).length > 0)) return {
|
|
6787
6826
|
finalPathInput: currentPathInput,
|
|
6788
6827
|
shouldClearDirectory: false
|
|
6789
6828
|
};
|
|
@@ -6945,26 +6984,25 @@ function displaySponsorsBox(sponsors$1) {
|
|
|
6945
6984
|
|
|
6946
6985
|
//#endregion
|
|
6947
6986
|
//#region src/index.ts
|
|
6948
|
-
const
|
|
6949
|
-
|
|
6950
|
-
init: t.procedure.meta({
|
|
6987
|
+
const router = os.router({
|
|
6988
|
+
init: os.meta({
|
|
6951
6989
|
description: "Create a new Better-T-Stack project",
|
|
6952
6990
|
default: true,
|
|
6953
6991
|
negateBooleans: true
|
|
6954
|
-
}).input(z.tuple([ProjectNameSchema.optional(), z.object({
|
|
6955
|
-
yes: z.boolean().optional().default(false).describe("Use default configuration"),
|
|
6956
|
-
yolo: z.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
|
|
6957
|
-
verbose: z.boolean().optional().default(false).describe("Show detailed result information"),
|
|
6992
|
+
}).input(z$1.tuple([ProjectNameSchema.optional(), z$1.object({
|
|
6993
|
+
yes: z$1.boolean().optional().default(false).describe("Use default configuration"),
|
|
6994
|
+
yolo: z$1.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
|
|
6995
|
+
verbose: z$1.boolean().optional().default(false).describe("Show detailed result information"),
|
|
6958
6996
|
database: DatabaseSchema.optional(),
|
|
6959
6997
|
orm: ORMSchema.optional(),
|
|
6960
6998
|
auth: AuthSchema.optional(),
|
|
6961
6999
|
payments: PaymentsSchema.optional(),
|
|
6962
|
-
frontend: z.array(FrontendSchema).optional(),
|
|
6963
|
-
addons: z.array(AddonsSchema).optional(),
|
|
6964
|
-
examples: z.array(ExamplesSchema).optional(),
|
|
6965
|
-
git: z.boolean().optional(),
|
|
7000
|
+
frontend: z$1.array(FrontendSchema).optional(),
|
|
7001
|
+
addons: z$1.array(AddonsSchema).optional(),
|
|
7002
|
+
examples: z$1.array(ExamplesSchema).optional(),
|
|
7003
|
+
git: z$1.boolean().optional(),
|
|
6966
7004
|
packageManager: PackageManagerSchema.optional(),
|
|
6967
|
-
install: z.boolean().optional(),
|
|
7005
|
+
install: z$1.boolean().optional(),
|
|
6968
7006
|
dbSetup: DatabaseSetupSchema.optional(),
|
|
6969
7007
|
backend: BackendSchema.optional(),
|
|
6970
7008
|
runtime: RuntimeSchema.optional(),
|
|
@@ -6972,10 +7010,10 @@ const router = t.router({
|
|
|
6972
7010
|
webDeploy: WebDeploySchema.optional(),
|
|
6973
7011
|
serverDeploy: ServerDeploySchema.optional(),
|
|
6974
7012
|
directoryConflict: DirectoryConflictSchema.optional(),
|
|
6975
|
-
renderTitle: z.boolean().optional(),
|
|
6976
|
-
disableAnalytics: z.boolean().optional().default(false).describe("Disable analytics"),
|
|
6977
|
-
manualDb: z.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
|
|
6978
|
-
})])).
|
|
7013
|
+
renderTitle: z$1.boolean().optional(),
|
|
7014
|
+
disableAnalytics: z$1.boolean().optional().default(false).describe("Disable analytics"),
|
|
7015
|
+
manualDb: z$1.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
|
|
7016
|
+
})])).handler(async ({ input }) => {
|
|
6979
7017
|
const [projectName, options] = input;
|
|
6980
7018
|
const combinedInput = {
|
|
6981
7019
|
projectName,
|
|
@@ -6984,18 +7022,18 @@ const router = t.router({
|
|
|
6984
7022
|
const result = await createProjectHandler(combinedInput);
|
|
6985
7023
|
if (options.verbose) return result;
|
|
6986
7024
|
}),
|
|
6987
|
-
add:
|
|
6988
|
-
addons: z.array(AddonsSchema).optional().default([]),
|
|
7025
|
+
add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z$1.tuple([z$1.object({
|
|
7026
|
+
addons: z$1.array(AddonsSchema).optional().default([]),
|
|
6989
7027
|
webDeploy: WebDeploySchema.optional(),
|
|
6990
7028
|
serverDeploy: ServerDeploySchema.optional(),
|
|
6991
|
-
projectDir: z.string().optional(),
|
|
6992
|
-
install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
7029
|
+
projectDir: z$1.string().optional(),
|
|
7030
|
+
install: z$1.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
6993
7031
|
packageManager: PackageManagerSchema.optional()
|
|
6994
|
-
})])).
|
|
7032
|
+
})])).handler(async ({ input }) => {
|
|
6995
7033
|
const [options] = input;
|
|
6996
7034
|
await addAddonsHandler(options);
|
|
6997
7035
|
}),
|
|
6998
|
-
sponsors:
|
|
7036
|
+
sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
|
|
6999
7037
|
try {
|
|
7000
7038
|
renderTitle();
|
|
7001
7039
|
intro(pc.magenta("Better-T-Stack Sponsors"));
|
|
@@ -7005,7 +7043,7 @@ const router = t.router({
|
|
|
7005
7043
|
handleError(error, "Failed to display sponsors");
|
|
7006
7044
|
}
|
|
7007
7045
|
}),
|
|
7008
|
-
docs:
|
|
7046
|
+
docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(async () => {
|
|
7009
7047
|
const DOCS_URL = "https://better-t-stack.dev/docs";
|
|
7010
7048
|
try {
|
|
7011
7049
|
await openUrl(DOCS_URL);
|
|
@@ -7014,7 +7052,7 @@ const router = t.router({
|
|
|
7014
7052
|
log.message(`Please visit ${DOCS_URL}`);
|
|
7015
7053
|
}
|
|
7016
7054
|
}),
|
|
7017
|
-
builder:
|
|
7055
|
+
builder: os.meta({ description: "Open the web-based stack builder" }).handler(async () => {
|
|
7018
7056
|
const BUILDER_URL = "https://better-t-stack.dev/new";
|
|
7019
7057
|
try {
|
|
7020
7058
|
await openUrl(BUILDER_URL);
|
|
@@ -7024,7 +7062,7 @@ const router = t.router({
|
|
|
7024
7062
|
}
|
|
7025
7063
|
})
|
|
7026
7064
|
});
|
|
7027
|
-
const caller =
|
|
7065
|
+
const caller = createRouterClient(router, { context: {} });
|
|
7028
7066
|
function createBtsCli() {
|
|
7029
7067
|
return createCli({
|
|
7030
7068
|
router,
|
|
@@ -7066,9 +7104,8 @@ function createBtsCli() {
|
|
|
7066
7104
|
* ```
|
|
7067
7105
|
*/
|
|
7068
7106
|
async function init(projectName, options) {
|
|
7069
|
-
const opts = options ?? {};
|
|
7070
7107
|
const programmaticOpts = {
|
|
7071
|
-
...
|
|
7108
|
+
...options ?? {},
|
|
7072
7109
|
verbose: true
|
|
7073
7110
|
};
|
|
7074
7111
|
const prev = process.env.BTS_PROGRAMMATIC;
|