create-better-t-stack 2.50.0 → 2.50.1-canary.58bbe5f6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +8 -9
- package/dist/index.js +1 -1
- package/dist/{src-BFx0Xu3C.js → src-CulfT5QB.js} +814 -425
- package/package.json +2 -1
- package/templates/addons/ruler/.ruler/bts.md.hbs +24 -14
- package/templates/api/orpc/fullstack/next/src/app/api/rpc/[[...rest]]/route.ts.hbs +50 -0
- package/templates/api/orpc/server/_gitignore +34 -0
- package/templates/api/orpc/server/package.json.hbs +22 -0
- package/templates/api/orpc/server/{base/src/lib → src}/context.ts.hbs +6 -6
- package/templates/{backend/server/server-base → api/orpc/server}/src/routers/index.ts.hbs +2 -2
- package/templates/api/orpc/server/tsconfig.json.hbs +10 -0
- package/templates/api/orpc/server/tsdown.config.ts.hbs +7 -0
- 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 +4 -2
- 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/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs +14 -0
- package/templates/api/trpc/server/_gitignore +34 -0
- package/templates/api/trpc/server/package.json.hbs +21 -0
- package/templates/api/trpc/server/{base/src/lib → src}/context.ts.hbs +6 -6
- package/templates/api/trpc/server/src/routers/index.ts.hbs +55 -0
- package/templates/api/trpc/server/tsconfig.json.hbs +10 -0
- package/templates/api/trpc/server/tsdown.config.ts.hbs +7 -0
- package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +6 -4
- package/templates/auth/better-auth/{server/next/src/app/api/auth/[...all]/route.ts → fullstack/next/src/app/api/auth/[...all]/route.ts.hbs} +1 -1
- package/templates/auth/better-auth/server/base/_gitignore +34 -0
- package/templates/auth/better-auth/server/base/package.json.hbs +21 -0
- package/templates/auth/better-auth/server/base/src/{lib/auth.ts.hbs → index.ts.hbs} +12 -12
- package/templates/auth/better-auth/server/base/tsconfig.json.hbs +10 -0
- package/templates/auth/better-auth/server/base/tsdown.config.ts.hbs +7 -0
- package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +2 -0
- package/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +31 -0
- package/templates/backend/server/{server-base → base}/package.json.hbs +1 -5
- package/templates/backend/server/base/tsconfig.json.hbs +13 -0
- package/templates/backend/server/base/tsdown.config.ts.hbs +9 -0
- package/templates/backend/server/elysia/src/index.ts.hbs +6 -6
- package/templates/backend/server/express/src/index.ts.hbs +6 -6
- package/templates/backend/server/fastify/src/index.ts.hbs +6 -6
- package/templates/backend/server/hono/src/index.ts.hbs +7 -7
- package/templates/base/_gitignore +47 -1
- package/templates/base/package.json.hbs +1 -3
- package/templates/{backend/server/server-base/tsconfig.json.hbs → base/tsconfig.base.json.hbs} +13 -11
- package/templates/base/tsconfig.json.hbs +3 -0
- package/templates/db/base/_gitignore +34 -0
- package/templates/db/base/package.json.hbs +21 -0
- package/templates/db/base/tsconfig.json.hbs +10 -0
- package/templates/db/base/tsdown.config.ts.hbs +7 -0
- package/templates/db/drizzle/mysql/drizzle.config.ts.hbs +11 -2
- package/templates/db/drizzle/mysql/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
- package/templates/db/drizzle/postgres/drizzle.config.ts.hbs +11 -2
- package/templates/db/drizzle/postgres/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
- package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +11 -2
- package/templates/db/drizzle/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +1 -1
- package/templates/db/prisma/mongodb/prisma.config.ts.hbs +9 -1
- package/templates/db/prisma/mongodb/src/index.ts.hbs +5 -0
- package/templates/db/prisma/mysql/prisma.config.ts.hbs +9 -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 +11 -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 +9 -1
- package/templates/db/prisma/sqlite/src/{db/index.ts.hbs → index.ts.hbs} +3 -3
- package/templates/deploy/alchemy/alchemy.run.ts.hbs +3 -3
- package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +15 -0
- 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/native/nativewind/tsconfig.json.hbs +1 -6
- package/templates/frontend/native/unistyles/tsconfig.json.hbs +1 -6
- package/templates/frontend/nuxt/tsconfig.json.hbs +0 -4
- package/templates/frontend/react/next/package.json.hbs +1 -1
- package/templates/frontend/react/next/tsconfig.json.hbs +0 -7
- package/templates/frontend/react/react-router/tsconfig.json.hbs +1 -6
- package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +1 -1
- package/templates/frontend/react/tanstack-router/tsconfig.json.hbs +1 -6
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +1 -1
- package/templates/frontend/react/tanstack-start/tsconfig.json.hbs +1 -6
- package/templates/frontend/solid/tsconfig.json.hbs +1 -6
- package/templates/frontend/svelte/tsconfig.json.hbs +1 -6
- package/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +0 -52
- package/templates/api/trpc/server/next/src/app/trpc/[trpc]/route.ts +0 -14
- package/templates/backend/server/next/next-env.d.ts +0 -5
- package/templates/backend/server/next/next.config.ts +0 -7
- package/templates/backend/server/next/package.json.hbs +0 -27
- package/templates/backend/server/next/src/app/route.ts +0 -5
- package/templates/backend/server/next/src/middleware.ts +0 -19
- package/templates/backend/server/next/tsconfig.json.hbs +0 -33
- package/templates/db/prisma/mongodb/src/db/index.ts.hbs +0 -5
- package/templates/examples/ai/server/next/src/app/ai/route.ts.hbs +0 -15
- /package/templates/api/orpc/server/{base/src/lib/orpc.ts.hbs → src/index.ts.hbs} +0 -0
- /package/templates/api/trpc/server/{base/src/lib/trpc.ts.hbs → src/index.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/mysql/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/postgres/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/{db/schema/auth.ts → schema/auth.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/{db/models/auth.model.ts → models/auth.model.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/{auth.prisma → auth.prisma.hbs} +0 -0
- /package/templates/auth/better-auth/web/nuxt/app/middleware/{auth.ts → auth.ts.hbs} +0 -0
- /package/templates/backend/server/{server-base → base}/_gitignore +0 -0
- /package/templates/db/mongoose/mongodb/src/{db/index.ts.hbs → index.ts.hbs} +0 -0
- /package/templates/examples/todo/server/drizzle/mysql/src/{db/schema → schema}/todo.ts +0 -0
- /package/templates/examples/todo/server/drizzle/postgres/src/{db/schema → schema}/todo.ts +0 -0
- /package/templates/examples/todo/server/drizzle/sqlite/src/{db/schema → schema}/todo.ts +0 -0
- /package/templates/examples/todo/server/mongoose/mongodb/src/{db/models/todo.model.ts → models/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
|
@@ -15,6 +15,7 @@ import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph"
|
|
|
15
15
|
import { glob } from "tinyglobby";
|
|
16
16
|
import handlebars from "handlebars";
|
|
17
17
|
import { Biome } from "@biomejs/js-api/nodejs";
|
|
18
|
+
import yaml from "yaml";
|
|
18
19
|
import os$1 from "node:os";
|
|
19
20
|
|
|
20
21
|
//#region src/utils/get-package-manager.ts
|
|
@@ -70,7 +71,7 @@ const dependencyVersionMap = {
|
|
|
70
71
|
"drizzle-orm": "^0.44.2",
|
|
71
72
|
"drizzle-kit": "^0.31.2",
|
|
72
73
|
"@planetscale/database": "^1.19.0",
|
|
73
|
-
"@libsql/client": "^0.
|
|
74
|
+
"@libsql/client": "^0.14.0",
|
|
74
75
|
"@neondatabase/serverless": "^1.0.1",
|
|
75
76
|
pg: "^8.14.1",
|
|
76
77
|
"@types/pg": "^8.11.11",
|
|
@@ -123,6 +124,7 @@ const dependencyVersionMap = {
|
|
|
123
124
|
"@trpc/tanstack-react-query": "^11.5.0",
|
|
124
125
|
"@trpc/server": "^11.5.0",
|
|
125
126
|
"@trpc/client": "^11.5.0",
|
|
127
|
+
next: "15.5.4",
|
|
126
128
|
convex: "^1.27.0",
|
|
127
129
|
"@convex-dev/react-query": "^0.0.0-alpha.8",
|
|
128
130
|
"convex-svelte": "^0.0.11",
|
|
@@ -146,7 +148,9 @@ const dependencyVersionMap = {
|
|
|
146
148
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
147
149
|
alchemy: "^0.70.0",
|
|
148
150
|
nitropack: "^2.12.4",
|
|
149
|
-
dotenv: "^17.2.
|
|
151
|
+
dotenv: "^17.2.2",
|
|
152
|
+
tsdown: "^0.15.5",
|
|
153
|
+
zod: "^4.1.11",
|
|
150
154
|
"@polar-sh/better-auth": "^1.1.3",
|
|
151
155
|
"@polar-sh/sdk": "^0.34.16"
|
|
152
156
|
};
|
|
@@ -195,9 +199,9 @@ const BackendSchema = z.enum([
|
|
|
195
199
|
"hono",
|
|
196
200
|
"express",
|
|
197
201
|
"fastify",
|
|
198
|
-
"next",
|
|
199
202
|
"elysia",
|
|
200
203
|
"convex",
|
|
204
|
+
"self",
|
|
201
205
|
"none"
|
|
202
206
|
]).describe("Backend framework");
|
|
203
207
|
const RuntimeSchema = z.enum([
|
|
@@ -343,6 +347,17 @@ function ensureSingleWebAndNative(frontends) {
|
|
|
343
347
|
if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
|
|
344
348
|
if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
|
|
345
349
|
}
|
|
350
|
+
const FULLSTACK_FRONTENDS$1 = ["next"];
|
|
351
|
+
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
352
|
+
const backend = config.backend || options.backend;
|
|
353
|
+
const frontends = config.frontend || options.frontend || [];
|
|
354
|
+
if (backend === "self") {
|
|
355
|
+
if (!frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f))) exitWithError("Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.");
|
|
356
|
+
if (frontends.length > 1) exitWithError("Backend 'self' (fullstack) can only be used with a single frontend framework.");
|
|
357
|
+
}
|
|
358
|
+
const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
|
|
359
|
+
if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) currently only supports Next.js frontend. Please use --frontend next or choose a different backend. Support for Nuxt, SvelteKit, and TanStack Start will be added in a future update.");
|
|
360
|
+
}
|
|
346
361
|
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
347
362
|
if (providedFlags.has("runtime") && options.runtime === "workers" && config.backend && config.backend !== "hono") exitWithError(`Cloudflare Workers runtime (--runtime workers) is only supported with Hono backend (--backend hono). Current backend: ${config.backend}. Please use '--backend hono' or choose a different runtime.`);
|
|
348
363
|
if (providedFlags.has("backend") && config.backend && config.backend !== "hono" && config.runtime === "workers") exitWithError(`Backend '${config.backend}' is not compatible with Cloudflare Workers runtime. Cloudflare Workers runtime is only supported with Hono backend. Please use '--backend hono' or choose a different runtime.`);
|
|
@@ -664,36 +679,34 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
|
|
|
664
679
|
|
|
665
680
|
//#endregion
|
|
666
681
|
//#region src/prompts/backend.ts
|
|
682
|
+
const FULLSTACK_FRONTENDS = ["next"];
|
|
667
683
|
async function getBackendFrameworkChoice(backendFramework, frontends) {
|
|
668
684
|
if (backendFramework !== void 0) return backendFramework;
|
|
669
685
|
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
|
|
670
|
-
const
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
hint: "Ergonomic web framework for building backend servers"
|
|
695
|
-
}
|
|
696
|
-
];
|
|
686
|
+
const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
|
|
687
|
+
const backendOptions = [];
|
|
688
|
+
if (hasFullstackFrontend) backendOptions.push({
|
|
689
|
+
value: "self",
|
|
690
|
+
label: "Self (Fullstack)",
|
|
691
|
+
hint: "Use frontend's built-in api routes"
|
|
692
|
+
});
|
|
693
|
+
backendOptions.push({
|
|
694
|
+
value: "hono",
|
|
695
|
+
label: "Hono",
|
|
696
|
+
hint: "Lightweight, ultrafast web framework"
|
|
697
|
+
}, {
|
|
698
|
+
value: "express",
|
|
699
|
+
label: "Express",
|
|
700
|
+
hint: "Fast, unopinionated, minimalist web framework for Node.js"
|
|
701
|
+
}, {
|
|
702
|
+
value: "fastify",
|
|
703
|
+
label: "Fastify",
|
|
704
|
+
hint: "Fast, low-overhead web framework for Node.js"
|
|
705
|
+
}, {
|
|
706
|
+
value: "elysia",
|
|
707
|
+
label: "Elysia",
|
|
708
|
+
hint: "Ergonomic web framework for building backend servers"
|
|
709
|
+
});
|
|
697
710
|
if (!hasIncompatibleFrontend) backendOptions.push({
|
|
698
711
|
value: "convex",
|
|
699
712
|
label: "Convex",
|
|
@@ -707,7 +720,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends) {
|
|
|
707
720
|
const response = await select({
|
|
708
721
|
message: "Select backend",
|
|
709
722
|
options: backendOptions,
|
|
710
|
-
initialValue: DEFAULT_CONFIG.backend
|
|
723
|
+
initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend
|
|
711
724
|
});
|
|
712
725
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
713
726
|
return response;
|
|
@@ -1086,9 +1099,8 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
|
1086
1099
|
//#endregion
|
|
1087
1100
|
//#region src/prompts/runtime.ts
|
|
1088
1101
|
async function getRuntimeChoice(runtime, backend) {
|
|
1089
|
-
if (backend === "convex" || backend === "none") return "none";
|
|
1102
|
+
if (backend === "convex" || backend === "none" || backend === "self") return "none";
|
|
1090
1103
|
if (runtime !== void 0) return runtime;
|
|
1091
|
-
if (backend === "next") return "node";
|
|
1092
1104
|
const runtimeOptions = [{
|
|
1093
1105
|
value: "bun",
|
|
1094
1106
|
label: "Bun",
|
|
@@ -1375,7 +1387,7 @@ const getLatestCLIVersion = () => {
|
|
|
1375
1387
|
*/
|
|
1376
1388
|
function isTelemetryEnabled() {
|
|
1377
1389
|
const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
|
|
1378
|
-
const BTS_TELEMETRY = "
|
|
1390
|
+
const BTS_TELEMETRY = "0";
|
|
1379
1391
|
if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
|
|
1380
1392
|
if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
|
|
1381
1393
|
return true;
|
|
@@ -1383,8 +1395,8 @@ function isTelemetryEnabled() {
|
|
|
1383
1395
|
|
|
1384
1396
|
//#endregion
|
|
1385
1397
|
//#region src/utils/analytics.ts
|
|
1386
|
-
const POSTHOG_API_KEY = "
|
|
1387
|
-
const POSTHOG_HOST = "
|
|
1398
|
+
const POSTHOG_API_KEY = "random";
|
|
1399
|
+
const POSTHOG_HOST = "random";
|
|
1388
1400
|
function generateSessionId() {
|
|
1389
1401
|
const rand = Math.random().toString(36).slice(2);
|
|
1390
1402
|
return `cli_${Date.now().toString(36)}${rand}`;
|
|
@@ -1765,8 +1777,8 @@ function validateBackendConstraints(config, providedFlags, options) {
|
|
|
1765
1777
|
].includes(f));
|
|
1766
1778
|
if (incompatibleFrontends.length > 0) exitWithError(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
|
|
1767
1779
|
}
|
|
1768
|
-
if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none") {
|
|
1769
|
-
if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex' or '--backend
|
|
1780
|
+
if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self") {
|
|
1781
|
+
if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex', '--backend none', or '--backend self'. Please choose 'bun', 'node', or remove the --runtime flag.");
|
|
1770
1782
|
}
|
|
1771
1783
|
if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
|
|
1772
1784
|
const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
|
|
@@ -1796,6 +1808,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
1796
1808
|
validateFrontendConstraints(config, providedFlags);
|
|
1797
1809
|
validateApiConstraints(config, options);
|
|
1798
1810
|
validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
|
|
1811
|
+
validateSelfBackendCompatibility(providedFlags, options, config);
|
|
1799
1812
|
validateWorkersCompatibility(providedFlags, options, config);
|
|
1800
1813
|
if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'wrangler' or 'alchemy' for --server-deploy.");
|
|
1801
1814
|
if (config.addons && config.addons.length > 0) {
|
|
@@ -1849,6 +1862,7 @@ const CORE_STACK_FLAGS = new Set([
|
|
|
1849
1862
|
"examples",
|
|
1850
1863
|
"auth",
|
|
1851
1864
|
"dbSetup",
|
|
1865
|
+
"payments",
|
|
1852
1866
|
"api",
|
|
1853
1867
|
"webDeploy",
|
|
1854
1868
|
"serverDeploy"
|
|
@@ -1992,15 +2006,17 @@ const addPackageDependency = async (opts) => {
|
|
|
1992
2006
|
if (!pkgJson.dependencies) pkgJson.dependencies = {};
|
|
1993
2007
|
if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
|
|
1994
2008
|
for (const pkgName of dependencies) {
|
|
1995
|
-
const version =
|
|
2009
|
+
const version = dependencyVersionMap[pkgName];
|
|
1996
2010
|
if (version) pkgJson.dependencies[pkgName] = version;
|
|
1997
2011
|
else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
|
|
1998
2012
|
}
|
|
1999
2013
|
for (const pkgName of devDependencies) {
|
|
2000
|
-
const version =
|
|
2014
|
+
const version = dependencyVersionMap[pkgName];
|
|
2001
2015
|
if (version) pkgJson.devDependencies[pkgName] = version;
|
|
2002
2016
|
else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
|
|
2003
2017
|
}
|
|
2018
|
+
for (const [pkgName, version] of Object.entries(customDependencies)) pkgJson.dependencies[pkgName] = version;
|
|
2019
|
+
for (const [pkgName, version] of Object.entries(customDevDependencies)) pkgJson.devDependencies[pkgName] = version;
|
|
2004
2020
|
await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
2005
2021
|
};
|
|
2006
2022
|
|
|
@@ -2062,11 +2078,14 @@ async function setupFumadocs(config) {
|
|
|
2062
2078
|
if (isCancel(template)) return exitCancelled("Operation cancelled");
|
|
2063
2079
|
const commandWithArgs = `create-fumadocs-app@latest fumadocs --template ${TEMPLATES[template].value} --src --no-install --pm ${packageManager} --no-eslint --no-git`;
|
|
2064
2080
|
const fumadocsInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2081
|
+
const s = spinner();
|
|
2082
|
+
s.start("Setting up Fumadocs...");
|
|
2065
2083
|
await execa(fumadocsInitCommand, {
|
|
2066
2084
|
cwd: path.join(projectDir, "apps"),
|
|
2067
2085
|
env: { CI: "true" },
|
|
2068
2086
|
shell: true
|
|
2069
2087
|
});
|
|
2088
|
+
s.stop("Fumadocs setup complete!");
|
|
2070
2089
|
const fumadocsDir = path.join(projectDir, "apps", "fumadocs");
|
|
2071
2090
|
const packageJsonPath = path.join(fumadocsDir, "package.json");
|
|
2072
2091
|
if (await fs.pathExists(packageJsonPath)) {
|
|
@@ -2307,11 +2326,14 @@ async function setupUltracite(config, hasHusky) {
|
|
|
2307
2326
|
if (hasHusky) ultraciteArgs.push("--integrations", "husky", "lint-staged");
|
|
2308
2327
|
const commandWithArgs = `ultracite@latest ${ultraciteArgs.join(" ")} --skip-install`;
|
|
2309
2328
|
const ultraciteInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
|
|
2329
|
+
const s = spinner();
|
|
2330
|
+
s.start("Setting up Ultracite...");
|
|
2310
2331
|
await execa(ultraciteInitCommand, {
|
|
2311
2332
|
cwd: projectDir,
|
|
2312
2333
|
env: { CI: "true" },
|
|
2313
2334
|
shell: true
|
|
2314
2335
|
});
|
|
2336
|
+
s.stop("Ultracite setup complete!");
|
|
2315
2337
|
if (hasHusky) await addPackageDependency({
|
|
2316
2338
|
devDependencies: ["husky", "lint-staged"],
|
|
2317
2339
|
projectDir
|
|
@@ -2655,8 +2677,12 @@ async function processTemplate(srcPath, destPath, context) {
|
|
|
2655
2677
|
}
|
|
2656
2678
|
handlebars.registerHelper("eq", (a, b) => a === b);
|
|
2657
2679
|
handlebars.registerHelper("ne", (a, b) => a !== b);
|
|
2658
|
-
handlebars.registerHelper("and", (
|
|
2659
|
-
|
|
2680
|
+
handlebars.registerHelper("and", (...args) => {
|
|
2681
|
+
return args.slice(0, -1).every((value) => value);
|
|
2682
|
+
});
|
|
2683
|
+
handlebars.registerHelper("or", (...args) => {
|
|
2684
|
+
return args.slice(0, -1).some((value) => value);
|
|
2685
|
+
});
|
|
2660
2686
|
handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
|
|
2661
2687
|
|
|
2662
2688
|
//#endregion
|
|
@@ -2718,6 +2744,10 @@ async function setupFrontendTemplates(projectDir, context) {
|
|
|
2718
2744
|
const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
|
|
2719
2745
|
if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
|
2720
2746
|
}
|
|
2747
|
+
if (context.backend === "self" && reactFramework === "next" && context.api !== "none") {
|
|
2748
|
+
const apiFullstackDir = path.join(PKG_ROOT, `templates/api/${context.api}/fullstack/next`);
|
|
2749
|
+
if (await fs.pathExists(apiFullstackDir)) await processAndCopyFiles("**/*", apiFullstackDir, webAppDir, context);
|
|
2750
|
+
}
|
|
2721
2751
|
}
|
|
2722
2752
|
} else if (hasNuxtWeb) {
|
|
2723
2753
|
const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
|
|
@@ -2758,35 +2788,52 @@ async function setupFrontendTemplates(projectDir, context) {
|
|
|
2758
2788
|
}
|
|
2759
2789
|
}
|
|
2760
2790
|
}
|
|
2761
|
-
async function
|
|
2762
|
-
if (context.
|
|
2791
|
+
async function setupApiPackage(projectDir, context) {
|
|
2792
|
+
if (context.api === "none") return;
|
|
2793
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
2794
|
+
await fs.ensureDir(apiPackageDir);
|
|
2795
|
+
const apiServerDir = path.join(PKG_ROOT, `templates/api/${context.api}/server`);
|
|
2796
|
+
if (await fs.pathExists(apiServerDir)) await processAndCopyFiles("**/*", apiServerDir, apiPackageDir, context);
|
|
2797
|
+
}
|
|
2798
|
+
async function setupDbPackage(projectDir, context) {
|
|
2799
|
+
if (context.database === "none" || context.orm === "none") return;
|
|
2800
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2801
|
+
await fs.ensureDir(dbPackageDir);
|
|
2802
|
+
const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
|
|
2803
|
+
if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
|
|
2804
|
+
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2805
|
+
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
|
|
2806
|
+
}
|
|
2807
|
+
async function setupConvexBackend(projectDir, context) {
|
|
2808
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2809
|
+
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2810
|
+
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2811
|
+
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2812
|
+
await fs.ensureDir(convexBackendDestDir);
|
|
2813
|
+
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2814
|
+
}
|
|
2815
|
+
async function setupServerApp(projectDir, context) {
|
|
2763
2816
|
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2764
|
-
if (context.backend === "convex") {
|
|
2765
|
-
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2766
|
-
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2767
|
-
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2768
|
-
await fs.ensureDir(convexBackendDestDir);
|
|
2769
|
-
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2770
|
-
return;
|
|
2771
|
-
}
|
|
2772
2817
|
await fs.ensureDir(serverAppDir);
|
|
2773
|
-
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/
|
|
2818
|
+
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
|
|
2774
2819
|
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2775
2820
|
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2776
2821
|
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2777
|
-
if (context.api !== "none") {
|
|
2778
|
-
const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
|
|
2779
|
-
if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
|
|
2780
|
-
const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
|
|
2781
|
-
if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
|
|
2782
|
-
}
|
|
2783
2822
|
}
|
|
2784
|
-
async function
|
|
2785
|
-
if (context.backend === "
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2823
|
+
async function setupBackendFramework(projectDir, context) {
|
|
2824
|
+
if (context.backend === "none") return;
|
|
2825
|
+
if (context.backend === "convex") {
|
|
2826
|
+
await setupConvexBackend(projectDir, context);
|
|
2827
|
+
return;
|
|
2828
|
+
}
|
|
2829
|
+
if (context.backend === "self") {
|
|
2830
|
+
await setupApiPackage(projectDir, context);
|
|
2831
|
+
await setupDbPackage(projectDir, context);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
await setupServerApp(projectDir, context);
|
|
2835
|
+
await setupApiPackage(projectDir, context);
|
|
2836
|
+
await setupDbPackage(projectDir, context);
|
|
2790
2837
|
}
|
|
2791
2838
|
async function setupAuthTemplate(projectDir, context) {
|
|
2792
2839
|
if (!context.auth || context.auth === "none") return;
|
|
@@ -2866,21 +2913,21 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2866
2913
|
}
|
|
2867
2914
|
return;
|
|
2868
2915
|
}
|
|
2869
|
-
if (serverAppDirExists && context.backend !== "convex") {
|
|
2916
|
+
if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
|
|
2917
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2918
|
+
await fs.ensureDir(authPackageDir);
|
|
2870
2919
|
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2871
|
-
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc,
|
|
2872
|
-
if (context.backend === "next") {
|
|
2873
|
-
const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
|
|
2874
|
-
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
|
|
2875
|
-
}
|
|
2920
|
+
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
|
|
2876
2921
|
if (context.orm !== "none" && context.database !== "none") {
|
|
2922
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2923
|
+
await fs.ensureDir(dbPackageDir);
|
|
2877
2924
|
const orm = context.orm;
|
|
2878
2925
|
const db = context.database;
|
|
2879
2926
|
let authDbSrc = "";
|
|
2880
2927
|
if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
|
|
2881
2928
|
else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
|
|
2882
2929
|
else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
|
|
2883
|
-
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc,
|
|
2930
|
+
if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
|
|
2884
2931
|
}
|
|
2885
2932
|
}
|
|
2886
2933
|
if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
|
|
@@ -2896,6 +2943,10 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2896
2943
|
if (reactFramework) {
|
|
2897
2944
|
const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
|
|
2898
2945
|
if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
|
|
2946
|
+
if (context.backend === "self" && reactFramework === "next") {
|
|
2947
|
+
const authFullstackSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/fullstack/next`);
|
|
2948
|
+
if (await fs.pathExists(authFullstackSrc)) await processAndCopyFiles("**/*", authFullstackSrc, webAppDir, context);
|
|
2949
|
+
}
|
|
2899
2950
|
}
|
|
2900
2951
|
} else if (hasNuxtWeb) {
|
|
2901
2952
|
const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
|
|
@@ -2926,9 +2977,11 @@ async function setupPaymentsTemplate(projectDir, context) {
|
|
|
2926
2977
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2927
2978
|
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2928
2979
|
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2929
|
-
if (serverAppDirExists && context.backend !== "convex") {
|
|
2980
|
+
if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
|
|
2981
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2982
|
+
await fs.ensureDir(authPackageDir);
|
|
2930
2983
|
const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
|
|
2931
|
-
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc,
|
|
2984
|
+
if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
|
|
2932
2985
|
}
|
|
2933
2986
|
const hasReactWeb = context.frontend.some((f) => [
|
|
2934
2987
|
"tanstack-router",
|
|
@@ -3004,17 +3057,19 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3004
3057
|
for (const example of context.examples) {
|
|
3005
3058
|
if (example === "none") continue;
|
|
3006
3059
|
const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
|
|
3007
|
-
if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
|
|
3060
|
+
if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex" && context.backend !== "none") {
|
|
3008
3061
|
const exampleServerSrc = path.join(exampleBaseDir, "server");
|
|
3009
|
-
if (
|
|
3010
|
-
const
|
|
3011
|
-
|
|
3062
|
+
if (context.api !== "none") {
|
|
3063
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
3064
|
+
await fs.ensureDir(apiPackageDir);
|
|
3065
|
+
const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
|
|
3066
|
+
if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
|
|
3012
3067
|
}
|
|
3013
3068
|
if (context.orm !== "none" && context.database !== "none") {
|
|
3014
|
-
const
|
|
3015
|
-
|
|
3069
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
3070
|
+
await fs.ensureDir(dbPackageDir);
|
|
3016
3071
|
const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
|
|
3017
|
-
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc,
|
|
3072
|
+
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
|
|
3018
3073
|
}
|
|
3019
3074
|
}
|
|
3020
3075
|
if (webAppDirExists) {
|
|
@@ -3034,6 +3089,10 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3034
3089
|
if (reactFramework) {
|
|
3035
3090
|
const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
|
|
3036
3091
|
if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
|
|
3092
|
+
if (context.backend === "self" && reactFramework === "next") {
|
|
3093
|
+
const exampleFullstackSrc = path.join(exampleBaseDir, "fullstack/next");
|
|
3094
|
+
if (await fs.pathExists(exampleFullstackSrc)) await processAndCopyFiles("**/*", exampleFullstackSrc, webAppDir, context, false);
|
|
3095
|
+
}
|
|
3037
3096
|
}
|
|
3038
3097
|
}
|
|
3039
3098
|
} else if (hasNuxtWeb) {
|
|
@@ -3081,30 +3140,34 @@ async function handleExtras(projectDir, context) {
|
|
|
3081
3140
|
}
|
|
3082
3141
|
async function setupDockerComposeTemplates(projectDir, context) {
|
|
3083
3142
|
if (context.dbSetup !== "docker" || context.database === "none") return;
|
|
3084
|
-
const
|
|
3143
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
3085
3144
|
const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
|
|
3086
|
-
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir,
|
|
3145
|
+
if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
|
|
3087
3146
|
}
|
|
3088
3147
|
async function setupDeploymentTemplates(projectDir, context) {
|
|
3089
|
-
|
|
3148
|
+
const isBackendSelf = context.backend === "self";
|
|
3149
|
+
if (context.webDeploy === "alchemy" || context.serverDeploy === "alchemy") {
|
|
3090
3150
|
const alchemyTemplateSrc = path.join(PKG_ROOT, "templates/deploy/alchemy");
|
|
3091
|
-
if (
|
|
3092
|
-
await
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
await
|
|
3107
|
-
|
|
3151
|
+
if (context.webDeploy === "alchemy" && (context.serverDeploy === "alchemy" || isBackendSelf)) {
|
|
3152
|
+
if (await fs.pathExists(alchemyTemplateSrc)) {
|
|
3153
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, projectDir, context);
|
|
3154
|
+
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3155
|
+
}
|
|
3156
|
+
} else {
|
|
3157
|
+
if (context.webDeploy === "alchemy") {
|
|
3158
|
+
const webAppDir = path.join(projectDir, "apps/web");
|
|
3159
|
+
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(webAppDir)) {
|
|
3160
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, webAppDir, context);
|
|
3161
|
+
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
if (context.serverDeploy === "alchemy" && !isBackendSelf) {
|
|
3165
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
3166
|
+
if (await fs.pathExists(alchemyTemplateSrc) && await fs.pathExists(serverAppDir)) {
|
|
3167
|
+
await processAndCopyFiles("alchemy.run.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
3168
|
+
await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
3169
|
+
await addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc);
|
|
3170
|
+
}
|
|
3108
3171
|
}
|
|
3109
3172
|
}
|
|
3110
3173
|
}
|
|
@@ -3127,7 +3190,7 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3127
3190
|
}
|
|
3128
3191
|
}
|
|
3129
3192
|
}
|
|
3130
|
-
if (context.serverDeploy !== "none" && context.serverDeploy !== "alchemy") {
|
|
3193
|
+
if (context.serverDeploy !== "none" && context.serverDeploy !== "alchemy" && !isBackendSelf) {
|
|
3131
3194
|
const serverAppDir = path.join(projectDir, "apps/server");
|
|
3132
3195
|
if (await fs.pathExists(serverAppDir)) {
|
|
3133
3196
|
const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.serverDeploy}/server`);
|
|
@@ -3135,6 +3198,18 @@ async function setupDeploymentTemplates(projectDir, context) {
|
|
|
3135
3198
|
}
|
|
3136
3199
|
}
|
|
3137
3200
|
}
|
|
3201
|
+
async function addEnvDtsToPackages(projectDir, context, alchemyTemplateSrc) {
|
|
3202
|
+
for (const packageName of [
|
|
3203
|
+
"packages/api",
|
|
3204
|
+
"packages/auth",
|
|
3205
|
+
"packages/db"
|
|
3206
|
+
]) {
|
|
3207
|
+
const packageDir = path.join(projectDir, packageName);
|
|
3208
|
+
if (await fs.pathExists(packageDir)) await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, packageDir, context);
|
|
3209
|
+
}
|
|
3210
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
3211
|
+
if (await fs.pathExists(serverAppDir)) await processAndCopyFiles("env.d.ts.hbs", alchemyTemplateSrc, serverAppDir, context);
|
|
3212
|
+
}
|
|
3138
3213
|
|
|
3139
3214
|
//#endregion
|
|
3140
3215
|
//#region src/helpers/core/add-addons.ts
|
|
@@ -3200,7 +3275,7 @@ async function setupServerDeploy(config) {
|
|
|
3200
3275
|
serverDir,
|
|
3201
3276
|
packageManager
|
|
3202
3277
|
});
|
|
3203
|
-
} else if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, packageManager);
|
|
3278
|
+
} else if (serverDeploy === "alchemy") await setupAlchemyServerDeploy(serverDir, packageManager, projectDir);
|
|
3204
3279
|
}
|
|
3205
3280
|
async function setupWorkersServerDeploy(serverDir, _packageManager) {
|
|
3206
3281
|
const packageJsonPath = path.join(serverDir, "package.json");
|
|
@@ -3237,18 +3312,18 @@ async function generateCloudflareWorkerTypes({ serverDir, packageManager }) {
|
|
|
3237
3312
|
log.warn(`Note: You can manually run 'cd apps/server && ${managerCmd} cf-typegen' in the project directory later`);
|
|
3238
3313
|
}
|
|
3239
3314
|
}
|
|
3240
|
-
async function setupAlchemyServerDeploy(serverDir, _packageManager) {
|
|
3315
|
+
async function setupAlchemyServerDeploy(serverDir, _packageManager, projectDir) {
|
|
3241
3316
|
if (!await fs.pathExists(serverDir)) return;
|
|
3242
3317
|
await addPackageDependency({
|
|
3243
3318
|
devDependencies: [
|
|
3244
3319
|
"alchemy",
|
|
3245
3320
|
"wrangler",
|
|
3246
3321
|
"@types/node",
|
|
3247
|
-
"dotenv",
|
|
3248
3322
|
"@cloudflare/workers-types"
|
|
3249
3323
|
],
|
|
3250
3324
|
projectDir: serverDir
|
|
3251
3325
|
});
|
|
3326
|
+
if (projectDir) await addAlchemyPackagesDependencies$1(projectDir);
|
|
3252
3327
|
const packageJsonPath = path.join(serverDir, "package.json");
|
|
3253
3328
|
if (await fs.pathExists(packageJsonPath)) {
|
|
3254
3329
|
const packageJson = await fs.readJson(packageJsonPath);
|
|
@@ -3261,6 +3336,19 @@ async function setupAlchemyServerDeploy(serverDir, _packageManager) {
|
|
|
3261
3336
|
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
3262
3337
|
}
|
|
3263
3338
|
}
|
|
3339
|
+
async function addAlchemyPackagesDependencies$1(projectDir) {
|
|
3340
|
+
for (const packageName of [
|
|
3341
|
+
"packages/api",
|
|
3342
|
+
"packages/auth",
|
|
3343
|
+
"packages/db"
|
|
3344
|
+
]) {
|
|
3345
|
+
const packageDir = path.join(projectDir, packageName);
|
|
3346
|
+
if (await fs.pathExists(packageDir)) await addPackageDependency({
|
|
3347
|
+
devDependencies: ["@cloudflare/workers-types"],
|
|
3348
|
+
projectDir: packageDir
|
|
3349
|
+
});
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
3264
3352
|
|
|
3265
3353
|
//#endregion
|
|
3266
3354
|
//#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
|
|
@@ -3271,7 +3359,6 @@ async function setupNextAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3271
3359
|
dependencies: ["@opennextjs/cloudflare"],
|
|
3272
3360
|
devDependencies: [
|
|
3273
3361
|
"alchemy",
|
|
3274
|
-
"dotenv",
|
|
3275
3362
|
"wrangler",
|
|
3276
3363
|
"@cloudflare/workers-types"
|
|
3277
3364
|
],
|
|
@@ -3307,7 +3394,6 @@ async function setupNuxtAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3307
3394
|
devDependencies: [
|
|
3308
3395
|
"alchemy",
|
|
3309
3396
|
"nitro-cloudflare-dev",
|
|
3310
|
-
"dotenv",
|
|
3311
3397
|
"wrangler"
|
|
3312
3398
|
],
|
|
3313
3399
|
projectDir: webAppDir
|
|
@@ -3370,7 +3456,7 @@ async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, option
|
|
|
3370
3456
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3371
3457
|
if (!await fs.pathExists(webAppDir)) return;
|
|
3372
3458
|
await addPackageDependency({
|
|
3373
|
-
devDependencies: ["alchemy"
|
|
3459
|
+
devDependencies: ["alchemy"],
|
|
3374
3460
|
projectDir: webAppDir
|
|
3375
3461
|
});
|
|
3376
3462
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -3391,7 +3477,7 @@ async function setupSolidAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3391
3477
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3392
3478
|
if (!await fs.pathExists(webAppDir)) return;
|
|
3393
3479
|
await addPackageDependency({
|
|
3394
|
-
devDependencies: ["alchemy"
|
|
3480
|
+
devDependencies: ["alchemy"],
|
|
3395
3481
|
projectDir: webAppDir
|
|
3396
3482
|
});
|
|
3397
3483
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -3412,11 +3498,7 @@ async function setupSvelteAlchemyDeploy(projectDir, _packageManager, options) {
|
|
|
3412
3498
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3413
3499
|
if (!await fs.pathExists(webAppDir)) return;
|
|
3414
3500
|
await addPackageDependency({
|
|
3415
|
-
devDependencies: [
|
|
3416
|
-
"alchemy",
|
|
3417
|
-
"@sveltejs/adapter-cloudflare",
|
|
3418
|
-
"dotenv"
|
|
3419
|
-
],
|
|
3501
|
+
devDependencies: ["alchemy", "@sveltejs/adapter-cloudflare"],
|
|
3420
3502
|
projectDir: webAppDir
|
|
3421
3503
|
});
|
|
3422
3504
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -3481,7 +3563,7 @@ async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, opt
|
|
|
3481
3563
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3482
3564
|
if (!await fs.pathExists(webAppDir)) return;
|
|
3483
3565
|
await addPackageDependency({
|
|
3484
|
-
devDependencies: ["alchemy"
|
|
3566
|
+
devDependencies: ["alchemy"],
|
|
3485
3567
|
projectDir: webAppDir
|
|
3486
3568
|
});
|
|
3487
3569
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -3502,11 +3584,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3502
3584
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3503
3585
|
if (!await fs.pathExists(webAppDir)) return;
|
|
3504
3586
|
await addPackageDependency({
|
|
3505
|
-
devDependencies: [
|
|
3506
|
-
"alchemy",
|
|
3507
|
-
"dotenv",
|
|
3508
|
-
"@cloudflare/vite-plugin"
|
|
3509
|
-
],
|
|
3587
|
+
devDependencies: ["alchemy", "@cloudflare/vite-plugin"],
|
|
3510
3588
|
projectDir: webAppDir
|
|
3511
3589
|
});
|
|
3512
3590
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -3561,7 +3639,7 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3561
3639
|
//#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
|
|
3562
3640
|
async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
3563
3641
|
await addPackageDependency({
|
|
3564
|
-
devDependencies: ["alchemy"
|
|
3642
|
+
devDependencies: ["alchemy"],
|
|
3565
3643
|
projectDir
|
|
3566
3644
|
});
|
|
3567
3645
|
const rootPkgPath = path.join(projectDir, "package.json");
|
|
@@ -3576,7 +3654,7 @@ async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
|
|
|
3576
3654
|
await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
|
|
3577
3655
|
}
|
|
3578
3656
|
const serverDir = path.join(projectDir, "apps/server");
|
|
3579
|
-
if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, packageManager);
|
|
3657
|
+
if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, packageManager, projectDir);
|
|
3580
3658
|
const frontend = config.frontend;
|
|
3581
3659
|
const isNext = frontend.includes("next");
|
|
3582
3660
|
const isNuxt = frontend.includes("nuxt");
|
|
@@ -3809,6 +3887,7 @@ async function setupWebDeploy(config) {
|
|
|
3809
3887
|
if (webDeploy !== "wrangler" && webDeploy !== "alchemy") return;
|
|
3810
3888
|
if (webDeploy === "alchemy" && serverDeploy === "alchemy") {
|
|
3811
3889
|
await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
|
|
3890
|
+
await addAlchemyPackagesDependencies(projectDir);
|
|
3812
3891
|
return;
|
|
3813
3892
|
}
|
|
3814
3893
|
const isNext = frontend.includes("next");
|
|
@@ -3832,6 +3911,7 @@ async function setupWebDeploy(config) {
|
|
|
3832
3911
|
else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
|
|
3833
3912
|
else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
|
|
3834
3913
|
else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
|
|
3914
|
+
await addAlchemyPackagesDependencies(projectDir);
|
|
3835
3915
|
}
|
|
3836
3916
|
}
|
|
3837
3917
|
async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
@@ -3849,6 +3929,19 @@ async function setupWorkersWebDeploy(projectDir, pkgManager) {
|
|
|
3849
3929
|
}
|
|
3850
3930
|
await setupWorkersVitePlugin(projectDir);
|
|
3851
3931
|
}
|
|
3932
|
+
async function addAlchemyPackagesDependencies(projectDir) {
|
|
3933
|
+
for (const packageName of [
|
|
3934
|
+
"packages/api",
|
|
3935
|
+
"packages/auth",
|
|
3936
|
+
"packages/db"
|
|
3937
|
+
]) {
|
|
3938
|
+
const packageDir = path.join(projectDir, packageName);
|
|
3939
|
+
if (await fs.pathExists(packageDir)) await addPackageDependency({
|
|
3940
|
+
devDependencies: ["@cloudflare/workers-types"],
|
|
3941
|
+
projectDir: packageDir
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3852
3945
|
|
|
3853
3946
|
//#endregion
|
|
3854
3947
|
//#region src/helpers/core/add-deployment.ts
|
|
@@ -3901,18 +3994,137 @@ async function addDeploymentToProject(input) {
|
|
|
3901
3994
|
}
|
|
3902
3995
|
}
|
|
3903
3996
|
|
|
3997
|
+
//#endregion
|
|
3998
|
+
//#region src/utils/setup-catalogs.ts
|
|
3999
|
+
async function setupCatalogs(projectDir, options) {
|
|
4000
|
+
if (options.packageManager === "npm") return;
|
|
4001
|
+
const packagePaths = [
|
|
4002
|
+
"apps/server",
|
|
4003
|
+
"apps/web",
|
|
4004
|
+
"packages/api",
|
|
4005
|
+
"packages/db",
|
|
4006
|
+
"packages/auth",
|
|
4007
|
+
"packages/backend"
|
|
4008
|
+
];
|
|
4009
|
+
const packagesInfo = [];
|
|
4010
|
+
for (const pkgPath of packagePaths) {
|
|
4011
|
+
const fullPath = path.join(projectDir, pkgPath);
|
|
4012
|
+
const pkgJsonPath = path.join(fullPath, "package.json");
|
|
4013
|
+
if (await fs.pathExists(pkgJsonPath)) {
|
|
4014
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
4015
|
+
packagesInfo.push({
|
|
4016
|
+
path: fullPath,
|
|
4017
|
+
dependencies: pkgJson.dependencies || {},
|
|
4018
|
+
devDependencies: pkgJson.devDependencies || {}
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
|
|
4023
|
+
if (Object.keys(catalog).length === 0) return;
|
|
4024
|
+
if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
|
|
4025
|
+
else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
|
|
4026
|
+
await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
|
|
4027
|
+
}
|
|
4028
|
+
function findDuplicateDependencies(packagesInfo, projectName) {
|
|
4029
|
+
const depCount = /* @__PURE__ */ new Map();
|
|
4030
|
+
const projectScope = `@${projectName}/`;
|
|
4031
|
+
for (const pkg of packagesInfo) {
|
|
4032
|
+
const allDeps = {
|
|
4033
|
+
...pkg.dependencies,
|
|
4034
|
+
...pkg.devDependencies
|
|
4035
|
+
};
|
|
4036
|
+
for (const [depName, version] of Object.entries(allDeps)) {
|
|
4037
|
+
if (depName.startsWith(projectScope)) continue;
|
|
4038
|
+
if (version.startsWith("workspace:")) continue;
|
|
4039
|
+
const existing = depCount.get(depName);
|
|
4040
|
+
if (existing) existing.packages.push(pkg.path);
|
|
4041
|
+
else depCount.set(depName, {
|
|
4042
|
+
version,
|
|
4043
|
+
packages: [pkg.path]
|
|
4044
|
+
});
|
|
4045
|
+
}
|
|
4046
|
+
}
|
|
4047
|
+
const catalog = {};
|
|
4048
|
+
for (const [depName, info] of depCount.entries()) if (info.packages.length > 1) catalog[depName] = info.version;
|
|
4049
|
+
return catalog;
|
|
4050
|
+
}
|
|
4051
|
+
async function setupBunCatalogs(projectDir, catalog) {
|
|
4052
|
+
const rootPkgJsonPath = path.join(projectDir, "package.json");
|
|
4053
|
+
const rootPkgJson = await fs.readJson(rootPkgJsonPath);
|
|
4054
|
+
if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
|
|
4055
|
+
if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
|
|
4056
|
+
packages: rootPkgJson.workspaces,
|
|
4057
|
+
catalog
|
|
4058
|
+
};
|
|
4059
|
+
else if (typeof rootPkgJson.workspaces === "object") {
|
|
4060
|
+
if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
|
|
4061
|
+
rootPkgJson.workspaces.catalog = {
|
|
4062
|
+
...rootPkgJson.workspaces.catalog,
|
|
4063
|
+
...catalog
|
|
4064
|
+
};
|
|
4065
|
+
}
|
|
4066
|
+
await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
|
|
4067
|
+
}
|
|
4068
|
+
async function setupPnpmCatalogs(projectDir, catalog) {
|
|
4069
|
+
const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
|
|
4070
|
+
if (!await fs.pathExists(workspaceYamlPath)) return;
|
|
4071
|
+
const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
|
|
4072
|
+
const workspaceYaml = yaml.parse(workspaceContent);
|
|
4073
|
+
if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
|
|
4074
|
+
workspaceYaml.catalog = {
|
|
4075
|
+
...workspaceYaml.catalog,
|
|
4076
|
+
...catalog
|
|
4077
|
+
};
|
|
4078
|
+
await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
|
|
4079
|
+
}
|
|
4080
|
+
async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
|
|
4081
|
+
for (const pkg of packagesInfo) {
|
|
4082
|
+
const pkgJsonPath = path.join(pkg.path, "package.json");
|
|
4083
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
4084
|
+
let updated = false;
|
|
4085
|
+
if (pkgJson.dependencies) {
|
|
4086
|
+
for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
|
|
4087
|
+
pkgJson.dependencies[depName] = "catalog:";
|
|
4088
|
+
updated = true;
|
|
4089
|
+
}
|
|
4090
|
+
}
|
|
4091
|
+
if (pkgJson.devDependencies) {
|
|
4092
|
+
for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
|
|
4093
|
+
pkgJson.devDependencies[depName] = "catalog:";
|
|
4094
|
+
updated = true;
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4100
|
+
|
|
3904
4101
|
//#endregion
|
|
3905
4102
|
//#region src/helpers/addons/examples-setup.ts
|
|
3906
4103
|
async function setupExamples(config) {
|
|
3907
|
-
const { examples, frontend, backend, projectDir } = config;
|
|
4104
|
+
const { examples, frontend, backend, projectDir, orm } = config;
|
|
3908
4105
|
if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
|
|
4106
|
+
const apiDir = path.join(projectDir, "packages/api");
|
|
4107
|
+
if (await fs.pathExists(apiDir) && backend !== "none") {
|
|
4108
|
+
if (orm === "drizzle") await addPackageDependency({
|
|
4109
|
+
dependencies: ["drizzle-orm"],
|
|
4110
|
+
projectDir: apiDir
|
|
4111
|
+
});
|
|
4112
|
+
else if (orm === "prisma") await addPackageDependency({
|
|
4113
|
+
dependencies: ["@prisma/client"],
|
|
4114
|
+
projectDir: apiDir
|
|
4115
|
+
});
|
|
4116
|
+
else if (orm === "mongoose") await addPackageDependency({
|
|
4117
|
+
dependencies: ["mongoose"],
|
|
4118
|
+
projectDir: apiDir
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
3909
4121
|
if (examples.includes("ai")) {
|
|
3910
4122
|
const webClientDir = path.join(projectDir, "apps/web");
|
|
3911
4123
|
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
3912
|
-
const
|
|
4124
|
+
const apiDir$1 = path.join(projectDir, "packages/api");
|
|
3913
4125
|
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
3914
4126
|
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
3915
|
-
const
|
|
4127
|
+
const apiDirExists = await fs.pathExists(apiDir$1);
|
|
3916
4128
|
const hasNuxt = frontend.includes("nuxt");
|
|
3917
4129
|
const hasSvelte = frontend.includes("svelte");
|
|
3918
4130
|
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
@@ -3933,9 +4145,13 @@ async function setupExamples(config) {
|
|
|
3933
4145
|
dependencies: ["ai", "@ai-sdk/react"],
|
|
3934
4146
|
projectDir: nativeClientDir
|
|
3935
4147
|
});
|
|
3936
|
-
if (
|
|
4148
|
+
if (apiDirExists && backend !== "none") await addPackageDependency({
|
|
3937
4149
|
dependencies: ["ai", "@ai-sdk/google"],
|
|
3938
|
-
projectDir:
|
|
4150
|
+
projectDir: apiDir$1
|
|
4151
|
+
});
|
|
4152
|
+
if (backend === "self" && webClientDirExists) await addPackageDependency({
|
|
4153
|
+
dependencies: ["ai", "@ai-sdk/google"],
|
|
4154
|
+
projectDir: webClientDir
|
|
3939
4155
|
});
|
|
3940
4156
|
}
|
|
3941
4157
|
}
|
|
@@ -4058,23 +4274,26 @@ async function setupApi(config) {
|
|
|
4058
4274
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4059
4275
|
const webDirExists = await fs.pathExists(webDir);
|
|
4060
4276
|
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
4061
|
-
|
|
4277
|
+
await fs.pathExists(serverDir);
|
|
4062
4278
|
const frontendType = getFrontendType(frontend);
|
|
4063
4279
|
if (!isConvex && api !== "none") {
|
|
4064
4280
|
const apiDeps = getApiDependencies(api, frontendType);
|
|
4065
|
-
|
|
4281
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
4282
|
+
if (apiDeps.server) {
|
|
4066
4283
|
await addPackageDependency({
|
|
4067
4284
|
dependencies: apiDeps.server.dependencies,
|
|
4068
|
-
projectDir:
|
|
4285
|
+
projectDir: apiPackageDir
|
|
4069
4286
|
});
|
|
4070
|
-
if (
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4287
|
+
if (backend === "self" && webDirExists) await addPackageDependency({
|
|
4288
|
+
dependencies: apiDeps.server.dependencies,
|
|
4289
|
+
projectDir: webDir
|
|
4290
|
+
});
|
|
4291
|
+
if (backend === "self") {
|
|
4292
|
+
const frameworkDeps = [];
|
|
4293
|
+
if (frontend.includes("next")) frameworkDeps.push("next");
|
|
4294
|
+
if (frameworkDeps.length > 0) await addPackageDependency({
|
|
4295
|
+
dependencies: frameworkDeps,
|
|
4296
|
+
projectDir: apiPackageDir
|
|
4078
4297
|
});
|
|
4079
4298
|
}
|
|
4080
4299
|
}
|
|
@@ -4120,7 +4339,7 @@ async function setupApi(config) {
|
|
|
4120
4339
|
//#endregion
|
|
4121
4340
|
//#region src/helpers/core/backend-setup.ts
|
|
4122
4341
|
async function setupBackendDependencies(config) {
|
|
4123
|
-
const { backend, runtime, api, projectDir } = config;
|
|
4342
|
+
const { backend, runtime, api, auth, examples, projectDir } = config;
|
|
4124
4343
|
if (backend === "convex") return;
|
|
4125
4344
|
const framework = backend;
|
|
4126
4345
|
const serverDir = path.join(projectDir, "apps/server");
|
|
@@ -4128,27 +4347,23 @@ async function setupBackendDependencies(config) {
|
|
|
4128
4347
|
const devDependencies = [];
|
|
4129
4348
|
if (framework === "hono") {
|
|
4130
4349
|
dependencies.push("hono");
|
|
4131
|
-
if (
|
|
4132
|
-
if (runtime === "node") {
|
|
4133
|
-
dependencies.push("@hono/node-server");
|
|
4134
|
-
devDependencies.push("tsx", "@types/node");
|
|
4135
|
-
}
|
|
4350
|
+
if (runtime === "node") dependencies.push("@hono/node-server");
|
|
4136
4351
|
} else if (framework === "elysia") {
|
|
4137
4352
|
dependencies.push("elysia", "@elysiajs/cors");
|
|
4138
|
-
if (
|
|
4139
|
-
if (runtime === "node") {
|
|
4140
|
-
dependencies.push("@elysiajs/node");
|
|
4141
|
-
devDependencies.push("tsx", "@types/node");
|
|
4142
|
-
}
|
|
4353
|
+
if (runtime === "node") dependencies.push("@elysiajs/node");
|
|
4143
4354
|
} else if (framework === "express") {
|
|
4144
4355
|
dependencies.push("express", "cors");
|
|
4145
4356
|
devDependencies.push("@types/express", "@types/cors");
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
dependencies.push("
|
|
4149
|
-
if (
|
|
4150
|
-
|
|
4151
|
-
if (
|
|
4357
|
+
} else if (framework === "fastify") dependencies.push("fastify", "@fastify/cors");
|
|
4358
|
+
if (api === "trpc") {
|
|
4359
|
+
dependencies.push("@trpc/server");
|
|
4360
|
+
if (framework === "hono") dependencies.push("@hono/trpc-server");
|
|
4361
|
+
else if (framework === "elysia") dependencies.push("@elysiajs/trpc");
|
|
4362
|
+
} else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
|
|
4363
|
+
if (auth === "better-auth") dependencies.push("better-auth");
|
|
4364
|
+
if (examples.includes("ai")) dependencies.push("ai", "@ai-sdk/google");
|
|
4365
|
+
if (runtime === "node") devDependencies.push("tsx", "@types/node");
|
|
4366
|
+
else if (runtime === "bun") devDependencies.push("@types/bun");
|
|
4152
4367
|
if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
|
|
4153
4368
|
dependencies,
|
|
4154
4369
|
devDependencies,
|
|
@@ -4166,7 +4381,7 @@ async function setupAuth(config) {
|
|
|
4166
4381
|
const nativeDir = path.join(projectDir, "apps/native");
|
|
4167
4382
|
const clientDirExists = await fs.pathExists(clientDir);
|
|
4168
4383
|
const nativeDirExists = await fs.pathExists(nativeDir);
|
|
4169
|
-
|
|
4384
|
+
await fs.pathExists(serverDir);
|
|
4170
4385
|
try {
|
|
4171
4386
|
if (backend === "convex") {
|
|
4172
4387
|
if (auth === "clerk" && clientDirExists) {
|
|
@@ -4222,9 +4437,11 @@ async function setupAuth(config) {
|
|
|
4222
4437
|
});
|
|
4223
4438
|
return;
|
|
4224
4439
|
}
|
|
4225
|
-
|
|
4440
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
4441
|
+
const authPackageDirExists = await fs.pathExists(authPackageDir);
|
|
4442
|
+
if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
|
|
4226
4443
|
dependencies: ["better-auth"],
|
|
4227
|
-
projectDir:
|
|
4444
|
+
projectDir: authPackageDir
|
|
4228
4445
|
});
|
|
4229
4446
|
if (frontend.some((f) => [
|
|
4230
4447
|
"react-router",
|
|
@@ -4246,9 +4463,9 @@ async function setupAuth(config) {
|
|
|
4246
4463
|
dependencies: ["better-auth", "@better-auth/expo"],
|
|
4247
4464
|
projectDir: nativeDir
|
|
4248
4465
|
});
|
|
4249
|
-
if (
|
|
4466
|
+
if (authPackageDirExists) await addPackageDependency({
|
|
4250
4467
|
dependencies: ["@better-auth/expo"],
|
|
4251
|
-
projectDir:
|
|
4468
|
+
projectDir: authPackageDir
|
|
4252
4469
|
});
|
|
4253
4470
|
}
|
|
4254
4471
|
}
|
|
@@ -4267,6 +4484,34 @@ function generateAuthSecret(length = 32) {
|
|
|
4267
4484
|
|
|
4268
4485
|
//#endregion
|
|
4269
4486
|
//#region src/helpers/core/env-setup.ts
|
|
4487
|
+
function getClientServerVar(frontend, backend) {
|
|
4488
|
+
const hasNextJs = frontend.includes("next");
|
|
4489
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
4490
|
+
const hasSvelte = frontend.includes("svelte");
|
|
4491
|
+
if (backend === "self") return {
|
|
4492
|
+
key: "",
|
|
4493
|
+
value: "",
|
|
4494
|
+
write: false
|
|
4495
|
+
};
|
|
4496
|
+
let key = "VITE_SERVER_URL";
|
|
4497
|
+
if (hasNextJs) key = "NEXT_PUBLIC_SERVER_URL";
|
|
4498
|
+
else if (hasNuxt) key = "NUXT_PUBLIC_SERVER_URL";
|
|
4499
|
+
else if (hasSvelte) key = "PUBLIC_SERVER_URL";
|
|
4500
|
+
return {
|
|
4501
|
+
key,
|
|
4502
|
+
value: "http://localhost:3000",
|
|
4503
|
+
write: true
|
|
4504
|
+
};
|
|
4505
|
+
}
|
|
4506
|
+
function getConvexVar(frontend) {
|
|
4507
|
+
const hasNextJs = frontend.includes("next");
|
|
4508
|
+
const hasNuxt = frontend.includes("nuxt");
|
|
4509
|
+
const hasSvelte = frontend.includes("svelte");
|
|
4510
|
+
if (hasNextJs) return "NEXT_PUBLIC_CONVEX_URL";
|
|
4511
|
+
if (hasNuxt) return "NUXT_PUBLIC_CONVEX_URL";
|
|
4512
|
+
if (hasSvelte) return "PUBLIC_CONVEX_URL";
|
|
4513
|
+
return "VITE_CONVEX_URL";
|
|
4514
|
+
}
|
|
4270
4515
|
async function addEnvVariablesToFile(filePath, variables) {
|
|
4271
4516
|
await fs.ensureDir(path.dirname(filePath));
|
|
4272
4517
|
let envContent = "";
|
|
@@ -4325,22 +4570,13 @@ async function setupEnvironmentVariables(config) {
|
|
|
4325
4570
|
if (hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte) {
|
|
4326
4571
|
const clientDir = path.join(projectDir, "apps/web");
|
|
4327
4572
|
if (await fs.pathExists(clientDir)) {
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
else if (hasNuxt) envVarName = "NUXT_PUBLIC_SERVER_URL";
|
|
4332
|
-
else if (hasSvelte) envVarName = "PUBLIC_SERVER_URL";
|
|
4333
|
-
if (backend === "convex") {
|
|
4334
|
-
if (hasNextJs) envVarName = "NEXT_PUBLIC_CONVEX_URL";
|
|
4335
|
-
else if (hasNuxt) envVarName = "NUXT_PUBLIC_CONVEX_URL";
|
|
4336
|
-
else if (hasSvelte) envVarName = "PUBLIC_CONVEX_URL";
|
|
4337
|
-
else envVarName = "VITE_CONVEX_URL";
|
|
4338
|
-
serverUrl = "https://<YOUR_CONVEX_URL>";
|
|
4339
|
-
}
|
|
4573
|
+
const baseVar = getClientServerVar(frontend, backend);
|
|
4574
|
+
const envVarName = backend === "convex" ? getConvexVar(frontend) : baseVar.key;
|
|
4575
|
+
const serverUrl = backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value;
|
|
4340
4576
|
const clientVars = [{
|
|
4341
4577
|
key: envVarName,
|
|
4342
4578
|
value: serverUrl,
|
|
4343
|
-
condition: true
|
|
4579
|
+
condition: backend === "convex" ? true : baseVar.write
|
|
4344
4580
|
}];
|
|
4345
4581
|
if (backend === "convex" && auth === "clerk") {
|
|
4346
4582
|
if (hasNextJs) clientVars.push({
|
|
@@ -4431,10 +4667,9 @@ async function setupEnvironmentVariables(config) {
|
|
|
4431
4667
|
return;
|
|
4432
4668
|
}
|
|
4433
4669
|
const serverDir = path.join(projectDir, "apps/server");
|
|
4434
|
-
if (!await fs.pathExists(serverDir)) return;
|
|
4435
|
-
const envPath = path.join(serverDir, ".env");
|
|
4436
4670
|
let corsOrigin = "http://localhost:3001";
|
|
4437
|
-
if (
|
|
4671
|
+
if (backend === "self") corsOrigin = "http://localhost:3001";
|
|
4672
|
+
else if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
|
|
4438
4673
|
let databaseUrl = null;
|
|
4439
4674
|
if (database !== "none" && dbSetup === "none") switch (database) {
|
|
4440
4675
|
case "postgres":
|
|
@@ -4448,15 +4683,13 @@ async function setupEnvironmentVariables(config) {
|
|
|
4448
4683
|
break;
|
|
4449
4684
|
case "sqlite":
|
|
4450
4685
|
if (config.runtime === "workers") databaseUrl = "http://127.0.0.1:8080";
|
|
4451
|
-
else
|
|
4686
|
+
else {
|
|
4687
|
+
const dbAppDir = backend === "self" ? "apps/web" : "apps/server";
|
|
4688
|
+
databaseUrl = `file:${path.join(config.projectDir, dbAppDir, "local.db")}`;
|
|
4689
|
+
}
|
|
4452
4690
|
break;
|
|
4453
4691
|
}
|
|
4454
4692
|
const serverVars = [
|
|
4455
|
-
{
|
|
4456
|
-
key: "CORS_ORIGIN",
|
|
4457
|
-
value: corsOrigin,
|
|
4458
|
-
condition: true
|
|
4459
|
-
},
|
|
4460
4693
|
{
|
|
4461
4694
|
key: "BETTER_AUTH_SECRET",
|
|
4462
4695
|
value: generateAuthSecret(),
|
|
@@ -4464,19 +4697,9 @@ async function setupEnvironmentVariables(config) {
|
|
|
4464
4697
|
},
|
|
4465
4698
|
{
|
|
4466
4699
|
key: "BETTER_AUTH_URL",
|
|
4467
|
-
value: "http://localhost:3000",
|
|
4700
|
+
value: backend === "self" ? "http://localhost:3001" : "http://localhost:3000",
|
|
4468
4701
|
condition: !!auth
|
|
4469
4702
|
},
|
|
4470
|
-
{
|
|
4471
|
-
key: "DATABASE_URL",
|
|
4472
|
-
value: databaseUrl,
|
|
4473
|
-
condition: database !== "none" && dbSetup === "none"
|
|
4474
|
-
},
|
|
4475
|
-
{
|
|
4476
|
-
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4477
|
-
value: "",
|
|
4478
|
-
condition: examples?.includes("ai") || false
|
|
4479
|
-
},
|
|
4480
4703
|
{
|
|
4481
4704
|
key: "POLAR_ACCESS_TOKEN",
|
|
4482
4705
|
value: "",
|
|
@@ -4486,9 +4709,27 @@ async function setupEnvironmentVariables(config) {
|
|
|
4486
4709
|
key: "POLAR_SUCCESS_URL",
|
|
4487
4710
|
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
4488
4711
|
condition: config.payments === "polar"
|
|
4712
|
+
},
|
|
4713
|
+
{
|
|
4714
|
+
key: "CORS_ORIGIN",
|
|
4715
|
+
value: corsOrigin,
|
|
4716
|
+
condition: true
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4720
|
+
value: "",
|
|
4721
|
+
condition: examples?.includes("ai") || false
|
|
4722
|
+
},
|
|
4723
|
+
{
|
|
4724
|
+
key: "DATABASE_URL",
|
|
4725
|
+
value: databaseUrl,
|
|
4726
|
+
condition: database !== "none" && dbSetup === "none"
|
|
4489
4727
|
}
|
|
4490
4728
|
];
|
|
4491
|
-
|
|
4729
|
+
if (backend === "self") {
|
|
4730
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
4731
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
|
|
4732
|
+
} else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
|
|
4492
4733
|
const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
|
|
4493
4734
|
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4494
4735
|
if (isUnifiedAlchemy) {
|
|
@@ -4508,12 +4749,15 @@ async function setupEnvironmentVariables(config) {
|
|
|
4508
4749
|
}]);
|
|
4509
4750
|
}
|
|
4510
4751
|
if (serverDeploy === "alchemy") {
|
|
4511
|
-
const
|
|
4512
|
-
if (await fs.pathExists(serverDir$1)) await addEnvVariablesToFile(path.join(serverDir$1, ".env"), [{
|
|
4752
|
+
const serverAlchemyVars = [{
|
|
4513
4753
|
key: "ALCHEMY_PASSWORD",
|
|
4514
4754
|
value: "please-change-this",
|
|
4515
4755
|
condition: true
|
|
4516
|
-
}]
|
|
4756
|
+
}];
|
|
4757
|
+
if (backend === "self") {
|
|
4758
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
4759
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverAlchemyVars);
|
|
4760
|
+
} else await addEnvVariablesToFile(path.join(serverDir, ".env"), serverAlchemyVars);
|
|
4517
4761
|
}
|
|
4518
4762
|
}
|
|
4519
4763
|
}
|
|
@@ -4521,9 +4765,10 @@ async function setupEnvironmentVariables(config) {
|
|
|
4521
4765
|
//#endregion
|
|
4522
4766
|
//#region src/helpers/database-providers/d1-setup.ts
|
|
4523
4767
|
async function setupCloudflareD1(config) {
|
|
4524
|
-
const { projectDir, serverDeploy, orm } = config;
|
|
4768
|
+
const { projectDir, serverDeploy, orm, backend } = config;
|
|
4525
4769
|
if (serverDeploy === "wrangler") {
|
|
4526
|
-
const
|
|
4770
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
4771
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
4527
4772
|
const variables = [
|
|
4528
4773
|
{
|
|
4529
4774
|
key: "CLOUDFLARE_ACCOUNT_ID",
|
|
@@ -4546,16 +4791,17 @@ async function setupCloudflareD1(config) {
|
|
|
4546
4791
|
} catch (_err) {}
|
|
4547
4792
|
}
|
|
4548
4793
|
if ((serverDeploy === "wrangler" || serverDeploy === "alchemy") && orm === "prisma") {
|
|
4549
|
-
const
|
|
4794
|
+
const targetApp2 = backend === "self" ? "apps/web" : "apps/server";
|
|
4795
|
+
const envPath = path.join(projectDir, targetApp2, ".env");
|
|
4550
4796
|
const variables = [{
|
|
4551
4797
|
key: "DATABASE_URL",
|
|
4552
|
-
value: "
|
|
4798
|
+
value: `file:${path.join(projectDir, "apps/server", "local.db")}`,
|
|
4553
4799
|
condition: true
|
|
4554
4800
|
}];
|
|
4555
4801
|
try {
|
|
4556
4802
|
await addEnvVariablesToFile(envPath, variables);
|
|
4557
4803
|
} catch (_err) {}
|
|
4558
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
4804
|
+
const serverDir = path.join(projectDir, backend === "self" ? "apps/web" : "apps/server");
|
|
4559
4805
|
await addPackageDependency({
|
|
4560
4806
|
dependencies: ["@prisma/adapter-d1"],
|
|
4561
4807
|
projectDir: serverDir
|
|
@@ -4606,14 +4852,13 @@ async function commandExists(command) {
|
|
|
4606
4852
|
//#endregion
|
|
4607
4853
|
//#region src/helpers/database-providers/mongodb-atlas-setup.ts
|
|
4608
4854
|
async function checkAtlasCLI() {
|
|
4609
|
-
const s = spinner();
|
|
4610
|
-
s.start("Checking for MongoDB Atlas CLI...");
|
|
4611
4855
|
try {
|
|
4612
4856
|
const exists = await commandExists("atlas");
|
|
4613
|
-
|
|
4857
|
+
if (exists) log.info("MongoDB Atlas CLI found");
|
|
4858
|
+
else log.warn(pc.yellow("MongoDB Atlas CLI not found"));
|
|
4614
4859
|
return exists;
|
|
4615
4860
|
} catch (_error) {
|
|
4616
|
-
|
|
4861
|
+
log.error(pc.red("Error checking MongoDB Atlas CLI"));
|
|
4617
4862
|
return false;
|
|
4618
4863
|
}
|
|
4619
4864
|
}
|
|
@@ -4624,12 +4869,13 @@ async function initMongoDBAtlas(serverDir) {
|
|
|
4624
4869
|
log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
|
|
4625
4870
|
return null;
|
|
4626
4871
|
}
|
|
4627
|
-
log.info(
|
|
4872
|
+
log.info("Running MongoDB Atlas setup...");
|
|
4628
4873
|
await execa("atlas", ["deployments", "setup"], {
|
|
4629
4874
|
cwd: serverDir,
|
|
4875
|
+
shell: true,
|
|
4630
4876
|
stdio: "inherit"
|
|
4631
4877
|
});
|
|
4632
|
-
log.
|
|
4878
|
+
log.success("MongoDB Atlas deployment ready");
|
|
4633
4879
|
const connectionString = await text({
|
|
4634
4880
|
message: "Enter your MongoDB connection string:",
|
|
4635
4881
|
placeholder: "mongodb+srv://username:password@cluster.mongodb.net/database",
|
|
@@ -4648,9 +4894,10 @@ async function initMongoDBAtlas(serverDir) {
|
|
|
4648
4894
|
return null;
|
|
4649
4895
|
}
|
|
4650
4896
|
}
|
|
4651
|
-
async function writeEnvFile$3(projectDir, config) {
|
|
4897
|
+
async function writeEnvFile$3(projectDir, backend, config) {
|
|
4652
4898
|
try {
|
|
4653
|
-
const
|
|
4899
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
4900
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
4654
4901
|
const variables = [{
|
|
4655
4902
|
key: "DATABASE_URL",
|
|
4656
4903
|
value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
|
|
@@ -4679,16 +4926,14 @@ ${pc.green("MongoDB Atlas Manual Setup Instructions:")}
|
|
|
4679
4926
|
`);
|
|
4680
4927
|
}
|
|
4681
4928
|
async function setupMongoDBAtlas(config, cliInput) {
|
|
4682
|
-
const { projectDir } = config;
|
|
4929
|
+
const { projectDir, backend } = config;
|
|
4683
4930
|
const manualDb = cliInput?.manualDb ?? false;
|
|
4684
|
-
const
|
|
4685
|
-
mainSpinner.start("Setting up MongoDB Atlas...");
|
|
4686
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
4931
|
+
const serverDir = path.join(projectDir, "packages/db");
|
|
4687
4932
|
try {
|
|
4688
4933
|
await fs.ensureDir(serverDir);
|
|
4689
4934
|
if (manualDb) {
|
|
4690
|
-
|
|
4691
|
-
await writeEnvFile$3(projectDir);
|
|
4935
|
+
log.info("MongoDB Atlas manual setup selected");
|
|
4936
|
+
await writeEnvFile$3(projectDir, backend);
|
|
4692
4937
|
displayManualSetupInstructions$3();
|
|
4693
4938
|
return;
|
|
4694
4939
|
}
|
|
@@ -4707,26 +4952,24 @@ async function setupMongoDBAtlas(config, cliInput) {
|
|
|
4707
4952
|
});
|
|
4708
4953
|
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
|
4709
4954
|
if (mode === "manual") {
|
|
4710
|
-
|
|
4711
|
-
await writeEnvFile$3(projectDir);
|
|
4955
|
+
log.info("MongoDB Atlas manual setup selected");
|
|
4956
|
+
await writeEnvFile$3(projectDir, backend);
|
|
4712
4957
|
displayManualSetupInstructions$3();
|
|
4713
4958
|
return;
|
|
4714
4959
|
}
|
|
4715
|
-
mainSpinner.stop("MongoDB Atlas setup ready");
|
|
4716
4960
|
const config$1 = await initMongoDBAtlas(serverDir);
|
|
4717
4961
|
if (config$1) {
|
|
4718
|
-
await writeEnvFile$3(projectDir, config$1);
|
|
4962
|
+
await writeEnvFile$3(projectDir, backend, config$1);
|
|
4719
4963
|
log.success(pc.green("MongoDB Atlas setup complete! Connection saved to .env file."));
|
|
4720
4964
|
} else {
|
|
4721
4965
|
log.warn(pc.yellow("Falling back to local MongoDB configuration"));
|
|
4722
|
-
await writeEnvFile$3(projectDir);
|
|
4966
|
+
await writeEnvFile$3(projectDir, backend);
|
|
4723
4967
|
displayManualSetupInstructions$3();
|
|
4724
4968
|
}
|
|
4725
4969
|
} catch (error) {
|
|
4726
|
-
mainSpinner.stop(pc.red("MongoDB Atlas setup failed"));
|
|
4727
4970
|
consola.error(pc.red(`Error during MongoDB Atlas setup: ${error instanceof Error ? error.message : String(error)}`));
|
|
4728
4971
|
try {
|
|
4729
|
-
await writeEnvFile$3(projectDir);
|
|
4972
|
+
await writeEnvFile$3(projectDir, backend);
|
|
4730
4973
|
displayManualSetupInstructions$3();
|
|
4731
4974
|
} catch {}
|
|
4732
4975
|
}
|
|
@@ -4783,7 +5026,7 @@ async function executeNeonCommand(packageManager, commandArgsString, spinnerText
|
|
|
4783
5026
|
}
|
|
4784
5027
|
async function createNeonProject(projectName, regionId, packageManager) {
|
|
4785
5028
|
try {
|
|
4786
|
-
const commandArgsString = `neonctl projects create --name ${projectName} --region-id ${regionId} --output json`;
|
|
5029
|
+
const commandArgsString = `neonctl@latest projects create --name ${projectName} --region-id ${regionId} --output json`;
|
|
4787
5030
|
const { stdout } = await executeNeonCommand(packageManager, commandArgsString, `Creating Neon project "${projectName}"...`);
|
|
4788
5031
|
const response = JSON.parse(stdout);
|
|
4789
5032
|
if (response.project && response.connection_uris && response.connection_uris.length > 0) {
|
|
@@ -4803,8 +5046,9 @@ async function createNeonProject(projectName, regionId, packageManager) {
|
|
|
4803
5046
|
consola$1.error(pc.red("Failed to create Neon project"));
|
|
4804
5047
|
}
|
|
4805
5048
|
}
|
|
4806
|
-
async function writeEnvFile$2(projectDir, config) {
|
|
4807
|
-
const
|
|
5049
|
+
async function writeEnvFile$2(projectDir, backend, config) {
|
|
5050
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5051
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
4808
5052
|
const variables = [{
|
|
4809
5053
|
key: "DATABASE_URL",
|
|
4810
5054
|
value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
|
|
@@ -4813,16 +5057,17 @@ async function writeEnvFile$2(projectDir, config) {
|
|
|
4813
5057
|
await addEnvVariablesToFile(envPath, variables);
|
|
4814
5058
|
return true;
|
|
4815
5059
|
}
|
|
4816
|
-
async function setupWithNeonDb(projectDir, packageManager) {
|
|
5060
|
+
async function setupWithNeonDb(projectDir, packageManager, backend) {
|
|
4817
5061
|
try {
|
|
4818
5062
|
const s = spinner();
|
|
4819
5063
|
s.start("Creating Neon database using neondb...");
|
|
4820
|
-
const
|
|
4821
|
-
|
|
4822
|
-
|
|
5064
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5065
|
+
const targetDir = path.join(projectDir, targetApp);
|
|
5066
|
+
await fs.ensureDir(targetDir);
|
|
5067
|
+
const packageCmd = getPackageExecutionCommand(packageManager, "neondb@latest --yes");
|
|
4823
5068
|
await execa(packageCmd, {
|
|
4824
5069
|
shell: true,
|
|
4825
|
-
cwd:
|
|
5070
|
+
cwd: targetDir
|
|
4826
5071
|
});
|
|
4827
5072
|
s.stop(pc.green("Neon database created successfully!"));
|
|
4828
5073
|
return true;
|
|
@@ -4831,23 +5076,23 @@ async function setupWithNeonDb(projectDir, packageManager) {
|
|
|
4831
5076
|
throw error;
|
|
4832
5077
|
}
|
|
4833
5078
|
}
|
|
4834
|
-
function displayManualSetupInstructions$2() {
|
|
5079
|
+
function displayManualSetupInstructions$2(target) {
|
|
4835
5080
|
log.info(`Manual Neon PostgreSQL Setup Instructions:
|
|
4836
5081
|
|
|
4837
5082
|
1. Visit https://neon.tech and create an account
|
|
4838
5083
|
2. Create a new project from the dashboard
|
|
4839
5084
|
3. Get your connection string
|
|
4840
|
-
4. Add the database URL to the .env file in
|
|
5085
|
+
4. Add the database URL to the .env file in ${target}/.env
|
|
4841
5086
|
|
|
4842
5087
|
DATABASE_URL="your_connection_string"`);
|
|
4843
5088
|
}
|
|
4844
5089
|
async function setupNeonPostgres(config, cliInput) {
|
|
4845
|
-
const { packageManager, projectDir } = config;
|
|
5090
|
+
const { packageManager, projectDir, backend } = config;
|
|
4846
5091
|
const manualDb = cliInput?.manualDb ?? false;
|
|
4847
5092
|
try {
|
|
4848
5093
|
if (manualDb) {
|
|
4849
|
-
await writeEnvFile$2(projectDir);
|
|
4850
|
-
displayManualSetupInstructions$2();
|
|
5094
|
+
await writeEnvFile$2(projectDir, backend);
|
|
5095
|
+
displayManualSetupInstructions$2(backend === "self" ? "apps/web" : "apps/server");
|
|
4851
5096
|
return;
|
|
4852
5097
|
}
|
|
4853
5098
|
const mode = await select({
|
|
@@ -4865,8 +5110,8 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
4865
5110
|
});
|
|
4866
5111
|
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
|
4867
5112
|
if (mode === "manual") {
|
|
4868
|
-
await writeEnvFile$2(projectDir);
|
|
4869
|
-
displayManualSetupInstructions$2();
|
|
5113
|
+
await writeEnvFile$2(projectDir, backend);
|
|
5114
|
+
displayManualSetupInstructions$2(backend === "self" ? "apps/web" : "apps/server");
|
|
4870
5115
|
return;
|
|
4871
5116
|
}
|
|
4872
5117
|
const setupMethod = await select({
|
|
@@ -4883,7 +5128,7 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
4883
5128
|
initialValue: "neondb"
|
|
4884
5129
|
});
|
|
4885
5130
|
if (isCancel(setupMethod)) return exitCancelled("Operation cancelled");
|
|
4886
|
-
if (setupMethod === "neondb") await setupWithNeonDb(projectDir, packageManager);
|
|
5131
|
+
if (setupMethod === "neondb") await setupWithNeonDb(projectDir, packageManager, backend);
|
|
4887
5132
|
else {
|
|
4888
5133
|
const suggestedProjectName = path.basename(projectDir);
|
|
4889
5134
|
const projectName = await text({
|
|
@@ -4901,22 +5146,22 @@ async function setupNeonPostgres(config, cliInput) {
|
|
|
4901
5146
|
if (!neonConfig) throw new Error("Failed to create project - couldn't get connection information");
|
|
4902
5147
|
const finalSpinner = spinner();
|
|
4903
5148
|
finalSpinner.start("Configuring database connection");
|
|
4904
|
-
await
|
|
4905
|
-
await writeEnvFile$2(projectDir, neonConfig);
|
|
5149
|
+
await writeEnvFile$2(projectDir, backend, neonConfig);
|
|
4906
5150
|
finalSpinner.stop("Neon database configured!");
|
|
4907
5151
|
}
|
|
4908
5152
|
} catch (error) {
|
|
4909
5153
|
if (error instanceof Error) consola$1.error(pc.red(error.message));
|
|
4910
|
-
await writeEnvFile$2(projectDir);
|
|
4911
|
-
displayManualSetupInstructions$2();
|
|
5154
|
+
await writeEnvFile$2(projectDir, backend);
|
|
5155
|
+
displayManualSetupInstructions$2(backend === "self" ? "apps/web" : "apps/server");
|
|
4912
5156
|
}
|
|
4913
5157
|
}
|
|
4914
5158
|
|
|
4915
5159
|
//#endregion
|
|
4916
5160
|
//#region src/helpers/database-providers/planetscale-setup.ts
|
|
4917
5161
|
async function setupPlanetScale(config) {
|
|
4918
|
-
const { projectDir, database, orm } = config;
|
|
4919
|
-
const
|
|
5162
|
+
const { projectDir, database, orm, backend } = config;
|
|
5163
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5164
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
4920
5165
|
if (database === "mysql" && orm === "drizzle") {
|
|
4921
5166
|
const variables = [
|
|
4922
5167
|
{
|
|
@@ -4940,7 +5185,7 @@ async function setupPlanetScale(config) {
|
|
|
4940
5185
|
condition: true
|
|
4941
5186
|
}
|
|
4942
5187
|
];
|
|
4943
|
-
await fs.ensureDir(path.join(projectDir,
|
|
5188
|
+
await fs.ensureDir(path.join(projectDir, targetApp));
|
|
4944
5189
|
await addEnvVariablesToFile(envPath, variables);
|
|
4945
5190
|
}
|
|
4946
5191
|
if (database === "postgres" && orm === "prisma") {
|
|
@@ -4949,7 +5194,7 @@ async function setupPlanetScale(config) {
|
|
|
4949
5194
|
value: "postgresql://username:password@host/database?sslaccept=strict",
|
|
4950
5195
|
condition: true
|
|
4951
5196
|
}];
|
|
4952
|
-
await fs.ensureDir(path.join(projectDir,
|
|
5197
|
+
await fs.ensureDir(path.join(projectDir, targetApp));
|
|
4953
5198
|
await addEnvVariablesToFile(envPath, variables);
|
|
4954
5199
|
}
|
|
4955
5200
|
if (database === "postgres" && orm === "drizzle") {
|
|
@@ -4958,7 +5203,7 @@ async function setupPlanetScale(config) {
|
|
|
4958
5203
|
value: "postgresql://username:password@host/database?sslmode=verify-full",
|
|
4959
5204
|
condition: true
|
|
4960
5205
|
}];
|
|
4961
|
-
await fs.ensureDir(path.join(projectDir,
|
|
5206
|
+
await fs.ensureDir(path.join(projectDir, targetApp));
|
|
4962
5207
|
await addEnvVariablesToFile(envPath, variables);
|
|
4963
5208
|
}
|
|
4964
5209
|
if (database === "mysql" && orm === "prisma") {
|
|
@@ -4967,7 +5212,7 @@ async function setupPlanetScale(config) {
|
|
|
4967
5212
|
value: "mysql://username:password@host/database?sslaccept=strict",
|
|
4968
5213
|
condition: true
|
|
4969
5214
|
}];
|
|
4970
|
-
await fs.ensureDir(path.join(projectDir,
|
|
5215
|
+
await fs.ensureDir(path.join(projectDir, targetApp));
|
|
4971
5216
|
await addEnvVariablesToFile(envPath, variables);
|
|
4972
5217
|
}
|
|
4973
5218
|
}
|
|
@@ -5037,7 +5282,7 @@ async function initPrismaDatabase(serverDir, packageManager) {
|
|
|
5037
5282
|
try {
|
|
5038
5283
|
const prismaDir = path.join(serverDir, "prisma");
|
|
5039
5284
|
await fs.ensureDir(prismaDir);
|
|
5040
|
-
log.info("Starting Prisma PostgreSQL setup.
|
|
5285
|
+
log.info("Starting Prisma PostgreSQL setup.");
|
|
5041
5286
|
const prismaInitCommand = getPackageExecutionCommand(packageManager, "prisma init --db");
|
|
5042
5287
|
await execa(prismaInitCommand, {
|
|
5043
5288
|
cwd: serverDir,
|
|
@@ -5059,9 +5304,10 @@ async function initPrismaDatabase(serverDir, packageManager) {
|
|
|
5059
5304
|
return null;
|
|
5060
5305
|
}
|
|
5061
5306
|
}
|
|
5062
|
-
async function writeEnvFile$1(projectDir, config) {
|
|
5307
|
+
async function writeEnvFile$1(projectDir, backend, config) {
|
|
5063
5308
|
try {
|
|
5064
|
-
const
|
|
5309
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5310
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
5065
5311
|
const variables = [{
|
|
5066
5312
|
key: "DATABASE_URL",
|
|
5067
5313
|
value: config?.databaseUrl ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
|
|
@@ -5077,31 +5323,32 @@ async function writeEnvFile$1(projectDir, config) {
|
|
|
5077
5323
|
consola$1.error("Failed to update environment configuration");
|
|
5078
5324
|
}
|
|
5079
5325
|
}
|
|
5080
|
-
async function addDotenvImportToPrismaConfig(projectDir) {
|
|
5326
|
+
async function addDotenvImportToPrismaConfig(projectDir, backend) {
|
|
5081
5327
|
try {
|
|
5082
|
-
const prismaConfigPath = path.join(projectDir, "
|
|
5328
|
+
const prismaConfigPath = path.join(projectDir, "packages/db/prisma.config.ts");
|
|
5083
5329
|
let content = await fs.readFile(prismaConfigPath, "utf8");
|
|
5084
|
-
content = `import "dotenv
|
|
5330
|
+
content = `import dotenv from "dotenv";\ndotenv.config({ path: "${backend === "self" ? "../../apps/web/.env" : "../../apps/server/.env"}" });\n${content}`;
|
|
5085
5331
|
await fs.writeFile(prismaConfigPath, content);
|
|
5086
5332
|
} catch (_error) {
|
|
5087
5333
|
consola$1.error("Failed to update prisma.config.ts");
|
|
5088
5334
|
}
|
|
5089
5335
|
}
|
|
5090
|
-
function displayManualSetupInstructions$1() {
|
|
5336
|
+
function displayManualSetupInstructions$1(target) {
|
|
5091
5337
|
log.info(`Manual Prisma PostgreSQL Setup Instructions:
|
|
5092
5338
|
|
|
5093
5339
|
1. Visit https://console.prisma.io and create an account
|
|
5094
5340
|
2. Create a new PostgreSQL database from the dashboard
|
|
5095
5341
|
3. Get your database URL
|
|
5096
|
-
4. Add the database URL to the .env file in
|
|
5342
|
+
4. Add the database URL to the .env file in ${target}/.env
|
|
5097
5343
|
|
|
5098
5344
|
DATABASE_URL="your_database_url"`);
|
|
5099
5345
|
}
|
|
5100
|
-
async function addPrismaAccelerateExtension(
|
|
5346
|
+
async function addPrismaAccelerateExtension(projectDir) {
|
|
5101
5347
|
try {
|
|
5348
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5102
5349
|
await addPackageDependency({
|
|
5103
5350
|
dependencies: ["@prisma/extension-accelerate"],
|
|
5104
|
-
projectDir:
|
|
5351
|
+
projectDir: dbPackageDir
|
|
5105
5352
|
});
|
|
5106
5353
|
return true;
|
|
5107
5354
|
} catch (_error) {
|
|
@@ -5110,14 +5357,14 @@ async function addPrismaAccelerateExtension(serverDir) {
|
|
|
5110
5357
|
}
|
|
5111
5358
|
}
|
|
5112
5359
|
async function setupPrismaPostgres(config, cliInput) {
|
|
5113
|
-
const { packageManager, projectDir, orm } = config;
|
|
5360
|
+
const { packageManager, projectDir, orm, backend } = config;
|
|
5114
5361
|
const manualDb = cliInput?.manualDb ?? false;
|
|
5115
|
-
const
|
|
5362
|
+
const dbDir = path.join(projectDir, "packages/db");
|
|
5116
5363
|
try {
|
|
5117
|
-
await fs.ensureDir(
|
|
5364
|
+
await fs.ensureDir(dbDir);
|
|
5118
5365
|
if (manualDb) {
|
|
5119
|
-
await writeEnvFile$1(projectDir);
|
|
5120
|
-
displayManualSetupInstructions$1();
|
|
5366
|
+
await writeEnvFile$1(projectDir, backend);
|
|
5367
|
+
displayManualSetupInstructions$1(backend === "self" ? "apps/web" : "apps/server");
|
|
5121
5368
|
return;
|
|
5122
5369
|
}
|
|
5123
5370
|
const mode = await select({
|
|
@@ -5135,8 +5382,8 @@ async function setupPrismaPostgres(config, cliInput) {
|
|
|
5135
5382
|
});
|
|
5136
5383
|
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
|
5137
5384
|
if (mode === "manual") {
|
|
5138
|
-
await writeEnvFile$1(projectDir);
|
|
5139
|
-
displayManualSetupInstructions$1();
|
|
5385
|
+
await writeEnvFile$1(projectDir, backend);
|
|
5386
|
+
displayManualSetupInstructions$1(backend === "self" ? "apps/web" : "apps/server");
|
|
5140
5387
|
return;
|
|
5141
5388
|
}
|
|
5142
5389
|
const setupOptions = [{
|
|
@@ -5156,26 +5403,26 @@ async function setupPrismaPostgres(config, cliInput) {
|
|
|
5156
5403
|
});
|
|
5157
5404
|
if (isCancel(setupMethod)) return exitCancelled("Operation cancelled");
|
|
5158
5405
|
let prismaConfig = null;
|
|
5159
|
-
if (setupMethod === "create-db") prismaConfig = await setupWithCreateDb(
|
|
5160
|
-
else prismaConfig = await initPrismaDatabase(
|
|
5406
|
+
if (setupMethod === "create-db") prismaConfig = await setupWithCreateDb(dbDir, packageManager, orm);
|
|
5407
|
+
else prismaConfig = await initPrismaDatabase(dbDir, packageManager);
|
|
5161
5408
|
if (prismaConfig) {
|
|
5162
|
-
await writeEnvFile$1(projectDir, prismaConfig);
|
|
5409
|
+
await writeEnvFile$1(projectDir, backend, prismaConfig);
|
|
5163
5410
|
if (orm === "prisma") {
|
|
5164
|
-
await addDotenvImportToPrismaConfig(projectDir);
|
|
5165
|
-
await addPrismaAccelerateExtension(
|
|
5411
|
+
await addDotenvImportToPrismaConfig(projectDir, backend);
|
|
5412
|
+
await addPrismaAccelerateExtension(projectDir);
|
|
5166
5413
|
}
|
|
5167
5414
|
const connectionType = orm === "drizzle" ? "direct connection" : "Prisma Accelerate";
|
|
5168
5415
|
log.success(pc.green(`Prisma Postgres database configured successfully with ${connectionType}!`));
|
|
5169
5416
|
if (prismaConfig.claimUrl) log.info(pc.blue(`Claim URL saved to .env: ${prismaConfig.claimUrl}`));
|
|
5170
5417
|
} else {
|
|
5171
|
-
await writeEnvFile$1(projectDir);
|
|
5172
|
-
displayManualSetupInstructions$1();
|
|
5418
|
+
await writeEnvFile$1(projectDir, backend);
|
|
5419
|
+
displayManualSetupInstructions$1(backend === "self" ? "apps/web" : "apps/server");
|
|
5173
5420
|
}
|
|
5174
5421
|
} catch (error) {
|
|
5175
5422
|
consola$1.error(pc.red(`Error during Prisma Postgres setup: ${error instanceof Error ? error.message : String(error)}`));
|
|
5176
5423
|
try {
|
|
5177
|
-
await writeEnvFile$1(projectDir);
|
|
5178
|
-
displayManualSetupInstructions$1();
|
|
5424
|
+
await writeEnvFile$1(projectDir, backend);
|
|
5425
|
+
displayManualSetupInstructions$1(backend === "self" ? "apps/web" : "apps/server");
|
|
5179
5426
|
} catch {}
|
|
5180
5427
|
log.info("Setup completed with manual configuration required.");
|
|
5181
5428
|
}
|
|
@@ -5183,9 +5430,10 @@ async function setupPrismaPostgres(config, cliInput) {
|
|
|
5183
5430
|
|
|
5184
5431
|
//#endregion
|
|
5185
5432
|
//#region src/helpers/database-providers/supabase-setup.ts
|
|
5186
|
-
async function writeSupabaseEnvFile(projectDir, databaseUrl) {
|
|
5433
|
+
async function writeSupabaseEnvFile(projectDir, backend, databaseUrl) {
|
|
5187
5434
|
try {
|
|
5188
|
-
const
|
|
5435
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5436
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
5189
5437
|
const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
|
|
5190
5438
|
await addEnvVariablesToFile(envPath, [{
|
|
5191
5439
|
key: "DATABASE_URL",
|
|
@@ -5262,23 +5510,23 @@ function displayManualSupabaseInstructions(output) {
|
|
|
5262
5510
|
log.info(`"Manual Supabase Setup Instructions:"
|
|
5263
5511
|
1. Ensure Docker is installed and running.
|
|
5264
5512
|
2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
|
|
5265
|
-
3. Run \`supabase init\` in your project's \`
|
|
5266
|
-
4. Run \`supabase start\` in your project's \`
|
|
5513
|
+
3. Run \`supabase init\` in your project's \`packages/db\` directory.
|
|
5514
|
+
4. Run \`supabase start\` in your project's \`packages/db\` directory.
|
|
5267
5515
|
5. Copy the 'DB URL' from the output.${output ? `
|
|
5268
5516
|
${pc.bold("Relevant output from `supabase start`:")}
|
|
5269
5517
|
${pc.dim(output)}` : ""}
|
|
5270
|
-
6. Add the DB URL to the .env file in \`
|
|
5518
|
+
6. Add the DB URL to the .env file in \`packages/db/.env\` as \`DATABASE_URL\`:
|
|
5271
5519
|
${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
|
|
5272
5520
|
}
|
|
5273
5521
|
async function setupSupabase(config, cliInput) {
|
|
5274
|
-
const { projectDir, packageManager } = config;
|
|
5522
|
+
const { projectDir, packageManager, backend } = config;
|
|
5275
5523
|
const manualDb = cliInput?.manualDb ?? false;
|
|
5276
|
-
const serverDir = path.join(projectDir, "
|
|
5524
|
+
const serverDir = path.join(projectDir, "packages", "db");
|
|
5277
5525
|
try {
|
|
5278
5526
|
await fs.ensureDir(serverDir);
|
|
5279
5527
|
if (manualDb) {
|
|
5280
5528
|
displayManualSupabaseInstructions();
|
|
5281
|
-
await writeSupabaseEnvFile(projectDir, "");
|
|
5529
|
+
await writeSupabaseEnvFile(projectDir, backend, "");
|
|
5282
5530
|
return;
|
|
5283
5531
|
}
|
|
5284
5532
|
const mode = await select({
|
|
@@ -5297,7 +5545,7 @@ async function setupSupabase(config, cliInput) {
|
|
|
5297
5545
|
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
|
5298
5546
|
if (mode === "manual") {
|
|
5299
5547
|
displayManualSupabaseInstructions();
|
|
5300
|
-
await writeSupabaseEnvFile(projectDir, "");
|
|
5548
|
+
await writeSupabaseEnvFile(projectDir, backend, "");
|
|
5301
5549
|
return;
|
|
5302
5550
|
}
|
|
5303
5551
|
if (!await initializeSupabase(serverDir, packageManager)) {
|
|
@@ -5310,7 +5558,7 @@ async function setupSupabase(config, cliInput) {
|
|
|
5310
5558
|
return;
|
|
5311
5559
|
}
|
|
5312
5560
|
const dbUrl = extractDbUrl(supabaseOutput);
|
|
5313
|
-
if (dbUrl) if (await writeSupabaseEnvFile(projectDir, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
|
|
5561
|
+
if (dbUrl) if (await writeSupabaseEnvFile(projectDir, backend, dbUrl)) log.success(pc.green("Supabase local development setup ready!"));
|
|
5314
5562
|
else {
|
|
5315
5563
|
log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
|
|
5316
5564
|
displayManualSupabaseInstructions(supabaseOutput);
|
|
@@ -5438,8 +5686,9 @@ async function createTursoDatabase(dbName, groupName) {
|
|
|
5438
5686
|
s.stop(pc.red("Failed to retrieve database connection details"));
|
|
5439
5687
|
}
|
|
5440
5688
|
}
|
|
5441
|
-
async function writeEnvFile(projectDir, config) {
|
|
5442
|
-
const
|
|
5689
|
+
async function writeEnvFile(projectDir, backend, config) {
|
|
5690
|
+
const targetApp = backend === "self" ? "apps/web" : "apps/server";
|
|
5691
|
+
const envPath = path.join(projectDir, targetApp, ".env");
|
|
5443
5692
|
const variables = [{
|
|
5444
5693
|
key: "DATABASE_URL",
|
|
5445
5694
|
value: config?.dbUrl ?? "",
|
|
@@ -5463,12 +5712,12 @@ DATABASE_URL=your_database_url
|
|
|
5463
5712
|
DATABASE_AUTH_TOKEN=your_auth_token`);
|
|
5464
5713
|
}
|
|
5465
5714
|
async function setupTurso(config, cliInput) {
|
|
5466
|
-
const { orm, projectDir } = config;
|
|
5715
|
+
const { orm, projectDir, backend } = config;
|
|
5467
5716
|
const manualDb = cliInput?.manualDb ?? false;
|
|
5468
5717
|
const setupSpinner = spinner();
|
|
5469
5718
|
try {
|
|
5470
5719
|
if (manualDb) {
|
|
5471
|
-
await writeEnvFile(projectDir);
|
|
5720
|
+
await writeEnvFile(projectDir, backend);
|
|
5472
5721
|
displayManualSetupInstructions();
|
|
5473
5722
|
return;
|
|
5474
5723
|
}
|
|
@@ -5487,7 +5736,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5487
5736
|
});
|
|
5488
5737
|
if (isCancel(mode)) return exitCancelled("Operation cancelled");
|
|
5489
5738
|
if (mode === "manual") {
|
|
5490
|
-
await writeEnvFile(projectDir);
|
|
5739
|
+
await writeEnvFile(projectDir, backend);
|
|
5491
5740
|
displayManualSetupInstructions();
|
|
5492
5741
|
return;
|
|
5493
5742
|
}
|
|
@@ -5497,7 +5746,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5497
5746
|
if (platform === "win32") {
|
|
5498
5747
|
if (setupSpinner) setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
|
|
5499
5748
|
log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
|
|
5500
|
-
await writeEnvFile(projectDir);
|
|
5749
|
+
await writeEnvFile(projectDir, backend);
|
|
5501
5750
|
displayManualSetupInstructions();
|
|
5502
5751
|
return;
|
|
5503
5752
|
}
|
|
@@ -5509,7 +5758,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5509
5758
|
});
|
|
5510
5759
|
if (isCancel(shouldInstall)) return exitCancelled("Operation cancelled");
|
|
5511
5760
|
if (!shouldInstall) {
|
|
5512
|
-
await writeEnvFile(projectDir);
|
|
5761
|
+
await writeEnvFile(projectDir, backend);
|
|
5513
5762
|
displayManualSetupInstructions();
|
|
5514
5763
|
return;
|
|
5515
5764
|
}
|
|
@@ -5531,7 +5780,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5531
5780
|
dbName = dbNameResponse;
|
|
5532
5781
|
try {
|
|
5533
5782
|
const config$1 = await createTursoDatabase(dbName, selectedGroup);
|
|
5534
|
-
await writeEnvFile(projectDir, config$1);
|
|
5783
|
+
await writeEnvFile(projectDir, backend, config$1);
|
|
5535
5784
|
success = true;
|
|
5536
5785
|
} catch (error) {
|
|
5537
5786
|
if (error instanceof Error && error.message === "DATABASE_EXISTS") {
|
|
@@ -5544,7 +5793,7 @@ async function setupTurso(config, cliInput) {
|
|
|
5544
5793
|
} catch (error) {
|
|
5545
5794
|
if (setupSpinner) setupSpinner.stop(pc.red("Turso CLI availability check failed"));
|
|
5546
5795
|
consola.error(pc.red(`Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`));
|
|
5547
|
-
await writeEnvFile(projectDir);
|
|
5796
|
+
await writeEnvFile(projectDir, backend);
|
|
5548
5797
|
displayManualSetupInstructions();
|
|
5549
5798
|
log.success("Setup completed with manual configuration required.");
|
|
5550
5799
|
}
|
|
@@ -5556,40 +5805,48 @@ async function setupDatabase(config, cliInput) {
|
|
|
5556
5805
|
const { database, orm, dbSetup, backend, projectDir } = config;
|
|
5557
5806
|
if (backend === "convex" || database === "none") {
|
|
5558
5807
|
if (backend !== "convex") {
|
|
5559
|
-
const serverDir
|
|
5560
|
-
const serverDbDir = path.join(serverDir
|
|
5808
|
+
const serverDir = path.join(projectDir, "apps/server");
|
|
5809
|
+
const serverDbDir = path.join(serverDir, "src/db");
|
|
5561
5810
|
if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
|
|
5562
5811
|
}
|
|
5563
5812
|
return;
|
|
5564
5813
|
}
|
|
5565
5814
|
const s = spinner();
|
|
5566
|
-
const
|
|
5567
|
-
if (!await fs.pathExists(
|
|
5815
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
5816
|
+
if (!await fs.pathExists(dbPackageDir)) return;
|
|
5568
5817
|
try {
|
|
5569
|
-
if (orm === "prisma")
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5818
|
+
if (orm === "prisma") {
|
|
5819
|
+
if (database === "mysql" && dbSetup === "planetscale") await addPackageDependency({
|
|
5820
|
+
dependencies: [
|
|
5821
|
+
"@prisma/client",
|
|
5822
|
+
"@prisma/adapter-planetscale",
|
|
5823
|
+
"@planetscale/database"
|
|
5824
|
+
],
|
|
5825
|
+
devDependencies: ["prisma"],
|
|
5826
|
+
projectDir: dbPackageDir
|
|
5827
|
+
});
|
|
5828
|
+
else if (database === "sqlite" && dbSetup === "turso") await addPackageDependency({
|
|
5829
|
+
dependencies: ["@prisma/client", "@prisma/adapter-libsql"],
|
|
5830
|
+
devDependencies: ["prisma"],
|
|
5831
|
+
projectDir: dbPackageDir
|
|
5832
|
+
});
|
|
5833
|
+
else await addPackageDependency({
|
|
5834
|
+
dependencies: ["@prisma/client"],
|
|
5835
|
+
devDependencies: ["prisma"],
|
|
5836
|
+
projectDir: dbPackageDir
|
|
5837
|
+
});
|
|
5838
|
+
if (backend === "self") {
|
|
5839
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
5840
|
+
if (await fs.pathExists(webDir)) await addPackageDependency({
|
|
5841
|
+
dependencies: ["@prisma/client"],
|
|
5842
|
+
projectDir: webDir
|
|
5843
|
+
});
|
|
5844
|
+
}
|
|
5845
|
+
} else if (orm === "drizzle") {
|
|
5589
5846
|
if (database === "sqlite") await addPackageDependency({
|
|
5590
5847
|
dependencies: ["drizzle-orm", "@libsql/client"],
|
|
5591
5848
|
devDependencies: ["drizzle-kit"],
|
|
5592
|
-
projectDir:
|
|
5849
|
+
projectDir: dbPackageDir
|
|
5593
5850
|
});
|
|
5594
5851
|
else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
|
|
5595
5852
|
dependencies: [
|
|
@@ -5598,32 +5855,32 @@ async function setupDatabase(config, cliInput) {
|
|
|
5598
5855
|
"ws"
|
|
5599
5856
|
],
|
|
5600
5857
|
devDependencies: ["drizzle-kit", "@types/ws"],
|
|
5601
|
-
projectDir:
|
|
5858
|
+
projectDir: dbPackageDir
|
|
5602
5859
|
});
|
|
5603
5860
|
else if (dbSetup === "planetscale") await addPackageDependency({
|
|
5604
5861
|
dependencies: ["drizzle-orm", "pg"],
|
|
5605
5862
|
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
5606
|
-
projectDir:
|
|
5863
|
+
projectDir: dbPackageDir
|
|
5607
5864
|
});
|
|
5608
5865
|
else await addPackageDependency({
|
|
5609
5866
|
dependencies: ["drizzle-orm", "pg"],
|
|
5610
5867
|
devDependencies: ["drizzle-kit", "@types/pg"],
|
|
5611
|
-
projectDir:
|
|
5868
|
+
projectDir: dbPackageDir
|
|
5612
5869
|
});
|
|
5613
5870
|
else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
|
|
5614
5871
|
dependencies: ["drizzle-orm", "@planetscale/database"],
|
|
5615
5872
|
devDependencies: ["drizzle-kit"],
|
|
5616
|
-
projectDir:
|
|
5873
|
+
projectDir: dbPackageDir
|
|
5617
5874
|
});
|
|
5618
5875
|
else await addPackageDependency({
|
|
5619
5876
|
dependencies: ["drizzle-orm", "mysql2"],
|
|
5620
5877
|
devDependencies: ["drizzle-kit"],
|
|
5621
|
-
projectDir:
|
|
5878
|
+
projectDir: dbPackageDir
|
|
5622
5879
|
});
|
|
5623
5880
|
} else if (orm === "mongoose") await addPackageDependency({
|
|
5624
5881
|
dependencies: ["mongoose"],
|
|
5625
5882
|
devDependencies: [],
|
|
5626
|
-
projectDir:
|
|
5883
|
+
projectDir: dbPackageDir
|
|
5627
5884
|
});
|
|
5628
5885
|
if (dbSetup === "docker") await setupDockerCompose(config);
|
|
5629
5886
|
else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
|
|
@@ -5646,7 +5903,7 @@ async function setupDatabase(config, cliInput) {
|
|
|
5646
5903
|
//#region src/helpers/core/runtime-setup.ts
|
|
5647
5904
|
async function setupRuntime(config) {
|
|
5648
5905
|
const { runtime, backend, projectDir } = config;
|
|
5649
|
-
if (backend === "convex" || backend === "
|
|
5906
|
+
if (backend === "convex" || backend === "self" || runtime === "none") return;
|
|
5650
5907
|
const serverDir = path.join(projectDir, "apps/server");
|
|
5651
5908
|
if (!await fs.pathExists(serverDir)) return;
|
|
5652
5909
|
if (runtime === "bun") await setupBunRuntime(serverDir, backend);
|
|
@@ -5736,7 +5993,7 @@ This project uses Convex as a backend. You'll need to set up Convex before runni
|
|
|
5736
5993
|
${packageManagerRunCmd} dev:setup
|
|
5737
5994
|
\`\`\`
|
|
5738
5995
|
|
|
5739
|
-
Follow the prompts to create a new Convex project and connect it to your application.${auth === "clerk" ? " See [Convex + Clerk guide](https://docs.convex.dev/auth/clerk) for auth setup." : ""}` : generateDatabaseSetup(database, auth, packageManagerRunCmd, orm, options.dbSetup, options.serverDeploy)}
|
|
5996
|
+
Follow the prompts to create a new Convex project and connect it to your application.${auth === "clerk" ? " See [Convex + Clerk guide](https://docs.convex.dev/auth/clerk) for auth setup." : ""}` : generateDatabaseSetup(database, auth, packageManagerRunCmd, orm, options.dbSetup, options.serverDeploy, options.backend)}
|
|
5740
5997
|
|
|
5741
5998
|
Then, run the development server:
|
|
5742
5999
|
|
|
@@ -5789,6 +6046,7 @@ function generateRunningInstructions(frontend, backend, webPort, hasNative, isCo
|
|
|
5789
6046
|
const instructions = [];
|
|
5790
6047
|
const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
|
|
5791
6048
|
const isBackendNone = backend === "none";
|
|
6049
|
+
const isBackendSelf = backend === "self";
|
|
5792
6050
|
if (!hasFrontendNone) {
|
|
5793
6051
|
const hasTanstackRouter = frontend.includes("tanstack-router");
|
|
5794
6052
|
const hasReactRouter = frontend.includes("react-router");
|
|
@@ -5797,17 +6055,19 @@ function generateRunningInstructions(frontend, backend, webPort, hasNative, isCo
|
|
|
5797
6055
|
const hasSvelte = frontend.includes("svelte");
|
|
5798
6056
|
const hasNuxt = frontend.includes("nuxt");
|
|
5799
6057
|
const hasSolid = frontend.includes("solid");
|
|
5800
|
-
if (hasTanstackRouter || hasReactRouter || hasNext || hasTanstackStart || hasSvelte || hasNuxt || hasSolid) instructions.push(`Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see
|
|
6058
|
+
if (hasTanstackRouter || hasReactRouter || hasNext || hasTanstackStart || hasSvelte || hasNuxt || hasSolid) if (isBackendSelf) instructions.push(`Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see your fullstack application.`);
|
|
6059
|
+
else instructions.push(`Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see the web application.`);
|
|
5801
6060
|
}
|
|
5802
6061
|
if (hasNative) instructions.push("Use the Expo Go app to run the mobile application.");
|
|
5803
6062
|
if (isConvex) instructions.push("Your app will connect to the Convex cloud backend automatically.");
|
|
5804
|
-
else if (!isBackendNone) instructions.push("The API is running at [http://localhost:3000](http://localhost:3000).");
|
|
6063
|
+
else if (!isBackendNone && !isBackendSelf) instructions.push("The API is running at [http://localhost:3000](http://localhost:3000).");
|
|
5805
6064
|
return instructions.join("\n");
|
|
5806
6065
|
}
|
|
5807
6066
|
function generateProjectStructure(projectName, frontend, backend, addons, isConvex, api, auth) {
|
|
5808
6067
|
const structure = [`${projectName}/`, "├── apps/"];
|
|
5809
6068
|
const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
|
|
5810
6069
|
const isBackendNone = backend === "none";
|
|
6070
|
+
const isBackendSelf = backend === "self";
|
|
5811
6071
|
if (!hasFrontendNone) {
|
|
5812
6072
|
const hasTanstackRouter = frontend.includes("tanstack-router");
|
|
5813
6073
|
const hasReactRouter = frontend.includes("react-router");
|
|
@@ -5825,21 +6085,32 @@ function generateProjectStructure(projectName, frontend, backend, addons, isConv
|
|
|
5825
6085
|
else if (hasSvelte) frontendType = "SvelteKit";
|
|
5826
6086
|
else if (hasNuxt) frontendType = "Nuxt";
|
|
5827
6087
|
else if (hasSolid) frontendType = "SolidJS";
|
|
5828
|
-
structure.push(`│
|
|
6088
|
+
if (isBackendSelf) structure.push(`│ └── web/ # Fullstack application (${frontendType})`);
|
|
6089
|
+
else structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
|
|
5829
6090
|
}
|
|
5830
6091
|
}
|
|
5831
|
-
if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
5832
|
-
|
|
5833
|
-
if (
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
if (auth === "clerk") structure.push("│ ├── convex/ # Convex functions and schema", "│ └── .env.local # Convex environment variables");
|
|
5837
|
-
} else if (!isBackendNone) {
|
|
6092
|
+
if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) if (isBackendSelf) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
6093
|
+
else structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
|
|
6094
|
+
if (addons.includes("starlight")) if (isBackendSelf) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
|
|
6095
|
+
else structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
|
|
6096
|
+
if (!isBackendSelf && !isBackendNone && !isConvex) {
|
|
5838
6097
|
const backendName = backend[0].toUpperCase() + backend.slice(1);
|
|
5839
6098
|
const apiName = api !== "none" ? api.toUpperCase() : "";
|
|
5840
6099
|
const backendDesc = apiName ? `${backendName}, ${apiName}` : backendName;
|
|
5841
6100
|
structure.push(`│ └── server/ # Backend API (${backendDesc})`);
|
|
5842
6101
|
}
|
|
6102
|
+
if (isConvex || !isBackendNone) {
|
|
6103
|
+
structure.push("├── packages/");
|
|
6104
|
+
if (isConvex) {
|
|
6105
|
+
structure.push("│ ├── backend/ # Convex backend functions and schema");
|
|
6106
|
+
if (auth === "clerk") structure.push("│ │ ├── convex/ # Convex functions and schema", "│ │ └── .env.local # Convex environment variables");
|
|
6107
|
+
}
|
|
6108
|
+
if (!isConvex) {
|
|
6109
|
+
structure.push("│ ├── api/ # API layer / business logic");
|
|
6110
|
+
if (auth !== "none") structure.push("│ ├── auth/ # Authentication configuration & logic");
|
|
6111
|
+
if (api !== "none" || auth !== "none") structure.push("│ └── db/ # Database schema & queries");
|
|
6112
|
+
}
|
|
6113
|
+
}
|
|
5843
6114
|
return structure.join("\n");
|
|
5844
6115
|
}
|
|
5845
6116
|
function generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api) {
|
|
@@ -5897,33 +6168,36 @@ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, ba
|
|
|
5897
6168
|
else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
|
|
5898
6169
|
return addonsList.join("\n");
|
|
5899
6170
|
}
|
|
5900
|
-
function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSetup, serverDeploy) {
|
|
6171
|
+
function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSetup, serverDeploy, backend) {
|
|
5901
6172
|
if (database === "none") return "";
|
|
6173
|
+
const isBackendSelf = backend === "self";
|
|
6174
|
+
const envPath = isBackendSelf ? "apps/web/.env" : "apps/server/.env";
|
|
6175
|
+
const dbLocalPath = isBackendSelf ? "apps/web" : "apps/server";
|
|
5902
6176
|
let setup = "## Database Setup\n\n";
|
|
5903
6177
|
if (database === "sqlite") setup += `This project uses SQLite${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
|
|
5904
6178
|
|
|
5905
6179
|
1. Start the local SQLite database:
|
|
5906
6180
|
${dbSetup === "d1" ? serverDeploy === "alchemy" ? "D1 local development and migrations are handled automatically by Alchemy during dev and deploy." : "Local development for a Cloudflare D1 database will already be running as part of the `wrangler dev` command." : `\`\`\`bash
|
|
5907
|
-
cd
|
|
6181
|
+
cd ${dbLocalPath} && ${packageManagerRunCmd} db:local
|
|
5908
6182
|
\`\`\`
|
|
5909
6183
|
`}
|
|
5910
6184
|
|
|
5911
|
-
2. Update your \`.env\` file in the
|
|
6185
|
+
2. Update your \`.env\` file in the \`${isBackendSelf ? "apps/web" : "apps/server"}\` directory with the appropriate connection details if needed.
|
|
5912
6186
|
`;
|
|
5913
6187
|
else if (database === "postgres") setup += `This project uses PostgreSQL${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
|
|
5914
6188
|
|
|
5915
6189
|
1. Make sure you have a PostgreSQL database set up.
|
|
5916
|
-
2. Update your \`
|
|
6190
|
+
2. Update your \`${envPath}\` file with your PostgreSQL connection details.
|
|
5917
6191
|
`;
|
|
5918
6192
|
else if (database === "mysql") setup += `This project uses MySQL${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
|
|
5919
6193
|
|
|
5920
6194
|
1. Make sure you have a MySQL database set up.
|
|
5921
|
-
2. Update your \`
|
|
6195
|
+
2. Update your \`${envPath}\` file with your MySQL connection details.
|
|
5922
6196
|
`;
|
|
5923
6197
|
else if (database === "mongodb") setup += `This project uses MongoDB ${orm === "mongoose" ? "with Mongoose" : orm === "prisma" ? "with Prisma ORM" : `with ${orm}`}.
|
|
5924
6198
|
|
|
5925
6199
|
1. Make sure you have MongoDB set up.
|
|
5926
|
-
2. Update your \`
|
|
6200
|
+
2. Update your \`${envPath}\` file with your MongoDB connection URI.
|
|
5927
6201
|
`;
|
|
5928
6202
|
setup += `
|
|
5929
6203
|
3. ${orm === "prisma" ? `Generate the Prisma client and push the schema:
|
|
@@ -5942,13 +6216,14 @@ ${packageManagerRunCmd} db:push
|
|
|
5942
6216
|
function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNative, addons, backend) {
|
|
5943
6217
|
const isConvex = backend === "convex";
|
|
5944
6218
|
const isBackendNone = backend === "none";
|
|
6219
|
+
const isBackendSelf = backend === "self";
|
|
5945
6220
|
let scripts = `- \`${packageManagerRunCmd} dev\`: Start all applications in development mode
|
|
5946
6221
|
- \`${packageManagerRunCmd} build\`: Build all applications`;
|
|
5947
|
-
scripts += `
|
|
6222
|
+
if (!isBackendSelf) scripts += `
|
|
5948
6223
|
- \`${packageManagerRunCmd} dev:web\`: Start only the web application`;
|
|
5949
6224
|
if (isConvex) scripts += `
|
|
5950
6225
|
- \`${packageManagerRunCmd} dev:setup\`: Setup and configure your Convex project`;
|
|
5951
|
-
else if (!isBackendNone) scripts += `
|
|
6226
|
+
else if (!isBackendNone && !isBackendSelf) scripts += `
|
|
5952
6227
|
- \`${packageManagerRunCmd} dev:server\`: Start only the server`;
|
|
5953
6228
|
scripts += `
|
|
5954
6229
|
- \`${packageManagerRunCmd} check-types\`: Check TypeScript types across all apps`;
|
|
@@ -5959,7 +6234,7 @@ function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNati
|
|
|
5959
6234
|
- \`${packageManagerRunCmd} db:push\`: Push schema changes to database
|
|
5960
6235
|
- \`${packageManagerRunCmd} db:studio\`: Open database studio UI`;
|
|
5961
6236
|
if (database === "sqlite" && orm === "drizzle") scripts += `
|
|
5962
|
-
- \`cd apps/server && ${packageManagerRunCmd} db:local\`: Start the local SQLite database`;
|
|
6237
|
+
- \`cd ${isBackendSelf ? "apps/web" : "apps/server"} && ${packageManagerRunCmd} db:local\`: Start the local SQLite database`;
|
|
5963
6238
|
}
|
|
5964
6239
|
if (addons.includes("biome")) scripts += `
|
|
5965
6240
|
- \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
|
|
@@ -6035,15 +6310,14 @@ async function initializeGit(projectDir, useGit) {
|
|
|
6035
6310
|
async function setupPayments(config) {
|
|
6036
6311
|
const { payments, projectDir, frontend } = config;
|
|
6037
6312
|
if (!payments || payments === "none") return;
|
|
6038
|
-
const serverDir = path.join(projectDir, "apps/server");
|
|
6039
6313
|
const clientDir = path.join(projectDir, "apps/web");
|
|
6040
|
-
const
|
|
6314
|
+
const authDir = path.join(projectDir, "packages/auth");
|
|
6041
6315
|
const clientDirExists = await fs.pathExists(clientDir);
|
|
6042
|
-
|
|
6316
|
+
const authDirExists = await fs.pathExists(authDir);
|
|
6043
6317
|
if (payments === "polar") {
|
|
6044
|
-
await addPackageDependency({
|
|
6318
|
+
if (authDirExists) await addPackageDependency({
|
|
6045
6319
|
dependencies: ["@polar-sh/better-auth", "@polar-sh/sdk"],
|
|
6046
|
-
projectDir:
|
|
6320
|
+
projectDir: authDir
|
|
6047
6321
|
});
|
|
6048
6322
|
if (clientDirExists) {
|
|
6049
6323
|
if (frontend.some((f) => [
|
|
@@ -6117,19 +6391,20 @@ async function getDockerStatus(database) {
|
|
|
6117
6391
|
async function displayPostInstallInstructions(config) {
|
|
6118
6392
|
const { api, database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend, dbSetup, webDeploy, serverDeploy } = config;
|
|
6119
6393
|
const isConvex = backend === "convex";
|
|
6394
|
+
const isBackendSelf = backend === "self";
|
|
6120
6395
|
const runCmd = packageManager === "npm" ? "npm run" : packageManager === "pnpm" ? "pnpm run" : "bun run";
|
|
6121
6396
|
const cdCmd = `cd ${relativePath}`;
|
|
6122
6397
|
const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
|
|
6123
|
-
const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy) : "";
|
|
6398
|
+
const databaseInstructions = !isConvex && database !== "none" ? await getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) : "";
|
|
6124
6399
|
const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
|
|
6125
6400
|
const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
|
|
6126
6401
|
const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
|
|
6127
6402
|
const pwaInstructions = addons?.includes("pwa") && frontend?.includes("react-router") ? getPwaInstructions() : "";
|
|
6128
6403
|
const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
|
|
6129
6404
|
const clerkInstructions = isConvex && config.auth === "clerk" ? getClerkInstructions() : "";
|
|
6130
|
-
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions() : "";
|
|
6131
|
-
const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy);
|
|
6132
|
-
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy);
|
|
6405
|
+
const polarInstructions = config.payments === "polar" && config.auth === "better-auth" ? getPolarInstructions(backend) : "";
|
|
6406
|
+
const wranglerDeployInstructions = getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
6407
|
+
const alchemyDeployInstructions = getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend);
|
|
6133
6408
|
const hasWeb = frontend?.some((f) => [
|
|
6134
6409
|
"tanstack-router",
|
|
6135
6410
|
"react-router",
|
|
@@ -6153,7 +6428,8 @@ async function displayPostInstallInstructions(config) {
|
|
|
6153
6428
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim(" (this will guide you through Convex project setup)")}\n`;
|
|
6154
6429
|
output += `${pc.cyan(`${stepCounter++}.`)} Copy environment variables from\n${pc.white(" packages/backend/.env.local")} to ${pc.white("apps/*/.env")}\n`;
|
|
6155
6430
|
output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
|
|
6156
|
-
} else {
|
|
6431
|
+
} else if (isBackendSelf) output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6432
|
+
else {
|
|
6157
6433
|
if (runtime !== "workers") output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n`;
|
|
6158
6434
|
if (runtime === "workers") {
|
|
6159
6435
|
if (dbSetup === "d1") output += `${pc.yellow("IMPORTANT:")} Complete D1 database setup first\n (see Database commands below)\n`;
|
|
@@ -6164,11 +6440,11 @@ async function displayPostInstallInstructions(config) {
|
|
|
6164
6440
|
output += `${pc.bold("Your project will be available at:")}\n`;
|
|
6165
6441
|
if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
|
|
6166
6442
|
else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
|
|
6167
|
-
if (!isConvex) {
|
|
6443
|
+
if (!isConvex && !isBackendSelf) {
|
|
6168
6444
|
output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
6169
|
-
if (api === "orpc")
|
|
6170
|
-
else output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
|
|
6445
|
+
if (api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
|
|
6171
6446
|
}
|
|
6447
|
+
if (isBackendSelf && api === "orpc") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:${webPort}/rpc/api\n`;
|
|
6172
6448
|
if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
|
6173
6449
|
if (addons?.includes("fumadocs")) output += `${pc.cyan("•")} Fumadocs: http://localhost:4000\n`;
|
|
6174
6450
|
if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
|
|
@@ -6200,7 +6476,7 @@ function getNativeInstructions(isConvex) {
|
|
|
6200
6476
|
function getLintingInstructions(runCmd) {
|
|
6201
6477
|
return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
|
|
6202
6478
|
}
|
|
6203
|
-
async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy) {
|
|
6479
|
+
async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup, serverDeploy, backend) {
|
|
6204
6480
|
const instructions = [];
|
|
6205
6481
|
if (dbSetup === "docker") {
|
|
6206
6482
|
const dockerStatus = await getDockerStatus(database);
|
|
@@ -6214,8 +6490,9 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup,
|
|
|
6214
6490
|
const packageManager = runCmd === "npm run" ? "npm" : runCmd || "npm";
|
|
6215
6491
|
instructions.push(`${pc.cyan("1.")} Login to Cloudflare: ${pc.white(`${packageManager} wrangler login`)}`);
|
|
6216
6492
|
instructions.push(`${pc.cyan("2.")} Create D1 database: ${pc.white(`${packageManager} wrangler d1 create your-database-name`)}`);
|
|
6217
|
-
|
|
6218
|
-
instructions.push(`${pc.cyan("
|
|
6493
|
+
const wranglerPath = backend === "self" ? "apps/web" : "apps/server";
|
|
6494
|
+
instructions.push(`${pc.cyan("3.")} Update ${wranglerPath}/wrangler.jsonc with database_id and database_name`);
|
|
6495
|
+
instructions.push(`${pc.cyan("4.")} Generate migrations: ${pc.white(`cd ${wranglerPath} && ${runCmd} db:generate`)}`);
|
|
6219
6496
|
instructions.push(`${pc.cyan("5.")} Apply migrations locally: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME --local`)}`);
|
|
6220
6497
|
instructions.push(`${pc.cyan("6.")} Apply migrations to production: ${pc.white(`${packageManager} wrangler d1 migrations apply YOUR_DB_NAME`)}`);
|
|
6221
6498
|
}
|
|
@@ -6239,7 +6516,10 @@ async function getDatabaseInstructions(database, orm, runCmd, runtime, dbSetup,
|
|
|
6239
6516
|
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6240
6517
|
if (dbSetup !== "d1") instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
|
|
6241
6518
|
if (!(dbSetup === "d1" && serverDeploy === "alchemy")) instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
|
|
6242
|
-
if (database === "sqlite" && dbSetup !== "d1")
|
|
6519
|
+
if (database === "sqlite" && dbSetup !== "d1") {
|
|
6520
|
+
const dbLocalPath = backend === "self" ? "apps/web" : "apps/server";
|
|
6521
|
+
instructions.push(`${pc.cyan("•")} Start local DB (if needed): ${`cd ${dbLocalPath} && ${runCmd} db:local`}`);
|
|
6522
|
+
}
|
|
6243
6523
|
} else if (orm === "mongoose") {
|
|
6244
6524
|
if (dbSetup === "docker") instructions.push(`${pc.cyan("•")} Start docker container: ${`${runCmd} db:start`}`);
|
|
6245
6525
|
} else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup\n required.`);
|
|
@@ -6260,32 +6540,115 @@ function getNoOrmWarning() {
|
|
|
6260
6540
|
function getBunWebNativeWarning() {
|
|
6261
6541
|
return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo.\n Use 'pnpm' if problems arise.`;
|
|
6262
6542
|
}
|
|
6263
|
-
function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy) {
|
|
6543
|
+
function getWranglerDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
6264
6544
|
const instructions = [];
|
|
6265
|
-
if (webDeploy === "wrangler")
|
|
6266
|
-
|
|
6545
|
+
if (webDeploy === "wrangler") {
|
|
6546
|
+
const deployPath = backend === "self" ? "apps/web" : "apps/web";
|
|
6547
|
+
instructions.push(`${pc.bold("Deploy web to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd ${deployPath} && ${runCmd} run deploy`}`);
|
|
6548
|
+
}
|
|
6549
|
+
if (serverDeploy === "wrangler" && backend !== "self") instructions.push(`${pc.bold("Deploy server to Cloudflare Workers:")}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} run deploy`}`);
|
|
6267
6550
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
6268
6551
|
}
|
|
6269
6552
|
function getClerkInstructions() {
|
|
6270
6553
|
return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
|
|
6271
6554
|
}
|
|
6272
|
-
function getPolarInstructions() {
|
|
6273
|
-
|
|
6555
|
+
function getPolarInstructions(backend) {
|
|
6556
|
+
const envPath = backend === "self" ? "apps/web/.env" : "apps/server/.env";
|
|
6557
|
+
return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`;
|
|
6274
6558
|
}
|
|
6275
|
-
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy) {
|
|
6559
|
+
function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend) {
|
|
6276
6560
|
const instructions = [];
|
|
6561
|
+
const isBackendSelf = backend === "self";
|
|
6277
6562
|
if (webDeploy === "alchemy" && serverDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy web with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/web && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/web && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/web && ${runCmd} destroy`}`);
|
|
6278
|
-
else if (serverDeploy === "alchemy" && webDeploy !== "alchemy") instructions.push(`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`);
|
|
6279
|
-
else if (webDeploy === "alchemy" && serverDeploy === "alchemy") instructions.push(`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6563
|
+
else if (serverDeploy === "alchemy" && webDeploy !== "alchemy" && !isBackendSelf) instructions.push(`${pc.bold("Deploy server with Alchemy:")}\n${pc.cyan("•")} Dev: ${`cd apps/server && ${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`cd apps/server && ${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`cd apps/server && ${runCmd} destroy`}`);
|
|
6564
|
+
else if (webDeploy === "alchemy" && (serverDeploy === "alchemy" || isBackendSelf)) instructions.push(`${pc.bold("Deploy with Alchemy:")}\n${pc.cyan("•")} Dev: ${`${runCmd} dev`}\n${pc.cyan("•")} Deploy: ${`${runCmd} deploy`}\n${pc.cyan("•")} Destroy: ${`${runCmd} destroy`}`);
|
|
6280
6565
|
return instructions.length ? `\n${instructions.join("\n")}` : "";
|
|
6281
6566
|
}
|
|
6282
6567
|
|
|
6568
|
+
//#endregion
|
|
6569
|
+
//#region src/helpers/core/workspace-setup.ts
|
|
6570
|
+
async function setupWorkspaceDependencies(projectDir, options) {
|
|
6571
|
+
const projectName = options.projectName;
|
|
6572
|
+
const workspaceVersion = options.packageManager === "npm" ? "*" : "workspace:*";
|
|
6573
|
+
const commonDeps = ["dotenv", "zod"];
|
|
6574
|
+
const commonDevDeps = ["tsdown"];
|
|
6575
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
6576
|
+
if (await fs.pathExists(dbPackageDir)) await addPackageDependency({
|
|
6577
|
+
dependencies: commonDeps,
|
|
6578
|
+
devDependencies: commonDevDeps,
|
|
6579
|
+
projectDir: dbPackageDir
|
|
6580
|
+
});
|
|
6581
|
+
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
6582
|
+
if (await fs.pathExists(authPackageDir)) await addPackageDependency({
|
|
6583
|
+
dependencies: commonDeps,
|
|
6584
|
+
devDependencies: commonDevDeps,
|
|
6585
|
+
customDependencies: { [`@${projectName}/db`]: workspaceVersion },
|
|
6586
|
+
projectDir: authPackageDir
|
|
6587
|
+
});
|
|
6588
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
6589
|
+
if (await fs.pathExists(apiPackageDir)) await addPackageDependency({
|
|
6590
|
+
dependencies: commonDeps,
|
|
6591
|
+
devDependencies: commonDevDeps,
|
|
6592
|
+
customDependencies: {
|
|
6593
|
+
[`@${projectName}/auth`]: workspaceVersion,
|
|
6594
|
+
[`@${projectName}/db`]: workspaceVersion
|
|
6595
|
+
},
|
|
6596
|
+
projectDir: apiPackageDir
|
|
6597
|
+
});
|
|
6598
|
+
const serverPackageDir = path.join(projectDir, "apps/server");
|
|
6599
|
+
if (await fs.pathExists(serverPackageDir)) await addPackageDependency({
|
|
6600
|
+
dependencies: commonDeps,
|
|
6601
|
+
devDependencies: commonDevDeps,
|
|
6602
|
+
customDependencies: {
|
|
6603
|
+
[`@${projectName}/api`]: workspaceVersion,
|
|
6604
|
+
[`@${projectName}/auth`]: workspaceVersion,
|
|
6605
|
+
[`@${projectName}/db`]: workspaceVersion
|
|
6606
|
+
},
|
|
6607
|
+
projectDir: serverPackageDir
|
|
6608
|
+
});
|
|
6609
|
+
const webPackageDir = path.join(projectDir, "apps/web");
|
|
6610
|
+
if (await fs.pathExists(webPackageDir)) {
|
|
6611
|
+
const webDeps = {};
|
|
6612
|
+
webDeps[`@${projectName}/api`] = workspaceVersion;
|
|
6613
|
+
webDeps[`@${projectName}/auth`] = workspaceVersion;
|
|
6614
|
+
webDeps[`@${projectName}/db`] = workspaceVersion;
|
|
6615
|
+
if (Object.keys(webDeps).length > 0) await addPackageDependency({
|
|
6616
|
+
customDependencies: webDeps,
|
|
6617
|
+
projectDir: webPackageDir
|
|
6618
|
+
});
|
|
6619
|
+
}
|
|
6620
|
+
const runtimeDevDeps = getRuntimeDevDeps(options);
|
|
6621
|
+
await addPackageDependency({
|
|
6622
|
+
dependencies: commonDeps,
|
|
6623
|
+
devDependencies: [...commonDevDeps, ...runtimeDevDeps],
|
|
6624
|
+
projectDir
|
|
6625
|
+
});
|
|
6626
|
+
}
|
|
6627
|
+
function getRuntimeDevDeps(options) {
|
|
6628
|
+
const { runtime, backend } = options;
|
|
6629
|
+
if (runtime === "none" && backend === "self") return ["@types/node"];
|
|
6630
|
+
if (runtime === "node") return ["@types/node"];
|
|
6631
|
+
if (runtime === "bun") return ["@types/bun"];
|
|
6632
|
+
if (runtime === "workers") return ["@types/node"];
|
|
6633
|
+
return [];
|
|
6634
|
+
}
|
|
6635
|
+
|
|
6283
6636
|
//#endregion
|
|
6284
6637
|
//#region src/helpers/core/project-config.ts
|
|
6285
6638
|
async function updatePackageConfigurations(projectDir, options) {
|
|
6286
6639
|
await updateRootPackageJson(projectDir, options);
|
|
6287
|
-
if (options.backend
|
|
6288
|
-
else
|
|
6640
|
+
if (options.backend === "convex") await updateConvexPackageJson(projectDir, options);
|
|
6641
|
+
else if (options.backend === "self") {
|
|
6642
|
+
await updateDbPackageJson(projectDir, options);
|
|
6643
|
+
await updateAuthPackageJson(projectDir, options);
|
|
6644
|
+
await updateApiPackageJson(projectDir, options);
|
|
6645
|
+
await setupWorkspaceDependencies(projectDir, options);
|
|
6646
|
+
} else if (options.backend !== "none") {
|
|
6647
|
+
await updateServerPackageJson(projectDir, options);
|
|
6648
|
+
await updateAuthPackageJson(projectDir, options);
|
|
6649
|
+
await updateApiPackageJson(projectDir, options);
|
|
6650
|
+
await setupWorkspaceDependencies(projectDir, options);
|
|
6651
|
+
}
|
|
6289
6652
|
}
|
|
6290
6653
|
async function updateRootPackageJson(projectDir, options) {
|
|
6291
6654
|
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
@@ -6295,6 +6658,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6295
6658
|
if (!packageJson.scripts) packageJson.scripts = {};
|
|
6296
6659
|
const scripts = packageJson.scripts;
|
|
6297
6660
|
const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
|
|
6661
|
+
const dbPackageName = `@${options.projectName}/db`;
|
|
6298
6662
|
let serverDevScript = "";
|
|
6299
6663
|
if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
|
|
6300
6664
|
else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
|
|
@@ -6311,24 +6675,24 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6311
6675
|
scripts["check-types"] = "turbo check-types";
|
|
6312
6676
|
scripts["dev:native"] = "turbo -F native dev";
|
|
6313
6677
|
scripts["dev:web"] = "turbo -F web dev";
|
|
6314
|
-
scripts["dev:server"] = serverDevScript;
|
|
6678
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6315
6679
|
if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
|
|
6316
6680
|
if (needsDbScripts) {
|
|
6317
|
-
scripts["db:push"] = `turbo -F ${
|
|
6318
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${
|
|
6681
|
+
scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
|
|
6682
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `turbo -F ${dbPackageName} db:studio`;
|
|
6319
6683
|
if (options.orm === "prisma") {
|
|
6320
|
-
scripts["db:generate"] = `turbo -F ${
|
|
6321
|
-
scripts["db:migrate"] = `turbo -F ${
|
|
6684
|
+
scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
|
|
6685
|
+
scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
|
|
6322
6686
|
} else if (options.orm === "drizzle") {
|
|
6323
|
-
scripts["db:generate"] = `turbo -F ${
|
|
6324
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${
|
|
6687
|
+
scripts["db:generate"] = `turbo -F ${dbPackageName} db:generate`;
|
|
6688
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `turbo -F ${dbPackageName} db:migrate`;
|
|
6325
6689
|
}
|
|
6326
6690
|
}
|
|
6327
6691
|
if (options.dbSetup === "docker") {
|
|
6328
|
-
scripts["db:start"] = `turbo -F ${
|
|
6329
|
-
scripts["db:watch"] = `turbo -F ${
|
|
6330
|
-
scripts["db:stop"] = `turbo -F ${
|
|
6331
|
-
scripts["db:down"] = `turbo -F ${
|
|
6692
|
+
scripts["db:start"] = `turbo -F ${dbPackageName} db:start`;
|
|
6693
|
+
scripts["db:watch"] = `turbo -F ${dbPackageName} db:watch`;
|
|
6694
|
+
scripts["db:stop"] = `turbo -F ${dbPackageName} db:stop`;
|
|
6695
|
+
scripts["db:down"] = `turbo -F ${dbPackageName} db:down`;
|
|
6332
6696
|
}
|
|
6333
6697
|
} else if (options.packageManager === "pnpm") {
|
|
6334
6698
|
scripts.dev = devScript;
|
|
@@ -6336,24 +6700,24 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6336
6700
|
scripts["check-types"] = "pnpm -r check-types";
|
|
6337
6701
|
scripts["dev:native"] = "pnpm --filter native dev";
|
|
6338
6702
|
scripts["dev:web"] = "pnpm --filter web dev";
|
|
6339
|
-
scripts["dev:server"] = serverDevScript;
|
|
6703
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6340
6704
|
if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
|
|
6341
6705
|
if (needsDbScripts) {
|
|
6342
|
-
scripts["db:push"] = `pnpm --filter ${
|
|
6343
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${
|
|
6706
|
+
scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
|
|
6707
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `pnpm --filter ${dbPackageName} db:studio`;
|
|
6344
6708
|
if (options.orm === "prisma") {
|
|
6345
|
-
scripts["db:generate"] = `pnpm --filter ${
|
|
6346
|
-
scripts["db:migrate"] = `pnpm --filter ${
|
|
6709
|
+
scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
|
|
6710
|
+
scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
|
|
6347
6711
|
} else if (options.orm === "drizzle") {
|
|
6348
|
-
scripts["db:generate"] = `pnpm --filter ${
|
|
6349
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${
|
|
6712
|
+
scripts["db:generate"] = `pnpm --filter ${dbPackageName} db:generate`;
|
|
6713
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `pnpm --filter ${dbPackageName} db:migrate`;
|
|
6350
6714
|
}
|
|
6351
6715
|
}
|
|
6352
6716
|
if (options.dbSetup === "docker") {
|
|
6353
|
-
scripts["db:start"] = `pnpm --filter ${
|
|
6354
|
-
scripts["db:watch"] = `pnpm --filter ${
|
|
6355
|
-
scripts["db:stop"] = `pnpm --filter ${
|
|
6356
|
-
scripts["db:down"] = `pnpm --filter ${
|
|
6717
|
+
scripts["db:start"] = `pnpm --filter ${dbPackageName} db:start`;
|
|
6718
|
+
scripts["db:watch"] = `pnpm --filter ${dbPackageName} db:watch`;
|
|
6719
|
+
scripts["db:stop"] = `pnpm --filter ${dbPackageName} db:stop`;
|
|
6720
|
+
scripts["db:down"] = `pnpm --filter ${dbPackageName} db:down`;
|
|
6357
6721
|
}
|
|
6358
6722
|
} else if (options.packageManager === "npm") {
|
|
6359
6723
|
scripts.dev = devScript;
|
|
@@ -6361,24 +6725,24 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6361
6725
|
scripts["check-types"] = "npm run check-types --workspaces";
|
|
6362
6726
|
scripts["dev:native"] = "npm run dev --workspace native";
|
|
6363
6727
|
scripts["dev:web"] = "npm run dev --workspace web";
|
|
6364
|
-
scripts["dev:server"] = serverDevScript;
|
|
6728
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6365
6729
|
if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
|
|
6366
6730
|
if (needsDbScripts) {
|
|
6367
|
-
scripts["db:push"] = `npm run db:push --workspace ${
|
|
6368
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${
|
|
6731
|
+
scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
|
|
6732
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `npm run db:studio --workspace ${dbPackageName}`;
|
|
6369
6733
|
if (options.orm === "prisma") {
|
|
6370
|
-
scripts["db:generate"] = `npm run db:generate --workspace ${
|
|
6371
|
-
scripts["db:migrate"] = `npm run db:migrate --workspace ${
|
|
6734
|
+
scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
|
|
6735
|
+
scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
|
|
6372
6736
|
} else if (options.orm === "drizzle") {
|
|
6373
|
-
scripts["db:generate"] = `npm run db:generate --workspace ${
|
|
6374
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${
|
|
6737
|
+
scripts["db:generate"] = `npm run db:generate --workspace ${dbPackageName}`;
|
|
6738
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `npm run db:migrate --workspace ${dbPackageName}`;
|
|
6375
6739
|
}
|
|
6376
6740
|
}
|
|
6377
6741
|
if (options.dbSetup === "docker") {
|
|
6378
|
-
scripts["db:start"] = `npm run db:start --workspace ${
|
|
6379
|
-
scripts["db:watch"] = `npm run db:watch --workspace ${
|
|
6380
|
-
scripts["db:stop"] = `npm run db:stop --workspace ${
|
|
6381
|
-
scripts["db:down"] = `npm run db:down --workspace ${
|
|
6742
|
+
scripts["db:start"] = `npm run db:start --workspace ${dbPackageName}`;
|
|
6743
|
+
scripts["db:watch"] = `npm run db:watch --workspace ${dbPackageName}`;
|
|
6744
|
+
scripts["db:stop"] = `npm run db:stop --workspace ${dbPackageName}`;
|
|
6745
|
+
scripts["db:down"] = `npm run db:down --workspace ${dbPackageName}`;
|
|
6382
6746
|
}
|
|
6383
6747
|
} else if (options.packageManager === "bun") {
|
|
6384
6748
|
scripts.dev = devScript;
|
|
@@ -6386,24 +6750,24 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6386
6750
|
scripts["check-types"] = "bun run --filter '*' check-types";
|
|
6387
6751
|
scripts["dev:native"] = "bun run --filter native dev";
|
|
6388
6752
|
scripts["dev:web"] = "bun run --filter web dev";
|
|
6389
|
-
scripts["dev:server"] = serverDevScript;
|
|
6753
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6390
6754
|
if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
|
|
6391
6755
|
if (needsDbScripts) {
|
|
6392
|
-
scripts["db:push"] = `bun run --filter ${
|
|
6393
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${
|
|
6756
|
+
scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
|
|
6757
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:studio"] = `bun run --filter ${dbPackageName} db:studio`;
|
|
6394
6758
|
if (options.orm === "prisma") {
|
|
6395
|
-
scripts["db:generate"] = `bun run --filter ${
|
|
6396
|
-
scripts["db:migrate"] = `bun run --filter ${
|
|
6759
|
+
scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
|
|
6760
|
+
scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
|
|
6397
6761
|
} else if (options.orm === "drizzle") {
|
|
6398
|
-
scripts["db:generate"] = `bun run --filter ${
|
|
6399
|
-
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${
|
|
6762
|
+
scripts["db:generate"] = `bun run --filter ${dbPackageName} db:generate`;
|
|
6763
|
+
if (!(options.dbSetup === "d1" && options.serverDeploy === "alchemy")) scripts["db:migrate"] = `bun run --filter ${dbPackageName} db:migrate`;
|
|
6400
6764
|
}
|
|
6401
6765
|
}
|
|
6402
6766
|
if (options.dbSetup === "docker") {
|
|
6403
|
-
scripts["db:start"] = `bun run --filter ${
|
|
6404
|
-
scripts["db:watch"] = `bun run --filter ${
|
|
6405
|
-
scripts["db:stop"] = `bun run --filter ${
|
|
6406
|
-
scripts["db:down"] = `bun run --filter ${
|
|
6767
|
+
scripts["db:start"] = `bun run --filter ${dbPackageName} db:start`;
|
|
6768
|
+
scripts["db:watch"] = `bun run --filter ${dbPackageName} db:watch`;
|
|
6769
|
+
scripts["db:stop"] = `bun run --filter ${dbPackageName} db:stop`;
|
|
6770
|
+
scripts["db:down"] = `bun run --filter ${dbPackageName} db:down`;
|
|
6407
6771
|
}
|
|
6408
6772
|
}
|
|
6409
6773
|
try {
|
|
@@ -6428,7 +6792,16 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
6428
6792
|
if (!await fs.pathExists(serverPackageJsonPath)) return;
|
|
6429
6793
|
const serverPackageJson = await fs.readJson(serverPackageJsonPath);
|
|
6430
6794
|
if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
|
|
6431
|
-
|
|
6795
|
+
await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
|
|
6796
|
+
await updateDbPackageJson(projectDir, options);
|
|
6797
|
+
}
|
|
6798
|
+
async function updateDbPackageJson(projectDir, options) {
|
|
6799
|
+
const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
|
|
6800
|
+
if (!await fs.pathExists(dbPackageJsonPath)) return;
|
|
6801
|
+
const dbPackageJson = await fs.readJson(dbPackageJsonPath);
|
|
6802
|
+
dbPackageJson.name = `@${options.projectName}/db`;
|
|
6803
|
+
if (!dbPackageJson.scripts) dbPackageJson.scripts = {};
|
|
6804
|
+
const scripts = dbPackageJson.scripts;
|
|
6432
6805
|
if (options.database !== "none") {
|
|
6433
6806
|
if (options.database === "sqlite" && options.orm === "drizzle" && options.dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
|
|
6434
6807
|
if (options.orm === "prisma") {
|
|
@@ -6449,7 +6822,21 @@ async function updateServerPackageJson(projectDir, options) {
|
|
|
6449
6822
|
scripts["db:stop"] = "docker compose stop";
|
|
6450
6823
|
scripts["db:down"] = "docker compose down";
|
|
6451
6824
|
}
|
|
6452
|
-
await fs.writeJson(
|
|
6825
|
+
await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
|
|
6826
|
+
}
|
|
6827
|
+
async function updateAuthPackageJson(projectDir, options) {
|
|
6828
|
+
const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
|
|
6829
|
+
if (!await fs.pathExists(authPackageJsonPath)) return;
|
|
6830
|
+
const authPackageJson = await fs.readJson(authPackageJsonPath);
|
|
6831
|
+
authPackageJson.name = `@${options.projectName}/auth`;
|
|
6832
|
+
await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
|
|
6833
|
+
}
|
|
6834
|
+
async function updateApiPackageJson(projectDir, options) {
|
|
6835
|
+
const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
|
|
6836
|
+
if (!await fs.pathExists(apiPackageJsonPath)) return;
|
|
6837
|
+
const apiPackageJson = await fs.readJson(apiPackageJsonPath);
|
|
6838
|
+
apiPackageJson.name = `@${options.projectName}/api`;
|
|
6839
|
+
await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
|
|
6453
6840
|
}
|
|
6454
6841
|
async function updateConvexPackageJson(projectDir, options) {
|
|
6455
6842
|
const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
|
|
@@ -6465,15 +6852,14 @@ async function updateConvexPackageJson(projectDir, options) {
|
|
|
6465
6852
|
async function createProject(options, cliInput) {
|
|
6466
6853
|
const projectDir = options.projectDir;
|
|
6467
6854
|
const isConvex = options.backend === "convex";
|
|
6855
|
+
const isSelfBackend = options.backend === "self";
|
|
6856
|
+
const needsServerSetup = !isConvex && !isSelfBackend;
|
|
6468
6857
|
try {
|
|
6469
6858
|
await fs.ensureDir(projectDir);
|
|
6470
6859
|
await copyBaseTemplate(projectDir, options);
|
|
6471
6860
|
await setupFrontendTemplates(projectDir, options);
|
|
6472
6861
|
await setupBackendFramework(projectDir, options);
|
|
6473
|
-
if (
|
|
6474
|
-
await setupDbOrmTemplates(projectDir, options);
|
|
6475
|
-
await setupDockerComposeTemplates(projectDir, options);
|
|
6476
|
-
}
|
|
6862
|
+
if (needsServerSetup) await setupDockerComposeTemplates(projectDir, options);
|
|
6477
6863
|
await setupAuthTemplate(projectDir, options);
|
|
6478
6864
|
if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
|
|
6479
6865
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
@@ -6481,9 +6867,11 @@ async function createProject(options, cliInput) {
|
|
|
6481
6867
|
await setupDeploymentTemplates(projectDir, options);
|
|
6482
6868
|
await setupApi(options);
|
|
6483
6869
|
if (!isConvex) {
|
|
6484
|
-
|
|
6870
|
+
if (needsServerSetup) {
|
|
6871
|
+
await setupBackendDependencies(options);
|
|
6872
|
+
await setupRuntime(options);
|
|
6873
|
+
}
|
|
6485
6874
|
await setupDatabase(options, cliInput);
|
|
6486
|
-
await setupRuntime(options);
|
|
6487
6875
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
|
|
6488
6876
|
}
|
|
6489
6877
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
@@ -6492,6 +6880,7 @@ async function createProject(options, cliInput) {
|
|
|
6492
6880
|
await handleExtras(projectDir, options);
|
|
6493
6881
|
await setupEnvironmentVariables(options);
|
|
6494
6882
|
await updatePackageConfigurations(projectDir, options);
|
|
6883
|
+
await setupCatalogs(projectDir, options);
|
|
6495
6884
|
await setupWebDeploy(options);
|
|
6496
6885
|
await setupServerDeploy(options);
|
|
6497
6886
|
await createReadme(projectDir, options);
|