create-better-t-stack 2.49.1-canary.80158905 → 2.50.0-canary.08568a05
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 +2 -2
- package/dist/index.js +1 -1
- package/dist/{src-CyG8-I-3.js → src-DfbhNFZ9.js} +342 -208
- package/package.json +2 -1
- package/templates/api/orpc/fullstack/next/src/app/api/rpc/[[...rest]]/route.ts.hbs +21 -0
- package/templates/api/orpc/server/{rest/src → src}/context.ts.hbs +1 -1
- package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +3 -1
- package/templates/api/trpc/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs +14 -0
- package/templates/api/trpc/server/{rest/src → src}/context.ts.hbs +1 -1
- package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +4 -2
- 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/src/index.ts.hbs +1 -1
- package/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs +1 -0
- package/templates/backend/server/elysia/src/index.ts.hbs +1 -1
- package/templates/backend/server/express/src/index.ts.hbs +1 -1
- package/templates/backend/server/fastify/src/index.ts.hbs +1 -1
- package/templates/backend/server/hono/src/index.ts.hbs +2 -2
- package/templates/db/drizzle/mysql/src/index.ts.hbs +1 -1
- package/templates/db/drizzle/postgres/src/index.ts.hbs +1 -1
- package/templates/db/drizzle/sqlite/src/index.ts.hbs +1 -1
- package/templates/deploy/wrangler/web/react/tanstack-start/wrangler.jsonc.hbs +1 -1
- 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 +1 -1
- package/templates/frontend/react/next/package.json.hbs +1 -1
- package/templates/frontend/react/tanstack-start/package.json.hbs +7 -7
- package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +4 -4
- package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +1 -1
- 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/examples/ai/server/next/src/app/ai/route.ts.hbs +0 -15
- /package/templates/api/orpc/server/{base/_gitignore → _gitignore} +0 -0
- /package/templates/api/orpc/server/{base/package.json.hbs → package.json.hbs} +0 -0
- /package/templates/api/orpc/server/{rest/src → src}/index.ts.hbs +0 -0
- /package/templates/api/orpc/server/{base/src → src}/routers/index.ts.hbs +0 -0
- /package/templates/api/orpc/server/{base/tsconfig.json.hbs → tsconfig.json.hbs} +0 -0
- /package/templates/api/orpc/server/{base/tsdown.config.ts.hbs → tsdown.config.ts.hbs} +0 -0
- /package/templates/api/trpc/server/{base/_gitignore → _gitignore} +0 -0
- /package/templates/api/trpc/server/{base/package.json.hbs → package.json.hbs} +0 -0
- /package/templates/api/trpc/server/{rest/src → src}/index.ts.hbs +0 -0
- /package/templates/api/trpc/server/{base/src → src}/routers/index.ts.hbs +0 -0
- /package/templates/api/trpc/server/{base/tsconfig.json.hbs → tsconfig.json.hbs} +0 -0
- /package/templates/api/trpc/server/{base/tsdown.config.ts.hbs → tsdown.config.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/mysql/src/schema/{auth.ts → auth.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/postgres/src/schema/{auth.ts → auth.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/drizzle/sqlite/src/schema/{auth.ts → auth.ts.hbs} +0 -0
- /package/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/{auth.model.ts → 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/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 → models}/todo.model.ts.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
|
|
@@ -65,7 +66,7 @@ const dependencyVersionMap = {
|
|
|
65
66
|
"@better-auth/expo": "^1.3.13",
|
|
66
67
|
"@clerk/nextjs": "^6.31.5",
|
|
67
68
|
"@clerk/clerk-react": "^5.45.0",
|
|
68
|
-
"@clerk/tanstack-react-start": "^0.
|
|
69
|
+
"@clerk/tanstack-react-start": "^0.25.1",
|
|
69
70
|
"@clerk/clerk-expo": "^2.14.25",
|
|
70
71
|
"drizzle-orm": "^0.44.2",
|
|
71
72
|
"drizzle-kit": "^0.31.2",
|
|
@@ -124,6 +125,7 @@ const dependencyVersionMap = {
|
|
|
124
125
|
"@trpc/tanstack-react-query": "^11.5.0",
|
|
125
126
|
"@trpc/server": "^11.5.0",
|
|
126
127
|
"@trpc/client": "^11.5.0",
|
|
128
|
+
"next": "15.5.4",
|
|
127
129
|
convex: "^1.27.0",
|
|
128
130
|
"@convex-dev/react-query": "^0.0.0-alpha.8",
|
|
129
131
|
"convex-svelte": "^0.0.11",
|
|
@@ -139,13 +141,13 @@ const dependencyVersionMap = {
|
|
|
139
141
|
"@tanstack/solid-query": "^5.87.4",
|
|
140
142
|
"@tanstack/solid-query-devtools": "^5.87.4",
|
|
141
143
|
"@tanstack/solid-router-devtools": "^1.131.44",
|
|
142
|
-
wrangler: "^4.
|
|
143
|
-
"@cloudflare/vite-plugin": "^1.
|
|
144
|
+
wrangler: "^4.40.3",
|
|
145
|
+
"@cloudflare/vite-plugin": "^1.13.8",
|
|
144
146
|
"@opennextjs/cloudflare": "^1.6.5",
|
|
145
147
|
"nitro-cloudflare-dev": "^0.2.2",
|
|
146
148
|
"@sveltejs/adapter-cloudflare": "^7.2.1",
|
|
147
149
|
"@cloudflare/workers-types": "^4.20250822.0",
|
|
148
|
-
alchemy: "^0.
|
|
150
|
+
alchemy: "^0.70.0",
|
|
149
151
|
nitropack: "^2.12.4",
|
|
150
152
|
dotenv: "^17.2.2",
|
|
151
153
|
tsdown: "^0.15.5",
|
|
@@ -198,9 +200,9 @@ const BackendSchema = z.enum([
|
|
|
198
200
|
"hono",
|
|
199
201
|
"express",
|
|
200
202
|
"fastify",
|
|
201
|
-
"next",
|
|
202
203
|
"elysia",
|
|
203
204
|
"convex",
|
|
205
|
+
"self",
|
|
204
206
|
"none"
|
|
205
207
|
]).describe("Backend framework");
|
|
206
208
|
const RuntimeSchema = z.enum([
|
|
@@ -346,6 +348,22 @@ function ensureSingleWebAndNative(frontends) {
|
|
|
346
348
|
if (web.length > 1) exitWithError("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
|
|
347
349
|
if (native.length > 1) exitWithError("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
|
|
348
350
|
}
|
|
351
|
+
const FULLSTACK_FRONTENDS$1 = [
|
|
352
|
+
"next",
|
|
353
|
+
"nuxt",
|
|
354
|
+
"svelte",
|
|
355
|
+
"tanstack-start"
|
|
356
|
+
];
|
|
357
|
+
function validateSelfBackendCompatibility(providedFlags, options, config) {
|
|
358
|
+
const backend = config.backend || options.backend;
|
|
359
|
+
const frontends = config.frontend || options.frontend || [];
|
|
360
|
+
if (backend === "self") {
|
|
361
|
+
if (!frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f))) exitWithError("Backend 'self' (fullstack) requires a fullstack-capable frontend. Please use --frontend with one of: next, nuxt, svelte, tanstack-start");
|
|
362
|
+
if (frontends.length > 1) exitWithError("Backend 'self' (fullstack) can only be used with a single frontend framework.");
|
|
363
|
+
}
|
|
364
|
+
const hasFullstackFrontend = frontends.some((f) => FULLSTACK_FRONTENDS$1.includes(f));
|
|
365
|
+
if (providedFlags.has("backend") && !hasFullstackFrontend && backend === "self") exitWithError("Backend 'self' (fullstack) is only compatible with fullstack-capable frontends: next, nuxt, svelte, tanstack-start. Please choose a different backend or use a fullstack frontend.");
|
|
366
|
+
}
|
|
349
367
|
function validateWorkersCompatibility(providedFlags, options, config) {
|
|
350
368
|
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.`);
|
|
351
369
|
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.`);
|
|
@@ -667,36 +685,39 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
|
|
|
667
685
|
|
|
668
686
|
//#endregion
|
|
669
687
|
//#region src/prompts/backend.ts
|
|
688
|
+
const FULLSTACK_FRONTENDS = [
|
|
689
|
+
"next",
|
|
690
|
+
"nuxt",
|
|
691
|
+
"svelte",
|
|
692
|
+
"tanstack-start"
|
|
693
|
+
];
|
|
670
694
|
async function getBackendFrameworkChoice(backendFramework, frontends) {
|
|
671
695
|
if (backendFramework !== void 0) return backendFramework;
|
|
672
696
|
const hasIncompatibleFrontend = frontends?.some((f) => f === "solid");
|
|
673
|
-
const
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
hint: "Ergonomic web framework for building backend servers"
|
|
698
|
-
}
|
|
699
|
-
];
|
|
697
|
+
const hasFullstackFrontend = frontends?.some((f) => FULLSTACK_FRONTENDS.includes(f));
|
|
698
|
+
const backendOptions = [];
|
|
699
|
+
if (hasFullstackFrontend) backendOptions.push({
|
|
700
|
+
value: "self",
|
|
701
|
+
label: "Self (Fullstack)",
|
|
702
|
+
hint: "Use frontend's built-in backend capabilities"
|
|
703
|
+
});
|
|
704
|
+
backendOptions.push({
|
|
705
|
+
value: "hono",
|
|
706
|
+
label: "Hono",
|
|
707
|
+
hint: "Lightweight, ultrafast web framework"
|
|
708
|
+
}, {
|
|
709
|
+
value: "express",
|
|
710
|
+
label: "Express",
|
|
711
|
+
hint: "Fast, unopinionated, minimalist web framework for Node.js"
|
|
712
|
+
}, {
|
|
713
|
+
value: "fastify",
|
|
714
|
+
label: "Fastify",
|
|
715
|
+
hint: "Fast, low-overhead web framework for Node.js"
|
|
716
|
+
}, {
|
|
717
|
+
value: "elysia",
|
|
718
|
+
label: "Elysia",
|
|
719
|
+
hint: "Ergonomic web framework for building backend servers"
|
|
720
|
+
});
|
|
700
721
|
if (!hasIncompatibleFrontend) backendOptions.push({
|
|
701
722
|
value: "convex",
|
|
702
723
|
label: "Convex",
|
|
@@ -710,7 +731,7 @@ async function getBackendFrameworkChoice(backendFramework, frontends) {
|
|
|
710
731
|
const response = await select({
|
|
711
732
|
message: "Select backend",
|
|
712
733
|
options: backendOptions,
|
|
713
|
-
initialValue: DEFAULT_CONFIG.backend
|
|
734
|
+
initialValue: hasFullstackFrontend ? "self" : DEFAULT_CONFIG.backend
|
|
714
735
|
});
|
|
715
736
|
if (isCancel(response)) return exitCancelled("Operation cancelled");
|
|
716
737
|
return response;
|
|
@@ -1089,9 +1110,8 @@ async function getPaymentsChoice(payments, auth, backend, frontends) {
|
|
|
1089
1110
|
//#endregion
|
|
1090
1111
|
//#region src/prompts/runtime.ts
|
|
1091
1112
|
async function getRuntimeChoice(runtime, backend) {
|
|
1092
|
-
if (backend === "convex" || backend === "none") return "none";
|
|
1113
|
+
if (backend === "convex" || backend === "none" || backend === "self") return "none";
|
|
1093
1114
|
if (runtime !== void 0) return runtime;
|
|
1094
|
-
if (backend === "next") return "node";
|
|
1095
1115
|
const runtimeOptions = [{
|
|
1096
1116
|
value: "bun",
|
|
1097
1117
|
label: "Bun",
|
|
@@ -1768,8 +1788,8 @@ function validateBackendConstraints(config, providedFlags, options) {
|
|
|
1768
1788
|
].includes(f));
|
|
1769
1789
|
if (incompatibleFrontends.length > 0) exitWithError(`Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join(", ")}. Please choose a different frontend or auth provider.`);
|
|
1770
1790
|
}
|
|
1771
|
-
if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none") {
|
|
1772
|
-
if (providedFlags.has("runtime") && options.runtime === "none") exitWithError("'--runtime none' is only supported with '--backend convex' or '--backend
|
|
1791
|
+
if (providedFlags.has("backend") && backend && backend !== "convex" && backend !== "none" && backend !== "self") {
|
|
1792
|
+
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.");
|
|
1773
1793
|
}
|
|
1774
1794
|
if (backend === "convex" && providedFlags.has("frontend") && options.frontend) {
|
|
1775
1795
|
const incompatibleFrontends = options.frontend.filter((f) => f === "solid");
|
|
@@ -1799,6 +1819,7 @@ function validateFullConfig(config, providedFlags, options) {
|
|
|
1799
1819
|
validateFrontendConstraints(config, providedFlags);
|
|
1800
1820
|
validateApiConstraints(config, options);
|
|
1801
1821
|
validateServerDeployRequiresBackend(config.serverDeploy, config.backend);
|
|
1822
|
+
validateSelfBackendCompatibility(providedFlags, options, config);
|
|
1802
1823
|
validateWorkersCompatibility(providedFlags, options, config);
|
|
1803
1824
|
if (config.runtime === "workers" && config.serverDeploy === "none") exitWithError("Cloudflare Workers runtime requires a server deployment. Please choose 'wrangler' or 'alchemy' for --server-deploy.");
|
|
1804
1825
|
if (config.addons && config.addons.length > 0) {
|
|
@@ -1852,6 +1873,7 @@ const CORE_STACK_FLAGS = new Set([
|
|
|
1852
1873
|
"examples",
|
|
1853
1874
|
"auth",
|
|
1854
1875
|
"dbSetup",
|
|
1876
|
+
"payments",
|
|
1855
1877
|
"api",
|
|
1856
1878
|
"webDeploy",
|
|
1857
1879
|
"serverDeploy"
|
|
@@ -2660,8 +2682,12 @@ async function processTemplate(srcPath, destPath, context) {
|
|
|
2660
2682
|
}
|
|
2661
2683
|
handlebars.registerHelper("eq", (a, b) => a === b);
|
|
2662
2684
|
handlebars.registerHelper("ne", (a, b) => a !== b);
|
|
2663
|
-
handlebars.registerHelper("and", (
|
|
2664
|
-
|
|
2685
|
+
handlebars.registerHelper("and", (...args) => {
|
|
2686
|
+
return args.slice(0, -1).every((value) => value);
|
|
2687
|
+
});
|
|
2688
|
+
handlebars.registerHelper("or", (...args) => {
|
|
2689
|
+
return args.slice(0, -1).some((value) => value);
|
|
2690
|
+
});
|
|
2665
2691
|
handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
|
|
2666
2692
|
|
|
2667
2693
|
//#endregion
|
|
@@ -2723,6 +2749,10 @@ async function setupFrontendTemplates(projectDir, context) {
|
|
|
2723
2749
|
const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
|
|
2724
2750
|
if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
|
|
2725
2751
|
}
|
|
2752
|
+
if (context.backend === "self" && reactFramework === "next" && context.api !== "none") {
|
|
2753
|
+
const apiFullstackDir = path.join(PKG_ROOT, `templates/api/${context.api}/fullstack/next`);
|
|
2754
|
+
if (await fs.pathExists(apiFullstackDir)) await processAndCopyFiles("**/*", apiFullstackDir, webAppDir, context);
|
|
2755
|
+
}
|
|
2726
2756
|
}
|
|
2727
2757
|
} else if (hasNuxtWeb) {
|
|
2728
2758
|
const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
|
|
@@ -2763,40 +2793,52 @@ async function setupFrontendTemplates(projectDir, context) {
|
|
|
2763
2793
|
}
|
|
2764
2794
|
}
|
|
2765
2795
|
}
|
|
2766
|
-
async function
|
|
2767
|
-
if (context.
|
|
2796
|
+
async function setupApiPackage(projectDir, context) {
|
|
2797
|
+
if (context.api === "none") return;
|
|
2798
|
+
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
2799
|
+
await fs.ensureDir(apiPackageDir);
|
|
2800
|
+
const apiServerDir = path.join(PKG_ROOT, `templates/api/${context.api}/server`);
|
|
2801
|
+
if (await fs.pathExists(apiServerDir)) await processAndCopyFiles("**/*", apiServerDir, apiPackageDir, context);
|
|
2802
|
+
}
|
|
2803
|
+
async function setupDbPackage(projectDir, context) {
|
|
2804
|
+
if (context.database === "none" || context.orm === "none") return;
|
|
2805
|
+
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2806
|
+
await fs.ensureDir(dbPackageDir);
|
|
2807
|
+
const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
|
|
2808
|
+
if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
|
|
2809
|
+
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2810
|
+
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
|
|
2811
|
+
}
|
|
2812
|
+
async function setupConvexBackend(projectDir, context) {
|
|
2813
|
+
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2814
|
+
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2815
|
+
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2816
|
+
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2817
|
+
await fs.ensureDir(convexBackendDestDir);
|
|
2818
|
+
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2819
|
+
}
|
|
2820
|
+
async function setupServerApp(projectDir, context) {
|
|
2768
2821
|
const serverAppDir = path.join(projectDir, "apps/server");
|
|
2769
|
-
if (context.backend === "convex") {
|
|
2770
|
-
if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
|
|
2771
|
-
const convexBackendDestDir = path.join(projectDir, "packages/backend");
|
|
2772
|
-
const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
|
|
2773
|
-
await fs.ensureDir(convexBackendDestDir);
|
|
2774
|
-
if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
|
|
2775
|
-
return;
|
|
2776
|
-
}
|
|
2777
2822
|
await fs.ensureDir(serverAppDir);
|
|
2778
2823
|
const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
|
|
2779
2824
|
if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
|
|
2780
2825
|
const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
|
|
2781
2826
|
if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2794
|
-
await fs.ensureDir(dbPackageDir);
|
|
2795
|
-
const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
|
|
2796
|
-
if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
|
|
2797
|
-
const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
|
|
2798
|
-
if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
|
|
2827
|
+
}
|
|
2828
|
+
async function setupBackendFramework(projectDir, context) {
|
|
2829
|
+
if (context.backend === "none") return;
|
|
2830
|
+
if (context.backend === "convex") {
|
|
2831
|
+
await setupConvexBackend(projectDir, context);
|
|
2832
|
+
return;
|
|
2833
|
+
}
|
|
2834
|
+
if (context.backend === "self") {
|
|
2835
|
+
await setupApiPackage(projectDir, context);
|
|
2836
|
+
await setupDbPackage(projectDir, context);
|
|
2837
|
+
return;
|
|
2799
2838
|
}
|
|
2839
|
+
await setupServerApp(projectDir, context);
|
|
2840
|
+
await setupApiPackage(projectDir, context);
|
|
2841
|
+
await setupDbPackage(projectDir, context);
|
|
2800
2842
|
}
|
|
2801
2843
|
async function setupAuthTemplate(projectDir, context) {
|
|
2802
2844
|
if (!context.auth || context.auth === "none") return;
|
|
@@ -2876,15 +2918,11 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2876
2918
|
}
|
|
2877
2919
|
return;
|
|
2878
2920
|
}
|
|
2879
|
-
if (serverAppDirExists && context.backend !== "convex") {
|
|
2921
|
+
if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
|
|
2880
2922
|
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2881
2923
|
await fs.ensureDir(authPackageDir);
|
|
2882
2924
|
const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
|
|
2883
2925
|
if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
|
|
2884
|
-
if (context.backend === "next") {
|
|
2885
|
-
const authServerNextSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/next`);
|
|
2886
|
-
if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, authPackageDir, context);
|
|
2887
|
-
}
|
|
2888
2926
|
if (context.orm !== "none" && context.database !== "none") {
|
|
2889
2927
|
const dbPackageDir = path.join(projectDir, "packages/db");
|
|
2890
2928
|
await fs.ensureDir(dbPackageDir);
|
|
@@ -2910,6 +2948,10 @@ async function setupAuthTemplate(projectDir, context) {
|
|
|
2910
2948
|
if (reactFramework) {
|
|
2911
2949
|
const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
|
|
2912
2950
|
if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
|
|
2951
|
+
if (context.backend === "self" && reactFramework === "next") {
|
|
2952
|
+
const authFullstackSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/fullstack/next`);
|
|
2953
|
+
if (await fs.pathExists(authFullstackSrc)) await processAndCopyFiles("**/*", authFullstackSrc, webAppDir, context);
|
|
2954
|
+
}
|
|
2913
2955
|
}
|
|
2914
2956
|
} else if (hasNuxtWeb) {
|
|
2915
2957
|
const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
|
|
@@ -2940,7 +2982,7 @@ async function setupPaymentsTemplate(projectDir, context) {
|
|
|
2940
2982
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
2941
2983
|
const serverAppDirExists = await fs.pathExists(serverAppDir);
|
|
2942
2984
|
const webAppDirExists = await fs.pathExists(webAppDir);
|
|
2943
|
-
if (serverAppDirExists && context.backend !== "convex") {
|
|
2985
|
+
if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
|
|
2944
2986
|
const authPackageDir = path.join(projectDir, "packages/auth");
|
|
2945
2987
|
await fs.ensureDir(authPackageDir);
|
|
2946
2988
|
const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
|
|
@@ -3020,7 +3062,7 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3020
3062
|
for (const example of context.examples) {
|
|
3021
3063
|
if (example === "none") continue;
|
|
3022
3064
|
const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
|
|
3023
|
-
if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
|
|
3065
|
+
if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex" && context.backend !== "none") {
|
|
3024
3066
|
const exampleServerSrc = path.join(exampleBaseDir, "server");
|
|
3025
3067
|
if (context.api !== "none") {
|
|
3026
3068
|
const apiPackageDir = path.join(projectDir, "packages/api");
|
|
@@ -3034,10 +3076,6 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3034
3076
|
const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
|
|
3035
3077
|
if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
|
|
3036
3078
|
}
|
|
3037
|
-
if (example === "ai" && context.backend === "next") {
|
|
3038
|
-
const aiNextServerSrc = path.join(exampleServerSrc, "next");
|
|
3039
|
-
if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
|
|
3040
|
-
}
|
|
3041
3079
|
}
|
|
3042
3080
|
if (webAppDirExists) {
|
|
3043
3081
|
if (hasReactWeb) {
|
|
@@ -3056,6 +3094,10 @@ async function setupExamplesTemplate(projectDir, context) {
|
|
|
3056
3094
|
if (reactFramework) {
|
|
3057
3095
|
const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
|
|
3058
3096
|
if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
|
|
3097
|
+
if (context.backend === "self" && reactFramework === "next") {
|
|
3098
|
+
const exampleFullstackSrc = path.join(exampleBaseDir, "fullstack/next");
|
|
3099
|
+
if (await fs.pathExists(exampleFullstackSrc)) await processAndCopyFiles("**/*", exampleFullstackSrc, webAppDir, context, false);
|
|
3100
|
+
}
|
|
3059
3101
|
}
|
|
3060
3102
|
}
|
|
3061
3103
|
} else if (hasNuxtWeb) {
|
|
@@ -3526,8 +3568,8 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3526
3568
|
await addPackageDependency({
|
|
3527
3569
|
devDependencies: [
|
|
3528
3570
|
"alchemy",
|
|
3529
|
-
"
|
|
3530
|
-
"
|
|
3571
|
+
"dotenv",
|
|
3572
|
+
"@cloudflare/vite-plugin"
|
|
3531
3573
|
],
|
|
3532
3574
|
projectDir: webAppDir
|
|
3533
3575
|
});
|
|
@@ -3555,17 +3597,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3555
3597
|
defaultImport: "alchemy"
|
|
3556
3598
|
});
|
|
3557
3599
|
else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
|
|
3558
|
-
const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
|
|
3559
|
-
let reactPluginIdentifier = "viteReact";
|
|
3560
|
-
if (!reactImport) sourceFile.addImportDeclaration({
|
|
3561
|
-
moduleSpecifier: "@vitejs/plugin-react",
|
|
3562
|
-
defaultImport: "viteReact"
|
|
3563
|
-
});
|
|
3564
|
-
else {
|
|
3565
|
-
const defaultImport = reactImport.getDefaultImport();
|
|
3566
|
-
if (defaultImport) reactPluginIdentifier = defaultImport.getText();
|
|
3567
|
-
else reactImport.setDefaultImport("viteReact");
|
|
3568
|
-
}
|
|
3569
3600
|
const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
|
|
3570
3601
|
if (!exportAssignment) return;
|
|
3571
3602
|
const defineConfigCall = exportAssignment.getExpression();
|
|
@@ -3573,47 +3604,11 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3573
3604
|
let configObject = defineConfigCall.getArguments()[0];
|
|
3574
3605
|
if (!configObject) configObject = defineConfigCall.addArgument("{}");
|
|
3575
3606
|
if (Node.isObjectLiteralExpression(configObject)) {
|
|
3576
|
-
if (!configObject.getProperty("build")) configObject.addPropertyAssignment({
|
|
3577
|
-
name: "build",
|
|
3578
|
-
initializer: `{
|
|
3579
|
-
target: "esnext",
|
|
3580
|
-
rollupOptions: {
|
|
3581
|
-
external: ["node:async_hooks", "cloudflare:workers"],
|
|
3582
|
-
},
|
|
3583
|
-
}`
|
|
3584
|
-
});
|
|
3585
3607
|
const pluginsProperty = configObject.getProperty("plugins");
|
|
3586
3608
|
if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
|
|
3587
3609
|
const initializer = pluginsProperty.getInitializer();
|
|
3588
3610
|
if (Node.isArrayLiteralExpression(initializer)) {
|
|
3589
|
-
if (!initializer.getElements().some((el) => el.getText().includes("alchemy"))) initializer.addElement("alchemy()");
|
|
3590
|
-
const tanstackElements = initializer.getElements().filter((el) => el.getText().includes("tanstackStart"));
|
|
3591
|
-
let needsReactPlugin = false;
|
|
3592
|
-
tanstackElements.forEach((element) => {
|
|
3593
|
-
if (Node.isCallExpression(element)) {
|
|
3594
|
-
const args = element.getArguments();
|
|
3595
|
-
if (args.length === 0) {
|
|
3596
|
-
element.addArgument(`{
|
|
3597
|
-
target: "cloudflare-module",
|
|
3598
|
-
customViteReactPlugin: true,
|
|
3599
|
-
}`);
|
|
3600
|
-
needsReactPlugin = true;
|
|
3601
|
-
} else if (args.length === 1 && Node.isObjectLiteralExpression(args[0])) {
|
|
3602
|
-
const configObj = args[0];
|
|
3603
|
-
if (!configObj.getProperty("target")) configObj.addPropertyAssignment({
|
|
3604
|
-
name: "target",
|
|
3605
|
-
initializer: "\"cloudflare-module\""
|
|
3606
|
-
});
|
|
3607
|
-
if (!!!configObj.getProperty("customViteReactPlugin")) configObj.addPropertyAssignment({
|
|
3608
|
-
name: "customViteReactPlugin",
|
|
3609
|
-
initializer: "true"
|
|
3610
|
-
});
|
|
3611
|
-
needsReactPlugin = true;
|
|
3612
|
-
}
|
|
3613
|
-
}
|
|
3614
|
-
});
|
|
3615
|
-
const hasReactPlugin = initializer.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier);
|
|
3616
|
-
if (needsReactPlugin && !hasReactPlugin) initializer.addElement(`${reactPluginIdentifier}()`);
|
|
3611
|
+
if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
|
|
3617
3612
|
}
|
|
3618
3613
|
} else configObject.addPropertyAssignment({
|
|
3619
3614
|
name: "plugins",
|
|
@@ -3624,16 +3619,6 @@ async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, opti
|
|
|
3624
3619
|
} catch (error) {
|
|
3625
3620
|
console.warn("Failed to update vite.config.ts:", error);
|
|
3626
3621
|
}
|
|
3627
|
-
const nitroConfigPath = path.join(webAppDir, "nitro.config.ts");
|
|
3628
|
-
await fs.writeFile(nitroConfigPath, `import { defineNitroConfig } from "nitropack/config";
|
|
3629
|
-
|
|
3630
|
-
export default defineNitroConfig({
|
|
3631
|
-
preset: "cloudflare-module",
|
|
3632
|
-
cloudflare: {
|
|
3633
|
-
nodeCompat: true,
|
|
3634
|
-
},
|
|
3635
|
-
});
|
|
3636
|
-
`, "utf-8");
|
|
3637
3622
|
}
|
|
3638
3623
|
|
|
3639
3624
|
//#endregion
|
|
@@ -3800,7 +3785,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
3800
3785
|
const webAppDir = path.join(projectDir, "apps/web");
|
|
3801
3786
|
if (!await fs.pathExists(webAppDir)) return;
|
|
3802
3787
|
await addPackageDependency({
|
|
3803
|
-
devDependencies: ["wrangler"],
|
|
3788
|
+
devDependencies: ["wrangler", "@cloudflare/vite-plugin"],
|
|
3804
3789
|
projectDir: webAppDir
|
|
3805
3790
|
});
|
|
3806
3791
|
const pkgPath = path.join(webAppDir, "package.json");
|
|
@@ -3817,6 +3802,12 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
3817
3802
|
if (!await fs.pathExists(viteConfigPath)) return;
|
|
3818
3803
|
const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
|
|
3819
3804
|
if (!sourceFile) return;
|
|
3805
|
+
const cfImport = sourceFile.getImportDeclaration("@cloudflare/vite-plugin");
|
|
3806
|
+
if (!cfImport) sourceFile.addImportDeclaration({
|
|
3807
|
+
moduleSpecifier: "@cloudflare/vite-plugin",
|
|
3808
|
+
namedImports: [{ name: "cloudflare" }]
|
|
3809
|
+
});
|
|
3810
|
+
else if (!cfImport.getNamedImports().some((ni) => ni.getName() === "cloudflare")) cfImport.addNamedImport({ name: "cloudflare" });
|
|
3820
3811
|
const reactImport = sourceFile.getImportDeclaration("@vitejs/plugin-react");
|
|
3821
3812
|
let reactPluginIdentifier = "viteReact";
|
|
3822
3813
|
if (!reactImport) sourceFile.addImportDeclaration({
|
|
@@ -3836,10 +3827,7 @@ async function setupTanstackStartWorkersDeploy(projectDir, packageManager) {
|
|
|
3836
3827
|
const configObj = defineCall.getArguments()[0];
|
|
3837
3828
|
if (!configObj) return;
|
|
3838
3829
|
const pluginsArray = ensureArrayProperty(configObj, "plugins");
|
|
3839
|
-
|
|
3840
|
-
const tanstackPluginText = "tanstackStart({ target: \"cloudflare-module\", customViteReactPlugin: true })";
|
|
3841
|
-
if (tanstackPluginIndex === -1) pluginsArray.addElement(tanstackPluginText);
|
|
3842
|
-
else pluginsArray.getElements()[tanstackPluginIndex].replaceWithText(tanstackPluginText);
|
|
3830
|
+
if (!pluginsArray.getElements().some((el) => el.getText().includes("cloudflare("))) pluginsArray.insertElement(0, "cloudflare({ viteEnvironment: { name: 'ssr' } })");
|
|
3843
3831
|
if (!pluginsArray.getElements().some((el) => Node.isCallExpression(el) && el.getExpression().getText() === reactPluginIdentifier)) {
|
|
3844
3832
|
const nextIndex = pluginsArray.getElements().findIndex((el) => el.getText().includes("tanstackStart(")) + 1;
|
|
3845
3833
|
if (nextIndex > 0) pluginsArray.insertElement(nextIndex, `${reactPluginIdentifier}()`);
|
|
@@ -3977,18 +3965,137 @@ async function addDeploymentToProject(input) {
|
|
|
3977
3965
|
}
|
|
3978
3966
|
}
|
|
3979
3967
|
|
|
3968
|
+
//#endregion
|
|
3969
|
+
//#region src/utils/setup-catalogs.ts
|
|
3970
|
+
async function setupCatalogs(projectDir, options) {
|
|
3971
|
+
if (options.packageManager === "npm") return;
|
|
3972
|
+
const packagePaths = [
|
|
3973
|
+
"apps/server",
|
|
3974
|
+
"apps/web",
|
|
3975
|
+
"packages/api",
|
|
3976
|
+
"packages/db",
|
|
3977
|
+
"packages/auth",
|
|
3978
|
+
"packages/backend"
|
|
3979
|
+
];
|
|
3980
|
+
const packagesInfo = [];
|
|
3981
|
+
for (const pkgPath of packagePaths) {
|
|
3982
|
+
const fullPath = path.join(projectDir, pkgPath);
|
|
3983
|
+
const pkgJsonPath = path.join(fullPath, "package.json");
|
|
3984
|
+
if (await fs.pathExists(pkgJsonPath)) {
|
|
3985
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
3986
|
+
packagesInfo.push({
|
|
3987
|
+
path: fullPath,
|
|
3988
|
+
dependencies: pkgJson.dependencies || {},
|
|
3989
|
+
devDependencies: pkgJson.devDependencies || {}
|
|
3990
|
+
});
|
|
3991
|
+
}
|
|
3992
|
+
}
|
|
3993
|
+
const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
|
|
3994
|
+
if (Object.keys(catalog).length === 0) return;
|
|
3995
|
+
if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
|
|
3996
|
+
else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
|
|
3997
|
+
await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
|
|
3998
|
+
}
|
|
3999
|
+
function findDuplicateDependencies(packagesInfo, projectName) {
|
|
4000
|
+
const depCount = /* @__PURE__ */ new Map();
|
|
4001
|
+
const projectScope = `@${projectName}/`;
|
|
4002
|
+
for (const pkg of packagesInfo) {
|
|
4003
|
+
const allDeps = {
|
|
4004
|
+
...pkg.dependencies,
|
|
4005
|
+
...pkg.devDependencies
|
|
4006
|
+
};
|
|
4007
|
+
for (const [depName, version] of Object.entries(allDeps)) {
|
|
4008
|
+
if (depName.startsWith(projectScope)) continue;
|
|
4009
|
+
if (version.startsWith("workspace:")) continue;
|
|
4010
|
+
const existing = depCount.get(depName);
|
|
4011
|
+
if (existing) existing.packages.push(pkg.path);
|
|
4012
|
+
else depCount.set(depName, {
|
|
4013
|
+
version,
|
|
4014
|
+
packages: [pkg.path]
|
|
4015
|
+
});
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
const catalog = {};
|
|
4019
|
+
for (const [depName, info] of depCount.entries()) if (info.packages.length > 1) catalog[depName] = info.version;
|
|
4020
|
+
return catalog;
|
|
4021
|
+
}
|
|
4022
|
+
async function setupBunCatalogs(projectDir, catalog) {
|
|
4023
|
+
const rootPkgJsonPath = path.join(projectDir, "package.json");
|
|
4024
|
+
const rootPkgJson = await fs.readJson(rootPkgJsonPath);
|
|
4025
|
+
if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
|
|
4026
|
+
if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
|
|
4027
|
+
packages: rootPkgJson.workspaces,
|
|
4028
|
+
catalog
|
|
4029
|
+
};
|
|
4030
|
+
else if (typeof rootPkgJson.workspaces === "object") {
|
|
4031
|
+
if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
|
|
4032
|
+
rootPkgJson.workspaces.catalog = {
|
|
4033
|
+
...rootPkgJson.workspaces.catalog,
|
|
4034
|
+
...catalog
|
|
4035
|
+
};
|
|
4036
|
+
}
|
|
4037
|
+
await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
|
|
4038
|
+
}
|
|
4039
|
+
async function setupPnpmCatalogs(projectDir, catalog) {
|
|
4040
|
+
const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
|
|
4041
|
+
if (!await fs.pathExists(workspaceYamlPath)) return;
|
|
4042
|
+
const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
|
|
4043
|
+
const workspaceYaml = yaml.parse(workspaceContent);
|
|
4044
|
+
if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
|
|
4045
|
+
workspaceYaml.catalog = {
|
|
4046
|
+
...workspaceYaml.catalog,
|
|
4047
|
+
...catalog
|
|
4048
|
+
};
|
|
4049
|
+
await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
|
|
4050
|
+
}
|
|
4051
|
+
async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
|
|
4052
|
+
for (const pkg of packagesInfo) {
|
|
4053
|
+
const pkgJsonPath = path.join(pkg.path, "package.json");
|
|
4054
|
+
const pkgJson = await fs.readJson(pkgJsonPath);
|
|
4055
|
+
let updated = false;
|
|
4056
|
+
if (pkgJson.dependencies) {
|
|
4057
|
+
for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
|
|
4058
|
+
pkgJson.dependencies[depName] = "catalog:";
|
|
4059
|
+
updated = true;
|
|
4060
|
+
}
|
|
4061
|
+
}
|
|
4062
|
+
if (pkgJson.devDependencies) {
|
|
4063
|
+
for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
|
|
4064
|
+
pkgJson.devDependencies[depName] = "catalog:";
|
|
4065
|
+
updated = true;
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
|
|
4069
|
+
}
|
|
4070
|
+
}
|
|
4071
|
+
|
|
3980
4072
|
//#endregion
|
|
3981
4073
|
//#region src/helpers/addons/examples-setup.ts
|
|
3982
4074
|
async function setupExamples(config) {
|
|
3983
|
-
const { examples, frontend, backend, projectDir } = config;
|
|
4075
|
+
const { examples, frontend, backend, projectDir, orm } = config;
|
|
3984
4076
|
if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
|
|
4077
|
+
const apiDir = path.join(projectDir, "packages/api");
|
|
4078
|
+
if (await fs.pathExists(apiDir) && backend !== "none") {
|
|
4079
|
+
if (orm === "drizzle") await addPackageDependency({
|
|
4080
|
+
dependencies: ["drizzle-orm"],
|
|
4081
|
+
projectDir: apiDir
|
|
4082
|
+
});
|
|
4083
|
+
else if (orm === "prisma") await addPackageDependency({
|
|
4084
|
+
dependencies: ["@prisma/client"],
|
|
4085
|
+
projectDir: apiDir
|
|
4086
|
+
});
|
|
4087
|
+
else if (orm === "mongoose") await addPackageDependency({
|
|
4088
|
+
dependencies: ["mongoose"],
|
|
4089
|
+
projectDir: apiDir
|
|
4090
|
+
});
|
|
4091
|
+
}
|
|
3985
4092
|
if (examples.includes("ai")) {
|
|
3986
4093
|
const webClientDir = path.join(projectDir, "apps/web");
|
|
3987
4094
|
const nativeClientDir = path.join(projectDir, "apps/native");
|
|
3988
|
-
const apiDir = path.join(projectDir, "packages/api");
|
|
4095
|
+
const apiDir$1 = path.join(projectDir, "packages/api");
|
|
3989
4096
|
const webClientDirExists = await fs.pathExists(webClientDir);
|
|
3990
4097
|
const nativeClientDirExists = await fs.pathExists(nativeClientDir);
|
|
3991
|
-
const apiDirExists = await fs.pathExists(apiDir);
|
|
4098
|
+
const apiDirExists = await fs.pathExists(apiDir$1);
|
|
3992
4099
|
const hasNuxt = frontend.includes("nuxt");
|
|
3993
4100
|
const hasSvelte = frontend.includes("svelte");
|
|
3994
4101
|
const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
|
|
@@ -4011,7 +4118,7 @@ async function setupExamples(config) {
|
|
|
4011
4118
|
});
|
|
4012
4119
|
if (apiDirExists && backend !== "none") await addPackageDependency({
|
|
4013
4120
|
dependencies: ["ai", "@ai-sdk/google"],
|
|
4014
|
-
projectDir: apiDir
|
|
4121
|
+
projectDir: apiDir$1
|
|
4015
4122
|
});
|
|
4016
4123
|
}
|
|
4017
4124
|
}
|
|
@@ -4149,6 +4256,9 @@ async function setupApi(config) {
|
|
|
4149
4256
|
else if (backend === "elysia") frameworkDeps.push("elysia");
|
|
4150
4257
|
else if (backend === "express") frameworkDeps.push("express", "@types/express");
|
|
4151
4258
|
else if (backend === "fastify") frameworkDeps.push("fastify");
|
|
4259
|
+
else if (backend === "self") {
|
|
4260
|
+
if (frontend.includes("next")) frameworkDeps.push("next");
|
|
4261
|
+
}
|
|
4152
4262
|
if (frameworkDeps.length > 0) await addPackageDependency({
|
|
4153
4263
|
dependencies: frameworkDeps,
|
|
4154
4264
|
projectDir: apiPackageDir
|
|
@@ -4447,7 +4557,7 @@ async function setupEnvironmentVariables(config) {
|
|
|
4447
4557
|
const clientVars = [{
|
|
4448
4558
|
key: envVarName,
|
|
4449
4559
|
value: serverUrl,
|
|
4450
|
-
condition:
|
|
4560
|
+
condition: backend !== "self"
|
|
4451
4561
|
}];
|
|
4452
4562
|
if (backend === "convex" && auth === "clerk") {
|
|
4453
4563
|
if (hasNextJs) clientVars.push({
|
|
@@ -4556,47 +4666,47 @@ async function setupEnvironmentVariables(config) {
|
|
|
4556
4666
|
else databaseUrl = `file:${path.join(config.projectDir, "apps/server", "local.db")}`;
|
|
4557
4667
|
break;
|
|
4558
4668
|
}
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
{
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
await addEnvVariablesToFile(
|
|
4599
|
-
}
|
|
4669
|
+
const serverVars = [
|
|
4670
|
+
{
|
|
4671
|
+
key: "BETTER_AUTH_SECRET",
|
|
4672
|
+
value: generateAuthSecret(),
|
|
4673
|
+
condition: !!auth
|
|
4674
|
+
},
|
|
4675
|
+
{
|
|
4676
|
+
key: "BETTER_AUTH_URL",
|
|
4677
|
+
value: "http://localhost:3000",
|
|
4678
|
+
condition: !!auth
|
|
4679
|
+
},
|
|
4680
|
+
{
|
|
4681
|
+
key: "POLAR_ACCESS_TOKEN",
|
|
4682
|
+
value: "",
|
|
4683
|
+
condition: config.payments === "polar"
|
|
4684
|
+
},
|
|
4685
|
+
{
|
|
4686
|
+
key: "POLAR_SUCCESS_URL",
|
|
4687
|
+
value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
|
|
4688
|
+
condition: config.payments === "polar"
|
|
4689
|
+
},
|
|
4690
|
+
{
|
|
4691
|
+
key: "CORS_ORIGIN",
|
|
4692
|
+
value: corsOrigin,
|
|
4693
|
+
condition: true
|
|
4694
|
+
},
|
|
4695
|
+
{
|
|
4696
|
+
key: "GOOGLE_GENERATIVE_AI_API_KEY",
|
|
4697
|
+
value: "",
|
|
4698
|
+
condition: examples?.includes("ai") || false
|
|
4699
|
+
},
|
|
4700
|
+
{
|
|
4701
|
+
key: "DATABASE_URL",
|
|
4702
|
+
value: databaseUrl,
|
|
4703
|
+
condition: database !== "none" && dbSetup === "none"
|
|
4704
|
+
}
|
|
4705
|
+
];
|
|
4706
|
+
if (backend === "self") {
|
|
4707
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
4708
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
|
|
4709
|
+
} else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
|
|
4600
4710
|
const isUnifiedAlchemy = webDeploy === "alchemy" && serverDeploy === "alchemy";
|
|
4601
4711
|
const isIndividualAlchemy = webDeploy === "alchemy" || serverDeploy === "alchemy";
|
|
4602
4712
|
if (isUnifiedAlchemy) {
|
|
@@ -4615,11 +4725,17 @@ async function setupEnvironmentVariables(config) {
|
|
|
4615
4725
|
condition: true
|
|
4616
4726
|
}]);
|
|
4617
4727
|
}
|
|
4618
|
-
if (serverDeploy === "alchemy")
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4728
|
+
if (serverDeploy === "alchemy") {
|
|
4729
|
+
const serverAlchemyVars = [{
|
|
4730
|
+
key: "ALCHEMY_PASSWORD",
|
|
4731
|
+
value: "please-change-this",
|
|
4732
|
+
condition: true
|
|
4733
|
+
}];
|
|
4734
|
+
if (backend === "self") {
|
|
4735
|
+
const webDir = path.join(projectDir, "apps/web");
|
|
4736
|
+
if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverAlchemyVars);
|
|
4737
|
+
} else await addEnvVariablesToFile(path.join(serverDir, ".env"), serverAlchemyVars);
|
|
4738
|
+
}
|
|
4623
4739
|
}
|
|
4624
4740
|
}
|
|
4625
4741
|
|
|
@@ -5756,7 +5872,7 @@ async function setupDatabase(config, cliInput) {
|
|
|
5756
5872
|
//#region src/helpers/core/runtime-setup.ts
|
|
5757
5873
|
async function setupRuntime(config) {
|
|
5758
5874
|
const { runtime, backend, projectDir } = config;
|
|
5759
|
-
if (backend === "convex" || backend === "
|
|
5875
|
+
if (backend === "convex" || backend === "self" || runtime === "none") return;
|
|
5760
5876
|
const serverDir = path.join(projectDir, "apps/server");
|
|
5761
5877
|
if (!await fs.pathExists(serverDir)) return;
|
|
5762
5878
|
if (runtime === "bun") await setupBunRuntime(serverDir, backend);
|
|
@@ -6276,7 +6392,7 @@ async function displayPostInstallInstructions(config) {
|
|
|
6276
6392
|
else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app\n (no frontend selected)\n`;
|
|
6277
6393
|
if (!isConvex) {
|
|
6278
6394
|
output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
|
|
6279
|
-
if (api === "orpc") if (backend === "
|
|
6395
|
+
if (api === "orpc") if (backend === "self") output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/rpc/api\n`;
|
|
6280
6396
|
else output += `${pc.cyan("•")} OpenAPI (Scalar UI): http://localhost:3000/api\n`;
|
|
6281
6397
|
}
|
|
6282
6398
|
if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
|
|
@@ -6431,10 +6547,17 @@ async function setupWorkspaceDependencies(projectDir, options) {
|
|
|
6431
6547
|
},
|
|
6432
6548
|
projectDir: serverPackageDir
|
|
6433
6549
|
});
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6550
|
+
const needsApiDependency = options.api && options.api !== "none";
|
|
6551
|
+
const webPackageDir = path.join(projectDir, "apps/web");
|
|
6552
|
+
if (await fs.pathExists(webPackageDir)) {
|
|
6553
|
+
const webDeps = {};
|
|
6554
|
+
if (options.backend === "self") {
|
|
6555
|
+
webDeps[`@${projectName}/api`] = workspaceVersion;
|
|
6556
|
+
webDeps[`@${projectName}/auth`] = workspaceVersion;
|
|
6557
|
+
webDeps[`@${projectName}/db`] = workspaceVersion;
|
|
6558
|
+
} else if (needsApiDependency) webDeps[`@${projectName}/api`] = workspaceVersion;
|
|
6559
|
+
if (Object.keys(webDeps).length > 0) await addPackageDependency({
|
|
6560
|
+
customDependencies: webDeps,
|
|
6438
6561
|
projectDir: webPackageDir
|
|
6439
6562
|
});
|
|
6440
6563
|
}
|
|
@@ -6444,12 +6567,18 @@ async function setupWorkspaceDependencies(projectDir, options) {
|
|
|
6444
6567
|
//#region src/helpers/core/project-config.ts
|
|
6445
6568
|
async function updatePackageConfigurations(projectDir, options) {
|
|
6446
6569
|
await updateRootPackageJson(projectDir, options);
|
|
6447
|
-
if (options.backend
|
|
6570
|
+
if (options.backend === "convex") await updateConvexPackageJson(projectDir, options);
|
|
6571
|
+
else if (options.backend === "self") {
|
|
6572
|
+
await updateDbPackageJson(projectDir, options);
|
|
6573
|
+
await updateAuthPackageJson(projectDir, options);
|
|
6574
|
+
await updateApiPackageJson(projectDir, options);
|
|
6575
|
+
await setupWorkspaceDependencies(projectDir, options);
|
|
6576
|
+
} else if (options.backend !== "none") {
|
|
6448
6577
|
await updateServerPackageJson(projectDir, options);
|
|
6449
6578
|
await updateAuthPackageJson(projectDir, options);
|
|
6450
6579
|
await updateApiPackageJson(projectDir, options);
|
|
6451
6580
|
await setupWorkspaceDependencies(projectDir, options);
|
|
6452
|
-
}
|
|
6581
|
+
}
|
|
6453
6582
|
}
|
|
6454
6583
|
async function updateRootPackageJson(projectDir, options) {
|
|
6455
6584
|
const rootPackageJsonPath = path.join(projectDir, "package.json");
|
|
@@ -6476,7 +6605,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6476
6605
|
scripts["check-types"] = "turbo check-types";
|
|
6477
6606
|
scripts["dev:native"] = "turbo -F native dev";
|
|
6478
6607
|
scripts["dev:web"] = "turbo -F web dev";
|
|
6479
|
-
scripts["dev:server"] = serverDevScript;
|
|
6608
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6480
6609
|
if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} dev:setup`;
|
|
6481
6610
|
if (needsDbScripts) {
|
|
6482
6611
|
scripts["db:push"] = `turbo -F ${dbPackageName} db:push`;
|
|
@@ -6501,7 +6630,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6501
6630
|
scripts["check-types"] = "pnpm -r check-types";
|
|
6502
6631
|
scripts["dev:native"] = "pnpm --filter native dev";
|
|
6503
6632
|
scripts["dev:web"] = "pnpm --filter web dev";
|
|
6504
|
-
scripts["dev:server"] = serverDevScript;
|
|
6633
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6505
6634
|
if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} dev:setup`;
|
|
6506
6635
|
if (needsDbScripts) {
|
|
6507
6636
|
scripts["db:push"] = `pnpm --filter ${dbPackageName} db:push`;
|
|
@@ -6526,7 +6655,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6526
6655
|
scripts["check-types"] = "npm run check-types --workspaces";
|
|
6527
6656
|
scripts["dev:native"] = "npm run dev --workspace native";
|
|
6528
6657
|
scripts["dev:web"] = "npm run dev --workspace web";
|
|
6529
|
-
scripts["dev:server"] = serverDevScript;
|
|
6658
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6530
6659
|
if (options.backend === "convex") scripts["dev:setup"] = `npm run dev:setup --workspace ${backendPackageName}`;
|
|
6531
6660
|
if (needsDbScripts) {
|
|
6532
6661
|
scripts["db:push"] = `npm run db:push --workspace ${dbPackageName}`;
|
|
@@ -6551,7 +6680,7 @@ async function updateRootPackageJson(projectDir, options) {
|
|
|
6551
6680
|
scripts["check-types"] = "bun run --filter '*' check-types";
|
|
6552
6681
|
scripts["dev:native"] = "bun run --filter native dev";
|
|
6553
6682
|
scripts["dev:web"] = "bun run --filter web dev";
|
|
6554
|
-
scripts["dev:server"] = serverDevScript;
|
|
6683
|
+
if (options.backend !== "self" && options.backend !== "none") scripts["dev:server"] = serverDevScript;
|
|
6555
6684
|
if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} dev:setup`;
|
|
6556
6685
|
if (needsDbScripts) {
|
|
6557
6686
|
scripts["db:push"] = `bun run --filter ${dbPackageName} db:push`;
|
|
@@ -6654,12 +6783,14 @@ async function updateConvexPackageJson(projectDir, options) {
|
|
|
6654
6783
|
async function createProject(options, cliInput) {
|
|
6655
6784
|
const projectDir = options.projectDir;
|
|
6656
6785
|
const isConvex = options.backend === "convex";
|
|
6786
|
+
const isSelfBackend = options.backend === "self";
|
|
6787
|
+
const needsServerSetup = !isConvex && !isSelfBackend;
|
|
6657
6788
|
try {
|
|
6658
6789
|
await fs.ensureDir(projectDir);
|
|
6659
6790
|
await copyBaseTemplate(projectDir, options);
|
|
6660
6791
|
await setupFrontendTemplates(projectDir, options);
|
|
6661
6792
|
await setupBackendFramework(projectDir, options);
|
|
6662
|
-
if (
|
|
6793
|
+
if (needsServerSetup) await setupDockerComposeTemplates(projectDir, options);
|
|
6663
6794
|
await setupAuthTemplate(projectDir, options);
|
|
6664
6795
|
if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
|
|
6665
6796
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
|
|
@@ -6667,9 +6798,11 @@ async function createProject(options, cliInput) {
|
|
|
6667
6798
|
await setupDeploymentTemplates(projectDir, options);
|
|
6668
6799
|
await setupApi(options);
|
|
6669
6800
|
if (!isConvex) {
|
|
6670
|
-
|
|
6801
|
+
if (needsServerSetup) {
|
|
6802
|
+
await setupBackendDependencies(options);
|
|
6803
|
+
await setupRuntime(options);
|
|
6804
|
+
}
|
|
6671
6805
|
await setupDatabase(options, cliInput);
|
|
6672
|
-
await setupRuntime(options);
|
|
6673
6806
|
if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
|
|
6674
6807
|
}
|
|
6675
6808
|
if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
|
|
@@ -6678,6 +6811,7 @@ async function createProject(options, cliInput) {
|
|
|
6678
6811
|
await handleExtras(projectDir, options);
|
|
6679
6812
|
await setupEnvironmentVariables(options);
|
|
6680
6813
|
await updatePackageConfigurations(projectDir, options);
|
|
6814
|
+
await setupCatalogs(projectDir, options);
|
|
6681
6815
|
await setupWebDeploy(options);
|
|
6682
6816
|
await setupServerDeploy(options);
|
|
6683
6817
|
await createReadme(projectDir, options);
|