create-better-t-stack 2.48.3 → 2.49.1-canary.206d95c1
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-CRbLnM5h.js} +557 -414
- package/package.json +10 -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,8 @@ 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
|
|
18
|
+
import yaml from "yaml";
|
|
19
|
+
import os$1 from "node:os";
|
|
18
20
|
|
|
19
21
|
//#region src/utils/get-package-manager.ts
|
|
20
22
|
const getUserPkgManager = () => {
|
|
@@ -70,6 +72,7 @@ const dependencyVersionMap = {
|
|
|
70
72
|
"drizzle-kit": "^0.31.2",
|
|
71
73
|
"@planetscale/database": "^1.19.0",
|
|
72
74
|
"@libsql/client": "^0.15.9",
|
|
75
|
+
libsql: "^0.5.22",
|
|
73
76
|
"@neondatabase/serverless": "^1.0.1",
|
|
74
77
|
pg: "^8.14.1",
|
|
75
78
|
"@types/pg": "^8.11.11",
|
|
@@ -145,7 +148,9 @@ const dependencyVersionMap = {
|
|
|
145
148
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
146
149
|
alchemy: "^0.67.0",
|
|
147
150
|
nitropack: "^2.12.4",
|
|
148
|
-
dotenv: "^17.2.
|
|
151
|
+
dotenv: "^17.2.2",
|
|
152
|
+
tsdown: "^0.15.5",
|
|
153
|
+
zod: "^4.1.11",
|
|
149
154
|
"@polar-sh/better-auth": "^1.1.3",
|
|
150
155
|
"@polar-sh/sdk": "^0.34.16"
|
|
151
156
|
};
|
|
@@ -263,7 +268,7 @@ const AuthSchema = z.enum([
|
|
|
263
268
|
]).describe("Authentication provider");
|
|
264
269
|
const PaymentsSchema = z.enum(["polar", "none"]).describe("Payments provider");
|
|
265
270
|
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
|
-
|
|
271
|
+
return ![
|
|
267
272
|
"<",
|
|
268
273
|
">",
|
|
269
274
|
":",
|
|
@@ -271,8 +276,7 @@ const ProjectNameSchema = z.string().min(1, "Project name cannot be empty").max(
|
|
|
271
276
|
"|",
|
|
272
277
|
"?",
|
|
273
278
|
"*"
|
|
274
|
-
];
|
|
275
|
-
return !invalidChars.some((char) => name.includes(char));
|
|
279
|
+
].some((char) => name.includes(char));
|
|
276
280
|
}, "Project name contains invalid characters").refine((name) => name.toLowerCase() !== "node_modules", "Project name is reserved").describe("Project name or path");
|
|
277
281
|
const WebDeploySchema = z.enum([
|
|
278
282
|
"wrangler",
|
|
@@ -360,12 +364,11 @@ function validateApiFrontendCompatibility(api, frontends = []) {
|
|
|
360
364
|
function isFrontendAllowedWithBackend(frontend, backend, auth) {
|
|
361
365
|
if (backend === "convex" && frontend === "solid") return false;
|
|
362
366
|
if (auth === "clerk" && backend === "convex") {
|
|
363
|
-
|
|
367
|
+
if ([
|
|
364
368
|
"nuxt",
|
|
365
369
|
"svelte",
|
|
366
370
|
"solid"
|
|
367
|
-
];
|
|
368
|
-
if (incompatibleFrontends.includes(frontend)) return false;
|
|
371
|
+
].includes(frontend)) return false;
|
|
369
372
|
}
|
|
370
373
|
return true;
|
|
371
374
|
}
|
|
@@ -385,8 +388,7 @@ function isExampleTodoAllowed(backend, database) {
|
|
|
385
388
|
return !(backend !== "convex" && backend !== "none" && database === "none");
|
|
386
389
|
}
|
|
387
390
|
function isExampleAIAllowed(_backend, frontends = []) {
|
|
388
|
-
|
|
389
|
-
if (includesSolid) return false;
|
|
391
|
+
if (frontends.includes("solid")) return false;
|
|
390
392
|
return true;
|
|
391
393
|
}
|
|
392
394
|
function validateWebDeployRequiresWebFrontend(webDeploy, hasWebFrontendFlag) {
|
|
@@ -398,8 +400,7 @@ function validateServerDeployRequiresBackend(serverDeploy, backend) {
|
|
|
398
400
|
function validateAddonCompatibility(addon, frontend, _auth) {
|
|
399
401
|
const compatibleFrontends = ADDON_COMPATIBILITY[addon];
|
|
400
402
|
if (compatibleFrontends.length > 0) {
|
|
401
|
-
|
|
402
|
-
if (!hasCompatibleFrontend) {
|
|
403
|
+
if (!frontend.some((f) => compatibleFrontends.includes(f))) {
|
|
403
404
|
const frontendList = compatibleFrontends.join(", ");
|
|
404
405
|
return {
|
|
405
406
|
isCompatible: false,
|
|
@@ -912,7 +913,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
912
913
|
if (isCancel(frontendTypes)) return exitCancelled("Operation cancelled");
|
|
913
914
|
const result = [];
|
|
914
915
|
if (frontendTypes.includes("web")) {
|
|
915
|
-
const
|
|
916
|
+
const webOptions = [
|
|
916
917
|
{
|
|
917
918
|
value: "tanstack-router",
|
|
918
919
|
label: "TanStack Router",
|
|
@@ -948,8 +949,7 @@ async function getFrontendChoice(frontendOptions, backend, auth) {
|
|
|
948
949
|
label: "TanStack Start",
|
|
949
950
|
hint: "SSR, Server Functions, API Routes and more with TanStack Router"
|
|
950
951
|
}
|
|
951
|
-
];
|
|
952
|
-
const webOptions = allWebOptions.filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
952
|
+
].filter((option) => isFrontendAllowedWithBackend(option.value, backend, auth));
|
|
953
953
|
const webFramework = await select({
|
|
954
954
|
message: "Choose web",
|
|
955
955
|
options: webOptions,
|
|
@@ -1069,20 +1069,18 @@ async function getPackageManagerChoice(packageManager) {
|
|
|
1069
1069
|
//#region src/prompts/payments.ts
|
|
1070
1070
|
async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
1071
1071
|
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
|
-
}];
|
|
1072
|
+
if (!(auth === "better-auth" && backend !== "convex" && (frontends?.length === 0 || splitFrontends(frontends).web.length > 0))) return "none";
|
|
1083
1073
|
const response = await select({
|
|
1084
1074
|
message: "Select payments provider",
|
|
1085
|
-
options
|
|
1075
|
+
options: [{
|
|
1076
|
+
value: "polar",
|
|
1077
|
+
label: "Polar",
|
|
1078
|
+
hint: "Turn your software into a business. 6 lines of code."
|
|
1079
|
+
}, {
|
|
1080
|
+
value: "none",
|
|
1081
|
+
label: "None",
|
|
1082
|
+
hint: "No payments integration"
|
|
1083
|
+
}],
|
|
1086
1084
|
initialValue: DEFAULT_CONFIG.payments
|
|
1087
1085
|
});
|
|
1088
1086
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
@@ -1211,12 +1209,11 @@ function getDeploymentDisplay(deployment) {
|
|
|
1211
1209
|
async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []) {
|
|
1212
1210
|
if (deployment !== void 0) return deployment;
|
|
1213
1211
|
if (!hasWebFrontend(frontend)) return "none";
|
|
1214
|
-
const
|
|
1212
|
+
const options = [
|
|
1215
1213
|
"wrangler",
|
|
1216
1214
|
"alchemy",
|
|
1217
1215
|
"none"
|
|
1218
|
-
]
|
|
1219
|
-
const options = availableDeployments.map((deploy) => {
|
|
1216
|
+
].map((deploy) => {
|
|
1220
1217
|
const { label, hint } = getDeploymentDisplay(deploy);
|
|
1221
1218
|
return {
|
|
1222
1219
|
value: deploy,
|
|
@@ -1322,14 +1319,12 @@ function validateDirectoryName(name) {
|
|
|
1322
1319
|
if (name === ".") return void 0;
|
|
1323
1320
|
const result = ProjectNameSchema.safeParse(name);
|
|
1324
1321
|
if (!result.success) return result.error.issues[0]?.message || "Invalid project name";
|
|
1325
|
-
return void 0;
|
|
1326
1322
|
}
|
|
1327
1323
|
async function getProjectName(initialName) {
|
|
1328
1324
|
if (initialName) {
|
|
1329
1325
|
if (initialName === ".") return initialName;
|
|
1330
1326
|
const finalDirName = path.basename(initialName);
|
|
1331
|
-
|
|
1332
|
-
if (!validationError) {
|
|
1327
|
+
if (!validateDirectoryName(finalDirName)) {
|
|
1333
1328
|
const projectDir = path.resolve(process.cwd(), initialName);
|
|
1334
1329
|
if (isPathWithinCwd(projectDir)) return initialName;
|
|
1335
1330
|
consola.error(pc.red("Project path must be within current directory"));
|
|
@@ -1358,7 +1353,6 @@ async function getProjectName(initialName) {
|
|
|
1358
1353
|
const projectDir = path.resolve(process.cwd(), nameToUse);
|
|
1359
1354
|
if (!isPathWithinCwd(projectDir)) return "Project path must be within current directory";
|
|
1360
1355
|
}
|
|
1361
|
-
return void 0;
|
|
1362
1356
|
}
|
|
1363
1357
|
});
|
|
1364
1358
|
if (isCancel(response)) return exitCancelled("Operation cancelled.");
|
|
@@ -1372,8 +1366,7 @@ async function getProjectName(initialName) {
|
|
|
1372
1366
|
//#region src/utils/get-latest-cli-version.ts
|
|
1373
1367
|
const getLatestCLIVersion = () => {
|
|
1374
1368
|
const packageJsonPath = path.join(PKG_ROOT, "package.json");
|
|
1375
|
-
|
|
1376
|
-
return packageJsonContent.version ?? "1.0.0";
|
|
1369
|
+
return fs.readJSONSync(packageJsonPath).version ?? "1.0.0";
|
|
1377
1370
|
};
|
|
1378
1371
|
|
|
1379
1372
|
//#endregion
|
|
@@ -1386,7 +1379,7 @@ const getLatestCLIVersion = () => {
|
|
|
1386
1379
|
*/
|
|
1387
1380
|
function isTelemetryEnabled() {
|
|
1388
1381
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
1389
|
-
const BTS_TELEMETRY = "
|
|
1382
|
+
const BTS_TELEMETRY = "0";
|
|
1390
1383
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
1391
1384
|
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
1392
1385
|
return true;
|
|
@@ -1394,12 +1387,11 @@ function isTelemetryEnabled() {
|
|
|
1394
1387
|
|
|
1395
1388
|
//#endregion
|
|
1396
1389
|
//#region src/utils/analytics.ts
|
|
1397
|
-
const POSTHOG_API_KEY = "
|
|
1398
|
-
const POSTHOG_HOST = "
|
|
1390
|
+
const POSTHOG_API_KEY = "random";
|
|
1391
|
+
const POSTHOG_HOST = "random";
|
|
1399
1392
|
function generateSessionId() {
|
|
1400
1393
|
const rand = Math.random().toString(36).slice(2);
|
|
1401
|
-
|
|
1402
|
-
return `cli_${now}${rand}`;
|
|
1394
|
+
return `cli_${Date.now().toString(36)}${rand}`;
|
|
1403
1395
|
}
|
|
1404
1396
|
async function trackProjectCreation(config, disableAnalytics = false) {
|
|
1405
1397
|
if (!isTelemetryEnabled() || disableAnalytics) return;
|
|
@@ -1506,9 +1498,7 @@ function generateReproducibleCommand(config) {
|
|
|
1506
1498
|
async function handleDirectoryConflict(currentPathInput, silent = false) {
|
|
1507
1499
|
while (true) {
|
|
1508
1500
|
const resolvedPath = path.resolve(process.cwd(), currentPathInput);
|
|
1509
|
-
|
|
1510
|
-
const dirIsNotEmpty = dirExists && (await fs.readdir(resolvedPath)).length > 0;
|
|
1511
|
-
if (!dirIsNotEmpty) return {
|
|
1501
|
+
if (!(await fs.pathExists(resolvedPath) && (await fs.readdir(resolvedPath)).length > 0)) return {
|
|
1512
1502
|
finalPathInput: currentPathInput,
|
|
1513
1503
|
shouldClearDirectory: false
|
|
1514
1504
|
};
|
|
@@ -1622,14 +1612,12 @@ const renderTitle = () => {
|
|
|
1622
1612
|
const terminalWidth = process.stdout.columns || 80;
|
|
1623
1613
|
const titleLines = TITLE_TEXT.split("\n");
|
|
1624
1614
|
const titleWidth = Math.max(...titleLines.map((line) => line.length));
|
|
1625
|
-
if (terminalWidth < titleWidth)
|
|
1626
|
-
const simplifiedTitle = `
|
|
1615
|
+
if (terminalWidth < titleWidth) console.log(gradient(Object.values(catppuccinTheme)).multiline(`
|
|
1627
1616
|
╔══════════════════╗
|
|
1628
1617
|
║ Better T Stack ║
|
|
1629
1618
|
╚══════════════════╝
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
} else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1619
|
+
`));
|
|
1620
|
+
else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
|
|
1633
1621
|
};
|
|
1634
1622
|
|
|
1635
1623
|
//#endregion
|
|
@@ -1755,8 +1743,7 @@ function validateConvexConstraints(config, providedFlags) {
|
|
|
1755
1743
|
"tanstack-start",
|
|
1756
1744
|
"next"
|
|
1757
1745
|
];
|
|
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'.");
|
|
1746
|
+
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
1747
|
}
|
|
1761
1748
|
}
|
|
1762
1749
|
function validateBackendNoneConstraints(config, providedFlags) {
|
|
@@ -1987,8 +1974,7 @@ async function updateBtsConfig(projectDir, updates) {
|
|
|
1987
1974
|
try {
|
|
1988
1975
|
const configPath = path.join(projectDir, BTS_CONFIG_FILE);
|
|
1989
1976
|
if (!await fs.pathExists(configPath)) return;
|
|
1990
|
-
|
|
1991
|
-
let modifiedContent = configContent;
|
|
1977
|
+
let modifiedContent = await fs.readFile(configPath, "utf-8");
|
|
1992
1978
|
for (const [key, value] of Object.entries(updates)) {
|
|
1993
1979
|
const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
|
|
1994
1980
|
tabSize: 2,
|
|
@@ -2010,15 +1996,17 @@ const addPackageDependency = async (opts) => {
|
|
|
2010
1996
|
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
2011
1997
|
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
2012
1998
|
for (const pkgName of dependencies) {
|
|
2013
|
-
const version =
|
|
1999
|
+
const version = dependencyVersionMap[pkgName];
|
|
2014
2000
|
if (version) pkgJson.dependencies[pkgName] = version;
|
|
2015
2001
|
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
2016
2002
|
}
|
|
2017
2003
|
for (const pkgName of devDependencies) {
|
|
2018
|
-
const version =
|
|
2004
|
+
const version = dependencyVersionMap[pkgName];
|
|
2019
2005
|
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
2020
2006
|
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
2021
2007
|
}
|
|
2008
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2009
|
+
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2022
2010
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2023
2011
|
};
|
|
2024
2012
|
|
|
@@ -2078,8 +2066,7 @@ async function setupFumadocs(config) {
|
|
|
2078
2066
|
initialValue: "next-mdx"
|
|
2079
2067
|
});
|
|
2080
2068
|
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`;
|
|
2069
|
+
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
|
|
2083
2070
|
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2084
2071
|
await execa(fumadocsInitCommand, {
|
|
2085
2072
|
cwd: path.join(projectDir, "apps"),
|
|
@@ -2112,27 +2099,26 @@ async function setupRuler(config) {
|
|
|
2112
2099
|
log.error(pc.red("Ruler template directory not found. Please ensure ruler addon is properly installed."));
|
|
2113
2100
|
return;
|
|
2114
2101
|
}
|
|
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
2102
|
const selectedEditors = await autocompleteMultiselect({
|
|
2134
2103
|
message: "Select AI assistants for Ruler",
|
|
2135
|
-
options: Object.entries(
|
|
2104
|
+
options: Object.entries({
|
|
2105
|
+
amp: { label: "AMP" },
|
|
2106
|
+
copilot: { label: "GitHub Copilot" },
|
|
2107
|
+
claude: { label: "Claude Code" },
|
|
2108
|
+
codex: { label: "OpenAI Codex CLI" },
|
|
2109
|
+
cursor: { label: "Cursor" },
|
|
2110
|
+
windsurf: { label: "Windsurf" },
|
|
2111
|
+
cline: { label: "Cline" },
|
|
2112
|
+
aider: { label: "Aider" },
|
|
2113
|
+
firebase: { label: "Firebase Studio" },
|
|
2114
|
+
"gemini-cli": { label: "Gemini CLI" },
|
|
2115
|
+
junie: { label: "Junie" },
|
|
2116
|
+
kilocode: { label: "Kilo Code" },
|
|
2117
|
+
opencode: { label: "OpenCode" },
|
|
2118
|
+
crush: { label: "Crush" },
|
|
2119
|
+
zed: { label: "Zed" },
|
|
2120
|
+
qwen: { label: "Qwen" }
|
|
2121
|
+
}).map(([key, v]) => ({
|
|
2136
2122
|
value: key,
|
|
2137
2123
|
label: v.label
|
|
2138
2124
|
})),
|
|
@@ -2145,8 +2131,7 @@ async function setupRuler(config) {
|
|
|
2145
2131
|
return;
|
|
2146
2132
|
}
|
|
2147
2133
|
const configFile = path.join(rulerDir, "ruler.toml");
|
|
2148
|
-
|
|
2149
|
-
let updatedConfig = currentConfig;
|
|
2134
|
+
let updatedConfig = await fs.readFile(configFile, "utf-8");
|
|
2150
2135
|
const defaultAgentsLine = `default_agents = [${selectedEditors.map((editor) => `"${editor}"`).join(", ")}]`;
|
|
2151
2136
|
updatedConfig = updatedConfig.replace(/default_agents = \[\]/, defaultAgentsLine);
|
|
2152
2137
|
await fs.writeFile(configFile, updatedConfig);
|
|
@@ -2189,7 +2174,7 @@ async function setupStarlight(config) {
|
|
|
2189
2174
|
const s = spinner();
|
|
2190
2175
|
try {
|
|
2191
2176
|
s.start("Setting up Starlight docs...");
|
|
2192
|
-
const
|
|
2177
|
+
const commandWithArgs = `create-astro@latest ${[
|
|
2193
2178
|
"docs",
|
|
2194
2179
|
"--template",
|
|
2195
2180
|
"starlight",
|
|
@@ -2198,9 +2183,7 @@ async function setupStarlight(config) {
|
|
|
2198
2183
|
"tailwind",
|
|
2199
2184
|
"--no-git",
|
|
2200
2185
|
"--skip-houston"
|
|
2201
|
-
]
|
|
2202
|
-
const starlightArgsString = starlightArgs.join(" ");
|
|
2203
|
-
const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
|
|
2186
|
+
].join(" ")}`;
|
|
2204
2187
|
const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2205
2188
|
await execa(starlightInitCommand, {
|
|
2206
2189
|
cwd: path.join(projectDir, "apps"),
|
|
@@ -2246,7 +2229,7 @@ async function setupTauri(config) {
|
|
|
2246
2229
|
const hasNext = frontend.includes("next");
|
|
2247
2230
|
const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
|
|
2248
2231
|
const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
|
|
2249
|
-
const
|
|
2232
|
+
const commandWithArgs = `@tauri-apps/cli@latest ${[
|
|
2250
2233
|
"init",
|
|
2251
2234
|
`--app-name=${path.basename(projectDir)}`,
|
|
2252
2235
|
`--window-title=${path.basename(projectDir)}`,
|
|
@@ -2254,9 +2237,7 @@ async function setupTauri(config) {
|
|
|
2254
2237
|
`--dev-url=${devUrl}`,
|
|
2255
2238
|
`--before-dev-command="${packageManager} run dev"`,
|
|
2256
2239
|
`--before-build-command="${packageManager} run build"`
|
|
2257
|
-
]
|
|
2258
|
-
const tauriArgsString = tauriArgs.join(" ");
|
|
2259
|
-
const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
|
|
2240
|
+
].join(" ")}`;
|
|
2260
2241
|
const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2261
2242
|
await execa(tauriInitCommand, {
|
|
2262
2243
|
cwd: clientPackageDir,
|
|
@@ -2330,8 +2311,7 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2330
2311
|
if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
|
|
2331
2312
|
if (rules.length > 0) ultraciteArgs.push("--rules", ...rules);
|
|
2332
2313
|
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2333
|
-
const
|
|
2334
|
-
const commandWithArgs = `ultracite@latest ${ultraciteArgsString} --skip-install`;
|
|
2314
|
+
const commandWithArgs = `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`;
|
|
2335
2315
|
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2336
2316
|
await execa(ultraciteInitCommand, {
|
|
2337
2317
|
cwd: projectDir,
|
|
@@ -2371,8 +2351,7 @@ function ensureArrayProperty(obj, name) {
|
|
|
2371
2351
|
async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
2372
2352
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
2373
2353
|
if (!sourceFile) throw new Error("vite config not found");
|
|
2374
|
-
|
|
2375
|
-
if (!hasImport) sourceFile.insertImportDeclaration(0, {
|
|
2354
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
|
|
2376
2355
|
namedImports: ["VitePWA"],
|
|
2377
2356
|
moduleSpecifier: "vite-plugin-pwa"
|
|
2378
2357
|
});
|
|
@@ -2381,12 +2360,10 @@ async function addPwaToViteConfig(viteConfigPath, projectName) {
|
|
|
2381
2360
|
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
2382
2361
|
});
|
|
2383
2362
|
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
2384
|
-
const
|
|
2385
|
-
const configObject = callExpr.getArguments()[0];
|
|
2363
|
+
const configObject = defineCall.getArguments()[0];
|
|
2386
2364
|
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
2387
2365
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
2388
|
-
|
|
2389
|
-
if (!alreadyPresent) pluginsArray.addElement(`VitePWA({
|
|
2366
|
+
if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
|
|
2390
2367
|
registerType: "autoUpdate",
|
|
2391
2368
|
manifest: {
|
|
2392
2369
|
name: "${projectName}",
|
|
@@ -2487,12 +2464,11 @@ async function setupHusky(projectDir, linter) {
|
|
|
2487
2464
|
}
|
|
2488
2465
|
}
|
|
2489
2466
|
async function setupPwa(projectDir, frontends) {
|
|
2490
|
-
|
|
2467
|
+
if (!frontends.some((f) => [
|
|
2491
2468
|
"react-router",
|
|
2492
2469
|
"tanstack-router",
|
|
2493
2470
|
"solid"
|
|
2494
|
-
].includes(f));
|
|
2495
|
-
if (!isCompatibleFrontend) return;
|
|
2471
|
+
].includes(f))) return;
|
|
2496
2472
|
const clientPackageDir = getWebAppDir(projectDir, frontends);
|
|
2497
2473
|
if (!await fs.pathExists(clientPackageDir)) return;
|
|
2498
2474
|
await addPackageDependency({
|
|
@@ -2592,8 +2568,7 @@ async function installDependencies({ projectDir, packageManager }) {
|
|
|
2592
2568
|
function initializeBiome() {
|
|
2593
2569
|
try {
|
|
2594
2570
|
const biome = new Biome();
|
|
2595
|
-
const
|
|
2596
|
-
const projectKey = result.projectKey;
|
|
2571
|
+
const projectKey = biome.openProject("./").projectKey;
|
|
2597
2572
|
biome.applyConfiguration(projectKey, {
|
|
2598
2573
|
formatter: {
|
|
2599
2574
|
enabled: true,
|
|
@@ -2615,27 +2590,25 @@ function initializeBiome() {
|
|
|
2615
2590
|
}
|
|
2616
2591
|
function isSupportedFile(filePath) {
|
|
2617
2592
|
const ext = path.extname(filePath).toLowerCase();
|
|
2618
|
-
|
|
2593
|
+
return [
|
|
2619
2594
|
".js",
|
|
2620
2595
|
".jsx",
|
|
2621
2596
|
".ts",
|
|
2622
2597
|
".tsx",
|
|
2623
2598
|
".json",
|
|
2624
2599
|
".jsonc"
|
|
2625
|
-
];
|
|
2626
|
-
return supportedExtensions.includes(ext);
|
|
2600
|
+
].includes(ext);
|
|
2627
2601
|
}
|
|
2628
2602
|
function shouldSkipFile(filePath) {
|
|
2629
2603
|
const basename = path.basename(filePath);
|
|
2630
|
-
|
|
2604
|
+
return [
|
|
2631
2605
|
".hbs",
|
|
2632
2606
|
"package-lock.json",
|
|
2633
2607
|
"yarn.lock",
|
|
2634
2608
|
"pnpm-lock.yaml",
|
|
2635
2609
|
"bun.lock",
|
|
2636
2610
|
".d.ts"
|
|
2637
|
-
];
|
|
2638
|
-
return skipPatterns.some((pattern) => basename.includes(pattern));
|
|
2611
|
+
].some((pattern) => basename.includes(pattern));
|
|
2639
2612
|
}
|
|
2640
2613
|
function formatFileWithBiome(filePath, content) {
|
|
2641
2614
|
if (!isSupportedFile(filePath) || shouldSkipFile(filePath)) return null;
|
|
@@ -2672,8 +2645,7 @@ async function processTemplate(srcPath, destPath, context) {
|
|
|
2672
2645
|
let content;
|
|
2673
2646
|
if (srcPath.endsWith(".hbs")) {
|
|
2674
2647
|
const templateContent = await fs.readFile(srcPath, "utf-8");
|
|
2675
|
-
|
|
2676
|
-
content = template(context);
|
|
2648
|
+
content = handlebars.compile(templateContent)(context);
|
|
2677
2649
|
} else content = await fs.readFile(srcPath, "utf-8");
|
|
2678
2650
|
try {
|
|
2679
2651
|
const formattedContent = await formatFileWithBiome(destPath, content);
|
|
@@ -2804,23 +2776,28 @@ async function setupBackendFramework(projectDir, context) {
|
|
|
2804
2776
|
return;
|
|
2805
2777
|
}
|
|
2806
2778
|
await fs.ensureDir(serverAppDir);
|
|
2807
|
-
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/
|
|
2779
|
+
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
|
|
2808
2780
|
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2809
2781
|
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2810
2782
|
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2811
2783
|
if (context.api !== "none") {
|
|
2784
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
2785
|
+
await fs.ensureDir(apiPackageDir);
|
|
2812
2786
|
const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
|
|
2813
|
-
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir,
|
|
2814
|
-
|
|
2815
|
-
if (
|
|
2787
|
+
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, apiPackageDir, context);
|
|
2788
|
+
let apiServerFrameworkDir = "";
|
|
2789
|
+
if (context.backend === "next") apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
|
|
2790
|
+
else apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/rest`);
|
|
2791
|
+
if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, apiPackageDir, context, true);
|
|
2792
|
+
}
|
|
2793
|
+
if (context.database !== "none" && context.orm !== "none") {
|
|
2794
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2795
|
+
await fs.ensureDir(dbPackageDir);
|
|
2796
|
+
const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
|
|
2797
|
+
if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
|
|
2798
|
+
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2799
|
+
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
|
|
2816
2800
|
}
|
|
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
2801
|
}
|
|
2825
2802
|
async function setupAuthTemplate(projectDir, context) {
|
|
2826
2803
|
if (!context.auth || context.auth === "none") return;
|
|
@@ -2901,20 +2878,24 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2901
2878
|
return;
|
|
2902
2879
|
}
|
|
2903
2880
|
if (serverAppDirExists && context.backend !== "convex") {
|
|
2881
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2882
|
+
await fs.ensureDir(authPackageDir);
|
|
2904
2883
|
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2905
|
-
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc,
|
|
2884
|
+
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
|
|
2906
2885
|
if (context.backend === "next") {
|
|
2907
2886
|
const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
|
|
2908
|
-
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc,
|
|
2887
|
+
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, authPackageDir, context);
|
|
2909
2888
|
}
|
|
2910
2889
|
if (context.orm !== "none" && context.database !== "none") {
|
|
2890
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2891
|
+
await fs.ensureDir(dbPackageDir);
|
|
2911
2892
|
const orm = context.orm;
|
|
2912
2893
|
const db = context.database;
|
|
2913
2894
|
let authDbSrc = "";
|
|
2914
2895
|
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
|
|
2915
2896
|
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
|
|
2916
2897
|
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,
|
|
2898
|
+
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
|
|
2918
2899
|
}
|
|
2919
2900
|
}
|
|
2920
2901
|
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
@@ -2961,8 +2942,10 @@ async function setupPaymentsTemplate(projectDir, context) {
|
|
|
2961
2942
|
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2962
2943
|
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2963
2944
|
if (serverAppDirExists && context.backend !== "convex") {
|
|
2945
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2946
|
+
await fs.ensureDir(authPackageDir);
|
|
2964
2947
|
const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
|
|
2965
|
-
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc,
|
|
2948
|
+
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
|
|
2966
2949
|
}
|
|
2967
2950
|
const hasReactWeb = context.frontend.some((f) => [
|
|
2968
2951
|
"tanstack-router",
|
|
@@ -3040,15 +3023,21 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3040
3023
|
const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
|
|
3041
3024
|
if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
|
|
3042
3025
|
const exampleServerSrc = path.join(exampleBaseDir, "server");
|
|
3043
|
-
if (
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3026
|
+
if (context.api !== "none") {
|
|
3027
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
3028
|
+
await fs.ensureDir(apiPackageDir);
|
|
3029
|
+
const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
|
|
3030
|
+
if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
|
|
3046
3031
|
}
|
|
3047
3032
|
if (context.orm !== "none" && context.database !== "none") {
|
|
3048
|
-
const
|
|
3049
|
-
|
|
3033
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
3034
|
+
await fs.ensureDir(dbPackageDir);
|
|
3050
3035
|
const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
|
|
3051
|
-
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc,
|
|
3036
|
+
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
|
|
3037
|
+
}
|
|
3038
|
+
if (example === "ai" && context.backend === "next") {
|
|
3039
|
+
const aiNextServerSrc = path.join(exampleServerSrc, "next");
|
|
3040
|
+
if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
|
|
3052
3041
|
}
|
|
3053
3042
|
}
|
|
3054
3043
|
if (webAppDirExists) {
|
|
@@ -3115,9 +3104,9 @@ async function handleExtras(projectDir, context) {
|
|
|
3115
3104
|
}
|
|
3116
3105
|
async function setupDockerComposeTemplates(projectDir, context) {
|
|
3117
3106
|
if (context.dbSetup !== "docker" || context.database === "none") return;
|
|
3118
|
-
const
|
|
3107
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
3119
3108
|
const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
|
|
3120
|
-
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir,
|
|
3109
|
+
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
|
|
3121
3110
|
}
|
|
3122
3111
|
async function setupDeploymentTemplates(projectDir, context) {
|
|
3123
3112
|
if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") if (context.webDeploy === "alchemy" && context.serverDeploy === "alchemy") {
|
|
@@ -3175,8 +3164,7 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3175
3164
|
async function addAddonsToProject(input) {
|
|
3176
3165
|
try {
|
|
3177
3166
|
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.");
|
|
3167
|
+
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
3168
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
3181
3169
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
3182
3170
|
const config = {
|
|
@@ -3323,15 +3311,13 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3323
3311
|
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
|
|
3324
3312
|
}
|
|
3325
3313
|
const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
|
|
3326
|
-
|
|
3314
|
+
await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
|
|
3327
3315
|
|
|
3328
3316
|
export default defineCloudflareConfig({});
|
|
3329
|
-
|
|
3330
|
-
await fs.writeFile(openNextConfigPath, openNextConfigContent);
|
|
3317
|
+
`);
|
|
3331
3318
|
const gitignorePath = path.join(webAppDir, ".gitignore");
|
|
3332
3319
|
if (await fs.pathExists(gitignorePath)) {
|
|
3333
|
-
|
|
3334
|
-
if (!gitignoreContent.includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
3320
|
+
if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
|
|
3335
3321
|
} else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
|
|
3336
3322
|
}
|
|
3337
3323
|
|
|
@@ -3367,8 +3353,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3367
3353
|
quoteKind: QuoteKind.Double
|
|
3368
3354
|
} });
|
|
3369
3355
|
project.addSourceFileAtPath(nuxtConfigPath);
|
|
3370
|
-
const
|
|
3371
|
-
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3356
|
+
const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
|
|
3372
3357
|
if (!exportAssignment) return;
|
|
3373
3358
|
const defineConfigCall = exportAssignment.getExpression();
|
|
3374
3359
|
if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
|
|
@@ -3389,8 +3374,7 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3389
3374
|
if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
|
|
3390
3375
|
const initializer = modulesProperty.getInitializer();
|
|
3391
3376
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3392
|
-
|
|
3393
|
-
if (!hasModule) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
3377
|
+
if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
|
|
3394
3378
|
}
|
|
3395
3379
|
} else if (!modulesProperty) configObject.addPropertyAssignment({
|
|
3396
3380
|
name: "modules",
|
|
@@ -3477,8 +3461,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3477
3461
|
} });
|
|
3478
3462
|
project.addSourceFileAtPath(svelteConfigPath);
|
|
3479
3463
|
const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
|
|
3480
|
-
const
|
|
3481
|
-
const adapterImport = importDeclarations.find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3464
|
+
const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
|
|
3482
3465
|
if (adapterImport) {
|
|
3483
3466
|
adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
|
|
3484
3467
|
adapterImport.removeDefaultImport();
|
|
@@ -3604,8 +3587,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3604
3587
|
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
3605
3588
|
const initializer = pluginsProperty.getInitializer();
|
|
3606
3589
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3607
|
-
|
|
3608
|
-
if (!hasShim) initializer.addElement("alchemy()");
|
|
3590
|
+
if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
|
|
3609
3591
|
const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
|
|
3610
3592
|
let needsReactPlugin = false;
|
|
3611
3593
|
tanstackElements.forEach((element) => {
|
|
@@ -3623,8 +3605,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3623
3605
|
name: "target",
|
|
3624
3606
|
initializer: "\"cloudflare-module\""
|
|
3625
3607
|
});
|
|
3626
|
-
|
|
3627
|
-
if (!hasCustomViteReactPlugin) configObj.addPropertyAssignment({
|
|
3608
|
+
if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
|
|
3628
3609
|
name: "customViteReactPlugin",
|
|
3629
3610
|
initializer: "true"
|
|
3630
3611
|
});
|
|
@@ -3645,7 +3626,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3645
3626
|
console.warn("Failed to update vite.config.ts:", error);
|
|
3646
3627
|
}
|
|
3647
3628
|
const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
|
|
3648
|
-
|
|
3629
|
+
await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
|
|
3649
3630
|
|
|
3650
3631
|
export default defineNitroConfig({
|
|
3651
3632
|
preset: "cloudflare-module",
|
|
@@ -3653,8 +3634,7 @@ export default defineNitroConfig({
|
|
|
3653
3634
|
nodeCompat: true,
|
|
3654
3635
|
},
|
|
3655
3636
|
});
|
|
3656
|
-
|
|
3657
|
-
await fs.writeFile(nitroConfigPath, nitroConfigContent, "utf-8");
|
|
3637
|
+
`, "utf-8");
|
|
3658
3638
|
}
|
|
3659
3639
|
|
|
3660
3640
|
//#endregion
|
|
@@ -3772,8 +3752,7 @@ async function setupNuxtWorkersDeploy(projectDir, packageManager) {
|
|
|
3772
3752
|
if (modulesProp && modulesProp.getKind() === SyntaxKind.PropertyAssignment) {
|
|
3773
3753
|
const arrayExpr = modulesProp.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression);
|
|
3774
3754
|
if (arrayExpr) {
|
|
3775
|
-
|
|
3776
|
-
if (!alreadyHas) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3755
|
+
if (!arrayExpr.getElements().some((el) => el.getText().replace(/['"`]/g, "") === "nitro-cloudflare-dev")) arrayExpr.addElement("'nitro-cloudflare-dev'");
|
|
3777
3756
|
}
|
|
3778
3757
|
} else configObj.addPropertyAssignment({
|
|
3779
3758
|
name: "modules",
|
|
@@ -3808,13 +3787,10 @@ async function setupSvelteWorkersDeploy(projectDir, packageManager) {
|
|
|
3808
3787
|
if (!sourceFile) return;
|
|
3809
3788
|
const adapterImport = sourceFile.getImportDeclarations().find((imp) => ["@sveltejs/adapter-auto", "@sveltejs/adapter-node"].includes(imp.getModuleSpecifierValue()));
|
|
3810
3789
|
if (adapterImport) adapterImport.setModuleSpecifier("@sveltejs/adapter-cloudflare");
|
|
3811
|
-
else {
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3816
|
-
});
|
|
3817
|
-
}
|
|
3790
|
+
else if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@sveltejs/adapter-cloudflare")) sourceFile.insertImportDeclaration(0, {
|
|
3791
|
+
defaultImport: "adapter",
|
|
3792
|
+
moduleSpecifier: "@sveltejs/adapter-cloudflare"
|
|
3793
|
+
});
|
|
3818
3794
|
await tsProject.save();
|
|
3819
3795
|
}
|
|
3820
3796
|
}
|
|
@@ -3865,8 +3841,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
3865
3841
|
const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
|
|
3866
3842
|
if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
|
|
3867
3843
|
else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
|
3868
|
-
|
|
3869
|
-
if (!hasReactPlugin) {
|
|
3844
|
+
if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
|
|
3870
3845
|
const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
|
|
3871
3846
|
if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
|
|
3872
3847
|
else pluginsArray.addElement(`${reactPluginIdentifier}()`);
|
|
@@ -3886,8 +3861,7 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
3886
3861
|
});
|
|
3887
3862
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3888
3863
|
if (!sourceFile) throw new Error("vite.config.ts not found in web app directory");
|
|
3889
|
-
|
|
3890
|
-
if (!hasCloudflareImport) sourceFile.insertImportDeclaration(0, {
|
|
3864
|
+
if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "@cloudflare/vite-plugin")) sourceFile.insertImportDeclaration(0, {
|
|
3891
3865
|
namedImports: ["cloudflare"],
|
|
3892
3866
|
moduleSpecifier: "@cloudflare/vite-plugin"
|
|
3893
3867
|
});
|
|
@@ -3896,12 +3870,10 @@ async function setupWorkersVitePlugin(projectDir) {
|
|
|
3896
3870
|
return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
|
|
3897
3871
|
});
|
|
3898
3872
|
if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
|
|
3899
|
-
const
|
|
3900
|
-
const configObject = callExpr.getArguments()[0];
|
|
3873
|
+
const configObject = defineCall.getArguments()[0];
|
|
3901
3874
|
if (!configObject) throw new Error("defineConfig argument is not an object literal");
|
|
3902
3875
|
const pluginsArray = ensureArrayProperty(configObject, "plugins");
|
|
3903
|
-
|
|
3904
|
-
if (!hasCloudflarePlugin) pluginsArray.addElement("cloudflare()");
|
|
3876
|
+
if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.addElement("cloudflare()");
|
|
3905
3877
|
await tsProject.save();
|
|
3906
3878
|
}
|
|
3907
3879
|
|
|
@@ -3960,8 +3932,7 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
|
3960
3932
|
async function addDeploymentToProject(input) {
|
|
3961
3933
|
try {
|
|
3962
3934
|
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.");
|
|
3935
|
+
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
3936
|
const detectedConfig = await detectProjectConfig(projectDir);
|
|
3966
3937
|
if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
|
|
3967
3938
|
if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
|
|
@@ -4007,6 +3978,110 @@ async function addDeploymentToProject(input) {
|
|
|
4007
3978
|
}
|
|
4008
3979
|
}
|
|
4009
3980
|
|
|
3981
|
+
//#endregion
|
|
3982
|
+
//#region src/utils/setup-catalogs.ts
|
|
3983
|
+
async function setupCatalogs(projectDir, options) {
|
|
3984
|
+
if (options.packageManager === "npm") return;
|
|
3985
|
+
const packagePaths = [
|
|
3986
|
+
"apps/server",
|
|
3987
|
+
"apps/web",
|
|
3988
|
+
"packages/api",
|
|
3989
|
+
"packages/db",
|
|
3990
|
+
"packages/auth",
|
|
3991
|
+
"packages/backend"
|
|
3992
|
+
];
|
|
3993
|
+
const packagesInfo = [];
|
|
3994
|
+
for (const pkgPath of packagePaths) {
|
|
3995
|
+
const fullPath = path.join(projectDir, pkgPath);
|
|
3996
|
+
const pkgJsonPath = path.join(fullPath, "package.json");
|
|
3997
|
+
if (await fs.pathExists(pkgJsonPath)) {
|
|
3998
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
3999
|
+
packagesInfo.push({
|
|
4000
|
+
path: fullPath,
|
|
4001
|
+
dependencies: pkgJson.dependencies || {},
|
|
4002
|
+
devDependencies: pkgJson.devDependencies || {}
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
4006
|
+
const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
|
|
4007
|
+
if (Object.keys(catalog).length === 0) return;
|
|
4008
|
+
if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
|
|
4009
|
+
else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
|
|
4010
|
+
await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
|
|
4011
|
+
}
|
|
4012
|
+
function findDuplicateDependencies(packagesInfo, projectName) {
|
|
4013
|
+
const depCount = /* @__PURE__ */ new Map();
|
|
4014
|
+
const projectScope = `@${projectName}/`;
|
|
4015
|
+
for (const pkg of packagesInfo) {
|
|
4016
|
+
const allDeps = {
|
|
4017
|
+
...pkg.dependencies,
|
|
4018
|
+
...pkg.devDependencies
|
|
4019
|
+
};
|
|
4020
|
+
for (const [depName, version] of Object.entries(allDeps)) {
|
|
4021
|
+
if (depName.startsWith(projectScope)) continue;
|
|
4022
|
+
if (version.startsWith("workspace:")) continue;
|
|
4023
|
+
const existing = depCount.get(depName);
|
|
4024
|
+
if (existing) existing.packages.push(pkg.path);
|
|
4025
|
+
else depCount.set(depName, {
|
|
4026
|
+
version,
|
|
4027
|
+
packages: [pkg.path]
|
|
4028
|
+
});
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
const catalog = {};
|
|
4032
|
+
for (const [depName, info] of depCount.entries()) if (info.packages.length > 1) catalog[depName] = info.version;
|
|
4033
|
+
return catalog;
|
|
4034
|
+
}
|
|
4035
|
+
async function setupBunCatalogs(projectDir, catalog) {
|
|
4036
|
+
const rootPkgJsonPath = path.join(projectDir, "package.json");
|
|
4037
|
+
const rootPkgJson = await fs.readJson(rootPkgJsonPath);
|
|
4038
|
+
if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
|
|
4039
|
+
if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
|
|
4040
|
+
packages: rootPkgJson.workspaces,
|
|
4041
|
+
catalog
|
|
4042
|
+
};
|
|
4043
|
+
else if (typeof rootPkgJson.workspaces === "object") {
|
|
4044
|
+
if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
|
|
4045
|
+
rootPkgJson.workspaces.catalog = {
|
|
4046
|
+
...rootPkgJson.workspaces.catalog,
|
|
4047
|
+
...catalog
|
|
4048
|
+
};
|
|
4049
|
+
}
|
|
4050
|
+
await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
|
|
4051
|
+
}
|
|
4052
|
+
async function setupPnpmCatalogs(projectDir, catalog) {
|
|
4053
|
+
const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
|
|
4054
|
+
if (!await fs.pathExists(workspaceYamlPath)) return;
|
|
4055
|
+
const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
|
|
4056
|
+
const workspaceYaml = yaml.parse(workspaceContent);
|
|
4057
|
+
if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
|
|
4058
|
+
workspaceYaml.catalog = {
|
|
4059
|
+
...workspaceYaml.catalog,
|
|
4060
|
+
...catalog
|
|
4061
|
+
};
|
|
4062
|
+
await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
|
|
4063
|
+
}
|
|
4064
|
+
async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
|
|
4065
|
+
for (const pkg of packagesInfo) {
|
|
4066
|
+
const pkgJsonPath = path.join(pkg.path, "package.json");
|
|
4067
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
4068
|
+
let updated = false;
|
|
4069
|
+
if (pkgJson.dependencies) {
|
|
4070
|
+
for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
|
|
4071
|
+
pkgJson.dependencies[depName] = "catalog:";
|
|
4072
|
+
updated = true;
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
if (pkgJson.devDependencies) {
|
|
4076
|
+
for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
|
|
4077
|
+
pkgJson.devDependencies[depName] = "catalog:";
|
|
4078
|
+
updated = true;
|
|
4079
|
+
}
|
|
4080
|
+
}
|
|
4081
|
+
if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4010
4085
|
//#endregion
|
|
4011
4086
|
//#region src/helpers/addons/examples-setup.ts
|
|
4012
4087
|
async function setupExamples(config) {
|
|
@@ -4015,10 +4090,10 @@ async function setupExamples(config) {
|
|
|
4015
4090
|
if (examples.includes("ai")) {
|
|
4016
4091
|
const webClientDir = path.join(projectDir, "apps/web");
|
|
4017
4092
|
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
4018
|
-
const
|
|
4093
|
+
const apiDir = path.join(projectDir, "packages/api");
|
|
4019
4094
|
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
4020
4095
|
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
4021
|
-
const
|
|
4096
|
+
const apiDirExists = await fs.pathExists(apiDir);
|
|
4022
4097
|
const hasNuxt = frontend.includes("nuxt");
|
|
4023
4098
|
const hasSvelte = frontend.includes("svelte");
|
|
4024
4099
|
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
@@ -4039,9 +4114,9 @@ async function setupExamples(config) {
|
|
|
4039
4114
|
dependencies: ["ai", "@ai-sdk/react"],
|
|
4040
4115
|
projectDir: nativeClientDir
|
|
4041
4116
|
});
|
|
4042
|
-
if (
|
|
4117
|
+
if (apiDirExists && backend !== "none") await addPackageDependency({
|
|
4043
4118
|
dependencies: ["ai", "@ai-sdk/google"],
|
|
4044
|
-
projectDir:
|
|
4119
|
+
projectDir: apiDir
|
|
4045
4120
|
});
|
|
4046
4121
|
}
|
|
4047
4122
|
}
|
|
@@ -4131,8 +4206,7 @@ function getQueryDependencies(frontend) {
|
|
|
4131
4206
|
"native-unistyles"
|
|
4132
4207
|
];
|
|
4133
4208
|
const deps = {};
|
|
4134
|
-
|
|
4135
|
-
if (needsReactQuery) {
|
|
4209
|
+
if (frontend.some((f) => reactBasedFrontends.includes(f))) {
|
|
4136
4210
|
const hasReactWeb = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
|
|
4137
4211
|
const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
|
|
4138
4212
|
if (hasReactWeb) deps.web = {
|
|
@@ -4158,32 +4232,54 @@ function getConvexDependencies(frontend) {
|
|
|
4158
4232
|
return deps;
|
|
4159
4233
|
}
|
|
4160
4234
|
async function setupApi(config) {
|
|
4161
|
-
const { api, projectName, frontend, backend, packageManager, projectDir } = config;
|
|
4235
|
+
const { api, projectName, frontend, backend, packageManager, projectDir, auth } = config;
|
|
4162
4236
|
const isConvex = backend === "convex";
|
|
4163
4237
|
const webDir = path.join(projectDir, "apps/web");
|
|
4164
4238
|
const nativeDir = path.join(projectDir, "apps/native");
|
|
4165
4239
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4166
4240
|
const webDirExists = await fs.pathExists(webDir);
|
|
4167
4241
|
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
4168
|
-
|
|
4242
|
+
await fs.pathExists(serverDir);
|
|
4169
4243
|
const frontendType = getFrontendType(frontend);
|
|
4170
4244
|
if (!isConvex && api !== "none") {
|
|
4171
4245
|
const apiDeps = getApiDependencies(api, frontendType);
|
|
4172
|
-
|
|
4246
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
4247
|
+
if (apiDeps.server) {
|
|
4173
4248
|
await addPackageDependency({
|
|
4174
4249
|
dependencies: apiDeps.server.dependencies,
|
|
4175
|
-
projectDir:
|
|
4250
|
+
projectDir: apiPackageDir
|
|
4251
|
+
});
|
|
4252
|
+
const frameworkDeps = [];
|
|
4253
|
+
if (backend === "hono") frameworkDeps.push("hono");
|
|
4254
|
+
else if (backend === "elysia") frameworkDeps.push("elysia");
|
|
4255
|
+
else if (backend === "express") frameworkDeps.push("express", "@types/express");
|
|
4256
|
+
else if (backend === "fastify") frameworkDeps.push("fastify");
|
|
4257
|
+
if (frameworkDeps.length > 0) await addPackageDependency({
|
|
4258
|
+
dependencies: frameworkDeps,
|
|
4259
|
+
projectDir: apiPackageDir
|
|
4176
4260
|
});
|
|
4177
4261
|
if (api === "trpc") {
|
|
4178
4262
|
if (backend === "hono") await addPackageDependency({
|
|
4179
4263
|
dependencies: ["@hono/trpc-server"],
|
|
4180
|
-
projectDir:
|
|
4264
|
+
projectDir: apiPackageDir
|
|
4181
4265
|
});
|
|
4182
4266
|
else if (backend === "elysia") await addPackageDependency({
|
|
4183
4267
|
dependencies: ["@elysiajs/trpc"],
|
|
4184
|
-
projectDir:
|
|
4268
|
+
projectDir: apiPackageDir
|
|
4269
|
+
});
|
|
4270
|
+
else if (backend === "express") await addPackageDependency({
|
|
4271
|
+
dependencies: ["@trpc/server"],
|
|
4272
|
+
projectDir: apiPackageDir
|
|
4273
|
+
});
|
|
4274
|
+
else if (backend === "fastify") await addPackageDependency({
|
|
4275
|
+
dependencies: ["@trpc/server"],
|
|
4276
|
+
projectDir: apiPackageDir
|
|
4185
4277
|
});
|
|
4186
4278
|
}
|
|
4279
|
+
if (auth === "better-auth") await addPackageDependency({
|
|
4280
|
+
dependencies: ["better-auth"],
|
|
4281
|
+
projectDir: apiPackageDir
|
|
4282
|
+
});
|
|
4187
4283
|
}
|
|
4188
4284
|
if (webDirExists && apiDeps.web) await addPackageDependency({
|
|
4189
4285
|
dependencies: apiDeps.web.dependencies,
|
|
@@ -4227,7 +4323,7 @@ async function setupApi(config) {
|
|
|
4227
4323
|
//#endregion
|
|
4228
4324
|
//#region src/helpers/core/backend-setup.ts
|
|
4229
4325
|
async function setupBackendDependencies(config) {
|
|
4230
|
-
const { backend, runtime, api, projectDir } = config;
|
|
4326
|
+
const { backend, runtime, api, auth, examples, projectDir } = config;
|
|
4231
4327
|
if (backend === "convex") return;
|
|
4232
4328
|
const framework = backend;
|
|
4233
4329
|
const serverDir = path.join(projectDir, "apps/server");
|
|
@@ -4255,6 +4351,13 @@ async function setupBackendDependencies(config) {
|
|
|
4255
4351
|
dependencies.push("fastify", "@fastify/cors");
|
|
4256
4352
|
if (runtime === "node") devDependencies.push("tsx", "@types/node");
|
|
4257
4353
|
}
|
|
4354
|
+
if (api === "trpc") {
|
|
4355
|
+
if (framework === "express") dependencies.push("@trpc/server");
|
|
4356
|
+
else if (framework === "fastify") dependencies.push("@trpc/server");
|
|
4357
|
+
else if (runtime === "workers") dependencies.push("@trpc/server");
|
|
4358
|
+
} else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
|
|
4359
|
+
if (auth === "better-auth") dependencies.push("better-auth");
|
|
4360
|
+
if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
|
|
4258
4361
|
if (runtime === "bun") devDependencies.push("@types/bun");
|
|
4259
4362
|
if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
|
|
4260
4363
|
dependencies,
|
|
@@ -4273,7 +4376,7 @@ async function setupAuth(config) {
|
|
|
4273
4376
|
const nativeDir = path.join(projectDir, "apps/native");
|
|
4274
4377
|
const clientDirExists = await fs.pathExists(clientDir);
|
|
4275
4378
|
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
4276
|
-
|
|
4379
|
+
await fs.pathExists(serverDir);
|
|
4277
4380
|
try {
|
|
4278
4381
|
if (backend === "convex") {
|
|
4279
4382
|
if (auth === "clerk" && clientDirExists) {
|
|
@@ -4295,8 +4398,7 @@ async function setupAuth(config) {
|
|
|
4295
4398
|
}
|
|
4296
4399
|
if (auth === "better-auth") {
|
|
4297
4400
|
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4298
|
-
|
|
4299
|
-
if (convexBackendDirExists) await addPackageDependency({
|
|
4401
|
+
if (await fs.pathExists(convexBackendDir)) await addPackageDependency({
|
|
4300
4402
|
dependencies: ["better-auth", "@convex-dev/better-auth"],
|
|
4301
4403
|
customDependencies: { "better-auth": "1.3.8" },
|
|
4302
4404
|
projectDir: convexBackendDir
|
|
@@ -4330,11 +4432,13 @@ async function setupAuth(config) {
|
|
|
4330
4432
|
});
|
|
4331
4433
|
return;
|
|
4332
4434
|
}
|
|
4333
|
-
|
|
4435
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
4436
|
+
const authPackageDirExists = await fs.pathExists(authPackageDir);
|
|
4437
|
+
if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
|
|
4334
4438
|
dependencies: ["better-auth"],
|
|
4335
|
-
projectDir:
|
|
4439
|
+
projectDir: authPackageDir
|
|
4336
4440
|
});
|
|
4337
|
-
|
|
4441
|
+
if (frontend.some((f) => [
|
|
4338
4442
|
"react-router",
|
|
4339
4443
|
"tanstack-router",
|
|
4340
4444
|
"tanstack-start",
|
|
@@ -4342,8 +4446,7 @@ async function setupAuth(config) {
|
|
|
4342
4446
|
"nuxt",
|
|
4343
4447
|
"svelte",
|
|
4344
4448
|
"solid"
|
|
4345
|
-
].includes(f))
|
|
4346
|
-
if (hasWebFrontend$1 && clientDirExists) {
|
|
4449
|
+
].includes(f)) && clientDirExists) {
|
|
4347
4450
|
if (auth === "better-auth") await addPackageDependency({
|
|
4348
4451
|
dependencies: ["better-auth"],
|
|
4349
4452
|
projectDir: clientDir
|
|
@@ -4355,9 +4458,9 @@ async function setupAuth(config) {
|
|
|
4355
4458
|
dependencies: ["better-auth", "@better-auth/expo"],
|
|
4356
4459
|
projectDir: nativeDir
|
|
4357
4460
|
});
|
|
4358
|
-
if (
|
|
4461
|
+
if (authPackageDirExists) await addPackageDependency({
|
|
4359
4462
|
dependencies: ["@better-auth/expo"],
|
|
4360
|
-
projectDir:
|
|
4463
|
+
projectDir: authPackageDir
|
|
4361
4464
|
});
|
|
4362
4465
|
}
|
|
4363
4466
|
}
|
|
@@ -4411,8 +4514,7 @@ async function addEnvVariablesToFile(filePath, variables) {
|
|
|
4411
4514
|
let exampleContentToAdd = "";
|
|
4412
4515
|
for (const exampleVar of exampleVariables) {
|
|
4413
4516
|
const key = exampleVar.split("=")[0];
|
|
4414
|
-
|
|
4415
|
-
if (!regex.test(exampleEnvContent)) {
|
|
4517
|
+
if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
|
|
4416
4518
|
exampleContentToAdd += `${exampleVar}\n`;
|
|
4417
4519
|
exampleModified = true;
|
|
4418
4520
|
}
|
|
@@ -4432,8 +4534,7 @@ async function setupEnvironmentVariables(config) {
|
|
|
4432
4534
|
const hasNuxt = frontend.includes("nuxt");
|
|
4433
4535
|
const hasSvelte = frontend.includes("svelte");
|
|
4434
4536
|
const hasSolid = frontend.includes("solid");
|
|
4435
|
-
|
|
4436
|
-
if (hasWebFrontend$1) {
|
|
4537
|
+
if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
|
|
4437
4538
|
const clientDir = path.join(projectDir, "apps/web");
|
|
4438
4539
|
if (await fs.pathExists(clientDir)) {
|
|
4439
4540
|
let envVarName = "VITE_SERVER_URL";
|
|
@@ -4522,15 +4623,12 @@ async function setupEnvironmentVariables(config) {
|
|
|
4522
4623
|
const convexBackendDir = path.join(projectDir, "packages/backend");
|
|
4523
4624
|
if (await fs.pathExists(convexBackendDir)) {
|
|
4524
4625
|
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
|
|
4626
|
+
if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) await fs.appendFile(envLocalPath, `# Set Convex environment variables
|
|
4527
4627
|
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
4528
4628
|
# npx convex env set SITE_URL http://localhost:3001
|
|
4529
4629
|
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
}
|
|
4533
|
-
const convexBackendVars = [{
|
|
4630
|
+
`);
|
|
4631
|
+
await addEnvVariablesToFile(envLocalPath, [{
|
|
4534
4632
|
key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
|
|
4535
4633
|
value: "",
|
|
4536
4634
|
condition: true,
|
|
@@ -4539,15 +4637,12 @@ async function setupEnvironmentVariables(config) {
|
|
|
4539
4637
|
key: "SITE_URL",
|
|
4540
4638
|
value: "http://localhost:3001",
|
|
4541
4639
|
condition: true
|
|
4542
|
-
}];
|
|
4543
|
-
await addEnvVariablesToFile(envLocalPath, convexBackendVars);
|
|
4640
|
+
}]);
|
|
4544
4641
|
}
|
|
4545
4642
|
}
|
|
4546
4643
|
return;
|
|
4547
4644
|
}
|
|
4548
4645
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4549
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
4550
|
-
const envPath = path.join(serverDir, ".env");
|
|
4551
4646
|
let corsOrigin = "http://localhost:3001";
|
|
4552
4647
|
if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
|
|
4553
4648
|
let databaseUrl = null;
|
|
@@ -4563,80 +4658,73 @@ async function setupEnvironmentVariables(config) {
|
|
|
4563
4658
|
break;
|
|
4564
4659
|
case "sqlite":
|
|
4565
4660
|
if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
|
|
4566
|
-
else databaseUrl = "
|
|
4661
|
+
else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
|
|
4567
4662
|
break;
|
|
4568
4663
|
}
|
|
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
|
-
|
|
4664
|
+
if (await fs.pathExists(serverDir)) {
|
|
4665
|
+
const serverEnvPath = path.join(serverDir, ".env");
|
|
4666
|
+
const serverVars = [
|
|
4667
|
+
{
|
|
4668
|
+
key: "BETTER_AUTH_SECRET",
|
|
4669
|
+
value: generateAuthSecret(),
|
|
4670
|
+
condition: !!auth
|
|
4671
|
+
},
|
|
4672
|
+
{
|
|
4673
|
+
key: "BETTER_AUTH_URL",
|
|
4674
|
+
value: "http://localhost:3000",
|
|
4675
|
+
condition: !!auth
|
|
4676
|
+
},
|
|
4677
|
+
{
|
|
4678
|
+
key: "POLAR_ACCESS_TOKEN",
|
|
4679
|
+
value: "",
|
|
4680
|
+
condition: config.payments === "polar"
|
|
4681
|
+
},
|
|
4682
|
+
{
|
|
4683
|
+
key: "POLAR_SUCCESS_URL",
|
|
4684
|
+
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
4685
|
+
condition: config.payments === "polar"
|
|
4686
|
+
},
|
|
4687
|
+
{
|
|
4688
|
+
key: "CORS_ORIGIN",
|
|
4689
|
+
value: corsOrigin,
|
|
4690
|
+
condition: true
|
|
4691
|
+
},
|
|
4692
|
+
{
|
|
4693
|
+
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4694
|
+
value: "",
|
|
4695
|
+
condition: examples?.includes("ai") || false
|
|
4696
|
+
},
|
|
4697
|
+
{
|
|
4698
|
+
key: "DATABASE_URL",
|
|
4699
|
+
value: databaseUrl,
|
|
4700
|
+
condition: database !== "none" && dbSetup === "none"
|
|
4701
|
+
}
|
|
4702
|
+
];
|
|
4703
|
+
await addEnvVariablesToFile(serverEnvPath, serverVars);
|
|
4704
|
+
}
|
|
4607
4705
|
const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
|
|
4608
4706
|
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4609
4707
|
if (isUnifiedAlchemy) {
|
|
4610
4708
|
const rootEnvPath = path.join(projectDir, ".env");
|
|
4611
|
-
|
|
4709
|
+
await addEnvVariablesToFile(rootEnvPath, [{
|
|
4612
4710
|
key: "ALCHEMY_PASSWORD",
|
|
4613
4711
|
value: "please-change-this",
|
|
4614
4712
|
condition: true
|
|
4615
|
-
}];
|
|
4616
|
-
await addEnvVariablesToFile(rootEnvPath, rootAlchemyVars);
|
|
4713
|
+
}]);
|
|
4617
4714
|
} else if (isIndividualAlchemy) {
|
|
4618
4715
|
if (webDeploy === "alchemy") {
|
|
4619
4716
|
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
|
-
}
|
|
4717
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), [{
|
|
4718
|
+
key: "ALCHEMY_PASSWORD",
|
|
4719
|
+
value: "please-change-this",
|
|
4720
|
+
condition: true
|
|
4721
|
+
}]);
|
|
4639
4722
|
}
|
|
4723
|
+
if (serverDeploy === "alchemy") await addEnvVariablesToFile(path.join(serverDir, ".env"), [{
|
|
4724
|
+
key: "ALCHEMY_PASSWORD",
|
|
4725
|
+
value: "please-change-this",
|
|
4726
|
+
condition: true
|
|
4727
|
+
}]);
|
|
4640
4728
|
}
|
|
4641
4729
|
}
|
|
4642
4730
|
|
|
@@ -4671,7 +4759,7 @@ async function setupCloudflareD1(config) {
|
|
|
4671
4759
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
|
4672
4760
|
const variables = [{
|
|
4673
4761
|
key: "DATABASE_URL",
|
|
4674
|
-
value: "
|
|
4762
|
+
value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
|
|
4675
4763
|
condition: true
|
|
4676
4764
|
}];
|
|
4677
4765
|
try {
|
|
@@ -4718,13 +4806,8 @@ function getDatabaseUrl(database, projectName) {
|
|
|
4718
4806
|
//#region src/utils/command-exists.ts
|
|
4719
4807
|
async function commandExists(command) {
|
|
4720
4808
|
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;
|
|
4809
|
+
if (process.platform === "win32") return (await execa("where", [command])).exitCode === 0;
|
|
4810
|
+
return (await execa("which", [command])).exitCode === 0;
|
|
4728
4811
|
} catch {
|
|
4729
4812
|
return false;
|
|
4730
4813
|
}
|
|
@@ -4746,8 +4829,7 @@ async function checkAtlasCLI() {
|
|
|
4746
4829
|
}
|
|
4747
4830
|
async function initMongoDBAtlas(serverDir) {
|
|
4748
4831
|
try {
|
|
4749
|
-
|
|
4750
|
-
if (!hasAtlas) {
|
|
4832
|
+
if (!await checkAtlasCLI()) {
|
|
4751
4833
|
consola.error(pc.red("MongoDB Atlas CLI not found."));
|
|
4752
4834
|
log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
|
|
4753
4835
|
return null;
|
|
@@ -5152,9 +5234,8 @@ async function setupWithCreateDb(serverDir, packageManager, orm) {
|
|
|
5152
5234
|
consola$1.error("Failed to parse create-db response");
|
|
5153
5235
|
return null;
|
|
5154
5236
|
}
|
|
5155
|
-
const databaseUrl = orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString;
|
|
5156
5237
|
return {
|
|
5157
|
-
databaseUrl,
|
|
5238
|
+
databaseUrl: orm === "drizzle" ? createDbResponse.directConnectionString : createDbResponse.connectionString,
|
|
5158
5239
|
claimUrl: createDbResponse.claimUrl
|
|
5159
5240
|
};
|
|
5160
5241
|
} catch (error) {
|
|
@@ -5208,9 +5289,9 @@ async function writeEnvFile$1(projectDir, config) {
|
|
|
5208
5289
|
}
|
|
5209
5290
|
async function addDotenvImportToPrismaConfig(projectDir) {
|
|
5210
5291
|
try {
|
|
5211
|
-
const prismaConfigPath = path.join(projectDir, "
|
|
5292
|
+
const prismaConfigPath = path.join(projectDir, "packages/db/prisma.config.ts");
|
|
5212
5293
|
let content = await fs.readFile(prismaConfigPath, "utf8");
|
|
5213
|
-
content = `import "dotenv
|
|
5294
|
+
content = `import dotenv from "dotenv";\ndotenv.config({ path: "../../apps/server/.env" });\n${content}`;
|
|
5214
5295
|
await fs.writeFile(prismaConfigPath, content);
|
|
5215
5296
|
} catch (_error) {
|
|
5216
5297
|
consola$1.error("Failed to update prisma.config.ts");
|
|
@@ -5226,11 +5307,12 @@ function displayManualSetupInstructions$1() {
|
|
|
5226
5307
|
|
|
5227
5308
|
DATABASE_URL="your_database_url"`);
|
|
5228
5309
|
}
|
|
5229
|
-
async function addPrismaAccelerateExtension(
|
|
5310
|
+
async function addPrismaAccelerateExtension(projectDir) {
|
|
5230
5311
|
try {
|
|
5312
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5231
5313
|
await addPackageDependency({
|
|
5232
5314
|
dependencies: ["@prisma/extension-accelerate"],
|
|
5233
|
-
projectDir:
|
|
5315
|
+
projectDir: dbPackageDir
|
|
5234
5316
|
});
|
|
5235
5317
|
return true;
|
|
5236
5318
|
} catch (_error) {
|
|
@@ -5291,7 +5373,7 @@ async function setupPrismaPostgres(config, cliInput) {
|
|
|
5291
5373
|
await writeEnvFile$1(projectDir, prismaConfig);
|
|
5292
5374
|
if (orm === "prisma") {
|
|
5293
5375
|
await addDotenvImportToPrismaConfig(projectDir);
|
|
5294
|
-
await addPrismaAccelerateExtension(
|
|
5376
|
+
await addPrismaAccelerateExtension(projectDir);
|
|
5295
5377
|
}
|
|
5296
5378
|
const connectionType = orm === "drizzle" ? "direct connection" : "Prisma Accelerate";
|
|
5297
5379
|
log.success(pc.green(`Prisma Postgres database configured successfully with ${connectionType}!`));
|
|
@@ -5316,7 +5398,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5316
5398
|
try {
|
|
5317
5399
|
const envPath = path.join(projectDir, "apps/server", ".env");
|
|
5318
5400
|
const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
|
5319
|
-
|
|
5401
|
+
await addEnvVariablesToFile(envPath, [{
|
|
5320
5402
|
key: "DATABASE_URL",
|
|
5321
5403
|
value: dbUrlToUse,
|
|
5322
5404
|
condition: true
|
|
@@ -5324,8 +5406,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5324
5406
|
key: "DIRECT_URL",
|
|
5325
5407
|
value: dbUrlToUse,
|
|
5326
5408
|
condition: true
|
|
5327
|
-
}];
|
|
5328
|
-
await addEnvVariablesToFile(envPath, variables);
|
|
5409
|
+
}]);
|
|
5329
5410
|
return true;
|
|
5330
5411
|
} catch (error) {
|
|
5331
5412
|
consola$1.error(pc.red("Failed to update .env file for Supabase."));
|
|
@@ -5334,8 +5415,7 @@ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
|
5334
5415
|
}
|
|
5335
5416
|
}
|
|
5336
5417
|
function extractDbUrl(output) {
|
|
5337
|
-
const
|
|
5338
|
-
const url = dbUrlMatch?.[1];
|
|
5418
|
+
const url = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/)?.[1];
|
|
5339
5419
|
if (url) return url;
|
|
5340
5420
|
return null;
|
|
5341
5421
|
}
|
|
@@ -5393,18 +5473,18 @@ function displayManualSupabaseInstructions(output) {
|
|
|
5393
5473
|
log.info(`"Manual Supabase Setup Instructions:"
|
|
5394
5474
|
1. Ensure Docker is installed and running.
|
|
5395
5475
|
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 \`
|
|
5476
|
+
3. Run \`supabase init\` in your project's \`packages/db\` directory.
|
|
5477
|
+
4. Run \`supabase start\` in your project's \`packages/db\` directory.
|
|
5398
5478
|
5. Copy the 'DB URL' from the output.${output ? `
|
|
5399
5479
|
${pc.bold("Relevant output from `supabase start`:")}
|
|
5400
5480
|
${pc.dim(output)}` : ""}
|
|
5401
|
-
6. Add the DB URL to the .env file in \`
|
|
5481
|
+
6. Add the DB URL to the .env file in \`packages/db/.env\` as \`DATABASE_URL\`:
|
|
5402
5482
|
${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
|
|
5403
5483
|
}
|
|
5404
5484
|
async function setupSupabase(config, cliInput) {
|
|
5405
5485
|
const { projectDir, packageManager } = config;
|
|
5406
5486
|
const manualDb = cliInput?.manualDb ?? false;
|
|
5407
|
-
const serverDir = path.join(projectDir, "
|
|
5487
|
+
const serverDir = path.join(projectDir, "packages", "db");
|
|
5408
5488
|
try {
|
|
5409
5489
|
await fs.ensureDir(serverDir);
|
|
5410
5490
|
if (manualDb) {
|
|
@@ -5431,8 +5511,7 @@ async function setupSupabase(config, cliInput) {
|
|
|
5431
5511
|
await writeSupabaseEnvFile(projectDir, "");
|
|
5432
5512
|
return;
|
|
5433
5513
|
}
|
|
5434
|
-
|
|
5435
|
-
if (!initialized) {
|
|
5514
|
+
if (!await initializeSupabase(serverDir, packageManager)) {
|
|
5436
5515
|
displayManualSupabaseInstructions();
|
|
5437
5516
|
return;
|
|
5438
5517
|
}
|
|
@@ -5442,14 +5521,12 @@ async function setupSupabase(config, cliInput) {
|
|
|
5442
5521
|
return;
|
|
5443
5522
|
}
|
|
5444
5523
|
const dbUrl = extractDbUrl(supabaseOutput);
|
|
5445
|
-
if (dbUrl)
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
}
|
|
5452
|
-
} else {
|
|
5524
|
+
if (dbUrl) if (await writeSupabaseEnvFile(projectDir, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
|
|
5525
|
+
else {
|
|
5526
|
+
log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
|
|
5527
|
+
displayManualSupabaseInstructions(supabaseOutput);
|
|
5528
|
+
}
|
|
5529
|
+
else {
|
|
5453
5530
|
log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
|
|
5454
5531
|
displayManualSupabaseInstructions(supabaseOutput);
|
|
5455
5532
|
}
|
|
@@ -5467,8 +5544,7 @@ async function isTursoInstalled() {
|
|
|
5467
5544
|
}
|
|
5468
5545
|
async function isTursoLoggedIn() {
|
|
5469
5546
|
try {
|
|
5470
|
-
|
|
5471
|
-
return !output.stdout.includes("You are not logged in");
|
|
5547
|
+
return !(await $`turso auth whoami`).stdout.includes("You are not logged in");
|
|
5472
5548
|
} catch {
|
|
5473
5549
|
return false;
|
|
5474
5550
|
}
|
|
@@ -5627,10 +5703,9 @@ async function setupTurso(config, cliInput) {
|
|
|
5627
5703
|
return;
|
|
5628
5704
|
}
|
|
5629
5705
|
setupSpinner.start("Checking Turso CLI availability...");
|
|
5630
|
-
const platform = os.platform();
|
|
5706
|
+
const platform = os$1.platform();
|
|
5631
5707
|
const isMac = platform === "darwin";
|
|
5632
|
-
|
|
5633
|
-
if (isWindows) {
|
|
5708
|
+
if (platform === "win32") {
|
|
5634
5709
|
if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
|
5635
5710
|
log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
|
|
5636
5711
|
await writeEnvFile(projectDir);
|
|
@@ -5638,8 +5713,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5638
5713
|
return;
|
|
5639
5714
|
}
|
|
5640
5715
|
if (setupSpinner) setupSpinner.stop("Turso CLI availability checked");
|
|
5641
|
-
|
|
5642
|
-
if (!isCliInstalled) {
|
|
5716
|
+
if (!await isTursoInstalled()) {
|
|
5643
5717
|
const shouldInstall = await confirm({
|
|
5644
5718
|
message: "Would you like to install Turso CLI?",
|
|
5645
5719
|
initialValue: true
|
|
@@ -5652,8 +5726,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5652
5726
|
}
|
|
5653
5727
|
await installTursoCLI(isMac);
|
|
5654
5728
|
}
|
|
5655
|
-
|
|
5656
|
-
if (!isLoggedIn) await loginToTurso();
|
|
5729
|
+
if (!await isTursoLoggedIn()) await loginToTurso();
|
|
5657
5730
|
const selectedGroup = await selectTursoGroup();
|
|
5658
5731
|
let success = false;
|
|
5659
5732
|
let dbName = "";
|
|
@@ -5694,15 +5767,15 @@ async function setupDatabase(config, cliInput) {
|
|
|
5694
5767
|
const { database, orm, dbSetup, backend, projectDir } = config;
|
|
5695
5768
|
if (backend === "convex" || database === "none") {
|
|
5696
5769
|
if (backend !== "convex") {
|
|
5697
|
-
const serverDir
|
|
5698
|
-
const serverDbDir = path.join(serverDir
|
|
5770
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
5771
|
+
const serverDbDir = path.join(serverDir, "src/db");
|
|
5699
5772
|
if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
|
|
5700
5773
|
}
|
|
5701
5774
|
return;
|
|
5702
5775
|
}
|
|
5703
5776
|
const s = spinner();
|
|
5704
|
-
const
|
|
5705
|
-
if (!await fs.pathExists(
|
|
5777
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5778
|
+
if (!await fs.pathExists(dbPackageDir)) return;
|
|
5706
5779
|
try {
|
|
5707
5780
|
if (orm === "prisma") if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
|
|
5708
5781
|
dependencies: [
|
|
@@ -5711,23 +5784,27 @@ async function setupDatabase(config, cliInput) {
|
|
|
5711
5784
|
"@planetscale/database"
|
|
5712
5785
|
],
|
|
5713
5786
|
devDependencies: ["prisma"],
|
|
5714
|
-
projectDir:
|
|
5787
|
+
projectDir: dbPackageDir
|
|
5715
5788
|
});
|
|
5716
5789
|
else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
|
|
5717
5790
|
dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
|
|
5718
5791
|
devDependencies: ["prisma"],
|
|
5719
|
-
projectDir:
|
|
5792
|
+
projectDir: dbPackageDir
|
|
5720
5793
|
});
|
|
5721
5794
|
else await addPackageDependency({
|
|
5722
5795
|
dependencies: ["@prisma/client"],
|
|
5723
5796
|
devDependencies: ["prisma"],
|
|
5724
|
-
projectDir:
|
|
5797
|
+
projectDir: dbPackageDir
|
|
5725
5798
|
});
|
|
5726
5799
|
else if (orm === "drizzle") {
|
|
5727
5800
|
if (database === "sqlite") await addPackageDependency({
|
|
5728
|
-
dependencies: [
|
|
5801
|
+
dependencies: [
|
|
5802
|
+
"drizzle-orm",
|
|
5803
|
+
"@libsql/client",
|
|
5804
|
+
"libsql"
|
|
5805
|
+
],
|
|
5729
5806
|
devDependencies: ["drizzle-kit"],
|
|
5730
|
-
projectDir:
|
|
5807
|
+
projectDir: dbPackageDir
|
|
5731
5808
|
});
|
|
5732
5809
|
else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
|
|
5733
5810
|
dependencies: [
|
|
@@ -5736,32 +5813,32 @@ async function setupDatabase(config, cliInput) {
|
|
|
5736
5813
|
"ws"
|
|
5737
5814
|
],
|
|
5738
5815
|
devDependencies: ["drizzle-kit", "@types/ws"],
|
|
5739
|
-
projectDir:
|
|
5816
|
+
projectDir: dbPackageDir
|
|
5740
5817
|
});
|
|
5741
5818
|
else if (dbSetup === "planetscale") await addPackageDependency({
|
|
5742
5819
|
dependencies: ["drizzle-orm", "pg"],
|
|
5743
5820
|
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
5744
|
-
projectDir:
|
|
5821
|
+
projectDir: dbPackageDir
|
|
5745
5822
|
});
|
|
5746
5823
|
else await addPackageDependency({
|
|
5747
5824
|
dependencies: ["drizzle-orm", "pg"],
|
|
5748
5825
|
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
5749
|
-
projectDir:
|
|
5826
|
+
projectDir: dbPackageDir
|
|
5750
5827
|
});
|
|
5751
5828
|
else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
|
|
5752
5829
|
dependencies: ["drizzle-orm", "@planetscale/database"],
|
|
5753
5830
|
devDependencies: ["drizzle-kit"],
|
|
5754
|
-
projectDir:
|
|
5831
|
+
projectDir: dbPackageDir
|
|
5755
5832
|
});
|
|
5756
5833
|
else await addPackageDependency({
|
|
5757
5834
|
dependencies: ["drizzle-orm", "mysql2"],
|
|
5758
5835
|
devDependencies: ["drizzle-kit"],
|
|
5759
|
-
projectDir:
|
|
5836
|
+
projectDir: dbPackageDir
|
|
5760
5837
|
});
|
|
5761
5838
|
} else if (orm === "mongoose") await addPackageDependency({
|
|
5762
5839
|
dependencies: ["mongoose"],
|
|
5763
5840
|
devDependencies: [],
|
|
5764
|
-
projectDir:
|
|
5841
|
+
projectDir: dbPackageDir
|
|
5765
5842
|
});
|
|
5766
5843
|
if (dbSetup === "docker") await setupDockerCompose(config);
|
|
5767
5844
|
else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
|
|
@@ -5910,8 +5987,7 @@ function generateStackDescription(frontend, backend, api, isConvex) {
|
|
|
5910
5987
|
const hasSvelte = frontend.includes("svelte");
|
|
5911
5988
|
const hasNuxt = frontend.includes("nuxt");
|
|
5912
5989
|
const hasSolid = frontend.includes("solid");
|
|
5913
|
-
|
|
5914
|
-
if (!hasFrontendNone) {
|
|
5990
|
+
if (!(frontend.length === 0 || frontend.includes("none"))) {
|
|
5915
5991
|
if (hasTanstackRouter) parts.push("React, TanStack Router");
|
|
5916
5992
|
else if (hasReactRouter) parts.push("React, React Router");
|
|
5917
5993
|
else if (hasNext) parts.push("Next.js");
|
|
@@ -5967,8 +6043,7 @@ function generateProjectStructure(projectName, frontend, backend, addons, isConv
|
|
|
5967
6043
|
structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
|
|
5968
6044
|
}
|
|
5969
6045
|
}
|
|
5970
|
-
|
|
5971
|
-
if (hasNative) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
6046
|
+
if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
5972
6047
|
if (addons.includes("starlight")) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
|
|
5973
6048
|
if (isConvex) {
|
|
5974
6049
|
structure.push("├── packages/");
|
|
@@ -6129,7 +6204,7 @@ SERVER_URL={your-production-server-domain}
|
|
|
6129
6204
|
CORS_ORIGIN={your-production-web-domain}
|
|
6130
6205
|
BETTER_AUTH_URL={your-production-server-domain}
|
|
6131
6206
|
\`\`\`
|
|
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.
|
|
6207
|
+
- 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
6208
|
`;
|
|
6134
6209
|
}
|
|
6135
6210
|
function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy) {
|
|
@@ -6152,12 +6227,11 @@ function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeplo
|
|
|
6152
6227
|
//#region src/helpers/core/git.ts
|
|
6153
6228
|
async function initializeGit(projectDir, useGit) {
|
|
6154
6229
|
if (!useGit) return;
|
|
6155
|
-
|
|
6230
|
+
if ((await $({
|
|
6156
6231
|
cwd: projectDir,
|
|
6157
6232
|
reject: false,
|
|
6158
6233
|
stderr: "pipe"
|
|
6159
|
-
})`git --version
|
|
6160
|
-
if (gitVersionResult.exitCode !== 0) {
|
|
6234
|
+
})`git --version`).exitCode !== 0) {
|
|
6161
6235
|
log.warn(pc.yellow("Git is not installed"));
|
|
6162
6236
|
return;
|
|
6163
6237
|
}
|
|
@@ -6187,7 +6261,7 @@ async function setupPayments(config) {
|
|
|
6187
6261
|
projectDir: serverDir
|
|
6188
6262
|
});
|
|
6189
6263
|
if (clientDirExists) {
|
|
6190
|
-
|
|
6264
|
+
if (frontend.some((f) => [
|
|
6191
6265
|
"react-router",
|
|
6192
6266
|
"tanstack-router",
|
|
6193
6267
|
"tanstack-start",
|
|
@@ -6195,8 +6269,7 @@ async function setupPayments(config) {
|
|
|
6195
6269
|
"nuxt",
|
|
6196
6270
|
"svelte",
|
|
6197
6271
|
"solid"
|
|
6198
|
-
].includes(f))
|
|
6199
|
-
if (hasWebFrontend$1) await addPackageDependency({
|
|
6272
|
+
].includes(f))) await addPackageDependency({
|
|
6200
6273
|
dependencies: ["@polar-sh/better-auth"],
|
|
6201
6274
|
projectDir: clientDir
|
|
6202
6275
|
});
|
|
@@ -6237,15 +6310,13 @@ function getDockerInstallInstructions(platform, database) {
|
|
|
6237
6310
|
return `${pc.yellow("IMPORTANT:")} Docker required for ${databaseName}. Install for ${platformName}:\n${pc.blue(installUrl)}`;
|
|
6238
6311
|
}
|
|
6239
6312
|
async function getDockerStatus(database) {
|
|
6240
|
-
const platform = os.platform();
|
|
6241
|
-
|
|
6242
|
-
if (!installed) return {
|
|
6313
|
+
const platform = os$1.platform();
|
|
6314
|
+
if (!await isDockerInstalled()) return {
|
|
6243
6315
|
installed: false,
|
|
6244
6316
|
running: false,
|
|
6245
6317
|
message: getDockerInstallInstructions(platform, database)
|
|
6246
6318
|
};
|
|
6247
|
-
|
|
6248
|
-
if (!running) return {
|
|
6319
|
+
if (!await isDockerRunning()) return {
|
|
6249
6320
|
installed: true,
|
|
6250
6321
|
running: false,
|
|
6251
6322
|
message: `${pc.yellow("IMPORTANT:")} Docker is installed but not running.`
|
|
@@ -6424,12 +6495,66 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
|
|
|
6424
6495
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
6425
6496
|
}
|
|
6426
6497
|
|
|
6498
|
+
//#endregion
|
|
6499
|
+
//#region src/helpers/core/workspace-setup.ts
|
|
6500
|
+
async function setupWorkspaceDependencies(projectDir, options) {
|
|
6501
|
+
const projectName = options.projectName;
|
|
6502
|
+
const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
|
|
6503
|
+
const commonDeps = ["dotenv", "zod"];
|
|
6504
|
+
const commonDevDeps = ["tsdown"];
|
|
6505
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
6506
|
+
if (await fs.pathExists(dbPackageDir)) await addPackageDependency({
|
|
6507
|
+
dependencies: commonDeps,
|
|
6508
|
+
devDependencies: commonDevDeps,
|
|
6509
|
+
projectDir: dbPackageDir
|
|
6510
|
+
});
|
|
6511
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
6512
|
+
if (await fs.pathExists(authPackageDir)) await addPackageDependency({
|
|
6513
|
+
dependencies: commonDeps,
|
|
6514
|
+
devDependencies: commonDevDeps,
|
|
6515
|
+
customDependencies: { [`@${projectName}/db`]: workspaceVersion },
|
|
6516
|
+
projectDir: authPackageDir
|
|
6517
|
+
});
|
|
6518
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
6519
|
+
if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
|
|
6520
|
+
dependencies: commonDeps,
|
|
6521
|
+
devDependencies: commonDevDeps,
|
|
6522
|
+
customDependencies: {
|
|
6523
|
+
[`@${projectName}/auth`]: workspaceVersion,
|
|
6524
|
+
[`@${projectName}/db`]: workspaceVersion
|
|
6525
|
+
},
|
|
6526
|
+
projectDir: apiPackageDir
|
|
6527
|
+
});
|
|
6528
|
+
const serverPackageDir = path.join(projectDir, "apps/server");
|
|
6529
|
+
if (await fs.pathExists(serverPackageDir)) await addPackageDependency({
|
|
6530
|
+
dependencies: commonDeps,
|
|
6531
|
+
devDependencies: commonDevDeps,
|
|
6532
|
+
customDependencies: {
|
|
6533
|
+
[`@${projectName}/api`]: workspaceVersion,
|
|
6534
|
+
[`@${projectName}/auth`]: workspaceVersion,
|
|
6535
|
+
[`@${projectName}/db`]: workspaceVersion
|
|
6536
|
+
},
|
|
6537
|
+
projectDir: serverPackageDir
|
|
6538
|
+
});
|
|
6539
|
+
if (options.api && options.api !== "none") {
|
|
6540
|
+
const webPackageDir = path.join(projectDir, "apps/web");
|
|
6541
|
+
if (await fs.pathExists(webPackageDir)) await addPackageDependency({
|
|
6542
|
+
customDependencies: { [`@${projectName}/api`]: workspaceVersion },
|
|
6543
|
+
projectDir: webPackageDir
|
|
6544
|
+
});
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
|
|
6427
6548
|
//#endregion
|
|
6428
6549
|
//#region src/helpers/core/project-config.ts
|
|
6429
6550
|
async function updatePackageConfigurations(projectDir, options) {
|
|
6430
6551
|
await updateRootPackageJson(projectDir, options);
|
|
6431
|
-
if (options.backend !== "convex")
|
|
6432
|
-
|
|
6552
|
+
if (options.backend !== "convex") {
|
|
6553
|
+
await updateServerPackageJson(projectDir, options);
|
|
6554
|
+
await updateAuthPackageJson(projectDir, options);
|
|
6555
|
+
await updateApiPackageJson(projectDir, options);
|
|
6556
|
+
await setupWorkspaceDependencies(projectDir, options);
|
|
6557
|
+
} else await updateConvexPackageJson(projectDir, options);
|
|
6433
6558
|
}
|
|
6434
6559
|
async function updateRootPackageJson(projectDir, options) {
|
|
6435
6560
|
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
@@ -6439,6 +6564,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6439
6564
|
if (!packageJson.scripts) packageJson.scripts = {};
|
|
6440
6565
|
const scripts = packageJson.scripts;
|
|
6441
6566
|
const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
|
|
6567
|
+
const dbPackageName = `@${options.projectName}/db`;
|
|
6442
6568
|
let serverDevScript = "";
|
|
6443
6569
|
if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
|
|
6444
6570
|
else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
|
|
@@ -6458,14 +6584,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6458
6584
|
scripts["dev:server"] = serverDevScript;
|
|
6459
6585
|
if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
|
|
6460
6586
|
if (needsDbScripts) {
|
|
6461
|
-
scripts["db:push"] = `turbo -F ${
|
|
6462
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${
|
|
6587
|
+
scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
|
|
6588
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${dbPackageName} db:studio`;
|
|
6463
6589
|
if (options.orm === "prisma") {
|
|
6464
|
-
scripts["db:generate"] = `turbo -F ${
|
|
6465
|
-
scripts["db:migrate"] = `turbo -F ${
|
|
6590
|
+
scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
|
|
6591
|
+
scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
|
|
6466
6592
|
} else if (options.orm === "drizzle") {
|
|
6467
|
-
scripts["db:generate"] = `turbo -F ${
|
|
6468
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${
|
|
6593
|
+
scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
|
|
6594
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
|
|
6469
6595
|
}
|
|
6470
6596
|
}
|
|
6471
6597
|
if (options.dbSetup === "docker") {
|
|
@@ -6483,14 +6609,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6483
6609
|
scripts["dev:server"] = serverDevScript;
|
|
6484
6610
|
if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
|
|
6485
6611
|
if (needsDbScripts) {
|
|
6486
|
-
scripts["db:push"] = `pnpm --filter ${
|
|
6487
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${
|
|
6612
|
+
scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
|
|
6613
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${dbPackageName} db:studio`;
|
|
6488
6614
|
if (options.orm === "prisma") {
|
|
6489
|
-
scripts["db:generate"] = `pnpm --filter ${
|
|
6490
|
-
scripts["db:migrate"] = `pnpm --filter ${
|
|
6615
|
+
scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
|
|
6616
|
+
scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
|
|
6491
6617
|
} else if (options.orm === "drizzle") {
|
|
6492
|
-
scripts["db:generate"] = `pnpm --filter ${
|
|
6493
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${
|
|
6618
|
+
scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
|
|
6619
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
|
|
6494
6620
|
}
|
|
6495
6621
|
}
|
|
6496
6622
|
if (options.dbSetup === "docker") {
|
|
@@ -6508,14 +6634,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6508
6634
|
scripts["dev:server"] = serverDevScript;
|
|
6509
6635
|
if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
|
|
6510
6636
|
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 ${
|
|
6637
|
+
scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
|
|
6638
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${dbPackageName}`;
|
|
6513
6639
|
if (options.orm === "prisma") {
|
|
6514
|
-
scripts["db:generate"] = `npm run db:generate --workspace ${
|
|
6515
|
-
scripts["db:migrate"] = `npm run db:migrate --workspace ${
|
|
6640
|
+
scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
|
|
6641
|
+
scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
|
|
6516
6642
|
} 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 ${
|
|
6643
|
+
scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
|
|
6644
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
|
|
6519
6645
|
}
|
|
6520
6646
|
}
|
|
6521
6647
|
if (options.dbSetup === "docker") {
|
|
@@ -6533,14 +6659,14 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6533
6659
|
scripts["dev:server"] = serverDevScript;
|
|
6534
6660
|
if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
|
|
6535
6661
|
if (needsDbScripts) {
|
|
6536
|
-
scripts["db:push"] = `bun run --filter ${
|
|
6537
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${
|
|
6662
|
+
scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
|
|
6663
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${dbPackageName} db:studio`;
|
|
6538
6664
|
if (options.orm === "prisma") {
|
|
6539
|
-
scripts["db:generate"] = `bun run --filter ${
|
|
6540
|
-
scripts["db:migrate"] = `bun run --filter ${
|
|
6665
|
+
scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
|
|
6666
|
+
scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
|
|
6541
6667
|
} 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 ${
|
|
6668
|
+
scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
|
|
6669
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
|
|
6544
6670
|
}
|
|
6545
6671
|
}
|
|
6546
6672
|
if (options.dbSetup === "docker") {
|
|
@@ -6560,8 +6686,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6560
6686
|
const workspaces = packageJson.workspaces;
|
|
6561
6687
|
if (options.backend === "convex") {
|
|
6562
6688
|
if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
|
|
6563
|
-
|
|
6564
|
-
if (needsAppsDir && !workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6689
|
+
if ((options.frontend.length > 0 || options.addons.includes("starlight")) && !workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6565
6690
|
} else {
|
|
6566
6691
|
if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
|
|
6567
6692
|
if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
|
|
@@ -6574,6 +6699,22 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
6574
6699
|
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
|
6575
6700
|
if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
|
|
6576
6701
|
const scripts = serverPackageJson.scripts;
|
|
6702
|
+
if (options.dbSetup === "docker") {
|
|
6703
|
+
scripts["db:start"] = "docker compose up -d";
|
|
6704
|
+
scripts["db:watch"] = "docker compose up";
|
|
6705
|
+
scripts["db:stop"] = "docker compose stop";
|
|
6706
|
+
scripts["db:down"] = "docker compose down";
|
|
6707
|
+
}
|
|
6708
|
+
await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
|
|
6709
|
+
await updateDbPackageJson(projectDir, options);
|
|
6710
|
+
}
|
|
6711
|
+
async function updateDbPackageJson(projectDir, options) {
|
|
6712
|
+
const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
|
|
6713
|
+
if (!await fs.pathExists(dbPackageJsonPath)) return;
|
|
6714
|
+
const dbPackageJson = await fs.readJson(dbPackageJsonPath);
|
|
6715
|
+
dbPackageJson.name = `@${options.projectName}/db`;
|
|
6716
|
+
if (!dbPackageJson.scripts) dbPackageJson.scripts = {};
|
|
6717
|
+
const scripts = dbPackageJson.scripts;
|
|
6577
6718
|
if (options.database !== "none") {
|
|
6578
6719
|
if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
|
|
6579
6720
|
if (options.orm === "prisma") {
|
|
@@ -6588,13 +6729,21 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
6588
6729
|
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = "drizzle-kit migrate";
|
|
6589
6730
|
}
|
|
6590
6731
|
}
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6732
|
+
await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
|
|
6733
|
+
}
|
|
6734
|
+
async function updateAuthPackageJson(projectDir, options) {
|
|
6735
|
+
const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
|
|
6736
|
+
if (!await fs.pathExists(authPackageJsonPath)) return;
|
|
6737
|
+
const authPackageJson = await fs.readJson(authPackageJsonPath);
|
|
6738
|
+
authPackageJson.name = `@${options.projectName}/auth`;
|
|
6739
|
+
await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
|
|
6740
|
+
}
|
|
6741
|
+
async function updateApiPackageJson(projectDir, options) {
|
|
6742
|
+
const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
|
|
6743
|
+
if (!await fs.pathExists(apiPackageJsonPath)) return;
|
|
6744
|
+
const apiPackageJson = await fs.readJson(apiPackageJsonPath);
|
|
6745
|
+
apiPackageJson.name = `@${options.projectName}/api`;
|
|
6746
|
+
await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
|
|
6598
6747
|
}
|
|
6599
6748
|
async function updateConvexPackageJson(projectDir, options) {
|
|
6600
6749
|
const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
|
|
@@ -6615,10 +6764,7 @@ async function createProject(options, cliInput) {
|
|
|
6615
6764
|
await copyBaseTemplate(projectDir, options);
|
|
6616
6765
|
await setupFrontendTemplates(projectDir, options);
|
|
6617
6766
|
await setupBackendFramework(projectDir, options);
|
|
6618
|
-
if (!isConvex)
|
|
6619
|
-
await setupDbOrmTemplates(projectDir, options);
|
|
6620
|
-
await setupDockerComposeTemplates(projectDir, options);
|
|
6621
|
-
}
|
|
6767
|
+
if (!isConvex) await setupDockerComposeTemplates(projectDir, options);
|
|
6622
6768
|
await setupAuthTemplate(projectDir, options);
|
|
6623
6769
|
if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
|
|
6624
6770
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
@@ -6637,6 +6783,7 @@ async function createProject(options, cliInput) {
|
|
|
6637
6783
|
await handleExtras(projectDir, options);
|
|
6638
6784
|
await setupEnvironmentVariables(options);
|
|
6639
6785
|
await updatePackageConfigurations(projectDir, options);
|
|
6786
|
+
await setupCatalogs(projectDir, options);
|
|
6640
6787
|
await setupWebDeploy(options);
|
|
6641
6788
|
await setupServerDeploy(options);
|
|
6642
6789
|
await createReadme(projectDir, options);
|
|
@@ -6781,9 +6928,7 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
|
|
|
6781
6928
|
finalPathInput: currentPathInput,
|
|
6782
6929
|
shouldClearDirectory: false
|
|
6783
6930
|
};
|
|
6784
|
-
|
|
6785
|
-
const isNotEmpty = dirContents.length > 0;
|
|
6786
|
-
if (!isNotEmpty) return {
|
|
6931
|
+
if (!((await fs.readdir(currentPath)).length > 0)) return {
|
|
6787
6932
|
finalPathInput: currentPathInput,
|
|
6788
6933
|
shouldClearDirectory: false
|
|
6789
6934
|
};
|
|
@@ -6945,26 +7090,25 @@ function displaySponsorsBox(sponsors$1) {
|
|
|
6945
7090
|
|
|
6946
7091
|
//#endregion
|
|
6947
7092
|
//#region src/index.ts
|
|
6948
|
-
const
|
|
6949
|
-
|
|
6950
|
-
init: t.procedure.meta({
|
|
7093
|
+
const router = os.router({
|
|
7094
|
+
init: os.meta({
|
|
6951
7095
|
description: "Create a new Better-T-Stack project",
|
|
6952
7096
|
default: true,
|
|
6953
7097
|
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"),
|
|
7098
|
+
}).input(z$1.tuple([ProjectNameSchema.optional(), z$1.object({
|
|
7099
|
+
yes: z$1.boolean().optional().default(false).describe("Use default configuration"),
|
|
7100
|
+
yolo: z$1.boolean().optional().default(false).describe("(WARNING - NOT RECOMMENDED) Bypass validations and compatibility checks"),
|
|
7101
|
+
verbose: z$1.boolean().optional().default(false).describe("Show detailed result information"),
|
|
6958
7102
|
database: DatabaseSchema.optional(),
|
|
6959
7103
|
orm: ORMSchema.optional(),
|
|
6960
7104
|
auth: AuthSchema.optional(),
|
|
6961
7105
|
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(),
|
|
7106
|
+
frontend: z$1.array(FrontendSchema).optional(),
|
|
7107
|
+
addons: z$1.array(AddonsSchema).optional(),
|
|
7108
|
+
examples: z$1.array(ExamplesSchema).optional(),
|
|
7109
|
+
git: z$1.boolean().optional(),
|
|
6966
7110
|
packageManager: PackageManagerSchema.optional(),
|
|
6967
|
-
install: z.boolean().optional(),
|
|
7111
|
+
install: z$1.boolean().optional(),
|
|
6968
7112
|
dbSetup: DatabaseSetupSchema.optional(),
|
|
6969
7113
|
backend: BackendSchema.optional(),
|
|
6970
7114
|
runtime: RuntimeSchema.optional(),
|
|
@@ -6972,10 +7116,10 @@ const router = t.router({
|
|
|
6972
7116
|
webDeploy: WebDeploySchema.optional(),
|
|
6973
7117
|
serverDeploy: ServerDeploySchema.optional(),
|
|
6974
7118
|
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
|
-
})])).
|
|
7119
|
+
renderTitle: z$1.boolean().optional(),
|
|
7120
|
+
disableAnalytics: z$1.boolean().optional().default(false).describe("Disable analytics"),
|
|
7121
|
+
manualDb: z$1.boolean().optional().default(false).describe("Skip automatic/manual database setup prompt and use manual setup")
|
|
7122
|
+
})])).handler(async ({ input }) => {
|
|
6979
7123
|
const [projectName, options] = input;
|
|
6980
7124
|
const combinedInput = {
|
|
6981
7125
|
projectName,
|
|
@@ -6984,18 +7128,18 @@ const router = t.router({
|
|
|
6984
7128
|
const result = await createProjectHandler(combinedInput);
|
|
6985
7129
|
if (options.verbose) return result;
|
|
6986
7130
|
}),
|
|
6987
|
-
add:
|
|
6988
|
-
addons: z.array(AddonsSchema).optional().default([]),
|
|
7131
|
+
add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z$1.tuple([z$1.object({
|
|
7132
|
+
addons: z$1.array(AddonsSchema).optional().default([]),
|
|
6989
7133
|
webDeploy: WebDeploySchema.optional(),
|
|
6990
7134
|
serverDeploy: ServerDeploySchema.optional(),
|
|
6991
|
-
projectDir: z.string().optional(),
|
|
6992
|
-
install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
7135
|
+
projectDir: z$1.string().optional(),
|
|
7136
|
+
install: z$1.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
|
|
6993
7137
|
packageManager: PackageManagerSchema.optional()
|
|
6994
|
-
})])).
|
|
7138
|
+
})])).handler(async ({ input }) => {
|
|
6995
7139
|
const [options] = input;
|
|
6996
7140
|
await addAddonsHandler(options);
|
|
6997
7141
|
}),
|
|
6998
|
-
sponsors:
|
|
7142
|
+
sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
|
|
6999
7143
|
try {
|
|
7000
7144
|
renderTitle();
|
|
7001
7145
|
intro(pc.magenta("Better-T-Stack Sponsors"));
|
|
@@ -7005,7 +7149,7 @@ const router = t.router({
|
|
|
7005
7149
|
handleError(error, "Failed to display sponsors");
|
|
7006
7150
|
}
|
|
7007
7151
|
}),
|
|
7008
|
-
docs:
|
|
7152
|
+
docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(async () => {
|
|
7009
7153
|
const DOCS_URL = "https://better-t-stack.dev/docs";
|
|
7010
7154
|
try {
|
|
7011
7155
|
await openUrl(DOCS_URL);
|
|
@@ -7014,7 +7158,7 @@ const router = t.router({
|
|
|
7014
7158
|
log.message(`Please visit ${DOCS_URL}`);
|
|
7015
7159
|
}
|
|
7016
7160
|
}),
|
|
7017
|
-
builder:
|
|
7161
|
+
builder: os.meta({ description: "Open the web-based stack builder" }).handler(async () => {
|
|
7018
7162
|
const BUILDER_URL = "https://better-t-stack.dev/new";
|
|
7019
7163
|
try {
|
|
7020
7164
|
await openUrl(BUILDER_URL);
|
|
@@ -7024,7 +7168,7 @@ const router = t.router({
|
|
|
7024
7168
|
}
|
|
7025
7169
|
})
|
|
7026
7170
|
});
|
|
7027
|
-
const caller =
|
|
7171
|
+
const caller = createRouterClient(router, { context: {} });
|
|
7028
7172
|
function createBtsCli() {
|
|
7029
7173
|
return createCli({
|
|
7030
7174
|
router,
|
|
@@ -7066,9 +7210,8 @@ function createBtsCli() {
|
|
|
7066
7210
|
* ```
|
|
7067
7211
|
*/
|
|
7068
7212
|
async function init(projectName, options) {
|
|
7069
|
-
const opts = options ?? {};
|
|
7070
7213
|
const programmaticOpts = {
|
|
7071
|
-
...
|
|
7214
|
+
...options ?? {},
|
|
7072
7215
|
verbose: true
|
|
7073
7216
|
};
|
|
7074
7217
|
const prev = process.env.BTS_PROGRAMMATIC;
|