create-better-t-stack 2.13.3 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,48 +1,1397 @@
1
1
  #!/usr/bin/env node
2
- import e from"node:path";import{cancel as t,confirm as n,group as r,intro as i,isCancel as a,log as o,multiselect as s,outro as c,password as l,select as u,spinner as d,text as f}from"@clack/prompts";import p,{consola as m}from"consola";import h from"fs-extra";import g from"picocolors";import _ from"yargs";import{hideBin as v}from"yargs/helpers";import{fileURLToPath as y}from"node:url";import{$ as b,execa as x}from"execa";import S from"node:os";import{globby as C}from"globby";import w from"handlebars";import T from"gradient-string";const E=()=>{let e=process.env.npm_config_user_agent;return e?.startsWith(`pnpm`)?`pnpm`:e?.startsWith(`bun`)?`bun`:`npm`},D=y(import.meta.url),O=e.dirname(D),k=e.join(O,`../`),A={projectName:`my-better-t-app`,projectDir:e.resolve(process.cwd(),`my-better-t-app`),relativePath:`my-better-t-app`,frontend:[`tanstack-router`],database:`sqlite`,orm:`drizzle`,auth:!0,addons:[`turborepo`],examples:[],git:!0,packageManager:E(),install:!0,dbSetup:`none`,backend:`hono`,runtime:`bun`,api:`trpc`},j={"better-auth":`^1.2.7`,"@better-auth/expo":`^1.2.7`,"drizzle-orm":`^0.38.4`,"drizzle-kit":`^0.30.5`,"@libsql/client":`^0.14.0`,pg:`^8.14.1`,"@types/pg":`^8.11.11`,mysql2:`^3.14.0`,"@prisma/client":`^6.7.0`,prisma:`^6.7.0`,mongoose:`^8.14.0`,"vite-plugin-pwa":`^0.21.2`,"@vite-pwa/assets-generator":`^0.2.6`,"@tauri-apps/cli":`^2.4.0`,"@biomejs/biome":`1.9.4`,husky:`^9.1.7`,"lint-staged":`^15.5.0`,"@hono/node-server":`^1.14.0`,tsx:`^4.19.2`,"@types/node":`^22.13.11`,"@types/bun":`^1.2.6`,"@elysiajs/node":`^1.2.6`,"@elysiajs/cors":`^1.2.0`,"@elysiajs/trpc":`^1.1.0`,elysia:`^1.2.25`,"@hono/trpc-server":`^0.3.4`,hono:`^4.7.6`,cors:`^2.8.5`,express:`^5.1.0`,"@types/express":`^5.0.1`,"@types/cors":`^2.8.17`,fastify:`^5.3.3`,"@fastify/cors":`^11.0.1`,turbo:`^2.4.2`,ai:`^4.3.16`,"@ai-sdk/google":`^1.2.3`,"@ai-sdk/vue":`^1.2.8`,"@ai-sdk/svelte":`^2.1.9`,"@ai-sdk/react":`^1.2.12`,"@prisma/extension-accelerate":`^1.3.0`,"@orpc/server":`^1.2.0`,"@orpc/client":`^1.2.0`,"@orpc/react-query":`^1.2.0`,"@orpc/solid-query":`^1.2.0`,"@orpc/vue-query":`^1.2.0`,"@orpc/svelte-query":`^1.2.0`,"@trpc/tanstack-react-query":`^11.0.0`,"@trpc/server":`^11.0.0`,"@trpc/client":`^11.0.0`,convex:`^1.23.0`,"@convex-dev/react-query":`^0.0.0-alpha.8`,"convex-svelte":`^0.0.11`,"@tanstack/svelte-query":`^5.74.4`,"@tanstack/react-query-devtools":`^5.69.0`,"@tanstack/react-query":`^5.69.0`,"@tanstack/solid-query":`^5.75.0`,"@tanstack/solid-query-devtools":`^5.75.0`},ee=()=>{let t=e.join(k,`package.json`),n=h.readJSONSync(t);return n.version??`1.0.0`};async function te(){let e=await _(v(process.argv)).scriptName(`create-better-t-stack`).usage(`$0 [project-directory] [options]`,`Create a new Better-T Stack project`).positional(`project-directory`,{describe:`Project name/directory`,type:`string`}).option(`yes`,{alias:`y`,type:`boolean`,describe:`Use default configuration and skip prompts`,default:!1}).option(`database`,{type:`string`,describe:`Database type`,choices:[`none`,`sqlite`,`postgres`,`mysql`,`mongodb`]}).option(`orm`,{type:`string`,describe:`ORM type`,choices:[`drizzle`,`prisma`,`mongoose`,`none`]}).option(`auth`,{type:`boolean`,describe:`Include authentication (use --no-auth to exclude)`}).option(`frontend`,{type:`array`,string:!0,describe:`Frontend types`,choices:[`tanstack-router`,`react-router`,`tanstack-start`,`next`,`nuxt`,`native-nativewind`,`native-unistyles`,`svelte`,`solid`,`none`]}).option(`addons`,{type:`array`,string:!0,describe:`Additional addons`,choices:[`pwa`,`tauri`,`starlight`,`biome`,`husky`,`turborepo`,`none`]}).option(`examples`,{type:`array`,string:!0,describe:`Examples to include`,choices:[`todo`,`ai`,`none`]}).option(`git`,{type:`boolean`,describe:`Initialize git repository (use --no-git to skip)`}).option(`package-manager`,{alias:`pm`,type:`string`,describe:`Package manager`,choices:[`npm`,`pnpm`,`bun`]}).option(`install`,{type:`boolean`,describe:`Install dependencies (use --no-install to skip)`}).option(`db-setup`,{type:`string`,describe:`Database setup`,choices:[`turso`,`neon`,`prisma-postgres`,`mongodb-atlas`,`supabase`,`none`]}).option(`backend`,{type:`string`,describe:`Backend framework`,choices:[`hono`,`express`,`fastify`,`next`,`elysia`,`convex`,`none`]}).option(`runtime`,{type:`string`,describe:`Runtime`,choices:[`bun`,`node`,`none`]}).option(`api`,{type:`string`,describe:`API type`,choices:[`trpc`,`orpc`,`none`]}).completion().recommendCommands().version(ee()).alias(`version`,`v`).help().alias(`help`,`h`).strict().wrap(null).parse();return e}const M=async t=>{let{dependencies:n=[],devDependencies:r=[],projectDir:i}=t,a=e.join(i,`package.json`),o=await h.readJson(a);o.dependencies||={},o.devDependencies||={};for(let e of n){let t=j[e];t?o.dependencies[e]=t:console.warn(`Warning: Dependency ${e} not found in version map.`)}for(let e of r){let t=j[e];t?o.devDependencies[e]=t:console.warn(`Warning: Dev dependency ${e} not found in version map.`)}await h.writeJson(a,o,{spaces:2})};function N(e,t){switch(e){case`pnpm`:return`pnpm dlx ${t}`;case`bun`:return`bunx ${t}`;default:return`npx ${t}`}}async function ne(t){let{packageManager:n,projectDir:r}=t,i=d();try{i.start(`Setting up Starlight docs...`);let t=[`docs`,`--template`,`starlight`,`--no-install`,`--add`,`tailwind`,`--no-git`,`--skip-houston`],a=t.join(` `),o=`create-astro@latest ${a}`,s=N(n,o);await x(s,{cwd:e.join(r,`apps`),env:{CI:`true`},shell:!0}),i.stop(`Starlight docs setup successfully!`)}catch(e){i.stop(g.red(`Failed to set up Starlight docs`)),e instanceof Error&&p.error(g.red(e.message))}}async function re(t){let{packageManager:n,frontend:r,projectDir:i}=t,a=d(),o=e.join(i,`apps/web`);if(await h.pathExists(o))try{a.start(`Setting up Tauri desktop app support...`),await M({devDependencies:[`@tauri-apps/cli`],projectDir:o});let t=e.join(o,`package.json`);if(await h.pathExists(t)){let e=await h.readJson(t);e.scripts={...e.scripts,tauri:`tauri`,"desktop:dev":`tauri dev`,"desktop:build":`tauri build`},await h.writeJson(t,e,{spaces:2})}let s=r.includes(`tanstack-router`),c=r.includes(`react-router`),l=r.includes(`nuxt`),u=r.includes(`svelte`),d=r.includes(`solid`),f=r.includes(`next`),p=c||u?`http://localhost:5173`:`http://localhost:3001`,m=l?`../.output/public`:u?`../build`:f?`../.next`:c?`../build/client`:`../dist`,g=[`init`,`--app-name=${e.basename(i)}`,`--window-title=${e.basename(i)}`,`--frontend-dist=${m}`,`--dev-url=${p}`,`--before-dev-command=\"${n} run dev\"`,`--before-build-command=\"${n} run build\"`],_=g.join(` `),v=`@tauri-apps/cli@latest ${_}`,y=N(n,v);await x(y,{cwd:o,env:{CI:`true`},shell:!0}),a.stop(`Tauri desktop app support configured successfully!`)}catch(e){a.stop(g.red(`Failed to set up Tauri`)),e instanceof Error&&m.error(g.red(e.message))}}async function ie(e){let{addons:t,frontend:n,projectDir:r}=e,i=n.includes(`react-router`)||n.includes(`tanstack-router`)||n.includes(`next`),a=n.includes(`nuxt`),o=n.includes(`svelte`),s=n.includes(`solid`),c=n.includes(`next`);t.includes(`turborepo`)&&await M({devDependencies:[`turbo`],projectDir:r}),t.includes(`pwa`)&&(i||s)&&await ce(r,n),t.includes(`tauri`)&&(i||a||o||s||c)&&await re(e),t.includes(`biome`)&&await oe(r),t.includes(`husky`)&&await se(r),t.includes(`starlight`)&&await ne(e)}function ae(t,n){return n.some(e=>[`react-router`,`tanstack-router`,`nuxt`,`svelte`,`solid`].includes(e)),e.join(t,`apps/web`)}async function oe(t){await M({devDependencies:[`@biomejs/biome`],projectDir:t});let n=e.join(t,`package.json`);if(await h.pathExists(n)){let e=await h.readJson(n);e.scripts={...e.scripts,check:`biome check --write .`},await h.writeJson(n,e,{spaces:2})}}async function se(t){await M({devDependencies:[`husky`,`lint-staged`],projectDir:t});let n=e.join(t,`package.json`);if(await h.pathExists(n)){let e=await h.readJson(n);e.scripts={...e.scripts,prepare:`husky`},e[`lint-staged`]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":[`biome check --write .`]},await h.writeJson(n,e,{spaces:2})}}async function ce(t,n){let r=n.some(e=>[`react-router`,`tanstack-router`,`solid`].includes(e));if(!r)return;let i=ae(t,n);if(!await h.pathExists(i))return;await M({dependencies:[`vite-plugin-pwa`],devDependencies:[`@vite-pwa/assets-generator`],projectDir:i});let a=e.join(i,`package.json`);if(await h.pathExists(a)){let e=await h.readJson(a);e.scripts={...e.scripts,"generate-pwa-assets":`pwa-assets-generator`},await h.writeJson(a,e,{spaces:2})}}async function le(t){let{api:n,projectName:r,frontend:i,backend:a,packageManager:o,projectDir:s}=t,c=a===`convex`,l=e.join(s,`apps/web`),u=e.join(s,`apps/native`),d=await h.pathExists(l),f=await h.pathExists(u),p=i.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),m=i.includes(`nuxt`),g=i.includes(`svelte`),_=i.includes(`solid`);if(!c&&n!==`none`){let r=e.join(s,`apps/server`),i=await h.pathExists(r);i&&(n===`orpc`?await M({dependencies:[`@orpc/server`,`@orpc/client`],projectDir:r}):n===`trpc`&&(await M({dependencies:[`@trpc/server`,`@trpc/client`],projectDir:r}),t.backend===`hono`?await M({dependencies:[`@hono/trpc-server`],projectDir:r}):t.backend===`elysia`&&await M({dependencies:[`@elysiajs/trpc`],projectDir:r}))),d&&(p?n===`orpc`?await M({dependencies:[`@orpc/react-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):n===`trpc`&&await M({dependencies:[`@trpc/tanstack-react-query`,`@trpc/client`,`@trpc/server`],projectDir:l}):m?n===`orpc`&&await M({dependencies:[`@orpc/vue-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):g?n===`orpc`&&await M({dependencies:[`@orpc/svelte-query`,`@orpc/client`,`@orpc/server`,`@tanstack/svelte-query`],projectDir:l}):_&&n===`orpc`&&await M({dependencies:[`@orpc/solid-query`,`@orpc/client`,`@orpc/server`,`@tanstack/solid-query`],projectDir:l})),f&&(n===`trpc`?await M({dependencies:[`@trpc/tanstack-react-query`,`@trpc/client`,`@trpc/server`],projectDir:u}):n===`orpc`&&await M({dependencies:[`@orpc/react-query`,`@orpc/client`,`@orpc/server`],projectDir:u}))}let v=[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`native-nativewind`,`native-unistyles`],y=i.includes(`solid`),b=i.some(e=>v.includes(e));if(b&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native-nativewind`&&e!==`native-unistyles`&&v.includes(e)),a=i.includes(`native-nativewind`)||i.includes(`native-unistyles`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(y&&!c){let t=[`@tanstack/solid-query`],n=[`@tanstack/solid-query-devtools`];if(d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}}if(c){if(d){let t=e.join(l,`package.json`);if(await h.pathExists(t))try{let e=[`convex`];i.includes(`tanstack-start`)&&e.push(`@convex-dev/react-query`),g&&e.push(`convex-svelte`),await M({dependencies:e,projectDir:l})}catch{}}if(f){let t=e.join(u,`package.json`);if(await h.pathExists(t))try{await M({dependencies:[`convex`],projectDir:u})}catch{}}let t=`@${r}/backend`,n=o===`npm`?`*`:`workspace:*`,a=async(e,t,n)=>{try{let r=await h.readJson(e);r.dependencies||={},r.dependencies[t]!==n&&(r.dependencies[t]=n,await h.writeJson(e,r,{spaces:2}))}catch{}};if(d){let r=e.join(l,`package.json`);await h.pathExists(r)&&await a(r,t,n)}if(f){let r=e.join(u,`package.json`);await h.pathExists(r)&&await a(r,t,n)}}}async function ue(t){let{auth:n,frontend:r,backend:i,projectDir:a}=t;if(i===`convex`||!n)return;let o=e.join(a,`apps/server`),s=e.join(a,`apps/web`),c=e.join(a,`apps/native`),l=await h.pathExists(s),u=await h.pathExists(c),d=await h.pathExists(o);try{d&&await M({dependencies:[`better-auth`],projectDir:o});let e=r.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].includes(e));e&&l&&await M({dependencies:[`better-auth`],projectDir:s}),(r.includes(`native-nativewind`)||r.includes(`native-unistyles`))&&u&&(await M({dependencies:[`better-auth`,`@better-auth/expo`],projectDir:c}),d&&await M({dependencies:[`@better-auth/expo`],projectDir:o}))}catch(e){p.error(g.red(`Failed to configure authentication dependencies`)),e instanceof Error&&p.error(g.red(e.message))}}function de(e=32){let t=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`,n=``,r=t.length;for(let i=0;i<e;i++)n+=t.charAt(Math.floor(Math.random()*r));return n}async function fe(t){let{backend:n,runtime:r,api:i,projectDir:a}=t;if(n===`convex`)return;let o=n,s=e.join(a,`apps/server`),c=[],l=[];o===`hono`?(c.push(`hono`),i===`trpc`&&c.push(`@hono/trpc-server`),r===`node`&&(c.push(`@hono/node-server`),l.push(`tsx`,`@types/node`))):o===`elysia`?(c.push(`elysia`,`@elysiajs/cors`),i===`trpc`&&c.push(`@elysiajs/trpc`),r===`node`&&(c.push(`@elysiajs/node`),l.push(`tsx`,`@types/node`))):o===`express`?(c.push(`express`,`cors`),l.push(`@types/express`,`@types/cors`),r===`node`&&l.push(`tsx`,`@types/node`)):o===`fastify`&&(c.push(`fastify`,`@fastify/cors`),r===`node`&&l.push(`tsx`,`@types/node`)),r===`bun`&&l.push(`@types/bun`),(c.length>0||l.length>0)&&await M({dependencies:c,devDependencies:l,projectDir:s})}async function P(e){try{let t=process.platform===`win32`;if(t){let t=await x(`where`,[e]);return t.exitCode===0}let n=await x(`which`,[e]);return n.exitCode===0}catch{return!1}}async function F(t,n){await h.ensureDir(e.dirname(t));let r=``;await h.pathExists(t)&&(r=await h.readFile(t,`utf8`));let i=!1,a=``,o=[];for(let{key:e,value:t,condition:s}of n)if(s){let n=RegExp(`^${e}=.*$`,`m`),s=t??``;if(o.push(`${e}=`),n.test(r)){let t=r.match(n);t&&t[0]!==`${e}=${s}`&&(r=r.replace(n,`${e}=${s}`),i=!0)}else a+=`${e}=${s}\n`,i=!0}a&&(r.length>0&&!r.endsWith(`
3
- `)&&(r+=`
4
- `),r+=a),i&&await h.writeFile(t,r.trimEnd());let s=t.replace(/\.env$/,`.env.example`),c=``;await h.pathExists(s)&&(c=await h.readFile(s,`utf8`));let l=!1,u=``;for(let e of o){let t=e.split(`=`)[0],n=RegExp(`^${t}=.*$`,`m`);n.test(c)||(u+=`${e}\n`,l=!0)}u&&(c.length>0&&!c.endsWith(`
5
- `)&&(c+=`
6
- `),c+=u),(l||!await h.pathExists(s))&&await h.writeFile(s,c.trimEnd())}async function pe(t){let{backend:n,frontend:r,database:i,auth:a,examples:o,dbSetup:s,projectDir:c}=t,l=r.includes(`react-router`),u=r.includes(`tanstack-router`),d=r.includes(`tanstack-start`),f=r.includes(`next`),p=r.includes(`nuxt`),m=r.includes(`svelte`),g=r.includes(`solid`),_=l||u||d||f||p||g||m;if(_){let t=e.join(c,`apps/web`);if(await h.pathExists(t)){let r=`VITE_SERVER_URL`,i=`http://localhost:3000`;f?r=`NEXT_PUBLIC_SERVER_URL`:p?r=`NUXT_PUBLIC_SERVER_URL`:m&&(r=`PUBLIC_SERVER_URL`),n===`convex`&&(r=f?`NEXT_PUBLIC_CONVEX_URL`:p?`NUXT_PUBLIC_CONVEX_URL`:m?`PUBLIC_CONVEX_URL`:`VITE_CONVEX_URL`,i=`https://<YOUR_CONVEX_URL>`);let a=[{key:r,value:i,condition:!0}];await F(e.join(t,`.env`),a)}}if(r.includes(`native-nativewind`)||r.includes(`native-unistyles`)){let t=e.join(c,`apps/native`);if(await h.pathExists(t)){let r=`EXPO_PUBLIC_SERVER_URL`,i=`http://localhost:3000`;n===`convex`&&(r=`EXPO_PUBLIC_CONVEX_URL`,i=`https://<YOUR_CONVEX_URL>`);let a=[{key:r,value:i,condition:!0}];await F(e.join(t,`.env`),a)}}if(n===`convex`)return;let v=e.join(c,`apps/server`);if(!await h.pathExists(v))return;let y=e.join(v,`.env`),b=`http://localhost:3001`;(l||m)&&(b=`http://localhost:5173`);let x=null,S=s===`turso`||s===`prisma-postgres`||s===`mongodb-atlas`||s===`neon`||s===`supabase`;if(i!==`none`&&!S)switch(i){case`postgres`:x=`postgresql://postgres:password@localhost:5432/postgres`;break;case`mysql`:x=`mysql://root:password@localhost:3306/mydb`;break;case`mongodb`:x=`mongodb://localhost:27017/mydatabase`;break;case`sqlite`:x=`file:./local.db`;break}let C=[{key:`CORS_ORIGIN`,value:b,condition:!0},{key:`BETTER_AUTH_SECRET`,value:de(),condition:!!a},{key:`BETTER_AUTH_URL`,value:`http://localhost:3000`,condition:!!a},{key:`DATABASE_URL`,value:x,condition:i!==`none`&&!S},{key:`GOOGLE_GENERATIVE_AI_API_KEY`,value:``,condition:o?.includes(`ai`)||!1}];await F(y,C)}async function me(){let e=d();e.start(`Checking for MongoDB Atlas CLI`);try{let t=await P(`atlas`);return e.stop(t?`MongoDB Atlas CLI found`:g.yellow(`MongoDB Atlas CLI not found`)),t}catch{return e.stop(g.red(`Error checking for MongoDB Atlas CLI`)),!1}}async function he(e){try{let n=await me();if(!n)return p.error(g.red(`MongoDB Atlas CLI not found.`)),o.info(g.yellow(`Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/`)),null;o.info(g.blue(`Running MongoDB Atlas setup...`)),await x(`atlas`,[`deployments`,`setup`],{cwd:e,stdio:`inherit`}),o.info(g.green(`Atlas setup complete!`));let r=await f({message:`Enter your MongoDB connection string:`,placeholder:`mongodb+srv://username:password@cluster.mongodb.net/database`,validate(e){if(!e)return`Please enter a connection string`;if(!e.startsWith(`mongodb`))return`URL should start with mongodb:// or mongodb+srv://`}});return a(r)?(t(`MongoDB setup cancelled`),null):{connectionString:r}}catch(e){return e instanceof Error&&p.error(g.red(e.message)),null}}async function I(t,n){try{let r=e.join(t,`apps/server`,`.env`),i=[{key:`DATABASE_URL`,value:n?.connectionString??`mongodb://localhost:27017/mydb`,condition:!0}];await F(r,i)}catch{p.error(`Failed to update environment configuration`)}}function L(){o.info(`
7
- ${g.green(`MongoDB Atlas Manual Setup Instructions:`)}
2
+ import path from "node:path";
3
+ import { cancel, confirm, group, intro, isCancel, log, multiselect, outro, password, select, spinner, text } from "@clack/prompts";
4
+ import consola, { consola as consola$1 } from "consola";
5
+ import fs from "fs-extra";
6
+ import pc from "picocolors";
7
+ import yargs from "yargs";
8
+ import { hideBin } from "yargs/helpers";
9
+ import { fileURLToPath } from "node:url";
10
+ import { $, execa } from "execa";
11
+ import os from "node:os";
12
+ import { globby } from "globby";
13
+ import handlebars from "handlebars";
14
+ import { PostHog } from "posthog-node";
15
+ import gradient from "gradient-string";
16
+
17
+ //#region src/utils/get-package-manager.ts
18
+ const getUserPkgManager = () => {
19
+ const userAgent = process.env.npm_config_user_agent;
20
+ if (userAgent?.startsWith("pnpm")) return "pnpm";
21
+ if (userAgent?.startsWith("bun")) return "bun";
22
+ return "npm";
23
+ };
24
+
25
+ //#endregion
26
+ //#region src/constants.ts
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const distPath = path.dirname(__filename);
29
+ const PKG_ROOT = path.join(distPath, "../");
30
+ const DEFAULT_CONFIG = {
31
+ projectName: "my-better-t-app",
32
+ projectDir: path.resolve(process.cwd(), "my-better-t-app"),
33
+ relativePath: "my-better-t-app",
34
+ frontend: ["tanstack-router"],
35
+ database: "sqlite",
36
+ orm: "drizzle",
37
+ auth: true,
38
+ addons: ["turborepo"],
39
+ examples: [],
40
+ git: true,
41
+ packageManager: getUserPkgManager(),
42
+ install: true,
43
+ dbSetup: "none",
44
+ backend: "hono",
45
+ runtime: "bun",
46
+ api: "trpc"
47
+ };
48
+ const dependencyVersionMap = {
49
+ "better-auth": "^1.2.7",
50
+ "@better-auth/expo": "^1.2.7",
51
+ "drizzle-orm": "^0.38.4",
52
+ "drizzle-kit": "^0.30.5",
53
+ "@libsql/client": "^0.14.0",
54
+ pg: "^8.14.1",
55
+ "@types/pg": "^8.11.11",
56
+ mysql2: "^3.14.0",
57
+ "@prisma/client": "^6.7.0",
58
+ prisma: "^6.7.0",
59
+ mongoose: "^8.14.0",
60
+ "vite-plugin-pwa": "^0.21.2",
61
+ "@vite-pwa/assets-generator": "^0.2.6",
62
+ "@tauri-apps/cli": "^2.4.0",
63
+ "@biomejs/biome": "1.9.4",
64
+ husky: "^9.1.7",
65
+ "lint-staged": "^15.5.0",
66
+ "@hono/node-server": "^1.14.0",
67
+ tsx: "^4.19.2",
68
+ "@types/node": "^22.13.11",
69
+ "@types/bun": "^1.2.6",
70
+ "@elysiajs/node": "^1.2.6",
71
+ "@elysiajs/cors": "^1.2.0",
72
+ "@elysiajs/trpc": "^1.1.0",
73
+ elysia: "^1.2.25",
74
+ "@hono/trpc-server": "^0.3.4",
75
+ hono: "^4.7.6",
76
+ cors: "^2.8.5",
77
+ express: "^5.1.0",
78
+ "@types/express": "^5.0.1",
79
+ "@types/cors": "^2.8.17",
80
+ fastify: "^5.3.3",
81
+ "@fastify/cors": "^11.0.1",
82
+ turbo: "^2.4.2",
83
+ ai: "^4.3.16",
84
+ "@ai-sdk/google": "^1.2.3",
85
+ "@ai-sdk/vue": "^1.2.8",
86
+ "@ai-sdk/svelte": "^2.1.9",
87
+ "@ai-sdk/react": "^1.2.12",
88
+ "@prisma/extension-accelerate": "^1.3.0",
89
+ "@orpc/server": "^1.2.0",
90
+ "@orpc/client": "^1.2.0",
91
+ "@orpc/react-query": "^1.2.0",
92
+ "@orpc/solid-query": "^1.2.0",
93
+ "@orpc/vue-query": "^1.2.0",
94
+ "@orpc/svelte-query": "^1.2.0",
95
+ "@trpc/tanstack-react-query": "^11.0.0",
96
+ "@trpc/server": "^11.0.0",
97
+ "@trpc/client": "^11.0.0",
98
+ convex: "^1.23.0",
99
+ "@convex-dev/react-query": "^0.0.0-alpha.8",
100
+ "convex-svelte": "^0.0.11",
101
+ "@tanstack/svelte-query": "^5.74.4",
102
+ "@tanstack/react-query-devtools": "^5.69.0",
103
+ "@tanstack/react-query": "^5.69.0",
104
+ "@tanstack/solid-query": "^5.75.0",
105
+ "@tanstack/solid-query-devtools": "^5.75.0"
106
+ };
107
+
108
+ //#endregion
109
+ //#region src/utils/get-latest-cli-version.ts
110
+ const getLatestCLIVersion = () => {
111
+ const packageJsonPath = path.join(PKG_ROOT, "package.json");
112
+ const packageJsonContent = fs.readJSONSync(packageJsonPath);
113
+ return packageJsonContent.version ?? "1.0.0";
114
+ };
115
+
116
+ //#endregion
117
+ //#region src/cli.ts
118
+ async function parseCliArguments() {
119
+ const argv = await yargs(hideBin(process.argv)).scriptName("create-better-t-stack").usage("$0 [project-directory] [options]", "Create a new Better-T Stack project").positional("project-directory", {
120
+ describe: "Project name/directory",
121
+ type: "string"
122
+ }).option("yes", {
123
+ alias: "y",
124
+ type: "boolean",
125
+ describe: "Use default configuration and skip prompts",
126
+ default: false
127
+ }).option("database", {
128
+ type: "string",
129
+ describe: "Database type",
130
+ choices: [
131
+ "none",
132
+ "sqlite",
133
+ "postgres",
134
+ "mysql",
135
+ "mongodb"
136
+ ]
137
+ }).option("orm", {
138
+ type: "string",
139
+ describe: "ORM type",
140
+ choices: [
141
+ "drizzle",
142
+ "prisma",
143
+ "mongoose",
144
+ "none"
145
+ ]
146
+ }).option("auth", {
147
+ type: "boolean",
148
+ describe: "Include authentication (use --no-auth to exclude)"
149
+ }).option("frontend", {
150
+ type: "array",
151
+ string: true,
152
+ describe: "Frontend types",
153
+ choices: [
154
+ "tanstack-router",
155
+ "react-router",
156
+ "tanstack-start",
157
+ "next",
158
+ "nuxt",
159
+ "native-nativewind",
160
+ "native-unistyles",
161
+ "svelte",
162
+ "solid",
163
+ "none"
164
+ ]
165
+ }).option("addons", {
166
+ type: "array",
167
+ string: true,
168
+ describe: "Additional addons",
169
+ choices: [
170
+ "pwa",
171
+ "tauri",
172
+ "starlight",
173
+ "biome",
174
+ "husky",
175
+ "turborepo",
176
+ "none"
177
+ ]
178
+ }).option("examples", {
179
+ type: "array",
180
+ string: true,
181
+ describe: "Examples to include",
182
+ choices: [
183
+ "todo",
184
+ "ai",
185
+ "none"
186
+ ]
187
+ }).option("git", {
188
+ type: "boolean",
189
+ describe: "Initialize git repository (use --no-git to skip)"
190
+ }).option("package-manager", {
191
+ alias: "pm",
192
+ type: "string",
193
+ describe: "Package manager",
194
+ choices: [
195
+ "npm",
196
+ "pnpm",
197
+ "bun"
198
+ ]
199
+ }).option("install", {
200
+ type: "boolean",
201
+ describe: "Install dependencies (use --no-install to skip)"
202
+ }).option("db-setup", {
203
+ type: "string",
204
+ describe: "Database setup",
205
+ choices: [
206
+ "turso",
207
+ "neon",
208
+ "prisma-postgres",
209
+ "mongodb-atlas",
210
+ "supabase",
211
+ "none"
212
+ ]
213
+ }).option("backend", {
214
+ type: "string",
215
+ describe: "Backend framework",
216
+ choices: [
217
+ "hono",
218
+ "express",
219
+ "fastify",
220
+ "next",
221
+ "elysia",
222
+ "convex",
223
+ "none"
224
+ ]
225
+ }).option("runtime", {
226
+ type: "string",
227
+ describe: "Runtime",
228
+ choices: [
229
+ "bun",
230
+ "node",
231
+ "none"
232
+ ]
233
+ }).option("api", {
234
+ type: "string",
235
+ describe: "API type",
236
+ choices: [
237
+ "trpc",
238
+ "orpc",
239
+ "none"
240
+ ]
241
+ }).completion().recommendCommands().version(getLatestCLIVersion()).alias("version", "v").help().alias("help", "h").strict().wrap(null).parse();
242
+ return argv;
243
+ }
244
+
245
+ //#endregion
246
+ //#region src/utils/add-package-deps.ts
247
+ const addPackageDependency = async (opts) => {
248
+ const { dependencies = [], devDependencies = [], projectDir } = opts;
249
+ const pkgJsonPath = path.join(projectDir, "package.json");
250
+ const pkgJson = await fs.readJson(pkgJsonPath);
251
+ if (!pkgJson.dependencies) pkgJson.dependencies = {};
252
+ if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
253
+ for (const pkgName of dependencies) {
254
+ const version = dependencyVersionMap[pkgName];
255
+ if (version) pkgJson.dependencies[pkgName] = version;
256
+ else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
257
+ }
258
+ for (const pkgName of devDependencies) {
259
+ const version = dependencyVersionMap[pkgName];
260
+ if (version) pkgJson.devDependencies[pkgName] = version;
261
+ else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
262
+ }
263
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
264
+ };
265
+
266
+ //#endregion
267
+ //#region src/utils/get-package-execution-command.ts
268
+ /**
269
+ * Returns the appropriate command for running a package without installing it globally,
270
+ * based on the selected package manager.
271
+ *
272
+ * @param packageManager - The selected package manager (e.g., 'npm', 'yarn', 'pnpm', 'bun').
273
+ * @param commandWithArgs - The command to run, including arguments (e.g., "prisma generate --schema=./prisma/schema.prisma").
274
+ * @returns The full command string (e.g., "npx prisma generate --schema=./prisma/schema.prisma").
275
+ */
276
+ function getPackageExecutionCommand(packageManager, commandWithArgs) {
277
+ switch (packageManager) {
278
+ case "pnpm": return `pnpm dlx ${commandWithArgs}`;
279
+ case "bun": return `bunx ${commandWithArgs}`;
280
+ default: return `npx ${commandWithArgs}`;
281
+ }
282
+ }
283
+
284
+ //#endregion
285
+ //#region src/helpers/setup/starlight-setup.ts
286
+ async function setupStarlight(config) {
287
+ const { packageManager, projectDir } = config;
288
+ const s = spinner();
289
+ try {
290
+ s.start("Setting up Starlight docs...");
291
+ const starlightArgs = [
292
+ "docs",
293
+ "--template",
294
+ "starlight",
295
+ "--no-install",
296
+ "--add",
297
+ "tailwind",
298
+ "--no-git",
299
+ "--skip-houston"
300
+ ];
301
+ const starlightArgsString = starlightArgs.join(" ");
302
+ const commandWithArgs = `create-astro@latest ${starlightArgsString}`;
303
+ const starlightInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
304
+ await execa(starlightInitCommand, {
305
+ cwd: path.join(projectDir, "apps"),
306
+ env: { CI: "true" },
307
+ shell: true
308
+ });
309
+ s.stop("Starlight docs setup successfully!");
310
+ } catch (error) {
311
+ s.stop(pc.red("Failed to set up Starlight docs"));
312
+ if (error instanceof Error) consola.error(pc.red(error.message));
313
+ }
314
+ }
315
+
316
+ //#endregion
317
+ //#region src/helpers/setup/tauri-setup.ts
318
+ async function setupTauri(config) {
319
+ const { packageManager, frontend, projectDir } = config;
320
+ const s = spinner();
321
+ const clientPackageDir = path.join(projectDir, "apps/web");
322
+ if (!await fs.pathExists(clientPackageDir)) return;
323
+ try {
324
+ s.start("Setting up Tauri desktop app support...");
325
+ await addPackageDependency({
326
+ devDependencies: ["@tauri-apps/cli"],
327
+ projectDir: clientPackageDir
328
+ });
329
+ const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
330
+ if (await fs.pathExists(clientPackageJsonPath)) {
331
+ const packageJson = await fs.readJson(clientPackageJsonPath);
332
+ packageJson.scripts = {
333
+ ...packageJson.scripts,
334
+ tauri: "tauri",
335
+ "desktop:dev": "tauri dev",
336
+ "desktop:build": "tauri build"
337
+ };
338
+ await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
339
+ }
340
+ const _hasTanstackRouter = frontend.includes("tanstack-router");
341
+ const hasReactRouter = frontend.includes("react-router");
342
+ const hasNuxt = frontend.includes("nuxt");
343
+ const hasSvelte = frontend.includes("svelte");
344
+ const _hasSolid = frontend.includes("solid");
345
+ const hasNext = frontend.includes("next");
346
+ const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
347
+ const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
348
+ const tauriArgs = [
349
+ "init",
350
+ `--app-name=${path.basename(projectDir)}`,
351
+ `--window-title=${path.basename(projectDir)}`,
352
+ `--frontend-dist=${frontendDist}`,
353
+ `--dev-url=${devUrl}`,
354
+ `--before-dev-command=\"${packageManager} run dev\"`,
355
+ `--before-build-command=\"${packageManager} run build\"`
356
+ ];
357
+ const tauriArgsString = tauriArgs.join(" ");
358
+ const commandWithArgs = `@tauri-apps/cli@latest ${tauriArgsString}`;
359
+ const tauriInitCommand = getPackageExecutionCommand(packageManager, commandWithArgs);
360
+ await execa(tauriInitCommand, {
361
+ cwd: clientPackageDir,
362
+ env: { CI: "true" },
363
+ shell: true
364
+ });
365
+ s.stop("Tauri desktop app support configured successfully!");
366
+ } catch (error) {
367
+ s.stop(pc.red("Failed to set up Tauri"));
368
+ if (error instanceof Error) consola$1.error(pc.red(error.message));
369
+ }
370
+ }
371
+
372
+ //#endregion
373
+ //#region src/helpers/setup/addons-setup.ts
374
+ async function setupAddons(config) {
375
+ const { addons, frontend, projectDir } = config;
376
+ const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
377
+ const hasNuxtFrontend = frontend.includes("nuxt");
378
+ const hasSvelteFrontend = frontend.includes("svelte");
379
+ const hasSolidFrontend = frontend.includes("solid");
380
+ const hasNextFrontend = frontend.includes("next");
381
+ if (addons.includes("turborepo")) await addPackageDependency({
382
+ devDependencies: ["turbo"],
383
+ projectDir
384
+ });
385
+ if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) await setupPwa(projectDir, frontend);
386
+ if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
387
+ if (addons.includes("biome")) await setupBiome(projectDir);
388
+ if (addons.includes("husky")) await setupHusky(projectDir);
389
+ if (addons.includes("starlight")) await setupStarlight(config);
390
+ }
391
+ function getWebAppDir(projectDir, frontends) {
392
+ if (frontends.some((f) => [
393
+ "react-router",
394
+ "tanstack-router",
395
+ "nuxt",
396
+ "svelte",
397
+ "solid"
398
+ ].includes(f))) return path.join(projectDir, "apps/web");
399
+ return path.join(projectDir, "apps/web");
400
+ }
401
+ async function setupBiome(projectDir) {
402
+ await addPackageDependency({
403
+ devDependencies: ["@biomejs/biome"],
404
+ projectDir
405
+ });
406
+ const packageJsonPath = path.join(projectDir, "package.json");
407
+ if (await fs.pathExists(packageJsonPath)) {
408
+ const packageJson = await fs.readJson(packageJsonPath);
409
+ packageJson.scripts = {
410
+ ...packageJson.scripts,
411
+ check: "biome check --write ."
412
+ };
413
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
414
+ }
415
+ }
416
+ async function setupHusky(projectDir) {
417
+ await addPackageDependency({
418
+ devDependencies: ["husky", "lint-staged"],
419
+ projectDir
420
+ });
421
+ const packageJsonPath = path.join(projectDir, "package.json");
422
+ if (await fs.pathExists(packageJsonPath)) {
423
+ const packageJson = await fs.readJson(packageJsonPath);
424
+ packageJson.scripts = {
425
+ ...packageJson.scripts,
426
+ prepare: "husky"
427
+ };
428
+ packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
429
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
430
+ }
431
+ }
432
+ async function setupPwa(projectDir, frontends) {
433
+ const isCompatibleFrontend = frontends.some((f) => [
434
+ "react-router",
435
+ "tanstack-router",
436
+ "solid"
437
+ ].includes(f));
438
+ if (!isCompatibleFrontend) return;
439
+ const clientPackageDir = getWebAppDir(projectDir, frontends);
440
+ if (!await fs.pathExists(clientPackageDir)) return;
441
+ await addPackageDependency({
442
+ dependencies: ["vite-plugin-pwa"],
443
+ devDependencies: ["@vite-pwa/assets-generator"],
444
+ projectDir: clientPackageDir
445
+ });
446
+ const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
447
+ if (await fs.pathExists(clientPackageJsonPath)) {
448
+ const packageJson = await fs.readJson(clientPackageJsonPath);
449
+ packageJson.scripts = {
450
+ ...packageJson.scripts,
451
+ "generate-pwa-assets": "pwa-assets-generator"
452
+ };
453
+ await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
454
+ }
455
+ }
456
+
457
+ //#endregion
458
+ //#region src/helpers/setup/api-setup.ts
459
+ async function setupApi(config) {
460
+ const { api, projectName, frontend, backend, packageManager, projectDir } = config;
461
+ const isConvex = backend === "convex";
462
+ const webDir = path.join(projectDir, "apps/web");
463
+ const nativeDir = path.join(projectDir, "apps/native");
464
+ const webDirExists = await fs.pathExists(webDir);
465
+ const nativeDirExists = await fs.pathExists(nativeDir);
466
+ const hasReactWeb = frontend.some((f) => [
467
+ "tanstack-router",
468
+ "react-router",
469
+ "tanstack-start",
470
+ "next"
471
+ ].includes(f));
472
+ const hasNuxtWeb = frontend.includes("nuxt");
473
+ const hasSvelteWeb = frontend.includes("svelte");
474
+ const hasSolidWeb = frontend.includes("solid");
475
+ if (!isConvex && api !== "none") {
476
+ const serverDir = path.join(projectDir, "apps/server");
477
+ const serverDirExists = await fs.pathExists(serverDir);
478
+ if (serverDirExists) {
479
+ if (api === "orpc") await addPackageDependency({
480
+ dependencies: ["@orpc/server", "@orpc/client"],
481
+ projectDir: serverDir
482
+ });
483
+ else if (api === "trpc") {
484
+ await addPackageDependency({
485
+ dependencies: ["@trpc/server", "@trpc/client"],
486
+ projectDir: serverDir
487
+ });
488
+ if (config.backend === "hono") await addPackageDependency({
489
+ dependencies: ["@hono/trpc-server"],
490
+ projectDir: serverDir
491
+ });
492
+ else if (config.backend === "elysia") await addPackageDependency({
493
+ dependencies: ["@elysiajs/trpc"],
494
+ projectDir: serverDir
495
+ });
496
+ }
497
+ }
498
+ if (webDirExists) {
499
+ if (hasReactWeb) {
500
+ if (api === "orpc") await addPackageDependency({
501
+ dependencies: [
502
+ "@orpc/react-query",
503
+ "@orpc/client",
504
+ "@orpc/server"
505
+ ],
506
+ projectDir: webDir
507
+ });
508
+ else if (api === "trpc") await addPackageDependency({
509
+ dependencies: [
510
+ "@trpc/tanstack-react-query",
511
+ "@trpc/client",
512
+ "@trpc/server"
513
+ ],
514
+ projectDir: webDir
515
+ });
516
+ } else if (hasNuxtWeb) {
517
+ if (api === "orpc") await addPackageDependency({
518
+ dependencies: [
519
+ "@orpc/vue-query",
520
+ "@orpc/client",
521
+ "@orpc/server"
522
+ ],
523
+ projectDir: webDir
524
+ });
525
+ } else if (hasSvelteWeb) {
526
+ if (api === "orpc") await addPackageDependency({
527
+ dependencies: [
528
+ "@orpc/svelte-query",
529
+ "@orpc/client",
530
+ "@orpc/server",
531
+ "@tanstack/svelte-query"
532
+ ],
533
+ projectDir: webDir
534
+ });
535
+ } else if (hasSolidWeb) {
536
+ if (api === "orpc") await addPackageDependency({
537
+ dependencies: [
538
+ "@orpc/solid-query",
539
+ "@orpc/client",
540
+ "@orpc/server",
541
+ "@tanstack/solid-query"
542
+ ],
543
+ projectDir: webDir
544
+ });
545
+ }
546
+ }
547
+ if (nativeDirExists) {
548
+ if (api === "trpc") await addPackageDependency({
549
+ dependencies: [
550
+ "@trpc/tanstack-react-query",
551
+ "@trpc/client",
552
+ "@trpc/server"
553
+ ],
554
+ projectDir: nativeDir
555
+ });
556
+ else if (api === "orpc") await addPackageDependency({
557
+ dependencies: [
558
+ "@orpc/react-query",
559
+ "@orpc/client",
560
+ "@orpc/server"
561
+ ],
562
+ projectDir: nativeDir
563
+ });
564
+ }
565
+ }
566
+ const reactBasedFrontends = [
567
+ "react-router",
568
+ "tanstack-router",
569
+ "tanstack-start",
570
+ "next",
571
+ "native-nativewind",
572
+ "native-unistyles"
573
+ ];
574
+ const needsSolidQuery = frontend.includes("solid");
575
+ const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));
576
+ if (needsReactQuery && !isConvex) {
577
+ const reactQueryDeps = ["@tanstack/react-query"];
578
+ const reactQueryDevDeps = ["@tanstack/react-query-devtools"];
579
+ const hasReactWeb$1 = frontend.some((f) => f !== "native-nativewind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
580
+ const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
581
+ if (hasReactWeb$1 && webDirExists) {
582
+ const webPkgJsonPath = path.join(webDir, "package.json");
583
+ if (await fs.pathExists(webPkgJsonPath)) try {
584
+ await addPackageDependency({
585
+ dependencies: reactQueryDeps,
586
+ devDependencies: reactQueryDevDeps,
587
+ projectDir: webDir
588
+ });
589
+ } catch (_error) {}
590
+ }
591
+ if (hasNative && nativeDirExists) {
592
+ const nativePkgJsonPath = path.join(nativeDir, "package.json");
593
+ if (await fs.pathExists(nativePkgJsonPath)) try {
594
+ await addPackageDependency({
595
+ dependencies: reactQueryDeps,
596
+ projectDir: nativeDir
597
+ });
598
+ } catch (_error) {}
599
+ }
600
+ }
601
+ if (needsSolidQuery && !isConvex) {
602
+ const solidQueryDeps = ["@tanstack/solid-query"];
603
+ const solidQueryDevDeps = ["@tanstack/solid-query-devtools"];
604
+ if (webDirExists) {
605
+ const webPkgJsonPath = path.join(webDir, "package.json");
606
+ if (await fs.pathExists(webPkgJsonPath)) try {
607
+ await addPackageDependency({
608
+ dependencies: solidQueryDeps,
609
+ devDependencies: solidQueryDevDeps,
610
+ projectDir: webDir
611
+ });
612
+ } catch (_error) {}
613
+ }
614
+ }
615
+ if (isConvex) {
616
+ if (webDirExists) {
617
+ const webPkgJsonPath = path.join(webDir, "package.json");
618
+ if (await fs.pathExists(webPkgJsonPath)) try {
619
+ const webDepsToAdd = ["convex"];
620
+ if (frontend.includes("tanstack-start")) webDepsToAdd.push("@convex-dev/react-query");
621
+ if (hasSvelteWeb) webDepsToAdd.push("convex-svelte");
622
+ await addPackageDependency({
623
+ dependencies: webDepsToAdd,
624
+ projectDir: webDir
625
+ });
626
+ } catch (_error) {}
627
+ }
628
+ if (nativeDirExists) {
629
+ const nativePkgJsonPath = path.join(nativeDir, "package.json");
630
+ if (await fs.pathExists(nativePkgJsonPath)) try {
631
+ await addPackageDependency({
632
+ dependencies: ["convex"],
633
+ projectDir: nativeDir
634
+ });
635
+ } catch (_error) {}
636
+ }
637
+ const backendPackageName = `@${projectName}/backend`;
638
+ const backendWorkspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
639
+ const addWorkspaceDepManually = async (pkgJsonPath, depName, depVersion) => {
640
+ try {
641
+ const pkgJson = await fs.readJson(pkgJsonPath);
642
+ if (!pkgJson.dependencies) pkgJson.dependencies = {};
643
+ if (pkgJson.dependencies[depName] !== depVersion) {
644
+ pkgJson.dependencies[depName] = depVersion;
645
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
646
+ }
647
+ } catch (_error) {}
648
+ };
649
+ if (webDirExists) {
650
+ const webPkgJsonPath = path.join(webDir, "package.json");
651
+ if (await fs.pathExists(webPkgJsonPath)) await addWorkspaceDepManually(webPkgJsonPath, backendPackageName, backendWorkspaceVersion);
652
+ }
653
+ if (nativeDirExists) {
654
+ const nativePkgJsonPath = path.join(nativeDir, "package.json");
655
+ if (await fs.pathExists(nativePkgJsonPath)) await addWorkspaceDepManually(nativePkgJsonPath, backendPackageName, backendWorkspaceVersion);
656
+ }
657
+ }
658
+ }
659
+
660
+ //#endregion
661
+ //#region src/helpers/setup/auth-setup.ts
662
+ async function setupAuth(config) {
663
+ const { auth, frontend, backend, projectDir } = config;
664
+ if (backend === "convex" || !auth) return;
665
+ const serverDir = path.join(projectDir, "apps/server");
666
+ const clientDir = path.join(projectDir, "apps/web");
667
+ const nativeDir = path.join(projectDir, "apps/native");
668
+ const clientDirExists = await fs.pathExists(clientDir);
669
+ const nativeDirExists = await fs.pathExists(nativeDir);
670
+ const serverDirExists = await fs.pathExists(serverDir);
671
+ try {
672
+ if (serverDirExists) await addPackageDependency({
673
+ dependencies: ["better-auth"],
674
+ projectDir: serverDir
675
+ });
676
+ const hasWebFrontend = frontend.some((f) => [
677
+ "react-router",
678
+ "tanstack-router",
679
+ "tanstack-start",
680
+ "next",
681
+ "nuxt",
682
+ "svelte"
683
+ ].includes(f));
684
+ if (hasWebFrontend && clientDirExists) await addPackageDependency({
685
+ dependencies: ["better-auth"],
686
+ projectDir: clientDir
687
+ });
688
+ if ((frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) && nativeDirExists) {
689
+ await addPackageDependency({
690
+ dependencies: ["better-auth", "@better-auth/expo"],
691
+ projectDir: nativeDir
692
+ });
693
+ if (serverDirExists) await addPackageDependency({
694
+ dependencies: ["@better-auth/expo"],
695
+ projectDir: serverDir
696
+ });
697
+ }
698
+ } catch (error) {
699
+ consola.error(pc.red("Failed to configure authentication dependencies"));
700
+ if (error instanceof Error) consola.error(pc.red(error.message));
701
+ }
702
+ }
703
+ function generateAuthSecret(length = 32) {
704
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
705
+ let result = "";
706
+ const charactersLength = characters.length;
707
+ for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength));
708
+ return result;
709
+ }
710
+
711
+ //#endregion
712
+ //#region src/helpers/setup/backend-setup.ts
713
+ async function setupBackendDependencies(config) {
714
+ const { backend, runtime, api, projectDir } = config;
715
+ if (backend === "convex") return;
716
+ const framework = backend;
717
+ const serverDir = path.join(projectDir, "apps/server");
718
+ const dependencies = [];
719
+ const devDependencies = [];
720
+ if (framework === "hono") {
721
+ dependencies.push("hono");
722
+ if (api === "trpc") dependencies.push("@hono/trpc-server");
723
+ if (runtime === "node") {
724
+ dependencies.push("@hono/node-server");
725
+ devDependencies.push("tsx", "@types/node");
726
+ }
727
+ } else if (framework === "elysia") {
728
+ dependencies.push("elysia", "@elysiajs/cors");
729
+ if (api === "trpc") dependencies.push("@elysiajs/trpc");
730
+ if (runtime === "node") {
731
+ dependencies.push("@elysiajs/node");
732
+ devDependencies.push("tsx", "@types/node");
733
+ }
734
+ } else if (framework === "express") {
735
+ dependencies.push("express", "cors");
736
+ devDependencies.push("@types/express", "@types/cors");
737
+ if (runtime === "node") devDependencies.push("tsx", "@types/node");
738
+ } else if (framework === "fastify") {
739
+ dependencies.push("fastify", "@fastify/cors");
740
+ if (runtime === "node") devDependencies.push("tsx", "@types/node");
741
+ }
742
+ if (runtime === "bun") devDependencies.push("@types/bun");
743
+ if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
744
+ dependencies,
745
+ devDependencies,
746
+ projectDir: serverDir
747
+ });
748
+ }
749
+
750
+ //#endregion
751
+ //#region src/utils/command-exists.ts
752
+ async function commandExists(command) {
753
+ try {
754
+ const isWindows = process.platform === "win32";
755
+ if (isWindows) {
756
+ const result$1 = await execa("where", [command]);
757
+ return result$1.exitCode === 0;
758
+ }
759
+ const result = await execa("which", [command]);
760
+ return result.exitCode === 0;
761
+ } catch {
762
+ return false;
763
+ }
764
+ }
765
+
766
+ //#endregion
767
+ //#region src/helpers/project-generation/env-setup.ts
768
+ async function addEnvVariablesToFile(filePath, variables) {
769
+ await fs.ensureDir(path.dirname(filePath));
770
+ let envContent = "";
771
+ if (await fs.pathExists(filePath)) envContent = await fs.readFile(filePath, "utf8");
772
+ let modified = false;
773
+ let contentToAdd = "";
774
+ const exampleVariables = [];
775
+ for (const { key, value, condition } of variables) if (condition) {
776
+ const regex = new RegExp(`^${key}=.*$`, "m");
777
+ const valueToWrite = value ?? "";
778
+ exampleVariables.push(`${key}=`);
779
+ if (regex.test(envContent)) {
780
+ const existingMatch = envContent.match(regex);
781
+ if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
782
+ envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
783
+ modified = true;
784
+ }
785
+ } else {
786
+ contentToAdd += `${key}=${valueToWrite}\n`;
787
+ modified = true;
788
+ }
789
+ }
790
+ if (contentToAdd) {
791
+ if (envContent.length > 0 && !envContent.endsWith("\n")) envContent += "\n";
792
+ envContent += contentToAdd;
793
+ }
794
+ if (modified) await fs.writeFile(filePath, envContent.trimEnd());
795
+ const exampleFilePath = filePath.replace(/\.env$/, ".env.example");
796
+ let exampleEnvContent = "";
797
+ if (await fs.pathExists(exampleFilePath)) exampleEnvContent = await fs.readFile(exampleFilePath, "utf8");
798
+ let exampleModified = false;
799
+ let exampleContentToAdd = "";
800
+ for (const exampleVar of exampleVariables) {
801
+ const key = exampleVar.split("=")[0];
802
+ const regex = new RegExp(`^${key}=.*$`, "m");
803
+ if (!regex.test(exampleEnvContent)) {
804
+ exampleContentToAdd += `${exampleVar}\n`;
805
+ exampleModified = true;
806
+ }
807
+ }
808
+ if (exampleContentToAdd) {
809
+ if (exampleEnvContent.length > 0 && !exampleEnvContent.endsWith("\n")) exampleEnvContent += "\n";
810
+ exampleEnvContent += exampleContentToAdd;
811
+ }
812
+ if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
813
+ }
814
+ async function setupEnvironmentVariables(config) {
815
+ const { backend, frontend, database, auth, examples, dbSetup, projectDir } = config;
816
+ const hasReactRouter = frontend.includes("react-router");
817
+ const hasTanStackRouter = frontend.includes("tanstack-router");
818
+ const hasTanStackStart = frontend.includes("tanstack-start");
819
+ const hasNextJs = frontend.includes("next");
820
+ const hasNuxt = frontend.includes("nuxt");
821
+ const hasSvelte = frontend.includes("svelte");
822
+ const hasSolid = frontend.includes("solid");
823
+ const hasWebFrontend = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
824
+ if (hasWebFrontend) {
825
+ const clientDir = path.join(projectDir, "apps/web");
826
+ if (await fs.pathExists(clientDir)) {
827
+ let envVarName = "VITE_SERVER_URL";
828
+ let serverUrl = "http://localhost:3000";
829
+ if (hasNextJs) envVarName = "NEXT_PUBLIC_SERVER_URL";
830
+ else if (hasNuxt) envVarName = "NUXT_PUBLIC_SERVER_URL";
831
+ else if (hasSvelte) envVarName = "PUBLIC_SERVER_URL";
832
+ if (backend === "convex") {
833
+ if (hasNextJs) envVarName = "NEXT_PUBLIC_CONVEX_URL";
834
+ else if (hasNuxt) envVarName = "NUXT_PUBLIC_CONVEX_URL";
835
+ else if (hasSvelte) envVarName = "PUBLIC_CONVEX_URL";
836
+ else envVarName = "VITE_CONVEX_URL";
837
+ serverUrl = "https://<YOUR_CONVEX_URL>";
838
+ }
839
+ const clientVars = [{
840
+ key: envVarName,
841
+ value: serverUrl,
842
+ condition: true
843
+ }];
844
+ await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
845
+ }
846
+ }
847
+ if (frontend.includes("native-nativewind") || frontend.includes("native-unistyles")) {
848
+ const nativeDir = path.join(projectDir, "apps/native");
849
+ if (await fs.pathExists(nativeDir)) {
850
+ let envVarName = "EXPO_PUBLIC_SERVER_URL";
851
+ let serverUrl = "http://localhost:3000";
852
+ if (backend === "convex") {
853
+ envVarName = "EXPO_PUBLIC_CONVEX_URL";
854
+ serverUrl = "https://<YOUR_CONVEX_URL>";
855
+ }
856
+ const nativeVars = [{
857
+ key: envVarName,
858
+ value: serverUrl,
859
+ condition: true
860
+ }];
861
+ await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
862
+ }
863
+ }
864
+ if (backend === "convex") return;
865
+ const serverDir = path.join(projectDir, "apps/server");
866
+ if (!await fs.pathExists(serverDir)) return;
867
+ const envPath = path.join(serverDir, ".env");
868
+ let corsOrigin = "http://localhost:3001";
869
+ if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
870
+ let databaseUrl = null;
871
+ const specializedSetup = dbSetup === "turso" || dbSetup === "prisma-postgres" || dbSetup === "mongodb-atlas" || dbSetup === "neon" || dbSetup === "supabase";
872
+ if (database !== "none" && !specializedSetup) switch (database) {
873
+ case "postgres":
874
+ databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
875
+ break;
876
+ case "mysql":
877
+ databaseUrl = "mysql://root:password@localhost:3306/mydb";
878
+ break;
879
+ case "mongodb":
880
+ databaseUrl = "mongodb://localhost:27017/mydatabase";
881
+ break;
882
+ case "sqlite":
883
+ databaseUrl = "file:./local.db";
884
+ break;
885
+ }
886
+ const serverVars = [
887
+ {
888
+ key: "CORS_ORIGIN",
889
+ value: corsOrigin,
890
+ condition: true
891
+ },
892
+ {
893
+ key: "BETTER_AUTH_SECRET",
894
+ value: generateAuthSecret(),
895
+ condition: !!auth
896
+ },
897
+ {
898
+ key: "BETTER_AUTH_URL",
899
+ value: "http://localhost:3000",
900
+ condition: !!auth
901
+ },
902
+ {
903
+ key: "DATABASE_URL",
904
+ value: databaseUrl,
905
+ condition: database !== "none" && !specializedSetup
906
+ },
907
+ {
908
+ key: "GOOGLE_GENERATIVE_AI_API_KEY",
909
+ value: "",
910
+ condition: examples?.includes("ai") || false
911
+ }
912
+ ];
913
+ await addEnvVariablesToFile(envPath, serverVars);
914
+ }
915
+
916
+ //#endregion
917
+ //#region src/helpers/database-providers/mongodb-atlas-setup.ts
918
+ async function checkAtlasCLI() {
919
+ const s = spinner();
920
+ s.start("Checking for MongoDB Atlas CLI...");
921
+ try {
922
+ const exists = await commandExists("atlas");
923
+ s.stop(exists ? "MongoDB Atlas CLI found" : pc.yellow("MongoDB Atlas CLI not found"));
924
+ return exists;
925
+ } catch (_error) {
926
+ s.stop(pc.red("Error checking MongoDB Atlas CLI"));
927
+ return false;
928
+ }
929
+ }
930
+ async function initMongoDBAtlas(serverDir) {
931
+ try {
932
+ const hasAtlas = await checkAtlasCLI();
933
+ if (!hasAtlas) {
934
+ consola.error(pc.red("MongoDB Atlas CLI not found."));
935
+ log.info(pc.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/"));
936
+ return null;
937
+ }
938
+ log.info(pc.blue("Running MongoDB Atlas setup..."));
939
+ await execa("atlas", ["deployments", "setup"], {
940
+ cwd: serverDir,
941
+ stdio: "inherit"
942
+ });
943
+ log.info(pc.green("MongoDB Atlas deployment ready"));
944
+ const connectionString = await text({
945
+ message: "Enter your MongoDB connection string:",
946
+ placeholder: "mongodb+srv://username:password@cluster.mongodb.net/database",
947
+ validate(value) {
948
+ if (!value) return "Please enter a connection string";
949
+ if (!value.startsWith("mongodb")) return "URL should start with mongodb:// or mongodb+srv://";
950
+ }
951
+ });
952
+ if (isCancel(connectionString)) {
953
+ cancel("MongoDB setup cancelled");
954
+ return null;
955
+ }
956
+ return { connectionString };
957
+ } catch (error) {
958
+ if (error instanceof Error) consola.error(pc.red(error.message));
959
+ return null;
960
+ }
961
+ }
962
+ async function writeEnvFile$3(projectDir, config) {
963
+ try {
964
+ const envPath = path.join(projectDir, "apps/server", ".env");
965
+ const variables = [{
966
+ key: "DATABASE_URL",
967
+ value: config?.connectionString ?? "mongodb://localhost:27017/mydb",
968
+ condition: true
969
+ }];
970
+ await addEnvVariablesToFile(envPath, variables);
971
+ } catch (_error) {
972
+ consola.error("Failed to update environment configuration");
973
+ }
974
+ }
975
+ function displayManualSetupInstructions$3() {
976
+ log.info(`
977
+ ${pc.green("MongoDB Atlas Manual Setup Instructions:")}
8
978
 
9
979
  1. Install Atlas CLI:
10
- ${g.blue(`https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/`)}
980
+ ${pc.blue("https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/")}
11
981
 
12
982
  2. Run the following command and follow the prompts:
13
- ${g.blue(`atlas deployments setup`)}
983
+ ${pc.blue("atlas deployments setup")}
14
984
 
15
985
  3. Get your connection string from the Atlas dashboard:
16
- Format: ${g.dim(`mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME`)}
986
+ Format: ${pc.dim("mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME")}
17
987
 
18
988
  4. Add the connection string to your .env file:
19
- ${g.dim(`DATABASE_URL="your_connection_string"`)}
20
- `)}async function ge(t){let{projectDir:n}=t,r=d();r.start(`Setting up MongoDB Atlas`);let i=e.join(n,`apps/server`);try{await h.ensureDir(i),r.stop(`Starting MongoDB Atlas setup`);let e=await he(i);e?(await I(n,e),o.success(g.green(`MongoDB Atlas setup complete! Connection saved to .env file.`))):(o.warn(g.yellow(`Falling back to local MongoDB configuration`)),await I(n),L())}catch(e){r.stop(g.red(`MongoDB Atlas setup failed`)),p.error(g.red(`Error during MongoDB Atlas setup: ${e instanceof Error?e.message:String(e)}`));try{await I(n),L()}catch{}}}async function _e(n,r){let i=d();try{i.start(`Initializing Prisma PostgreSQL`);let s=e.join(n,`prisma`);await h.ensureDir(s),i.stop(`Initializing Prisma. Follow the prompts below:`);let c=N(r,`prisma init --db`);await x(c,{cwd:n,stdio:`inherit`,shell:!0}),o.info(g.yellow(`Please copy the Prisma Postgres URL from the output above.
21
- It looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=...`));let u=await l({message:`Paste your Prisma Postgres database URL:`,validate(e){if(!e)return`Please enter a database URL`;if(!e.startsWith(`prisma+postgres://`))return`URL should start with prisma+postgres://`}});return a(u)?(t(`Database setup cancelled`),null):{databaseUrl:u}}catch(e){return i.stop(g.red(`Failed to initialize Prisma PostgreSQL`)),e instanceof Error&&m.error(e.message),null}}async function R(t,n){try{let r=e.join(t,`apps/server`,`.env`),i=[{key:`DATABASE_URL`,value:n?.databaseUrl??`postgresql://postgres:postgres@localhost:5432/mydb?schema=public`,condition:!0}];await F(r,i)}catch{m.error(`Failed to update environment configuration`)}}function z(){o.info(`Manual Prisma PostgreSQL Setup Instructions:
989
+ ${pc.dim("DATABASE_URL=\"your_connection_string\"")}
990
+ `);
991
+ }
992
+ async function setupMongoDBAtlas(config) {
993
+ const { projectDir } = config;
994
+ const mainSpinner = spinner();
995
+ mainSpinner.start("Setting up MongoDB Atlas...");
996
+ const serverDir = path.join(projectDir, "apps/server");
997
+ try {
998
+ await fs.ensureDir(serverDir);
999
+ mainSpinner.stop("MongoDB Atlas setup ready");
1000
+ const config$1 = await initMongoDBAtlas(serverDir);
1001
+ if (config$1) {
1002
+ await writeEnvFile$3(projectDir, config$1);
1003
+ log.success(pc.green("MongoDB Atlas setup complete! Connection saved to .env file."));
1004
+ } else {
1005
+ log.warn(pc.yellow("Falling back to local MongoDB configuration"));
1006
+ await writeEnvFile$3(projectDir);
1007
+ displayManualSetupInstructions$3();
1008
+ }
1009
+ } catch (error) {
1010
+ mainSpinner.stop(pc.red("MongoDB Atlas setup failed"));
1011
+ consola.error(pc.red(`Error during MongoDB Atlas setup: ${error instanceof Error ? error.message : String(error)}`));
1012
+ try {
1013
+ await writeEnvFile$3(projectDir);
1014
+ displayManualSetupInstructions$3();
1015
+ } catch {}
1016
+ }
1017
+ }
1018
+
1019
+ //#endregion
1020
+ //#region src/helpers/database-providers/prisma-postgres-setup.ts
1021
+ async function initPrismaDatabase(serverDir, packageManager) {
1022
+ const s = spinner();
1023
+ try {
1024
+ s.start("Initializing Prisma PostgreSQL...");
1025
+ const prismaDir = path.join(serverDir, "prisma");
1026
+ await fs.ensureDir(prismaDir);
1027
+ s.stop("Prisma PostgreSQL initialized. Follow the prompts below:");
1028
+ const prismaInitCommand = getPackageExecutionCommand(packageManager, "prisma init --db");
1029
+ await execa(prismaInitCommand, {
1030
+ cwd: serverDir,
1031
+ stdio: "inherit",
1032
+ shell: true
1033
+ });
1034
+ log.info(pc.yellow("Please copy the Prisma Postgres URL from the output above.\nIt looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=..."));
1035
+ const databaseUrl = await password({
1036
+ message: "Paste your Prisma Postgres database URL:",
1037
+ validate(value) {
1038
+ if (!value) return "Please enter a database URL";
1039
+ if (!value.startsWith("prisma+postgres://")) return "URL should start with prisma+postgres://";
1040
+ }
1041
+ });
1042
+ if (isCancel(databaseUrl)) {
1043
+ cancel("Database setup cancelled");
1044
+ return null;
1045
+ }
1046
+ return { databaseUrl };
1047
+ } catch (error) {
1048
+ s.stop(pc.red("Prisma PostgreSQL initialization failed"));
1049
+ if (error instanceof Error) consola$1.error(error.message);
1050
+ return null;
1051
+ }
1052
+ }
1053
+ async function writeEnvFile$2(projectDir, config) {
1054
+ try {
1055
+ const envPath = path.join(projectDir, "apps/server", ".env");
1056
+ const variables = [{
1057
+ key: "DATABASE_URL",
1058
+ value: config?.databaseUrl ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
1059
+ condition: true
1060
+ }];
1061
+ await addEnvVariablesToFile(envPath, variables);
1062
+ } catch (_error) {
1063
+ consola$1.error("Failed to update environment configuration");
1064
+ }
1065
+ }
1066
+ function displayManualSetupInstructions$2() {
1067
+ log.info(`Manual Prisma PostgreSQL Setup Instructions:
22
1068
 
23
1069
  1. Visit https://console.prisma.io and create an account
24
1070
  2. Create a new PostgreSQL database from the dashboard
25
1071
  3. Get your database URL
26
1072
  4. Add the database URL to the .env file in apps/server/.env
27
1073
 
28
- DATABASE_URL="your_database_url"`)}async function ve(t){try{await M({dependencies:[`@prisma/extension-accelerate`],projectDir:t});let n=e.join(t,`prisma/index.ts`),r=`
1074
+ DATABASE_URL="your_database_url"`);
1075
+ }
1076
+ async function addPrismaAccelerateExtension(serverDir) {
1077
+ try {
1078
+ await addPackageDependency({
1079
+ dependencies: ["@prisma/extension-accelerate"],
1080
+ projectDir: serverDir
1081
+ });
1082
+ const prismaIndexPath = path.join(serverDir, "prisma/index.ts");
1083
+ const prismaIndexContent = `
29
1084
  import { PrismaClient } from "./generated/client";
30
1085
  import { withAccelerate } from "@prisma/extension-accelerate";
31
1086
 
32
1087
  const prisma = new PrismaClient().$extends(withAccelerate());
33
1088
 
34
1089
  export default prisma;
35
- `;await h.writeFile(n,r.trim());let i=e.join(t,`src/db/index.ts`);if(await h.pathExists(i)){let e=await h.readFile(i,`utf8`);e.includes(`@prisma/extension-accelerate`)||(e=`import { withAccelerate } from "@prisma/extension-accelerate";\n${e}`,e=e.replace(`export const db = new PrismaClient();`,`export const db = new PrismaClient().$extends(withAccelerate());`),await h.writeFile(i,e))}return!0}catch{return o.warn(g.yellow(`Could not add Prisma Accelerate extension automatically`)),!1}}async function ye(t){let{packageManager:n,projectDir:r}=t,i=e.join(r,`apps/server`),a=d();a.start(`Setting up Prisma PostgreSQL`);try{await h.ensureDir(i),a.stop(`Starting Prisma setup`);let e=await _e(i,n);if(e)await R(r,e),await ve(i),o.success(g.green(`Prisma PostgreSQL database configured successfully!`)),o.info(g.cyan('NOTE: Make sure to uncomment `import "dotenv/config";` in `apps/server/src/prisma.config.ts` to load environment variables.'));else{let e=d();e.start(`Setting up fallback configuration`),await R(r),e.stop(`Manual setup required`),z()}}catch(e){a.stop(g.red(`Prisma PostgreSQL setup failed`)),m.error(g.red(`Error during Prisma PostgreSQL setup: ${e instanceof Error?e.message:String(e)}`));try{await R(r),z()}catch{}o.info(`Setup completed with manual configuration required.`)}}async function be(t,n){try{let r=e.join(t,`apps/server`,`.env`),i=n||`postgresql://postgres:postgres@127.0.0.1:54322/postgres`,a=[{key:`DATABASE_URL`,value:i,condition:!0},{key:`DIRECT_URL`,value:i,condition:!0}];return await F(r,a),!0}catch(e){return m.error(g.red(`Failed to update .env file for Supabase.`)),e instanceof Error&&m.error(e.message),!1}}function xe(e){let t=e.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/),n=t?.[1];return n||null}async function Se(e,t){o.info(`Initializing Supabase project...`);try{let n=N(t,`supabase init`);return await x(n,{cwd:e,stdio:`inherit`,shell:!0}),o.success(`Supabase project initialized successfully.`),!0}catch(e){return m.error(g.red(`Failed to initialize Supabase project.`)),e instanceof Error?m.error(e.message):m.error(String(e)),e instanceof Error&&e.message.includes(`ENOENT`)&&(o.error(g.red(`Supabase CLI not found. Please install it globally or ensure it's in your PATH.`)),o.info(`You can install it using: npm install -g supabase`)),!1}}async function Ce(e,t){o.info(`Starting Supabase services (this may take a moment)...`);let n=N(t,`supabase start`);try{let t=x(n,{cwd:e,shell:!0}),r=``;return t.stdout&&t.stdout.on(`data`,e=>{let t=e.toString();process.stdout.write(t),r+=t}),t.stderr&&t.stderr.pipe(process.stderr),await t,await new Promise(e=>setTimeout(e,100)),r}catch(e){m.error(g.red(`Failed to start Supabase services.`));let t=e;return t?.message?(m.error(`Error details: ${t.message}`),t.message.includes(`Docker is not running`)&&o.error(g.red(`Docker is not running. Please start Docker and try again.`))):m.error(String(e)),null}}function B(e){o.info(`"Manual Supabase Setup Instructions:"
1090
+ `;
1091
+ await fs.writeFile(prismaIndexPath, prismaIndexContent.trim());
1092
+ const dbFilePath = path.join(serverDir, "src/db/index.ts");
1093
+ if (await fs.pathExists(dbFilePath)) {
1094
+ let dbFileContent = await fs.readFile(dbFilePath, "utf8");
1095
+ if (!dbFileContent.includes("@prisma/extension-accelerate")) {
1096
+ dbFileContent = `import { withAccelerate } from "@prisma/extension-accelerate";\n${dbFileContent}`;
1097
+ dbFileContent = dbFileContent.replace("export const db = new PrismaClient();", "export const db = new PrismaClient().$extends(withAccelerate());");
1098
+ await fs.writeFile(dbFilePath, dbFileContent);
1099
+ }
1100
+ }
1101
+ return true;
1102
+ } catch (_error) {
1103
+ log.warn(pc.yellow("Could not add Prisma Accelerate extension automatically"));
1104
+ return false;
1105
+ }
1106
+ }
1107
+ async function setupPrismaPostgres(config) {
1108
+ const { packageManager, projectDir } = config;
1109
+ const serverDir = path.join(projectDir, "apps/server");
1110
+ const s = spinner();
1111
+ s.start("Setting up Prisma PostgreSQL...");
1112
+ try {
1113
+ await fs.ensureDir(serverDir);
1114
+ s.stop("Prisma PostgreSQL setup ready");
1115
+ const config$1 = await initPrismaDatabase(serverDir, packageManager);
1116
+ if (config$1) {
1117
+ await writeEnvFile$2(projectDir, config$1);
1118
+ await addPrismaAccelerateExtension(serverDir);
1119
+ log.success(pc.green("Prisma PostgreSQL database configured successfully!"));
1120
+ log.info(pc.cyan("NOTE: Make sure to uncomment `import \"dotenv/config\";` in `apps/server/src/prisma.config.ts` to load environment variables."));
1121
+ } else {
1122
+ const fallbackSpinner = spinner();
1123
+ fallbackSpinner.start("Setting up fallback configuration...");
1124
+ await writeEnvFile$2(projectDir);
1125
+ fallbackSpinner.stop("Fallback configuration ready");
1126
+ displayManualSetupInstructions$2();
1127
+ }
1128
+ } catch (error) {
1129
+ s.stop(pc.red("Prisma PostgreSQL setup failed"));
1130
+ consola$1.error(pc.red(`Error during Prisma PostgreSQL setup: ${error instanceof Error ? error.message : String(error)}`));
1131
+ try {
1132
+ await writeEnvFile$2(projectDir);
1133
+ displayManualSetupInstructions$2();
1134
+ } catch {}
1135
+ log.info("Setup completed with manual configuration required.");
1136
+ }
1137
+ }
1138
+
1139
+ //#endregion
1140
+ //#region src/helpers/database-providers/supabase-setup.ts
1141
+ async function writeSupabaseEnvFile(projectDir, databaseUrl) {
1142
+ try {
1143
+ const envPath = path.join(projectDir, "apps/server", ".env");
1144
+ const dbUrlToUse = databaseUrl || "postgresql://postgres:postgres@127.0.0.1:54322/postgres";
1145
+ const variables = [{
1146
+ key: "DATABASE_URL",
1147
+ value: dbUrlToUse,
1148
+ condition: true
1149
+ }, {
1150
+ key: "DIRECT_URL",
1151
+ value: dbUrlToUse,
1152
+ condition: true
1153
+ }];
1154
+ await addEnvVariablesToFile(envPath, variables);
1155
+ return true;
1156
+ } catch (error) {
1157
+ consola$1.error(pc.red("Failed to update .env file for Supabase."));
1158
+ if (error instanceof Error) consola$1.error(error.message);
1159
+ return false;
1160
+ }
1161
+ }
1162
+ function extractDbUrl(output) {
1163
+ const dbUrlMatch = output.match(/DB URL:\s*(postgresql:\/\/[^\s]+)/);
1164
+ const url = dbUrlMatch?.[1];
1165
+ if (url) return url;
1166
+ return null;
1167
+ }
1168
+ async function initializeSupabase(serverDir, packageManager) {
1169
+ log.info("Initializing Supabase project...");
1170
+ try {
1171
+ const supabaseInitCommand = getPackageExecutionCommand(packageManager, "supabase init");
1172
+ await execa(supabaseInitCommand, {
1173
+ cwd: serverDir,
1174
+ stdio: "inherit",
1175
+ shell: true
1176
+ });
1177
+ log.success("Supabase project initialized");
1178
+ return true;
1179
+ } catch (error) {
1180
+ consola$1.error(pc.red("Failed to initialize Supabase project."));
1181
+ if (error instanceof Error) consola$1.error(error.message);
1182
+ else consola$1.error(String(error));
1183
+ if (error instanceof Error && error.message.includes("ENOENT")) {
1184
+ log.error(pc.red("Supabase CLI not found. Please install it globally or ensure it's in your PATH."));
1185
+ log.info("You can install it using: npm install -g supabase");
1186
+ }
1187
+ return false;
1188
+ }
1189
+ }
1190
+ async function startSupabase(serverDir, packageManager) {
1191
+ log.info("Starting Supabase services (this may take a moment)...");
1192
+ const supabaseStartCommand = getPackageExecutionCommand(packageManager, "supabase start");
1193
+ try {
1194
+ const subprocess = execa(supabaseStartCommand, {
1195
+ cwd: serverDir,
1196
+ shell: true
1197
+ });
1198
+ let stdoutData = "";
1199
+ if (subprocess.stdout) subprocess.stdout.on("data", (data) => {
1200
+ const text$1 = data.toString();
1201
+ process.stdout.write(text$1);
1202
+ stdoutData += text$1;
1203
+ });
1204
+ if (subprocess.stderr) subprocess.stderr.pipe(process.stderr);
1205
+ await subprocess;
1206
+ await new Promise((resolve) => setTimeout(resolve, 100));
1207
+ return stdoutData;
1208
+ } catch (error) {
1209
+ consola$1.error(pc.red("Failed to start Supabase services."));
1210
+ const execaError = error;
1211
+ if (execaError?.message) {
1212
+ consola$1.error(`Error details: ${execaError.message}`);
1213
+ if (execaError.message.includes("Docker is not running")) log.error(pc.red("Docker is not running. Please start Docker and try again."));
1214
+ } else consola$1.error(String(error));
1215
+ return null;
1216
+ }
1217
+ }
1218
+ function displayManualSupabaseInstructions(output) {
1219
+ log.info(`"Manual Supabase Setup Instructions:"
36
1220
  1. Ensure Docker is installed and running.
37
1221
  2. Install the Supabase CLI (e.g., \`npm install -g supabase\`).
38
1222
  3. Run \`supabase init\` in your project's \`apps/server\` directory.
39
1223
  4. Run \`supabase start\` in your project's \`apps/server\` directory.
40
- 5. Copy the 'DB URL' from the output.${e?`
41
- ${g.bold("Relevant output from `supabase start`:")}
42
- ${g.dim(e)}`:``}
1224
+ 5. Copy the 'DB URL' from the output.${output ? `
1225
+ ${pc.bold("Relevant output from `supabase start`:")}
1226
+ ${pc.dim(output)}` : ""}
43
1227
  6. Add the DB URL to the .env file in \`apps/server/.env\` as \`DATABASE_URL\`:
44
- ${g.gray(`DATABASE_URL="your_supabase_db_url"`)}`)}async function we(t){let{projectDir:n,packageManager:r}=t,i=e.join(n,`apps`,`server`);try{await h.ensureDir(i);let e=await Se(i,r);if(!e){B();return}let t=await Ce(i,r);if(!t){B();return}let a=xe(t);if(a){let e=await be(n,a);e?o.success(g.green(`Supabase local development setup complete!`)):(o.error(g.red(`Supabase setup completed, but failed to update .env automatically.`)),B(t))}else o.error(g.yellow(`Supabase started, but could not extract DB URL automatically.`)),B(t)}catch(e){e instanceof Error?m.error(g.red(`Error during Supabase setup: ${e.message}`)):m.error(g.red(`An unknown error occurred during Supabase setup: ${String(e)}`)),B()}}async function Te(){return P(`turso`)}async function Ee(){try{let e=await b`turso auth whoami`;return!e.stdout.includes(`You are not logged in`)}catch{return!1}}async function De(){let e=d();try{return e.start(`Logging in to Turso...`),await b`turso auth login`,e.stop(`Logged in to Turso successfully!`),!0}catch{e.stop(g.red(`Failed to log in to Turso`))}}async function Oe(e){let t=d();try{if(t.start(`Installing Turso CLI...`),e)await b`brew install tursodatabase/tap/turso`;else{let{stdout:e}=await b`curl -sSfL https://get.tur.so/install.sh`;await b`bash -c '${e}'`}return t.stop(`Turso CLI installed successfully!`),!0}catch(e){if(e instanceof Error&&e.message.includes(`User force closed`))throw t.stop(`Turso CLI installation cancelled`),o.warn(g.yellow(`Turso CLI installation cancelled by user`)),Error(`Installation cancelled`);t.stop(g.red(`Failed to install Turso CLI`))}}async function ke(){let e=d();try{e.start(`Fetching Turso groups...`);let{stdout:t}=await b`turso group list`,n=t.trim().split(`
45
- `);if(n.length<=1)return e.stop(`No Turso groups found`),[];let r=n.slice(1).map(e=>{let[t,n,r,i]=e.trim().split(/\s{2,}/);return{name:t,locations:n,version:r,status:i}});return e.stop(`Found ${r.length} Turso groups`),r}catch(t){return e.stop(g.red(`Error fetching Turso groups`)),console.error(`Error fetching Turso groups:`,t),[]}}async function Ae(){let e=await ke();if(e.length===0)return null;if(e.length===1)return o.info(`Using the only available group: ${g.blue(e[0].name)}`),e[0].name;let n=e.map(e=>({value:e.name,label:`${e.name} (${e.locations})`})),r=await u({message:`Select a Turso database group:`,options:n});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function je(e,t){let n=d();try{n.start(`Creating Turso database "${e}"${t?` in group "${t}"`:``}...`),t?await b`turso db create ${e} --group ${t}`:await b`turso db create ${e}`,n.stop(`Created database "${e}"`)}catch(t){if(n.stop(g.red(`Failed to create database "${e}"`)),t instanceof Error&&t.message.includes(`already exists`))throw Error(`DATABASE_EXISTS`)}n.start(`Retrieving database connection details...`);try{let{stdout:t}=await b`turso db show ${e} --url`,{stdout:r}=await b`turso db tokens create ${e}`;return n.stop(`Retrieved database connection details`),{dbUrl:t.trim(),authToken:r.trim()}}catch{n.stop(g.red(`Failed to retrieve database connection details`))}}async function V(t,n){let r=e.join(t,`apps/server`,`.env`),i=[{key:`DATABASE_URL`,value:n?.dbUrl??``,condition:!0},{key:`DATABASE_AUTH_TOKEN`,value:n?.authToken??``,condition:!0}];await F(r,i)}function H(){o.info(`Manual Turso Setup Instructions:
1228
+ ${pc.gray("DATABASE_URL=\"your_supabase_db_url\"")}`);
1229
+ }
1230
+ async function setupSupabase(config) {
1231
+ const { projectDir, packageManager } = config;
1232
+ const serverDir = path.join(projectDir, "apps", "server");
1233
+ try {
1234
+ await fs.ensureDir(serverDir);
1235
+ const initialized = await initializeSupabase(serverDir, packageManager);
1236
+ if (!initialized) {
1237
+ displayManualSupabaseInstructions();
1238
+ return;
1239
+ }
1240
+ const supabaseOutput = await startSupabase(serverDir, packageManager);
1241
+ if (!supabaseOutput) {
1242
+ displayManualSupabaseInstructions();
1243
+ return;
1244
+ }
1245
+ const dbUrl = extractDbUrl(supabaseOutput);
1246
+ if (dbUrl) {
1247
+ const envUpdated = await writeSupabaseEnvFile(projectDir, dbUrl);
1248
+ if (envUpdated) log.success(pc.green("Supabase local development setup ready!"));
1249
+ else {
1250
+ log.error(pc.red("Supabase setup completed, but failed to update .env automatically."));
1251
+ displayManualSupabaseInstructions(supabaseOutput);
1252
+ }
1253
+ } else {
1254
+ log.error(pc.yellow("Supabase started, but could not extract DB URL automatically."));
1255
+ displayManualSupabaseInstructions(supabaseOutput);
1256
+ }
1257
+ } catch (error) {
1258
+ if (error instanceof Error) consola$1.error(pc.red(`Error during Supabase setup: ${error.message}`));
1259
+ else consola$1.error(pc.red(`An unknown error occurred during Supabase setup: ${String(error)}`));
1260
+ displayManualSupabaseInstructions();
1261
+ }
1262
+ }
1263
+
1264
+ //#endregion
1265
+ //#region src/helpers/database-providers/turso-setup.ts
1266
+ async function isTursoInstalled() {
1267
+ return commandExists("turso");
1268
+ }
1269
+ async function isTursoLoggedIn() {
1270
+ try {
1271
+ const output = await $`turso auth whoami`;
1272
+ return !output.stdout.includes("You are not logged in");
1273
+ } catch {
1274
+ return false;
1275
+ }
1276
+ }
1277
+ async function loginToTurso() {
1278
+ const s = spinner();
1279
+ try {
1280
+ s.start("Logging in to Turso...");
1281
+ await $`turso auth login`;
1282
+ s.stop("Logged into Turso");
1283
+ return true;
1284
+ } catch (_error) {
1285
+ s.stop(pc.red("Failed to log in to Turso"));
1286
+ }
1287
+ }
1288
+ async function installTursoCLI(isMac) {
1289
+ const s = spinner();
1290
+ try {
1291
+ s.start("Installing Turso CLI...");
1292
+ if (isMac) await $`brew install tursodatabase/tap/turso`;
1293
+ else {
1294
+ const { stdout: installScript } = await $`curl -sSfL https://get.tur.so/install.sh`;
1295
+ await $`bash -c '${installScript}'`;
1296
+ }
1297
+ s.stop("Turso CLI installed");
1298
+ return true;
1299
+ } catch (error) {
1300
+ if (error instanceof Error && error.message.includes("User force closed")) {
1301
+ s.stop("Turso CLI installation cancelled");
1302
+ log.warn(pc.yellow("Turso CLI installation cancelled by user"));
1303
+ throw new Error("Installation cancelled");
1304
+ }
1305
+ s.stop(pc.red("Failed to install Turso CLI"));
1306
+ }
1307
+ }
1308
+ async function getTursoGroups() {
1309
+ const s = spinner();
1310
+ try {
1311
+ s.start("Fetching Turso groups...");
1312
+ const { stdout } = await $`turso group list`;
1313
+ const lines = stdout.trim().split("\n");
1314
+ if (lines.length <= 1) {
1315
+ s.stop("No Turso groups found");
1316
+ return [];
1317
+ }
1318
+ const groups = lines.slice(1).map((line) => {
1319
+ const [name, locations, version, status] = line.trim().split(/\s{2,}/);
1320
+ return {
1321
+ name,
1322
+ locations,
1323
+ version,
1324
+ status
1325
+ };
1326
+ });
1327
+ s.stop(`Found ${groups.length} Turso groups`);
1328
+ return groups;
1329
+ } catch (error) {
1330
+ s.stop(pc.red("Error fetching Turso groups"));
1331
+ console.error("Error fetching Turso groups:", error);
1332
+ return [];
1333
+ }
1334
+ }
1335
+ async function selectTursoGroup() {
1336
+ const groups = await getTursoGroups();
1337
+ if (groups.length === 0) return null;
1338
+ if (groups.length === 1) {
1339
+ log.info(`Using the only available group: ${pc.blue(groups[0].name)}`);
1340
+ return groups[0].name;
1341
+ }
1342
+ const groupOptions = groups.map((group$1) => ({
1343
+ value: group$1.name,
1344
+ label: `${group$1.name} (${group$1.locations})`
1345
+ }));
1346
+ const selectedGroup = await select({
1347
+ message: "Select a Turso database group:",
1348
+ options: groupOptions
1349
+ });
1350
+ if (isCancel(selectedGroup)) {
1351
+ cancel(pc.red("Operation cancelled"));
1352
+ process.exit(0);
1353
+ }
1354
+ return selectedGroup;
1355
+ }
1356
+ async function createTursoDatabase(dbName, groupName) {
1357
+ const s = spinner();
1358
+ try {
1359
+ s.start(`Creating Turso database "${dbName}"${groupName ? ` in group "${groupName}"` : ""}...`);
1360
+ if (groupName) await $`turso db create ${dbName} --group ${groupName}`;
1361
+ else await $`turso db create ${dbName}`;
1362
+ s.stop(`Turso database "${dbName}" created`);
1363
+ } catch (error) {
1364
+ s.stop(pc.red(`Failed to create database "${dbName}"`));
1365
+ if (error instanceof Error && error.message.includes("already exists")) throw new Error("DATABASE_EXISTS");
1366
+ }
1367
+ s.start("Retrieving database connection details...");
1368
+ try {
1369
+ const { stdout: dbUrl } = await $`turso db show ${dbName} --url`;
1370
+ const { stdout: authToken } = await $`turso db tokens create ${dbName}`;
1371
+ s.stop("Database connection details retrieved");
1372
+ return {
1373
+ dbUrl: dbUrl.trim(),
1374
+ authToken: authToken.trim()
1375
+ };
1376
+ } catch (_error) {
1377
+ s.stop(pc.red("Failed to retrieve database connection details"));
1378
+ }
1379
+ }
1380
+ async function writeEnvFile$1(projectDir, config) {
1381
+ const envPath = path.join(projectDir, "apps/server", ".env");
1382
+ const variables = [{
1383
+ key: "DATABASE_URL",
1384
+ value: config?.dbUrl ?? "",
1385
+ condition: true
1386
+ }, {
1387
+ key: "DATABASE_AUTH_TOKEN",
1388
+ value: config?.authToken ?? "",
1389
+ condition: true
1390
+ }];
1391
+ await addEnvVariablesToFile(envPath, variables);
1392
+ }
1393
+ function displayManualSetupInstructions$1() {
1394
+ log.info(`Manual Turso Setup Instructions:
46
1395
 
47
1396
  1. Visit https://turso.tech and create an account
48
1397
  2. Create a new database from the dashboard
@@ -50,122 +1399,2039 @@ ${g.dim(e)}`:``}
50
1399
  4. Add these credentials to the .env file in apps/server/.env
51
1400
 
52
1401
  DATABASE_URL=your_database_url
53
- DATABASE_AUTH_TOKEN=your_auth_token`)}async function Me(r){let{orm:i,projectDir:s}=r,c=i===`drizzle`,l=d();l.start(`Setting up Turso database`);try{let r=S.platform(),i=r===`darwin`,c=r===`linux`,u=r===`win32`;if(u){l.stop(g.yellow(`Turso setup not supported on Windows`)),o.warn(g.yellow(`Automatic Turso setup is not supported on Windows.`)),await V(s),H();return}l.stop(`Checking Turso CLI`);let p=await Te();if(!p){let e=await n({message:`Would you like to install Turso CLI?`,initialValue:!0});if(a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),!e){await V(s),H();return}await Oe(i)}let m=await Ee();m||await De();let h=await Ae(),_=!1,v=``,y=e.basename(s);for(;!_;){let e=await f({message:`Enter a name for your database:`,defaultValue:y,initialValue:y,placeholder:y});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),v=e;try{let e=await je(v,h),t=d();t.start(`Writing configuration to .env file`),await V(s,e),t.stop(`Turso database configured successfully!`),_=!0}catch(e){e instanceof Error&&e.message===`DATABASE_EXISTS`&&(o.warn(g.yellow(`Database "${g.red(v)}" already exists`)),y=`${v}-${Math.floor(Math.random()*1e3)}`)}}}catch(e){l.stop(g.red(`Failed to set up Turso database`)),p.error(g.red(`Error during Turso setup: ${e instanceof Error?e.message:String(e)}`)),await V(s),H(),o.success(`Setup completed with manual configuration required.`)}}const U=[{label:`AWS US East (N. Virginia)`,value:`aws-us-east-1`},{label:`AWS US East (Ohio)`,value:`aws-us-east-2`},{label:`AWS US West (Oregon)`,value:`aws-us-west-2`},{label:`AWS Europe (Frankfurt)`,value:`aws-eu-central-1`},{label:`AWS Asia Pacific (Singapore)`,value:`aws-ap-southeast-1`},{label:`AWS Asia Pacific (Sydney)`,value:`aws-ap-southeast-2`},{label:`Azure East US 2 region (Virginia)`,value:`azure-eastus2`}];async function W(e,t,n){let r=d();try{let i=N(e,t);n&&r.start(n);let a=await x(i,{shell:!0});return n&&r.stop(g.green(`Completed`)),a}catch(e){throw r&&r.stop(g.red(`Failed: ${n}`)),e}}async function Ne(e){try{let t=`neonctl projects list`,n=await W(e,t);return!n.stdout.includes(`not authenticated`)&&!n.stdout.includes(`error`)}catch{return!1}}async function Pe(e){try{return await W(e,`neonctl auth`,`Authenticating with Neon...`),o.success(`Authenticated with Neon successfully!`),!0}catch{m.error(g.red(`Failed to authenticate with Neon`))}}async function Fe(e,t,n){try{let r=`neonctl projects create --name ${e} --region-id ${t} --output json`,{stdout:i}=await W(n,r,`Creating Neon project "${e}"...`),a=JSON.parse(i);if(a.project&&a.connection_uris&&a.connection_uris.length>0){let e=a.project.id,t=a.connection_uris[0].connection_uri,n=a.connection_uris[0].connection_parameters;return{connectionString:t,projectId:e,dbName:n.database,roleName:n.role}}return m.error(g.red(`Failed to extract connection information from response`)),null}catch{m.error(g.red(`Failed to create Neon project`))}}async function G(t,n){let r=e.join(t,`apps/server`,`.env`),i=[{key:`DATABASE_URL`,value:n?.connectionString??`postgresql://postgres:postgres@localhost:5432/mydb?schema=public`,condition:!0}];return await F(r,i),!0}function Ie(){o.info(`Manual Neon PostgreSQL Setup Instructions:
1402
+ DATABASE_AUTH_TOKEN=your_auth_token`);
1403
+ }
1404
+ async function setupTurso(config) {
1405
+ const { orm, projectDir } = config;
1406
+ const _isDrizzle = orm === "drizzle";
1407
+ const setupSpinner = spinner();
1408
+ setupSpinner.start("Checking Turso CLI availability...");
1409
+ try {
1410
+ const platform = os.platform();
1411
+ const isMac = platform === "darwin";
1412
+ const _isLinux = platform === "linux";
1413
+ const isWindows = platform === "win32";
1414
+ if (isWindows) {
1415
+ setupSpinner.stop(pc.yellow("Turso setup not supported on Windows"));
1416
+ log.warn(pc.yellow("Automatic Turso setup is not supported on Windows."));
1417
+ await writeEnvFile$1(projectDir);
1418
+ displayManualSetupInstructions$1();
1419
+ return;
1420
+ }
1421
+ setupSpinner.stop("Turso CLI availability checked");
1422
+ const isCliInstalled = await isTursoInstalled();
1423
+ if (!isCliInstalled) {
1424
+ const shouldInstall = await confirm({
1425
+ message: "Would you like to install Turso CLI?",
1426
+ initialValue: true
1427
+ });
1428
+ if (isCancel(shouldInstall)) {
1429
+ cancel(pc.red("Operation cancelled"));
1430
+ process.exit(0);
1431
+ }
1432
+ if (!shouldInstall) {
1433
+ await writeEnvFile$1(projectDir);
1434
+ displayManualSetupInstructions$1();
1435
+ return;
1436
+ }
1437
+ await installTursoCLI(isMac);
1438
+ }
1439
+ const isLoggedIn = await isTursoLoggedIn();
1440
+ if (!isLoggedIn) await loginToTurso();
1441
+ const selectedGroup = await selectTursoGroup();
1442
+ let success = false;
1443
+ let dbName = "";
1444
+ let suggestedName = path.basename(projectDir);
1445
+ while (!success) {
1446
+ const dbNameResponse = await text({
1447
+ message: "Enter a name for your database:",
1448
+ defaultValue: suggestedName,
1449
+ initialValue: suggestedName,
1450
+ placeholder: suggestedName
1451
+ });
1452
+ if (isCancel(dbNameResponse)) {
1453
+ cancel(pc.red("Operation cancelled"));
1454
+ process.exit(0);
1455
+ }
1456
+ dbName = dbNameResponse;
1457
+ try {
1458
+ const config$1 = await createTursoDatabase(dbName, selectedGroup);
1459
+ await writeEnvFile$1(projectDir, config$1);
1460
+ success = true;
1461
+ } catch (error) {
1462
+ if (error instanceof Error && error.message === "DATABASE_EXISTS") {
1463
+ log.warn(pc.yellow(`Database "${pc.red(dbName)}" already exists`));
1464
+ suggestedName = `${dbName}-${Math.floor(Math.random() * 1e3)}`;
1465
+ } else throw error;
1466
+ }
1467
+ }
1468
+ log.success("Turso database setup completed successfully!");
1469
+ } catch (error) {
1470
+ setupSpinner.stop(pc.red("Turso CLI availability check failed"));
1471
+ consola.error(pc.red(`Error during Turso setup: ${error instanceof Error ? error.message : String(error)}`));
1472
+ await writeEnvFile$1(projectDir);
1473
+ displayManualSetupInstructions$1();
1474
+ log.success("Setup completed with manual configuration required.");
1475
+ }
1476
+ }
1477
+
1478
+ //#endregion
1479
+ //#region src/helpers/database-providers/neon-setup.ts
1480
+ const NEON_REGIONS = [
1481
+ {
1482
+ label: "AWS US East (N. Virginia)",
1483
+ value: "aws-us-east-1"
1484
+ },
1485
+ {
1486
+ label: "AWS US East (Ohio)",
1487
+ value: "aws-us-east-2"
1488
+ },
1489
+ {
1490
+ label: "AWS US West (Oregon)",
1491
+ value: "aws-us-west-2"
1492
+ },
1493
+ {
1494
+ label: "AWS Europe (Frankfurt)",
1495
+ value: "aws-eu-central-1"
1496
+ },
1497
+ {
1498
+ label: "AWS Asia Pacific (Singapore)",
1499
+ value: "aws-ap-southeast-1"
1500
+ },
1501
+ {
1502
+ label: "AWS Asia Pacific (Sydney)",
1503
+ value: "aws-ap-southeast-2"
1504
+ },
1505
+ {
1506
+ label: "Azure East US 2 region (Virginia)",
1507
+ value: "azure-eastus2"
1508
+ }
1509
+ ];
1510
+ async function executeNeonCommand(packageManager, commandArgsString, spinnerText) {
1511
+ const s = spinner();
1512
+ try {
1513
+ const fullCommand = getPackageExecutionCommand(packageManager, commandArgsString);
1514
+ if (spinnerText) s.start(spinnerText);
1515
+ const result = await execa(fullCommand, { shell: true });
1516
+ if (spinnerText) s.stop(pc.green(spinnerText.replace("...", "").replace("ing ", "ed ").trim()));
1517
+ return result;
1518
+ } catch (error) {
1519
+ if (s) s.stop(pc.red(`Failed: ${spinnerText || "Command execution"}`));
1520
+ throw error;
1521
+ }
1522
+ }
1523
+ async function isNeonAuthenticated(packageManager) {
1524
+ try {
1525
+ const commandArgsString = "neonctl projects list";
1526
+ const result = await executeNeonCommand(packageManager, commandArgsString);
1527
+ return !result.stdout.includes("not authenticated") && !result.stdout.includes("error");
1528
+ } catch {
1529
+ return false;
1530
+ }
1531
+ }
1532
+ async function authenticateWithNeon(packageManager) {
1533
+ try {
1534
+ await executeNeonCommand(packageManager, "neonctl auth", "Authenticating with Neon...");
1535
+ return true;
1536
+ } catch (_error) {
1537
+ consola$1.error(pc.red("Failed to authenticate with Neon"));
1538
+ }
1539
+ }
1540
+ async function createNeonProject(projectName, regionId, packageManager) {
1541
+ try {
1542
+ const commandArgsString = `neonctl projects create --name ${projectName} --region-id ${regionId} --output json`;
1543
+ const { stdout } = await executeNeonCommand(packageManager, commandArgsString, `Creating Neon project "${projectName}"...`);
1544
+ const response = JSON.parse(stdout);
1545
+ if (response.project && response.connection_uris && response.connection_uris.length > 0) {
1546
+ const projectId = response.project.id;
1547
+ const connectionUri = response.connection_uris[0].connection_uri;
1548
+ const params = response.connection_uris[0].connection_parameters;
1549
+ return {
1550
+ connectionString: connectionUri,
1551
+ projectId,
1552
+ dbName: params.database,
1553
+ roleName: params.role
1554
+ };
1555
+ }
1556
+ consola$1.error(pc.red("Failed to extract connection information from response"));
1557
+ return null;
1558
+ } catch (_error) {
1559
+ consola$1.error(pc.red("Failed to create Neon project"));
1560
+ }
1561
+ }
1562
+ async function writeEnvFile(projectDir, config) {
1563
+ const envPath = path.join(projectDir, "apps/server", ".env");
1564
+ const variables = [{
1565
+ key: "DATABASE_URL",
1566
+ value: config?.connectionString ?? "postgresql://postgres:postgres@localhost:5432/mydb?schema=public",
1567
+ condition: true
1568
+ }];
1569
+ await addEnvVariablesToFile(envPath, variables);
1570
+ return true;
1571
+ }
1572
+ function displayManualSetupInstructions() {
1573
+ log.info(`Manual Neon PostgreSQL Setup Instructions:
54
1574
 
55
1575
  1. Visit https://neon.tech and create an account
56
1576
  2. Create a new project from the dashboard
57
1577
  3. Get your connection string
58
1578
  4. Add the database URL to the .env file in apps/server/.env
59
1579
 
60
- DATABASE_URL="your_connection_string"`)}async function Le(n){let{packageManager:r,projectDir:i}=n,s=d();s.start(`Setting up Neon PostgreSQL`);try{let n=await Ne(r);s.stop(`Setting up Neon PostgreSQL`),n||(o.info(`Please authenticate with Neon to continue:`),await Pe(r));let c=e.basename(i),l=await f({message:`Enter a name for your Neon project:`,defaultValue:c,initialValue:c}),p=await u({message:`Select a region for your Neon project:`,options:U,initialValue:U[0].value});(a(l)||a(p))&&(t(g.red(`Operation cancelled`)),process.exit(0));let m=await Fe(l,p,r);if(!m)throw Error(`Failed to create project - couldn't get connection information`);let _=d();_.start(`Configuring database connection`),await h.ensureDir(e.join(i,`apps/server`)),await G(i,m),_.stop(`Neon database configured successfully!`)}catch(e){s.stop(g.red(`Neon PostgreSQL setup failed`)),e instanceof Error&&m.error(g.red(e.message)),await G(i),Ie()}}async function Re(t){let{database:n,orm:r,dbSetup:i,backend:a,projectDir:o}=t;if(a===`convex`||n===`none`){if(a!==`convex`){let t=e.join(o,`apps/server`),n=e.join(t,`src/db`);await h.pathExists(n)&&await h.remove(n)}return}let s=d(),c=e.join(o,`apps/server`);if(await h.pathExists(c))try{r===`prisma`?await M({dependencies:[`@prisma/client`],devDependencies:[`prisma`],projectDir:c}):r===`drizzle`?n===`sqlite`?await M({dependencies:[`drizzle-orm`,`@libsql/client`],devDependencies:[`drizzle-kit`],projectDir:c}):n===`postgres`?await M({dependencies:[`drizzle-orm`,`pg`],devDependencies:[`drizzle-kit`,`@types/pg`],projectDir:c}):n===`mysql`&&await M({dependencies:[`drizzle-orm`,`mysql2`],devDependencies:[`drizzle-kit`],projectDir:c}):r===`mongoose`&&await M({dependencies:[`mongoose`],devDependencies:[],projectDir:c}),n===`sqlite`&&i===`turso`?await Me(t):n===`postgres`?r===`prisma`&&i===`prisma-postgres`?await ye(t):i===`neon`?await Le(t):i===`supabase`&&await we(t):n===`mongodb`&&i===`mongodb-atlas`&&await ge(t)}catch(e){s.stop(g.red(`Failed to set up database`)),e instanceof Error&&p.error(g.red(e.message))}}async function ze(t){let{examples:n,frontend:r,backend:i,projectDir:a}=t;if(!(i===`convex`||!n||n.length===0||n[0]===`none`)&&n.includes(`ai`)){let t=e.join(a,`apps/web`),n=e.join(a,`apps/server`),o=await h.pathExists(t),s=await h.pathExists(n),c=r.includes(`nuxt`),l=r.includes(`svelte`),u=r.includes(`react-router`)||r.includes(`tanstack-router`)||r.includes(`next`)||r.includes(`tanstack-start`);if(o){let e=[`ai`];c?e.push(`@ai-sdk/vue`):l?e.push(`@ai-sdk/svelte`):u&&e.push(`@ai-sdk/react`),await M({dependencies:e,projectDir:t})}s&&i!==`none`&&await M({dependencies:[`ai`,`@ai-sdk/google`],projectDir:n})}}async function Be(t){let{runtime:n,backend:r,projectDir:i}=t;if(r===`convex`||r===`next`||n===`none`)return;let a=e.join(i,`apps/server`);await h.pathExists(a)&&(n===`bun`?await Ve(a,r):n===`node`&&await He(a,r))}async function Ve(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts={...i.scripts,dev:`bun run --hot src/index.ts`,start:`bun run dist/src/index.js`},await h.writeJson(r,i,{spaces:2}),await M({devDependencies:[`@types/bun`],projectDir:t})}async function He(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts={...i.scripts,dev:`tsx watch src/index.ts`,start:`node dist/src/index.js`},await h.writeJson(r,i,{spaces:2}),await M({devDependencies:[`tsx`,`@types/node`],projectDir:t}),n===`hono`?await M({dependencies:[`@hono/node-server`],projectDir:t}):n===`elysia`&&await M({dependencies:[`@elysiajs/node`],projectDir:t})}async function Ue(t,n){let r=e.join(t,`README.md`),i=We(n);try{await h.writeFile(r,i)}catch(e){p.error(`Failed to create README.md file:`,e)}}function We(e){let{projectName:t,packageManager:n,database:r,auth:i,addons:a=[],orm:o=`drizzle`,runtime:s=`bun`,frontend:c=[`tanstack-router`],backend:l=`hono`,api:u=`trpc`}=e,d=l===`convex`,f=c.includes(`react-router`),p=c.includes(`tanstack-router`),m=c.includes(`native-nativewind`)||c.includes(`native-unistyles`),h=c.includes(`next`),g=c.includes(`tanstack-start`),_=c.includes(`svelte`),v=c.includes(`solid`),y=c.includes(`nuxt`),b=n===`npm`?`npm run`:n,x=`3001`;return(f||_)&&(x=`5173`),`# ${t}
1580
+ DATABASE_URL="your_connection_string"`);
1581
+ }
1582
+ async function setupNeonPostgres(config) {
1583
+ const { packageManager, projectDir } = config;
1584
+ const setupSpinner = spinner();
1585
+ setupSpinner.start("Checking Neon authentication...");
1586
+ try {
1587
+ const isAuthenticated = await isNeonAuthenticated(packageManager);
1588
+ setupSpinner.stop("Neon authentication checked");
1589
+ if (!isAuthenticated) {
1590
+ log.info("Please authenticate with Neon to continue:");
1591
+ await authenticateWithNeon(packageManager);
1592
+ }
1593
+ const suggestedProjectName = path.basename(projectDir);
1594
+ const projectName = await text({
1595
+ message: "Enter a name for your Neon project:",
1596
+ defaultValue: suggestedProjectName,
1597
+ initialValue: suggestedProjectName
1598
+ });
1599
+ const regionId = await select({
1600
+ message: "Select a region for your Neon project:",
1601
+ options: NEON_REGIONS,
1602
+ initialValue: NEON_REGIONS[0].value
1603
+ });
1604
+ if (isCancel(projectName) || isCancel(regionId)) {
1605
+ cancel(pc.red("Operation cancelled"));
1606
+ process.exit(0);
1607
+ }
1608
+ const config$1 = await createNeonProject(projectName, regionId, packageManager);
1609
+ if (!config$1) throw new Error("Failed to create project - couldn't get connection information");
1610
+ const finalSpinner = spinner();
1611
+ finalSpinner.start("Configuring database connection");
1612
+ await fs.ensureDir(path.join(projectDir, "apps/server"));
1613
+ await writeEnvFile(projectDir, config$1);
1614
+ finalSpinner.stop("Neon database configured!");
1615
+ } catch (error) {
1616
+ setupSpinner.stop(pc.red("Neon authentication check failed"));
1617
+ if (error instanceof Error) consola$1.error(pc.red(error.message));
1618
+ await writeEnvFile(projectDir);
1619
+ displayManualSetupInstructions();
1620
+ }
1621
+ }
1622
+
1623
+ //#endregion
1624
+ //#region src/helpers/setup/db-setup.ts
1625
+ async function setupDatabase(config) {
1626
+ const { database, orm, dbSetup, backend, projectDir } = config;
1627
+ if (backend === "convex" || database === "none") {
1628
+ if (backend !== "convex") {
1629
+ const serverDir$1 = path.join(projectDir, "apps/server");
1630
+ const serverDbDir = path.join(serverDir$1, "src/db");
1631
+ if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
1632
+ }
1633
+ return;
1634
+ }
1635
+ const s = spinner();
1636
+ const serverDir = path.join(projectDir, "apps/server");
1637
+ if (!await fs.pathExists(serverDir)) return;
1638
+ try {
1639
+ if (orm === "prisma") await addPackageDependency({
1640
+ dependencies: ["@prisma/client"],
1641
+ devDependencies: ["prisma"],
1642
+ projectDir: serverDir
1643
+ });
1644
+ else if (orm === "drizzle") {
1645
+ if (database === "sqlite") await addPackageDependency({
1646
+ dependencies: ["drizzle-orm", "@libsql/client"],
1647
+ devDependencies: ["drizzle-kit"],
1648
+ projectDir: serverDir
1649
+ });
1650
+ else if (database === "postgres") await addPackageDependency({
1651
+ dependencies: ["drizzle-orm", "pg"],
1652
+ devDependencies: ["drizzle-kit", "@types/pg"],
1653
+ projectDir: serverDir
1654
+ });
1655
+ else if (database === "mysql") await addPackageDependency({
1656
+ dependencies: ["drizzle-orm", "mysql2"],
1657
+ devDependencies: ["drizzle-kit"],
1658
+ projectDir: serverDir
1659
+ });
1660
+ } else if (orm === "mongoose") await addPackageDependency({
1661
+ dependencies: ["mongoose"],
1662
+ devDependencies: [],
1663
+ projectDir: serverDir
1664
+ });
1665
+ if (database === "sqlite" && dbSetup === "turso") await setupTurso(config);
1666
+ else if (database === "postgres") {
1667
+ if (orm === "prisma" && dbSetup === "prisma-postgres") await setupPrismaPostgres(config);
1668
+ else if (dbSetup === "neon") await setupNeonPostgres(config);
1669
+ else if (dbSetup === "supabase") await setupSupabase(config);
1670
+ } else if (database === "mongodb" && dbSetup === "mongodb-atlas") await setupMongoDBAtlas(config);
1671
+ } catch (error) {
1672
+ s.stop(pc.red("Failed to set up database"));
1673
+ if (error instanceof Error) consola.error(pc.red(error.message));
1674
+ }
1675
+ }
1676
+
1677
+ //#endregion
1678
+ //#region src/helpers/setup/examples-setup.ts
1679
+ async function setupExamples(config) {
1680
+ const { examples, frontend, backend, projectDir } = config;
1681
+ if (backend === "convex" || !examples || examples.length === 0 || examples[0] === "none") return;
1682
+ if (examples.includes("ai")) {
1683
+ const clientDir = path.join(projectDir, "apps/web");
1684
+ const serverDir = path.join(projectDir, "apps/server");
1685
+ const clientDirExists = await fs.pathExists(clientDir);
1686
+ const serverDirExists = await fs.pathExists(serverDir);
1687
+ const hasNuxt = frontend.includes("nuxt");
1688
+ const hasSvelte = frontend.includes("svelte");
1689
+ const hasReact = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
1690
+ if (clientDirExists) {
1691
+ const dependencies = ["ai"];
1692
+ if (hasNuxt) dependencies.push("@ai-sdk/vue");
1693
+ else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
1694
+ else if (hasReact) dependencies.push("@ai-sdk/react");
1695
+ await addPackageDependency({
1696
+ dependencies,
1697
+ projectDir: clientDir
1698
+ });
1699
+ }
1700
+ if (serverDirExists && backend !== "none") await addPackageDependency({
1701
+ dependencies: ["ai", "@ai-sdk/google"],
1702
+ projectDir: serverDir
1703
+ });
1704
+ }
1705
+ }
61
1706
 
62
- This project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack that combines ${p?`React, TanStack Router`:f?`React, React Router`:h?`Next.js`:g?`React, TanStack Start`:_?`SvelteKit`:y?`Nuxt`:v?`SolidJS`:``}, ${l[0].toUpperCase()+l.slice(1)}${d?``:`, ${u.toUpperCase()}`}, and more.
1707
+ //#endregion
1708
+ //#region src/helpers/setup/runtime-setup.ts
1709
+ async function setupRuntime(config) {
1710
+ const { runtime, backend, projectDir } = config;
1711
+ if (backend === "convex" || backend === "next" || runtime === "none") return;
1712
+ const serverDir = path.join(projectDir, "apps/server");
1713
+ if (!await fs.pathExists(serverDir)) return;
1714
+ if (runtime === "bun") await setupBunRuntime(serverDir, backend);
1715
+ else if (runtime === "node") await setupNodeRuntime(serverDir, backend);
1716
+ }
1717
+ async function setupBunRuntime(serverDir, _backend) {
1718
+ const packageJsonPath = path.join(serverDir, "package.json");
1719
+ if (!await fs.pathExists(packageJsonPath)) return;
1720
+ const packageJson = await fs.readJson(packageJsonPath);
1721
+ packageJson.scripts = {
1722
+ ...packageJson.scripts,
1723
+ dev: "bun run --hot src/index.ts",
1724
+ start: "bun run dist/src/index.js"
1725
+ };
1726
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1727
+ await addPackageDependency({
1728
+ devDependencies: ["@types/bun"],
1729
+ projectDir: serverDir
1730
+ });
1731
+ }
1732
+ async function setupNodeRuntime(serverDir, backend) {
1733
+ const packageJsonPath = path.join(serverDir, "package.json");
1734
+ if (!await fs.pathExists(packageJsonPath)) return;
1735
+ const packageJson = await fs.readJson(packageJsonPath);
1736
+ packageJson.scripts = {
1737
+ ...packageJson.scripts,
1738
+ dev: "tsx watch src/index.ts",
1739
+ start: "node dist/src/index.js"
1740
+ };
1741
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
1742
+ await addPackageDependency({
1743
+ devDependencies: ["tsx", "@types/node"],
1744
+ projectDir: serverDir
1745
+ });
1746
+ if (backend === "hono") await addPackageDependency({
1747
+ dependencies: ["@hono/node-server"],
1748
+ projectDir: serverDir
1749
+ });
1750
+ else if (backend === "elysia") await addPackageDependency({
1751
+ dependencies: ["@elysiajs/node"],
1752
+ projectDir: serverDir
1753
+ });
1754
+ }
1755
+
1756
+ //#endregion
1757
+ //#region src/helpers/project-generation/create-readme.ts
1758
+ async function createReadme(projectDir, options) {
1759
+ const readmePath = path.join(projectDir, "README.md");
1760
+ const content = generateReadmeContent(options);
1761
+ try {
1762
+ await fs.writeFile(readmePath, content);
1763
+ } catch (error) {
1764
+ consola.error("Failed to create README.md file:", error);
1765
+ }
1766
+ }
1767
+ function generateReadmeContent(options) {
1768
+ const { projectName, packageManager, database, auth, addons = [], orm = "drizzle", runtime = "bun", frontend = ["tanstack-router"], backend = "hono", api = "trpc" } = options;
1769
+ const isConvex = backend === "convex";
1770
+ const hasReactRouter = frontend.includes("react-router");
1771
+ const hasTanstackRouter = frontend.includes("tanstack-router");
1772
+ const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
1773
+ const hasNext = frontend.includes("next");
1774
+ const hasTanstackStart = frontend.includes("tanstack-start");
1775
+ const hasSvelte = frontend.includes("svelte");
1776
+ const hasSolid = frontend.includes("solid");
1777
+ const hasNuxt = frontend.includes("nuxt");
1778
+ const packageManagerRunCmd = packageManager === "npm" ? "npm run" : packageManager;
1779
+ let webPort = "3001";
1780
+ if (hasReactRouter || hasSvelte) webPort = "5173";
1781
+ return `# ${projectName}
1782
+
1783
+ This project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack that combines ${hasTanstackRouter ? "React, TanStack Router" : hasReactRouter ? "React, React Router" : hasNext ? "Next.js" : hasTanstackStart ? "React, TanStack Start" : hasSvelte ? "SvelteKit" : hasNuxt ? "Nuxt" : hasSolid ? "SolidJS" : ""}, ${backend[0].toUpperCase() + backend.slice(1)}${isConvex ? "" : `, ${api.toUpperCase()}`}, and more.
63
1784
 
64
1785
  ## Features
65
1786
 
66
- ${Ge(r,i,a,o,s,c,l,u)}
1787
+ ${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api)}
67
1788
 
68
1789
  ## Getting Started
69
1790
 
70
1791
  First, install the dependencies:
71
1792
 
72
1793
  \`\`\`bash
73
- ${n} install
1794
+ ${packageManager} install
74
1795
  \`\`\`
75
- ${d?`
1796
+ ${isConvex ? `
76
1797
  ## Convex Setup
77
1798
 
78
1799
  This project uses Convex as a backend. You'll need to set up Convex before running the app:
79
1800
 
80
1801
  \`\`\`bash
81
- ${b} dev:setup
1802
+ ${packageManagerRunCmd} dev:setup
82
1803
  \`\`\`
83
1804
 
84
- Follow the prompts to create a new Convex project and connect it to your application.`:Ke(r,i,b,o)}
1805
+ Follow the prompts to create a new Convex project and connect it to your application.` : generateDatabaseSetup(database, auth, packageManagerRunCmd, orm)}
85
1806
 
86
1807
  Then, run the development server:
87
1808
 
88
1809
  \`\`\`bash
89
- ${b} dev
1810
+ ${packageManagerRunCmd} dev
90
1811
  \`\`\`
91
1812
 
92
- ${p||f||h||g||_||y||v?`Open [http://localhost:${x}](http://localhost:${x}) in your browser to see the web application.`:``}
93
- ${m?`Use the Expo Go app to run the mobile application.
94
- `:``}
95
- ${d?`Your app will connect to the Convex cloud backend automatically.`:`The API is running at [http://localhost:3000](http://localhost:3000).`}
96
-
97
- ${a.includes(`pwa`)&&f?`
98
- ## PWA Support with React Router v7
1813
+ ${hasTanstackRouter || hasReactRouter || hasNext || hasTanstackStart || hasSvelte || hasNuxt || hasSolid ? `Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see the web application.` : ""}
1814
+ ${hasNative ? "Use the Expo Go app to run the mobile application.\n" : ""}
1815
+ ${isConvex ? "Your app will connect to the Convex cloud backend automatically." : "The API is running at [http://localhost:3000](http://localhost:3000)."}
99
1816
 
100
- There is a known compatibility issue between VitePWA and React Router v7.
101
- See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809
102
- `:``}
1817
+ ${addons.includes("pwa") && hasReactRouter ? "\n## PWA Support with React Router v7\n\nThere is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\n" : ""}
103
1818
 
104
1819
  ## Project Structure
105
1820
 
106
1821
  \`\`\`
107
- ${t}/
1822
+ ${projectName}/
108
1823
  ├── apps/
109
- ${p||f||h||g||_||y||v?`│ ├── web/ # Frontend application (${p?`React + TanStack Router`:f?`React + React Router`:h?`Next.js`:g?`React + TanStack Start`:_?`SvelteKit`:y?`Nuxt`:v?`SolidJS`:``})\n`:``}${m?`│ ├── native/ # Mobile application (React Native, Expo)
110
- `:``}${a.includes(`starlight`)?`│ ├── docs/ # Documentation site (Astro Starlight)
111
- `:``}${d?`├── packages/
112
- │ └── backend/ # Convex backend functions and schema
113
- `:`│ └── server/ # Backend API (${l[0].toUpperCase()+l.slice(1)}, ${u.toUpperCase()})`}
1824
+ ${hasTanstackRouter || hasReactRouter || hasNext || hasTanstackStart || hasSvelte || hasNuxt || hasSolid ? `│ ├── web/ # Frontend application (${hasTanstackRouter ? "React + TanStack Router" : hasReactRouter ? "React + React Router" : hasNext ? "Next.js" : hasTanstackStart ? "React + TanStack Start" : hasSvelte ? "SvelteKit" : hasNuxt ? "Nuxt" : hasSolid ? "SolidJS" : ""})\n` : ""}${hasNative ? "│ ├── native/ # Mobile application (React Native, Expo)\n" : ""}${addons.includes("starlight") ? "│ ├── docs/ # Documentation site (Astro Starlight)\n" : ""}${isConvex ? "├── packages/\n│ └── backend/ # Convex backend functions and schema\n" : `│ └── server/ # Backend API (${backend[0].toUpperCase() + backend.slice(1)}, ${api.toUpperCase()})`}
114
1825
  \`\`\`
115
1826
 
116
1827
  ## Available Scripts
117
1828
 
118
- ${qe(b,r,o,i,m,a,l)}
119
- `}function Ge(e,t,n,r,i,a,o,s){let c=o===`convex`,l=a.includes(`tanstack-router`),u=a.includes(`react-router`),d=a.includes(`native-nativewind`)||a.includes(`native-unistyles`),f=a.includes(`next`),p=a.includes(`tanstack-start`),m=a.includes(`svelte`),h=a.includes(`nuxt`),g=a.includes(`solid`),_=[`- **TypeScript** - For type safety and improved developer experience`];l?_.push(`- **TanStack Router** - File-based routing with full type safety`):u?_.push(`- **React Router** - Declarative routing for React`):f?_.push(`- **Next.js** - Full-stack React framework`):p?_.push(`- **TanStack Start** - SSR framework with TanStack Router`):m?_.push(`- **SvelteKit** - Web framework for building Svelte apps`):h?_.push(`- **Nuxt** - The Intuitive Vue Framework`):g&&_.push(`- **SolidJS** - Simple and performant reactivity`),d&&(_.push(`- **React Native** - Build mobile apps using React`),_.push(`- **Expo** - Tools for React Native development`)),_.push(`- **TailwindCSS** - Utility-first CSS for rapid UI development`,`- **shadcn/ui** - Reusable UI components`),c?_.push(`- **Convex** - Reactive backend-as-a-service platform`):(o===`hono`?_.push(`- **Hono** - Lightweight, performant server framework`):o===`express`?_.push(`- **Express** - Fast, unopinionated web framework`):o===`fastify`?_.push(`- **Fastify** - Fast, low-overhead web framework`):o===`elysia`?_.push(`- **Elysia** - Type-safe, high-performance framework`):o===`next`&&_.push(`- **Next.js** - Full-stack React framework`),s===`trpc`?_.push(`- **tRPC** - End-to-end type-safe APIs`):s===`orpc`&&_.push(`- **oRPC** - End-to-end type-safe APIs with OpenAPI integration`),_.push(`- **${i===`bun`?`Bun`:`Node.js`}** - Runtime environment`)),e!==`none`&&!c&&_.push(`- **${r===`drizzle`?`Drizzle`:r===`prisma`?`Prisma`:`Mongoose`}** - TypeScript-first ORM`,`- **${e===`sqlite`?`SQLite/Turso`:e===`postgres`?`PostgreSQL`:e===`mysql`?`MySQL`:`MongoDB`}** - Database engine`),t&&!c&&_.push(`- **Authentication** - Email & password authentication with Better Auth`);for(let e of n)e===`pwa`?_.push(`- **PWA** - Progressive Web App support`):e===`tauri`?_.push(`- **Tauri** - Build native desktop applications`):e===`biome`?_.push(`- **Biome** - Linting and formatting`):e===`husky`?_.push(`- **Husky** - Git hooks for code quality`):e===`starlight`?_.push(`- **Starlight** - Documentation site with Astro`):e===`turborepo`&&_.push(`- **Turborepo** - Optimized monorepo build system`);return _.join(`
120
- `)}function Ke(e,t,n,r){if(e===`none`)return``;let i=`## Database Setup
121
-
122
- `;return e===`sqlite`?i+=`This project uses SQLite${r===`drizzle`?` with Drizzle ORM`:` with Prisma`}.
1829
+ ${generateScriptsList(packageManagerRunCmd, database, orm, auth, hasNative, addons, backend)}
1830
+ `;
1831
+ }
1832
+ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api) {
1833
+ const isConvex = backend === "convex";
1834
+ const hasTanstackRouter = frontend.includes("tanstack-router");
1835
+ const hasReactRouter = frontend.includes("react-router");
1836
+ const hasNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
1837
+ const hasNext = frontend.includes("next");
1838
+ const hasTanstackStart = frontend.includes("tanstack-start");
1839
+ const hasSvelte = frontend.includes("svelte");
1840
+ const hasNuxt = frontend.includes("nuxt");
1841
+ const hasSolid = frontend.includes("solid");
1842
+ const addonsList = ["- **TypeScript** - For type safety and improved developer experience"];
1843
+ if (hasTanstackRouter) addonsList.push("- **TanStack Router** - File-based routing with full type safety");
1844
+ else if (hasReactRouter) addonsList.push("- **React Router** - Declarative routing for React");
1845
+ else if (hasNext) addonsList.push("- **Next.js** - Full-stack React framework");
1846
+ else if (hasTanstackStart) addonsList.push("- **TanStack Start** - SSR framework with TanStack Router");
1847
+ else if (hasSvelte) addonsList.push("- **SvelteKit** - Web framework for building Svelte apps");
1848
+ else if (hasNuxt) addonsList.push("- **Nuxt** - The Intuitive Vue Framework");
1849
+ else if (hasSolid) addonsList.push("- **SolidJS** - Simple and performant reactivity");
1850
+ if (hasNative) {
1851
+ addonsList.push("- **React Native** - Build mobile apps using React");
1852
+ addonsList.push("- **Expo** - Tools for React Native development");
1853
+ }
1854
+ addonsList.push("- **TailwindCSS** - Utility-first CSS for rapid UI development", "- **shadcn/ui** - Reusable UI components");
1855
+ if (isConvex) addonsList.push("- **Convex** - Reactive backend-as-a-service platform");
1856
+ else {
1857
+ if (backend === "hono") addonsList.push("- **Hono** - Lightweight, performant server framework");
1858
+ else if (backend === "express") addonsList.push("- **Express** - Fast, unopinionated web framework");
1859
+ else if (backend === "fastify") addonsList.push("- **Fastify** - Fast, low-overhead web framework");
1860
+ else if (backend === "elysia") addonsList.push("- **Elysia** - Type-safe, high-performance framework");
1861
+ else if (backend === "next") addonsList.push("- **Next.js** - Full-stack React framework");
1862
+ if (api === "trpc") addonsList.push("- **tRPC** - End-to-end type-safe APIs");
1863
+ else if (api === "orpc") addonsList.push("- **oRPC** - End-to-end type-safe APIs with OpenAPI integration");
1864
+ addonsList.push(`- **${runtime === "bun" ? "Bun" : "Node.js"}** - Runtime environment`);
1865
+ }
1866
+ if (database !== "none" && !isConvex) addonsList.push(`- **${orm === "drizzle" ? "Drizzle" : orm === "prisma" ? "Prisma" : "Mongoose"}** - TypeScript-first ORM`, `- **${database === "sqlite" ? "SQLite/Turso" : database === "postgres" ? "PostgreSQL" : database === "mysql" ? "MySQL" : "MongoDB"}** - Database engine`);
1867
+ if (auth && !isConvex) addonsList.push("- **Authentication** - Email & password authentication with Better Auth");
1868
+ for (const addon of addons) if (addon === "pwa") addonsList.push("- **PWA** - Progressive Web App support");
1869
+ else if (addon === "tauri") addonsList.push("- **Tauri** - Build native desktop applications");
1870
+ else if (addon === "biome") addonsList.push("- **Biome** - Linting and formatting");
1871
+ else if (addon === "husky") addonsList.push("- **Husky** - Git hooks for code quality");
1872
+ else if (addon === "starlight") addonsList.push("- **Starlight** - Documentation site with Astro");
1873
+ else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
1874
+ return addonsList.join("\n");
1875
+ }
1876
+ function generateDatabaseSetup(database, auth, packageManagerRunCmd, orm) {
1877
+ if (database === "none") return "";
1878
+ let setup = "## Database Setup\n\n";
1879
+ if (database === "sqlite") setup += `This project uses SQLite${orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"}.
123
1880
 
124
1881
  1. Start the local SQLite database:
125
1882
  \`\`\`bash
126
- cd apps/server && ${n} db:local
1883
+ cd apps/server && ${packageManagerRunCmd} db:local
127
1884
  \`\`\`
128
1885
 
129
1886
  2. Update your \`.env\` file in the \`apps/server\` directory with the appropriate connection details if needed.
130
- `:e===`postgres`?i+=`This project uses PostgreSQL${r===`drizzle`?` with Drizzle ORM`:` with Prisma`}.
1887
+ `;
1888
+ else if (database === "postgres") setup += `This project uses PostgreSQL${orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"}.
131
1889
 
132
1890
  1. Make sure you have a PostgreSQL database set up.
133
1891
  2. Update your \`apps/server/.env\` file with your PostgreSQL connection details.
134
- `:e===`mysql`?i+=`This project uses MySQL${r===`drizzle`?` with Drizzle ORM`:` with Prisma`}.
1892
+ `;
1893
+ else if (database === "mysql") setup += `This project uses MySQL${orm === "drizzle" ? " with Drizzle ORM" : " with Prisma"}.
135
1894
 
136
1895
  1. Make sure you have a MySQL database set up.
137
1896
  2. Update your \`apps/server/.env\` file with your MySQL connection details.
138
- `:e===`mongodb`&&(i+=`This project uses MongoDB ${r===`mongoose`?`with Mongoose`:`with Prisma ORM`}.
1897
+ `;
1898
+ else if (database === "mongodb") setup += `This project uses MongoDB ${orm === "mongoose" ? "with Mongoose" : "with Prisma ORM"}.
139
1899
 
140
1900
  1. Make sure you have MongoDB set up.
141
1901
  2. Update your \`apps/server/.env\` file with your MongoDB connection URI.
142
- `),i+=`
143
- 3. ${r===`prisma`?`Generate the Prisma client and push the schema:
1902
+ `;
1903
+ setup += `
1904
+ ${auth ? "3" : "3"}. ${orm === "prisma" ? `Generate the Prisma client and push the schema:
1905
+ \`\`\`bash
1906
+ ${packageManagerRunCmd} db:push
1907
+ \`\`\`` : orm === "drizzle" ? `Apply the schema to your database:
144
1908
  \`\`\`bash
145
- ${n} db:push
146
- \`\`\``:`Apply the schema to your database:
1909
+ ${packageManagerRunCmd} db:push
1910
+ \`\`\`` : `Apply the schema to your database:
147
1911
  \`\`\`bash
148
- ${n} db:push
1912
+ ${packageManagerRunCmd} db:push
149
1913
  \`\`\``}
150
- `,i}function qe(e,t,n,r,i,a,o){let s=o===`convex`,c=`- \`${e} dev\`: Start all applications in development mode
151
- - \`${e} build\`: Build all applications
152
- - \`${e} dev:web\`: Start only the web application`;return s?c+=`
153
- - \`${e} dev:setup\`: Setup and configure your Convex project`:c+=`
154
- - \`${e} dev:server\`: Start only the server`,c+=`
155
- - \`${e} check-types\`: Check TypeScript types across all apps`,i&&(c+=`
156
- - \`${e} dev:native\`: Start the React Native/Expo development server`),t!==`none`&&!s&&(c+=`
157
- - \`${e} db:push\`: Push schema changes to database
158
- - \`${e} db:studio\`: Open database studio UI`,t===`sqlite`&&n===`drizzle`&&(c+=`
159
- - \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),a.includes(`biome`)&&(c+=`
160
- - \`${e} check\`: Run Biome formatting and linting`),a.includes(`pwa`)&&(c+=`
161
- - \`cd apps/web && ${e} generate-pwa-assets\`: Generate PWA assets`),a.includes(`tauri`)&&(c+=`
162
- - \`cd apps/web && ${e} desktop:dev\`: Start Tauri desktop app in development
163
- - \`cd apps/web && ${e} desktop:build\`: Build Tauri desktop app`),a.includes(`starlight`)&&(c+=`
164
- - \`cd apps/docs && ${e} dev\`: Start documentation site
165
- - \`cd apps/docs && ${e} build\`: Build documentation site`),c}async function Je({projectDir:e,packageManager:t,addons:n=[]}){let r=d();try{r.start(`Running ${t} install...`),await b({cwd:e,stderr:`inherit`})`${t} install`,r.stop(`Dependencies installed successfully`),(n.includes(`biome`)||n.includes(`husky`))&&await Ye(e,t)}catch(e){r.stop(g.red(`Failed to install dependencies`)),e instanceof Error&&p.error(g.red(`Installation error: ${e.message}`))}}async function Ye(e,t){let n=d();try{n.start(`Running Biome format check...`),await b({cwd:e,stderr:`inherit`})`${t} biome check --write .`,n.stop(`Biome check completed successfully`)}catch{n.stop(g.yellow(`Biome check encountered issues`)),o.warn(g.yellow(`Some files may need manual formatting`))}}function Xe(e){let{database:t,relativePath:n,packageManager:r,depsInstalled:i,orm:a,addons:o,runtime:s,frontend:c,backend:l}=e,u=l===`convex`,d=r===`npm`?`npm run`:r,f=`cd ${n}`,p=o?.includes(`husky`)||o?.includes(`biome`),h=!u&&t!==`none`?$e(t,a,d,s):``,_=o?.includes(`tauri`)?et(d):``,v=p?Qe(d):``,y=c?.includes(`native-nativewind`)||c?.includes(`native-unistyles`)?Ze(u):``,b=o?.includes(`pwa`)&&(c?.includes(`react-router`)||c?.includes(`tanstack-router`))?tt():``,x=o?.includes(`starlight`)?nt(d):``,S=c?.some(e=>[`tanstack-router`,`react-router`,`next`,`tanstack-start`,`nuxt`,`svelte`,`solid`].includes(e)),C=c?.includes(`native-nativewind`)||c?.includes(`native-unistyles`),w=r===`bun`&&C&&S?it():``,T=!u&&t!==`none`&&a===`none`?rt():``,E=c?.includes(`react-router`),D=c?.includes(`svelte`),O=E||D?`5173`:`3001`,k=N(r,`taze -r`),A=`${g.bold(`Next steps`)}\n${g.cyan(`1.`)} ${f}\n`,j=2;i||(A+=`${g.cyan(`${j++}.`)} ${r} install\n`),u&&(A+=`${g.cyan(`${j++}.`)} ${d} dev:setup ${g.dim(`(this will guide you through Convex project setup)`)}\n`),A+=`${g.cyan(`${j++}.`)} ${d} dev\n\n`,A+=`${g.bold(`Your project will be available at:`)}\n`,S?A+=`${g.cyan(`•`)} Frontend: http://localhost:${O}\n`:!C&&!o?.includes(`starlight`)&&(A+=`${g.yellow(`NOTE:`)} You are creating a backend-only app (no frontend selected)\n`),u||(A+=`${g.cyan(`•`)} Backend API: http://localhost:3000\n`),o?.includes(`starlight`)&&(A+=`${g.cyan(`•`)} Docs: http://localhost:4321\n`),y&&(A+=`\n${y.trim()}\n`),h&&(A+=`\n${h.trim()}\n`),_&&(A+=`\n${_.trim()}\n`),v&&(A+=`\n${v.trim()}\n`),b&&(A+=`\n${b.trim()}\n`),x&&(A+=`\n${x.trim()}\n`),T&&(A+=`\n${T.trim()}\n`),w&&(A+=`\n${w.trim()}\n`),A+=`\n${g.bold(`Update all dependencies:
166
- `)}${g.cyan(k)}\n\n`,A+=`${g.bold(`Like Better-T Stack?`)} Please consider giving us a star on GitHub:\n`,A+=g.cyan(`https://github.com/AmanVarshney01/create-better-t-stack`),m.box(A)}function Ze(e){let t=e?`EXPO_PUBLIC_CONVEX_URL`:`EXPO_PUBLIC_SERVER_URL`,n=e?`https://<YOUR_CONVEX_URL>`:`http://<YOUR_LOCAL_IP>:3000`,r=`.env`,i=e?`your Convex deployment URL (find after running 'dev:setup')`:`your local IP address`;return`${g.yellow(`NOTE:`)} For Expo connectivity issues, update apps/native/${r} \nwith ${i}:\n${`${t}=${n}`}\n`}function Qe(e){return`${g.bold(`Linting and formatting:`)}\n${g.cyan(`•`)} Format and lint fix: ${`${e} check`}\n`}function $e(e,t,n,r){let i=[];return t===`prisma`?(e===`sqlite`&&i.push(`${g.yellow(`NOTE:`)} Turso support with Prisma is in Early Access and requires additional setup.`,`Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso`),r===`bun`&&i.push(`${g.yellow(`NOTE:`)} Prisma with Bun may require additional configuration. If you encounter errors,\nfollow the guidance provided in the error messages`),i.push(`${g.cyan(`•`)} Apply schema: ${`${n} db:push`}`),i.push(`${g.cyan(`•`)} Database UI: ${`${n} db:studio`}`)):t===`drizzle`?(i.push(`${g.cyan(`•`)} Apply schema: ${`${n} db:push`}`),i.push(`${g.cyan(`•`)} Database UI: ${`${n} db:studio`}`),e===`sqlite`&&i.push(`${g.cyan(`•`)} Start local DB (if needed): ${`cd apps/server && ${n} db:local`}`)):t===`none`&&i.push(`${g.yellow(`NOTE:`)} Manual database schema setup required.`),i.length?`${g.bold(`Database commands:`)}\n${i.join(`
167
- `)}`:``}function et(e){return`\n${g.bold(`Desktop app with Tauri:`)}\n${g.cyan(`•`)} Start desktop app: ${`cd apps/web && ${e} desktop:dev`}\n${g.cyan(`•`)} Build desktop app: ${`cd apps/web && ${e} desktop:build`}\n${g.yellow(`NOTE:`)} Tauri requires Rust and platform-specific dependencies.\nSee: https://v2.tauri.app/start/prerequisites/`}function tt(){return`\n${g.bold(`PWA with React Router v7:`)}\n${g.yellow(`NOTE:`)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`}function nt(e){return`\n${g.bold(`Documentation with Starlight:`)}\n${g.cyan(`•`)} Start docs site: ${`cd apps/docs && ${e} dev`}\n${g.cyan(`•`)} Build docs site: ${`cd apps/docs && ${e} build`}`}function rt(){return`\n${g.yellow(`WARNING:`)} Database selected without an ORM. Features requiring database access (e.g., examples, auth) need manual setup.`}function it(){return`\n${g.yellow(`WARNING:`)} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.`}async function at(e,t){await ot(e,t),t.backend===`convex`?await ct(e,t):await st(e,t)}async function ot(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.name=n.projectName,i.scripts||={};let a=i.scripts,s=n.backend===`convex`?`@${n.projectName}/backend`:`server`,c=``;n.addons.includes(`turborepo`)?c=`turbo -F ${s} dev`:n.packageManager===`bun`?c=`bun run --filter ${s} dev`:n.packageManager===`pnpm`?c=`pnpm --filter ${s} dev`:n.packageManager===`npm`&&(c=`npm run dev --workspace ${s}`);let l=``;n.packageManager===`pnpm`?l=`pnpm -r dev`:n.packageManager===`npm`?l=`npm run dev --workspaces`:n.packageManager===`bun`&&(l=`bun run --filter '*' dev`);let u=n.backend!==`convex`&&n.database!==`none`&&n.orm!==`none`&&n.orm!==`mongoose`;n.addons.includes(`turborepo`)?(a.dev=`turbo dev`,a.build=`turbo build`,a[`check-types`]=`turbo check-types`,a[`dev:native`]=`turbo -F native dev`,a[`dev:web`]=`turbo -F web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`turbo -F ${s} setup`),u&&(a[`db:push`]=`turbo -F ${s} db:push`,a[`db:studio`]=`turbo -F ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`turbo -F ${s} db:generate`,a[`db:migrate`]=`turbo -F ${s} db:migrate`))):n.packageManager===`pnpm`?(a.dev=l,a.build=`pnpm -r build`,a[`check-types`]=`pnpm -r check-types`,a[`dev:native`]=`pnpm --filter native dev`,a[`dev:web`]=`pnpm --filter web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`pnpm --filter ${s} setup`),u&&(a[`db:push`]=`pnpm --filter ${s} db:push`,a[`db:studio`]=`pnpm --filter ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`pnpm --filter ${s} db:generate`,a[`db:migrate`]=`pnpm --filter ${s} db:migrate`))):n.packageManager===`npm`?(a.dev=l,a.build=`npm run build --workspaces`,a[`check-types`]=`npm run check-types --workspaces`,a[`dev:native`]=`npm run dev --workspace native`,a[`dev:web`]=`npm run dev --workspace web`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`npm run setup --workspace ${s}`),u&&(a[`db:push`]=`npm run db:push --workspace ${s}`,a[`db:studio`]=`npm run db:studio --workspace ${s}`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`npm run db:generate --workspace ${s}`,a[`db:migrate`]=`npm run db:migrate --workspace ${s}`))):n.packageManager===`bun`&&(a.dev=l,a.build=`bun run --filter '*' build`,a[`check-types`]=`bun run --filter '*' check-types`,a[`dev:native`]=`bun run --filter native dev`,a[`dev:web`]=`bun run --filter web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`bun run --filter ${s} setup`),u&&(a[`db:push`]=`bun run --filter ${s} db:push`,a[`db:studio`]=`bun run --filter ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`bun run --filter ${s} db:generate`,a[`db:migrate`]=`bun run --filter ${s} db:migrate`))),n.addons.includes(`biome`)&&(a.check=`biome check --write .`),n.addons.includes(`husky`)&&(a.prepare=`husky`,i[`lint-staged`]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":[`biome check --write .`]});try{let{stdout:e}=await x(n.packageManager,[`-v`],{cwd:t});i.packageManager=`${n.packageManager}@${e.trim()}`}catch{o.warn(`Could not determine ${n.packageManager} version.`)}i.workspaces||=[];let d=i.workspaces;if(n.backend===`convex`){d.includes(`packages/*`)||d.push(`packages/*`);let e=n.frontend.length>0||n.addons.includes(`starlight`);e&&!d.includes(`apps/*`)&&d.push(`apps/*`)}else d.includes(`apps/*`)||d.push(`apps/*`),d.includes(`packages/*`)||d.push(`packages/*`);await h.writeJson(r,i,{spaces:2})}async function st(t,n){let r=e.join(t,`apps/server/package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts||={};let a=i.scripts;n.database!==`none`&&(n.database===`sqlite`&&n.orm===`drizzle`&&(a[`db:local`]=`turso dev --db-file local.db`),n.orm===`prisma`?(a[`db:push`]=`prisma db push --schema ./prisma/schema`,a[`db:studio`]=`prisma studio`,a[`db:generate`]=`prisma generate --schema ./prisma/schema`,a[`db:migrate`]=`prisma migrate dev`):n.orm===`drizzle`&&(a[`db:push`]=`drizzle-kit push`,a[`db:studio`]=`drizzle-kit studio`,a[`db:generate`]=`drizzle-kit generate`,a[`db:migrate`]=`drizzle-kit migrate`)),await h.writeJson(r,i,{spaces:2})}async function ct(t,n){let r=e.join(t,`packages/backend/package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.name=`@${n.projectName}/backend`,i.scripts||={},await h.writeJson(r,i,{spaces:2})}async function lt(e,t){if(!t)return;let n=await b({cwd:e,reject:!1,stderr:`pipe`})`git --version`;if(n.exitCode!==0){o.warn(g.yellow(`Git is not installed`));return}let r=await b({cwd:e,reject:!1,stderr:`pipe`})`git init`;if(r.exitCode!==0)throw Error(`Git initialization failed: ${r.stderr}`)}async function K(t,n,r){try{let i=await h.readFile(t,`utf-8`),a=w.compile(i),o=a(r);await h.ensureDir(e.dirname(n)),await h.writeFile(n,o)}catch(e){throw p.error(`Error processing template ${t}:`,e),Error(`Failed to process template ${t}`)}}w.registerHelper(`or`,(e,t)=>e||t),w.registerHelper(`eq`,(e,t)=>e===t),w.registerHelper(`includes`,(e,t)=>Array.isArray(e)&&e.includes(t));async function q(t,n,r,i,a=!0){let o=await C(t,{cwd:n,dot:!0,onlyFiles:!0,absolute:!1});for(let t of o){let o=e.join(n,t),s=t;t.endsWith(`.hbs`)&&(s=t.slice(0,-4));let c=e.basename(t);c===`_gitignore`?s=e.join(e.dirname(t),`.gitignore`):c===`_npmrc`&&(s=e.join(e.dirname(t),`.npmrc`));let l=e.join(r,s);try{if(await h.ensureDir(e.dirname(l)),!a&&await h.pathExists(l))continue;o.endsWith(`.hbs`)?await K(o,l,i):await h.copy(o,l,{overwrite:!0})}catch{}}}async function ut(t,n){let r=e.join(k,`templates/base`);await q([`**/*`],r,t,n),await h.ensureDir(e.join(t,`packages`))}async function dt(t,n){let r=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),i=n.frontend.includes(`nuxt`),a=n.frontend.includes(`svelte`),o=n.frontend.includes(`solid`),s=n.frontend.includes(`native-nativewind`),c=n.frontend.includes(`native-unistyles`),l=s||c,u=n.backend===`convex`;if(r||i||a||o){let s=e.join(t,`apps/web`);if(await h.ensureDir(s),r){let t=e.join(k,`templates/frontend/react/web-base`);await h.pathExists(t)&&await q(`**/*`,t,s,n);let r=n.frontend.find(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e));if(r){let t=e.join(k,`templates/frontend/react/${r}`);if(await h.pathExists(t)&&await q(`**/*`,t,s,n),!u&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/react/base`);await h.pathExists(t)&&await q(`**/*`,t,s,n)}}}else if(i){let t=e.join(k,`templates/frontend/nuxt`);if(await h.pathExists(t)&&await q(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/nuxt`);await h.pathExists(t)&&await q(`**/*`,t,s,n)}}else if(a){let t=e.join(k,`templates/frontend/svelte`);if(await h.pathExists(t)&&await q(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/svelte`);await h.pathExists(t)&&await q(`**/*`,t,s,n)}}else if(o){let t=e.join(k,`templates/frontend/solid`);if(await h.pathExists(t)&&await q(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/solid`);await h.pathExists(t)&&await q(`**/*`,t,s,n)}}}if(s||c){let r=e.join(t,`apps/native`);await h.ensureDir(r);let i=e.join(k,`templates/frontend/native/native-base`);await h.pathExists(i)&&await q(`**/*`,i,r,n);let a=``;s?a=`nativewind`:c&&(a=`unistyles`);let o=e.join(k,`templates/frontend/native/${a}`);if(await h.pathExists(o)&&await q(`**/*`,o,r,n,!0),!u&&(n.api===`trpc`||n.api===`orpc`)){let t=e.join(k,`templates/api/${n.api}/native`);await h.pathExists(t)&&await q(`**/*`,t,r,n)}}}async function ft(t,n){if(n.backend===`none`)return;let r=e.join(t,`apps/server`);if(n.backend===`convex`){await h.pathExists(r)&&await h.remove(r);let i=e.join(t,`packages/backend`),a=e.join(k,`templates/backend/convex/packages/backend`);await h.ensureDir(i),await h.pathExists(a)&&await q(`**/*`,a,i,n);return}await h.ensureDir(r);let i=e.join(k,`templates/backend/server/server-base`);await h.pathExists(i)&&await q(`**/*`,i,r,n);let a=e.join(k,`templates/backend/server/${n.backend}`);if(await h.pathExists(a)&&await q(`**/*`,a,r,n,!0),n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/server/base`);await h.pathExists(t)&&await q(`**/*`,t,r,n,!0);let i=e.join(k,`templates/api/${n.api}/server/${n.backend}`);await h.pathExists(i)&&await q(`**/*`,i,r,n,!0)}}async function pt(t,n){if(n.backend===`convex`||n.orm===`none`||n.database===`none`)return;let r=e.join(t,`apps/server`);await h.ensureDir(r);let i=e.join(k,`templates/db/${n.orm}/${n.database}`);await h.pathExists(i)&&await q(`**/*`,i,r,n)}async function mt(t,n){if(n.backend===`convex`||!n.auth)return;let r=e.join(t,`apps/server`),i=e.join(t,`apps/web`),a=e.join(t,`apps/native`),o=await h.pathExists(r),s=await h.pathExists(i),c=await h.pathExists(a),l=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),u=n.frontend.includes(`nuxt`),d=n.frontend.includes(`svelte`),f=n.frontend.includes(`solid`),p=n.frontend.includes(`native-nativewind`),m=n.frontend.includes(`native-unistyles`),g=p||m;if(o){let t=e.join(k,`templates/auth/server/base`);if(await h.pathExists(t)&&await q(`**/*`,t,r,n),n.backend===`next`){let t=e.join(k,`templates/auth/server/next`);await h.pathExists(t)&&await q(`**/*`,t,r,n)}if(n.orm!==`none`&&n.database!==`none`){let t=n.orm,i=n.database,a=``;t===`drizzle`?a=e.join(k,`templates/auth/server/db/drizzle/${i}`):t===`prisma`?a=e.join(k,`templates/auth/server/db/prisma/${i}`):t===`mongoose`&&(a=e.join(k,`templates/auth/server/db/mongoose/${i}`)),a&&await h.pathExists(a)&&await q(`**/*`,a,r,n)}}if((l||u||d||f)&&s){if(l){let t=e.join(k,`templates/auth/web/react/base`);await h.pathExists(t)&&await q(`**/*`,t,i,n);let r=n.frontend.find(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e));if(r){let t=e.join(k,`templates/auth/web/react/${r}`);await h.pathExists(t)&&await q(`**/*`,t,i,n)}}else if(u){let t=e.join(k,`templates/auth/web/nuxt`);await h.pathExists(t)&&await q(`**/*`,t,i,n)}else if(d){if(n.api===`orpc`){let t=e.join(k,`templates/auth/web/svelte`);await h.pathExists(t)&&await q(`**/*`,t,i,n)}}else if(f&&n.api===`orpc`){let t=e.join(k,`templates/auth/web/solid`);await h.pathExists(t)&&await q(`**/*`,t,i,n)}}if(g&&c){let t=e.join(k,`templates/auth/native/native-base`);await h.pathExists(t)&&await q(`**/*`,t,a,n);let r=``;if(p?r=`nativewind`:m&&(r=`unistyles`),r){let t=e.join(k,`templates/auth/native/${r}`);await h.pathExists(t)&&await q(`**/*`,t,a,n)}}}async function ht(t,n){if(!(!n.addons||n.addons.length===0))for(let r of n.addons){if(r===`none`)continue;let i=e.join(k,`templates/addons/${r}`),a=t;if(r===`pwa`){let r=e.join(t,`apps/web`);if(!await h.pathExists(r))continue;if(a=r,n.frontend.includes(`next`))i=e.join(k,`templates/addons/pwa/apps/web/next`);else if(n.frontend.some(e=>[`tanstack-router`,`react-router`,`solid`].includes(e)))i=e.join(k,`templates/addons/pwa/apps/web/vite`);else continue}await h.pathExists(i)&&await q(`**/*`,i,a,n)}}async function gt(t,n){if(!n.examples||n.examples.length===0||n.examples[0]===`none`)return;let r=e.join(t,`apps/server`),i=e.join(t,`apps/web`),a=await h.pathExists(r),o=await h.pathExists(i),s=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),c=n.frontend.includes(`nuxt`),l=n.frontend.includes(`svelte`),u=n.frontend.includes(`solid`);for(let t of n.examples){if(t===`none`)continue;let d=e.join(k,`templates/examples/${t}`);if(a&&n.backend!==`convex`&&n.backend!==`none`){let i=e.join(d,`server`);if(t===`ai`&&n.backend===`next`){let t=e.join(i,`next`);await h.pathExists(t)&&await q(`**/*`,t,r,n,!1)}if(n.orm!==`none`&&n.database!==`none`){let t=e.join(i,n.orm,`base`);await h.pathExists(t)&&await q(`**/*`,t,r,n,!1);let a=e.join(i,n.orm,n.database);await h.pathExists(a)&&await q(`**/*`,a,r,n,!1)}let a=[`${n.orm}/**`];t===`ai`&&n.backend===`next`&&a.push(`next/**`);let o=await C([`**/*.ts`,`**/*.hbs`],{cwd:i,onlyFiles:!0,deep:1,ignore:a});for(let t of o){let a=e.join(i,t),o=e.join(r,t.replace(`.hbs`,``));try{a.endsWith(`.hbs`)?await K(a,o,n):await h.pathExists(o)||await h.copy(a,o,{overwrite:!1})}catch{}}}if(o){if(s){let t=e.join(d,`web/react`);if(await h.pathExists(t)){let r=n.frontend.find(e=>[`next`,`react-router`,`tanstack-router`,`tanstack-start`].includes(e));if(r){let a=e.join(t,r);await h.pathExists(a)&&await q(`**/*`,a,i,n,!1)}}}else if(c){let t=e.join(d,`web/nuxt`);await h.pathExists(t)&&await q(`**/*`,t,i,n,!1)}else if(l){let t=e.join(d,`web/svelte`);await h.pathExists(t)&&await q(`**/*`,t,i,n,!1)}else if(u){let t=e.join(d,`web/solid`);await h.pathExists(t)&&await q(`**/*`,t,i,n,!1)}}}}async function _t(t,n){let r=e.join(k,`templates/extras`),i=n.frontend.includes(`native-nativewind`),a=n.frontend.includes(`native-unistyles`),o=i||a;if(n.packageManager===`pnpm`){let n=e.join(r,`pnpm-workspace.yaml`),i=e.join(t,`pnpm-workspace.yaml`);await h.pathExists(n)&&await h.copy(n,i)}if(n.packageManager===`pnpm`&&(o||n.frontend.includes(`nuxt`))){let i=e.join(r,`_npmrc.hbs`),a=e.join(t,`.npmrc`);await h.pathExists(i)&&await K(i,a,n)}}async function vt(e){let n=e.projectDir,r=e.backend===`convex`;try{return await h.ensureDir(n),await ut(n,e),await dt(n,e),await ft(n,e),r||(await pt(n,e),await mt(n,e)),e.examples.length>0&&e.examples[0]!==`none`&&await gt(n,e),await ht(n,e),await le(e),r||(await fe(e),await Re(e),await Be(e),e.examples.length>0&&e.examples[0]!==`none`&&await ze(e)),e.addons.length>0&&e.addons[0]!==`none`&&await ie(e),!r&&e.auth&&await ue(e),await _t(n,e),await pe(e),await at(n,e),await Ue(n,e),await lt(n,e.git),o.success(`Project template successfully scaffolded!`),e.install&&await Je({projectDir:n,packageManager:e.packageManager,addons:e.addons}),Xe({...e,depsInstalled:e.install}),n}catch(e){e instanceof Error?(t(g.red(`Error during project creation: ${e.message}`)),console.error(e.stack),process.exit(1)):(t(g.red(`An unexpected error occurred: ${String(e)}`)),console.error(e),process.exit(1))}}async function yt(e,n){if(e!==void 0)return e;let r=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`solid`)||n?.includes(`next`),i=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`nuxt`)||n?.includes(`svelte`)||n?.includes(`solid`)||n?.includes(`next`),o=[{value:`turborepo`,label:`Turborepo (Recommended)`,hint:`Optimize builds for monorepos`},{value:`starlight`,label:`Starlight`,hint:`Add Astro Starlight documentation site`},{value:`biome`,label:`Biome`,hint:`Add Biome for linting and formatting`},{value:`husky`,label:`Husky`,hint:`Add Git hooks with Husky, lint-staged (requires Biome)`},{value:`pwa`,label:`PWA (Progressive Web App)`,hint:`Make your app installable and work offline`},{value:`tauri`,label:`Tauri Desktop App`,hint:`Build native desktop apps from your web frontend`}],c=o.filter(e=>e.value===`pwa`?r:e.value===`tauri`?i:!0),l=A.addons.filter(e=>c.some(t=>t.value===e)),u=await s({message:`Select addons`,options:c,initialValues:l,required:!1});return a(u)&&(t(g.red(`Operation cancelled`)),process.exit(0)),u.includes(`husky`)&&!u.includes(`biome`)&&u.push(`biome`),u}async function bt(e,n,r){if(r===`convex`||r===`none`)return`none`;if(e)return e;let i=n?.includes(`nuxt`),o=n?.includes(`svelte`),s=n?.includes(`solid`),c=[{value:`trpc`,label:`tRPC`,hint:`End-to-end typesafe APIs made easy`},{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs that adhere to OpenAPI standards`},{value:`none`,label:`None`,hint:`No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)`}];(i||o||s)&&(c=[{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs (Recommended for ${i?`Nuxt`:o?`Svelte`:`Solid`} frontend)`},{value:`none`,label:`None`,hint:`No API layer`}]);let l=await u({message:`Select API type`,options:c,initialValue:c[0].value});return a(l)&&(t(g.red(`Operation cancelled`)),process.exit(0)),l}async function xt(e,r,i){if(i===`convex`||!r)return!1;if(e!==void 0)return e;let o=await n({message:`Add authentication with Better-Auth?`,initialValue:A.auth});return a(o)&&(t(g.red(`Operation cancelled`)),process.exit(0)),o}async function St(e,n){if(e!==void 0)return e;let r=n?.some(e=>e===`nuxt`||e===`solid`),i=[{value:`hono`,label:`Hono`,hint:`Lightweight, ultrafast web framework`},{value:`next`,label:`Next.js`,hint:`Nextjs API routes`},{value:`express`,label:`Express`,hint:`Fast, unopinionated, minimalist web framework for Node.js`},{value:`fastify`,label:`Fastify`,hint:`Fast, low-overhead web framework for Node.js`},{value:`elysia`,label:`Elysia`,hint:`Ergonomic web framework for building backend servers`}];r||i.push({value:`convex`,label:`Convex`,hint:`Reactive backend-as-a-service platform`}),i.push({value:`none`,label:`None`,hint:`No backend server`});let o=A.backend;r&&o===`convex`&&(o=`hono`);let s=await u({message:`Select backend`,options:i,initialValue:o});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function Ct(e,n){if(n===`convex`||n===`none`)return`none`;if(e!==void 0)return e;let r=await u({message:`Select database`,options:[{value:`none`,label:`None`,hint:`No database setup`},{value:`sqlite`,label:`SQLite`,hint:`lightweight, server-less, embedded relational database`},{value:`postgres`,label:`PostgreSQL`,hint:`powerful, open source object-relational database system`},{value:`mysql`,label:`MySQL`,hint:`popular open-source relational database system`},{value:`mongodb`,label:`MongoDB`,hint:`open-source NoSQL database that stores data in JSON-like documents called BSON`}],initialValue:A.database});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function wt(e,n,r,i){if(i===`convex`)return`none`;if(n!==void 0)return n;if(e===`none`||e===`sqlite`&&r===`prisma`)return`none`;let o=[];if(e===`sqlite`)o=[{value:`turso`,label:`Turso`,hint:`SQLite for Production. Powered by libSQL`},{value:`none`,label:`None`,hint:`Manual setup`}];else if(e===`postgres`)o=[{value:`neon`,label:`Neon Postgres`,hint:`Serverless Postgres with branching capability`},{value:`supabase`,label:`Supabase`,hint:`Local Supabase stack (requires Docker)`},...r===`prisma`?[{value:`prisma-postgres`,label:`Prisma Postgres`,hint:`Instant Postgres for Global Applications`}]:[],{value:`none`,label:`None`,hint:`Manual setup`}];else if(e===`mongodb`)o=[{value:`mongodb-atlas`,label:`MongoDB Atlas`,hint:`The most effective way to deploy MongoDB`},{value:`none`,label:`None`,hint:`Manual setup`}];else return`none`;let s=await u({message:`Select ${e} setup option`,options:o,initialValue:`none`});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function Tt(e,n,r,i,o){if(o===`none`)return[];if(e!==void 0)return e;if(i===`convex`)return[`todo`];if(i===`none`||n===`none`)return[];let c=r&&r.length===1&&(r[0]===`native-nativewind`||r[0]===`native-unistyles`);if(c)return[];let l=r?.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`,`solid`].includes(e))??!1,u=!r||r.length===0;if(!l&&!u)return[];let d=[],f=[{value:`todo`,label:`Todo App`,hint:`A simple CRUD example app`}];return i!==`elysia`&&!r?.includes(`solid`)&&f.push({value:`ai`,label:`AI Chat`,hint:`A simple AI chat interface using AI SDK`}),d=await s({message:`Include examples`,options:f,required:!1,initialValues:A.examples}),a(d)&&(t(g.red(`Operation cancelled`)),process.exit(0)),d}async function Et(e,n){if(e!==void 0)return e;let r=await s({message:`Select platforms to develop for`,options:[{value:`web`,label:`Web`,hint:`React, Vue or Svelte Web Application`},{value:`native`,label:`Native`,hint:`Create a React Native/Expo app`}],required:!1,initialValues:[`web`]});a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0));let i=[];if(r.includes(`web`)){let e=[{value:`tanstack-router`,label:`TanStack Router`,hint:`Modern and scalable routing for React Applications`},{value:`react-router`,label:`React Router`,hint:`A user‑obsessed, standards‑focused, multi‑strategy router`},{value:`next`,label:`Next.js`,hint:`The React Framework for the Web`},{value:`nuxt`,label:`Nuxt`,hint:`The Progressive Web Framework for Vue.js`},{value:`svelte`,label:`Svelte`,hint:`web development for the rest of us`},{value:`solid`,label:`Solid`,hint:`Simple and performant reactivity for building user interfaces`},{value:`tanstack-start`,label:`TanStack Start (beta)`,hint:`SSR, Server Functions, API Routes and more with TanStack Router`}],r=e.filter(e=>n===`convex`?e.value!==`nuxt`&&e.value!==`solid`:!0),o=await u({message:`Choose frontend`,options:r,initialValue:A.frontend[0]});a(o)&&(t(g.red(`Operation cancelled`)),process.exit(0)),i.push(o)}if(r.includes(`native`)){let e=await u({message:`Choose native framework`,options:[{value:`native-nativewind`,label:`NativeWind`,hint:`Use Tailwind CSS for React Native`},{value:`native-unistyles`,label:`Unistyles`,hint:`Consistent styling for React Native`}],initialValue:`native-nativewind`});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),i.push(e)}return i}async function Dt(e){if(e!==void 0)return e;let r=await n({message:`Initialize git repository?`,initialValue:A.git});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function Ot(e){if(e!==void 0)return e;let r=await n({message:`Install dependencies?`,initialValue:A.install});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}const J={prisma:{value:`prisma`,label:`Prisma`,hint:`Powerful, feature-rich ORM`},mongoose:{value:`mongoose`,label:`Mongoose`,hint:`Elegant object modeling tool`},drizzle:{value:`drizzle`,label:`Drizzle`,hint:`Lightweight and performant TypeScript ORM`}};async function kt(e,n,r,i){if(i===`convex`||!n)return`none`;if(e!==void 0)return e;let o=[...r===`mongodb`?[J.prisma,J.mongoose]:[J.drizzle,J.prisma]],s=await u({message:`Select ORM`,options:o,initialValue:r===`mongodb`?`prisma`:A.orm});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function At(e){if(e!==void 0)return e;let n=E(),r=await u({message:`Choose package manager`,options:[{value:`npm`,label:`npm`,hint:`Node Package Manager`},{value:`pnpm`,label:`pnpm`,hint:`Fast, disk space efficient package manager`},{value:`bun`,label:`bun`,hint:`All-in-one JavaScript runtime & toolkit`}],initialValue:n});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function jt(e,n){if(n===`convex`||n===`none`)return`none`;if(e!==void 0)return e;if(n===`next`)return`node`;let r=await u({message:`Select runtime`,options:[{value:`bun`,label:`Bun`,hint:`Fast all-in-one JavaScript runtime`},{value:`node`,label:`Node.js`,hint:`Traditional Node.js runtime`}],initialValue:A.runtime});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function Mt(e,n,i,a){let o=await r({frontend:()=>Et(e.frontend,e.backend),backend:({results:t})=>St(e.backend,t.frontend),runtime:({results:t})=>jt(e.runtime,t.backend),database:({results:t})=>Ct(e.database,t.backend),orm:({results:t})=>kt(e.orm,t.database!==`none`,t.database,t.backend),api:({results:t})=>bt(e.api,t.frontend,t.backend),auth:({results:t})=>xt(e.auth,t.database!==`none`,t.backend),addons:({results:t})=>yt(e.addons,t.frontend),examples:({results:t})=>Tt(e.examples,t.database,t.frontend,t.backend,t.api),dbSetup:({results:t})=>wt(t.database??`none`,e.dbSetup,t.orm,t.backend),git:()=>Dt(e.git),packageManager:()=>At(e.packageManager),install:()=>Ot(e.install)},{onCancel:()=>{t(g.red(`Operation cancelled`)),process.exit(0)}});return o.backend===`convex`&&(o.runtime=`none`,o.database=`none`,o.orm=`none`,o.api=`none`,o.auth=!1,o.dbSetup=`none`,o.examples=[`todo`]),o.backend===`none`&&(o.runtime=`none`,o.database=`none`,o.orm=`none`,o.api=`none`,o.auth=!1,o.dbSetup=`none`,o.examples=[]),{projectName:n,projectDir:i,relativePath:a,frontend:o.frontend,backend:o.backend,runtime:o.runtime,database:o.database,orm:o.orm,auth:o.auth,addons:o.addons,examples:o.examples,git:o.git,packageManager:o.packageManager,install:o.install,dbSetup:o.dbSetup,api:o.api}}const Nt=[`<`,`>`,`:`,`"`,`|`,`?`,`*`],Y=255;function X(e){if(e!==`.`){if(!e)return`Project name cannot be empty`;if(e.length>Y)return`Project name must be less than ${Y} characters`;if(Nt.some(t=>e.includes(t)))return`Project name contains invalid characters`;if(e.startsWith(`.`)||e.startsWith(`-`))return`Project name cannot start with a dot or dash`;if(e.toLowerCase()===`node_modules`)return`Project name is reserved`}}async function Z(n){if(n){if(n===`.`)return n;let t=e.basename(n),r=X(t);if(!r)return n}let r=!1,i=``,o=A.projectName,s=1;for(;h.pathExistsSync(e.resolve(process.cwd(),o))&&h.readdirSync(e.resolve(process.cwd(),o)).length>0;)o=`${A.projectName}-${s}`,s++;for(;!r;){let s=await f({message:`Enter your project name or path (relative to current directory)`,placeholder:o,initialValue:n,defaultValue:o,validate:t=>{let n=t.trim()||o,r=e.basename(n),i=X(r);if(i)return i;if(n!==`.`){let t=e.resolve(process.cwd(),n);if(!t.startsWith(process.cwd()))return`Project path must be within current directory`}}});a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),i=s||o,r=!0}return i}function Q(e){let t=[];if(e.projectName&&t.push(`${g.blue(`Project Name:`)} ${e.projectName}`),e.frontend!==void 0){let n=Array.isArray(e.frontend)?e.frontend:[e.frontend],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Frontend:`)} ${r}`)}if(e.backend!==void 0&&t.push(`${g.blue(`Backend:`)} ${String(e.backend)}`),e.runtime!==void 0&&t.push(`${g.blue(`Runtime:`)} ${String(e.runtime)}`),e.api!==void 0&&t.push(`${g.blue(`API:`)} ${String(e.api)}`),e.database!==void 0&&t.push(`${g.blue(`Database:`)} ${String(e.database)}`),e.orm!==void 0&&t.push(`${g.blue(`ORM:`)} ${String(e.orm)}`),e.auth!==void 0){let n=typeof e.auth==`boolean`?e.auth?`Yes`:`No`:String(e.auth);t.push(`${g.blue(`Authentication:`)} ${n}`)}if(e.addons!==void 0){let n=Array.isArray(e.addons)?e.addons:[e.addons],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Addons:`)} ${r}`)}if(e.examples!==void 0){let n=Array.isArray(e.examples)?e.examples:[e.examples],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Examples:`)} ${r}`)}if(e.git!==void 0){let n=typeof e.git==`boolean`?e.git?`Yes`:`No`:String(e.git);t.push(`${g.blue(`Git Init:`)} ${n}`)}if(e.packageManager!==void 0&&t.push(`${g.blue(`Package Manager:`)} ${String(e.packageManager)}`),e.install!==void 0){let n=typeof e.install==`boolean`?e.install?`Yes`:`No`:String(e.install);t.push(`${g.blue(`Install Dependencies:`)} ${n}`)}return e.dbSetup!==void 0&&t.push(`${g.blue(`Database Setup:`)} ${String(e.dbSetup)}`),t.length===0?g.yellow(`No configuration selected.`):t.join(`
168
- `)}function Pt(e){let t=[];e.frontend&&e.frontend.length>0?t.push(`--frontend ${e.frontend.join(` `)}`):t.push(`--frontend none`),t.push(`--backend ${e.backend}`),t.push(`--runtime ${e.runtime}`),t.push(`--database ${e.database}`),t.push(`--orm ${e.orm}`),t.push(`--api ${e.api}`),t.push(e.auth?`--auth`:`--no-auth`),e.addons&&e.addons.length>0?t.push(`--addons ${e.addons.join(` `)}`):t.push(`--addons none`),e.examples&&e.examples.length>0?t.push(`--examples ${e.examples.join(` `)}`):t.push(`--examples none`),t.push(`--db-setup ${e.dbSetup}`),t.push(e.git?`--git`:`--no-git`),t.push(`--package-manager ${e.packageManager}`),t.push(e.install?`--install`:`--no-install`);let n=``,r=e.packageManager;r===`npm`?n=`npx create-better-t-stack@latest`:r===`pnpm`?n=`pnpm create better-t-stack@latest`:r===`bun`&&(n=`bun create better-t-stack@latest`);let i=e.relativePath?` ${e.relativePath}`:``;return`${n}${i} ${t.join(` `)}`}const $=`
1914
+ `;
1915
+ return setup;
1916
+ }
1917
+ function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNative, addons, backend) {
1918
+ const isConvex = backend === "convex";
1919
+ let scripts = `- \`${packageManagerRunCmd} dev\`: Start all applications in development mode
1920
+ - \`${packageManagerRunCmd} build\`: Build all applications
1921
+ - \`${packageManagerRunCmd} dev:web\`: Start only the web application`;
1922
+ if (isConvex) scripts += `
1923
+ - \`${packageManagerRunCmd} dev:setup\`: Setup and configure your Convex project`;
1924
+ else scripts += `
1925
+ - \`${packageManagerRunCmd} dev:server\`: Start only the server`;
1926
+ scripts += `
1927
+ - \`${packageManagerRunCmd} check-types\`: Check TypeScript types across all apps`;
1928
+ if (hasNative) scripts += `
1929
+ - \`${packageManagerRunCmd} dev:native\`: Start the React Native/Expo development server`;
1930
+ if (database !== "none" && !isConvex) {
1931
+ scripts += `
1932
+ - \`${packageManagerRunCmd} db:push\`: Push schema changes to database
1933
+ - \`${packageManagerRunCmd} db:studio\`: Open database studio UI`;
1934
+ if (database === "sqlite" && orm === "drizzle") scripts += `
1935
+ - \`cd apps/server && ${packageManagerRunCmd} db:local\`: Start the local SQLite database`;
1936
+ }
1937
+ if (addons.includes("biome")) scripts += `
1938
+ - \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
1939
+ if (addons.includes("pwa")) scripts += `
1940
+ - \`cd apps/web && ${packageManagerRunCmd} generate-pwa-assets\`: Generate PWA assets`;
1941
+ if (addons.includes("tauri")) scripts += `
1942
+ - \`cd apps/web && ${packageManagerRunCmd} desktop:dev\`: Start Tauri desktop app in development
1943
+ - \`cd apps/web && ${packageManagerRunCmd} desktop:build\`: Build Tauri desktop app`;
1944
+ if (addons.includes("starlight")) scripts += `
1945
+ - \`cd apps/docs && ${packageManagerRunCmd} dev\`: Start documentation site
1946
+ - \`cd apps/docs && ${packageManagerRunCmd} build\`: Build documentation site`;
1947
+ return scripts;
1948
+ }
1949
+
1950
+ //#endregion
1951
+ //#region src/helpers/project-generation/install-dependencies.ts
1952
+ async function installDependencies({ projectDir, packageManager, addons = [] }) {
1953
+ const s = spinner();
1954
+ try {
1955
+ s.start(`Running ${packageManager} install...`);
1956
+ await $({
1957
+ cwd: projectDir,
1958
+ stderr: "inherit"
1959
+ })`${packageManager} install`;
1960
+ s.stop("Dependencies installed successfully");
1961
+ if (addons.includes("biome") || addons.includes("husky")) await runBiomeCheck(projectDir, packageManager);
1962
+ } catch (error) {
1963
+ s.stop(pc.red("Failed to install dependencies"));
1964
+ if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
1965
+ }
1966
+ }
1967
+ async function runBiomeCheck(projectDir, packageManager) {
1968
+ const s = spinner();
1969
+ try {
1970
+ s.start("Running Biome format check...");
1971
+ await $({
1972
+ cwd: projectDir,
1973
+ stderr: "inherit"
1974
+ })`${packageManager} biome check --write .`;
1975
+ s.stop("Biome check completed successfully");
1976
+ } catch (_error) {
1977
+ s.stop(pc.yellow("Biome check encountered issues"));
1978
+ log.warn(pc.yellow("Some files may need manual formatting"));
1979
+ }
1980
+ }
1981
+
1982
+ //#endregion
1983
+ //#region src/helpers/project-generation/post-installation.ts
1984
+ function displayPostInstallInstructions(config) {
1985
+ const { database, relativePath, packageManager, depsInstalled, orm, addons, runtime, frontend, backend } = config;
1986
+ const isConvex = backend === "convex";
1987
+ const runCmd = packageManager === "npm" ? "npm run" : packageManager;
1988
+ const cdCmd = `cd ${relativePath}`;
1989
+ const hasHuskyOrBiome = addons?.includes("husky") || addons?.includes("biome");
1990
+ const databaseInstructions = !isConvex && database !== "none" ? getDatabaseInstructions(database, orm, runCmd, runtime) : "";
1991
+ const tauriInstructions = addons?.includes("tauri") ? getTauriInstructions(runCmd) : "";
1992
+ const lintingInstructions = hasHuskyOrBiome ? getLintingInstructions(runCmd) : "";
1993
+ const nativeInstructions = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex) : "";
1994
+ const pwaInstructions = addons?.includes("pwa") && (frontend?.includes("react-router") || frontend?.includes("tanstack-router")) ? getPwaInstructions() : "";
1995
+ const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : "";
1996
+ const hasWeb = frontend?.some((f) => [
1997
+ "tanstack-router",
1998
+ "react-router",
1999
+ "next",
2000
+ "tanstack-start",
2001
+ "nuxt",
2002
+ "svelte",
2003
+ "solid"
2004
+ ].includes(f));
2005
+ const hasNative = frontend?.includes("native-nativewind") || frontend?.includes("native-unistyles");
2006
+ const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
2007
+ const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
2008
+ const hasReactRouter = frontend?.includes("react-router");
2009
+ const hasSvelte = frontend?.includes("svelte");
2010
+ const webPort = hasReactRouter || hasSvelte ? "5173" : "3001";
2011
+ const tazeCommand = getPackageExecutionCommand(packageManager, "taze -r");
2012
+ let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
2013
+ let stepCounter = 2;
2014
+ if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
2015
+ if (isConvex) {
2016
+ output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup ${pc.dim("(this will guide you through Convex project setup)")}\n`;
2017
+ output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
2018
+ } else output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev\n\n`;
2019
+ output += `${pc.bold("Your project will be available at:")}\n`;
2020
+ if (hasWeb) output += `${pc.cyan("•")} Frontend: http://localhost:${webPort}\n`;
2021
+ else if (!hasNative && !addons?.includes("starlight")) output += `${pc.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)\n`;
2022
+ if (!isConvex) output += `${pc.cyan("•")} Backend API: http://localhost:3000\n`;
2023
+ if (addons?.includes("starlight")) output += `${pc.cyan("•")} Docs: http://localhost:4321\n`;
2024
+ if (nativeInstructions) output += `\n${nativeInstructions.trim()}\n`;
2025
+ if (databaseInstructions) output += `\n${databaseInstructions.trim()}\n`;
2026
+ if (tauriInstructions) output += `\n${tauriInstructions.trim()}\n`;
2027
+ if (lintingInstructions) output += `\n${lintingInstructions.trim()}\n`;
2028
+ if (pwaInstructions) output += `\n${pwaInstructions.trim()}\n`;
2029
+ if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
2030
+ if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
2031
+ if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
2032
+ output += `\n${pc.bold("Update all dependencies:\n")}${pc.cyan(tazeCommand)}\n\n`;
2033
+ output += `${pc.bold("Like Better-T Stack?")} Please consider giving us a star on GitHub:\n`;
2034
+ output += pc.cyan("https://github.com/AmanVarshney01/create-better-t-stack");
2035
+ consola$1.box(output);
2036
+ }
2037
+ function getNativeInstructions(isConvex) {
2038
+ const envVar = isConvex ? "EXPO_PUBLIC_CONVEX_URL" : "EXPO_PUBLIC_SERVER_URL";
2039
+ const exampleUrl = isConvex ? "https://<YOUR_CONVEX_URL>" : "http://<YOUR_LOCAL_IP>:3000";
2040
+ const envFileName = ".env";
2041
+ const ipNote = isConvex ? "your Convex deployment URL (find after running 'dev:setup')" : "your local IP address";
2042
+ return `${pc.yellow("NOTE:")} For Expo connectivity issues, update apps/native/${envFileName} \nwith ${ipNote}:\n${`${envVar}=${exampleUrl}`}\n`;
2043
+ }
2044
+ function getLintingInstructions(runCmd) {
2045
+ return `${pc.bold("Linting and formatting:")}\n${pc.cyan("•")} Format and lint fix: ${`${runCmd} check`}\n`;
2046
+ }
2047
+ function getDatabaseInstructions(database, orm, runCmd, runtime) {
2048
+ const instructions = [];
2049
+ if (orm === "prisma") {
2050
+ if (database === "sqlite") instructions.push(`${pc.yellow("NOTE:")} Turso support with Prisma is in Early Access and requires additional setup.`, `Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso`);
2051
+ if (runtime === "bun") instructions.push(`${pc.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors,\nfollow the guidance provided in the error messages`);
2052
+ instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
2053
+ instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
2054
+ } else if (orm === "drizzle") {
2055
+ instructions.push(`${pc.cyan("•")} Apply schema: ${`${runCmd} db:push`}`);
2056
+ instructions.push(`${pc.cyan("•")} Database UI: ${`${runCmd} db:studio`}`);
2057
+ if (database === "sqlite") instructions.push(`${pc.cyan("•")} Start local DB (if needed): ${`cd apps/server && ${runCmd} db:local`}`);
2058
+ } else if (orm === "none") instructions.push(`${pc.yellow("NOTE:")} Manual database schema setup required.`);
2059
+ return instructions.length ? `${pc.bold("Database commands:")}\n${instructions.join("\n")}` : "";
2060
+ }
2061
+ function getTauriInstructions(runCmd) {
2062
+ return `\n${pc.bold("Desktop app with Tauri:")}\n${pc.cyan("•")} Start desktop app: ${`cd apps/web && ${runCmd} desktop:dev`}\n${pc.cyan("•")} Build desktop app: ${`cd apps/web && ${runCmd} desktop:build`}\n${pc.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.\nSee: https://v2.tauri.app/start/prerequisites/`;
2063
+ }
2064
+ function getPwaInstructions() {
2065
+ return `\n${pc.bold("PWA with React Router v7:")}\n${pc.yellow("NOTE:")} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`;
2066
+ }
2067
+ function getStarlightInstructions(runCmd) {
2068
+ return `\n${pc.bold("Documentation with Starlight:")}\n${pc.cyan("•")} Start docs site: ${`cd apps/docs && ${runCmd} dev`}\n${pc.cyan("•")} Build docs site: ${`cd apps/docs && ${runCmd} build`}`;
2069
+ }
2070
+ function getNoOrmWarning() {
2071
+ return `\n${pc.yellow("WARNING:")} Database selected without an ORM. Features requiring database access (e.g., examples, auth) need manual setup.`;
2072
+ }
2073
+ function getBunWebNativeWarning() {
2074
+ return `\n${pc.yellow("WARNING:")} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.`;
2075
+ }
2076
+
2077
+ //#endregion
2078
+ //#region src/helpers/project-generation/project-config.ts
2079
+ async function updatePackageConfigurations(projectDir, options) {
2080
+ await updateRootPackageJson(projectDir, options);
2081
+ if (options.backend !== "convex") await updateServerPackageJson(projectDir, options);
2082
+ else await updateConvexPackageJson(projectDir, options);
2083
+ }
2084
+ async function updateRootPackageJson(projectDir, options) {
2085
+ const rootPackageJsonPath = path.join(projectDir, "package.json");
2086
+ if (!await fs.pathExists(rootPackageJsonPath)) return;
2087
+ const packageJson = await fs.readJson(rootPackageJsonPath);
2088
+ packageJson.name = options.projectName;
2089
+ if (!packageJson.scripts) packageJson.scripts = {};
2090
+ const scripts = packageJson.scripts;
2091
+ const backendPackageName = options.backend === "convex" ? `@${options.projectName}/backend` : "server";
2092
+ let serverDevScript = "";
2093
+ if (options.addons.includes("turborepo")) serverDevScript = `turbo -F ${backendPackageName} dev`;
2094
+ else if (options.packageManager === "bun") serverDevScript = `bun run --filter ${backendPackageName} dev`;
2095
+ else if (options.packageManager === "pnpm") serverDevScript = `pnpm --filter ${backendPackageName} dev`;
2096
+ else if (options.packageManager === "npm") serverDevScript = `npm run dev --workspace ${backendPackageName}`;
2097
+ let devScript = "";
2098
+ if (options.packageManager === "pnpm") devScript = "pnpm -r dev";
2099
+ else if (options.packageManager === "npm") devScript = "npm run dev --workspaces";
2100
+ else if (options.packageManager === "bun") devScript = "bun run --filter '*' dev";
2101
+ const needsDbScripts = options.backend !== "convex" && options.database !== "none" && options.orm !== "none" && options.orm !== "mongoose";
2102
+ if (options.addons.includes("turborepo")) {
2103
+ scripts.dev = "turbo dev";
2104
+ scripts.build = "turbo build";
2105
+ scripts["check-types"] = "turbo check-types";
2106
+ scripts["dev:native"] = "turbo -F native dev";
2107
+ scripts["dev:web"] = "turbo -F web dev";
2108
+ scripts["dev:server"] = serverDevScript;
2109
+ if (options.backend === "convex") scripts["dev:setup"] = `turbo -F ${backendPackageName} setup`;
2110
+ if (needsDbScripts) {
2111
+ scripts["db:push"] = `turbo -F ${backendPackageName} db:push`;
2112
+ scripts["db:studio"] = `turbo -F ${backendPackageName} db:studio`;
2113
+ if (options.orm === "prisma") {
2114
+ scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
2115
+ scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
2116
+ } else if (options.orm === "drizzle") {
2117
+ scripts["db:generate"] = `turbo -F ${backendPackageName} db:generate`;
2118
+ scripts["db:migrate"] = `turbo -F ${backendPackageName} db:migrate`;
2119
+ }
2120
+ }
2121
+ } else if (options.packageManager === "pnpm") {
2122
+ scripts.dev = devScript;
2123
+ scripts.build = "pnpm -r build";
2124
+ scripts["check-types"] = "pnpm -r check-types";
2125
+ scripts["dev:native"] = "pnpm --filter native dev";
2126
+ scripts["dev:web"] = "pnpm --filter web dev";
2127
+ scripts["dev:server"] = serverDevScript;
2128
+ if (options.backend === "convex") scripts["dev:setup"] = `pnpm --filter ${backendPackageName} setup`;
2129
+ if (needsDbScripts) {
2130
+ scripts["db:push"] = `pnpm --filter ${backendPackageName} db:push`;
2131
+ scripts["db:studio"] = `pnpm --filter ${backendPackageName} db:studio`;
2132
+ if (options.orm === "prisma") {
2133
+ scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
2134
+ scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
2135
+ } else if (options.orm === "drizzle") {
2136
+ scripts["db:generate"] = `pnpm --filter ${backendPackageName} db:generate`;
2137
+ scripts["db:migrate"] = `pnpm --filter ${backendPackageName} db:migrate`;
2138
+ }
2139
+ }
2140
+ } else if (options.packageManager === "npm") {
2141
+ scripts.dev = devScript;
2142
+ scripts.build = "npm run build --workspaces";
2143
+ scripts["check-types"] = "npm run check-types --workspaces";
2144
+ scripts["dev:native"] = "npm run dev --workspace native";
2145
+ scripts["dev:web"] = "npm run dev --workspace web";
2146
+ scripts["dev:server"] = serverDevScript;
2147
+ if (options.backend === "convex") scripts["dev:setup"] = `npm run setup --workspace ${backendPackageName}`;
2148
+ if (needsDbScripts) {
2149
+ scripts["db:push"] = `npm run db:push --workspace ${backendPackageName}`;
2150
+ scripts["db:studio"] = `npm run db:studio --workspace ${backendPackageName}`;
2151
+ if (options.orm === "prisma") {
2152
+ scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
2153
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
2154
+ } else if (options.orm === "drizzle") {
2155
+ scripts["db:generate"] = `npm run db:generate --workspace ${backendPackageName}`;
2156
+ scripts["db:migrate"] = `npm run db:migrate --workspace ${backendPackageName}`;
2157
+ }
2158
+ }
2159
+ } else if (options.packageManager === "bun") {
2160
+ scripts.dev = devScript;
2161
+ scripts.build = "bun run --filter '*' build";
2162
+ scripts["check-types"] = "bun run --filter '*' check-types";
2163
+ scripts["dev:native"] = "bun run --filter native dev";
2164
+ scripts["dev:web"] = "bun run --filter web dev";
2165
+ scripts["dev:server"] = serverDevScript;
2166
+ if (options.backend === "convex") scripts["dev:setup"] = `bun run --filter ${backendPackageName} setup`;
2167
+ if (needsDbScripts) {
2168
+ scripts["db:push"] = `bun run --filter ${backendPackageName} db:push`;
2169
+ scripts["db:studio"] = `bun run --filter ${backendPackageName} db:studio`;
2170
+ if (options.orm === "prisma") {
2171
+ scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
2172
+ scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
2173
+ } else if (options.orm === "drizzle") {
2174
+ scripts["db:generate"] = `bun run --filter ${backendPackageName} db:generate`;
2175
+ scripts["db:migrate"] = `bun run --filter ${backendPackageName} db:migrate`;
2176
+ }
2177
+ }
2178
+ }
2179
+ if (options.addons.includes("biome")) scripts.check = "biome check --write .";
2180
+ if (options.addons.includes("husky")) {
2181
+ scripts.prepare = "husky";
2182
+ packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
2183
+ }
2184
+ try {
2185
+ const { stdout } = await execa(options.packageManager, ["-v"], { cwd: projectDir });
2186
+ packageJson.packageManager = `${options.packageManager}@${stdout.trim()}`;
2187
+ } catch (_e) {
2188
+ log.warn(`Could not determine ${options.packageManager} version.`);
2189
+ }
2190
+ if (!packageJson.workspaces) packageJson.workspaces = [];
2191
+ const workspaces = packageJson.workspaces;
2192
+ if (options.backend === "convex") {
2193
+ if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
2194
+ const needsAppsDir = options.frontend.length > 0 || options.addons.includes("starlight");
2195
+ if (needsAppsDir && !workspaces.includes("apps/*")) workspaces.push("apps/*");
2196
+ } else {
2197
+ if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
2198
+ if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
2199
+ }
2200
+ await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
2201
+ }
2202
+ async function updateServerPackageJson(projectDir, options) {
2203
+ const serverPackageJsonPath = path.join(projectDir, "apps/server/package.json");
2204
+ if (!await fs.pathExists(serverPackageJsonPath)) return;
2205
+ const serverPackageJson = await fs.readJson(serverPackageJsonPath);
2206
+ if (!serverPackageJson.scripts) serverPackageJson.scripts = {};
2207
+ const scripts = serverPackageJson.scripts;
2208
+ if (options.database !== "none") {
2209
+ if (options.database === "sqlite" && options.orm === "drizzle") scripts["db:local"] = "turso dev --db-file local.db";
2210
+ if (options.orm === "prisma") {
2211
+ scripts["db:push"] = "prisma db push --schema ./prisma/schema";
2212
+ scripts["db:studio"] = "prisma studio";
2213
+ scripts["db:generate"] = "prisma generate --schema ./prisma/schema";
2214
+ scripts["db:migrate"] = "prisma migrate dev";
2215
+ } else if (options.orm === "drizzle") {
2216
+ scripts["db:push"] = "drizzle-kit push";
2217
+ scripts["db:studio"] = "drizzle-kit studio";
2218
+ scripts["db:generate"] = "drizzle-kit generate";
2219
+ scripts["db:migrate"] = "drizzle-kit migrate";
2220
+ }
2221
+ }
2222
+ await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
2223
+ }
2224
+ async function updateConvexPackageJson(projectDir, options) {
2225
+ const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
2226
+ if (!await fs.pathExists(convexPackageJsonPath)) return;
2227
+ const convexPackageJson = await fs.readJson(convexPackageJsonPath);
2228
+ convexPackageJson.name = `@${options.projectName}/backend`;
2229
+ if (!convexPackageJson.scripts) convexPackageJson.scripts = {};
2230
+ await fs.writeJson(convexPackageJsonPath, convexPackageJson, { spaces: 2 });
2231
+ }
2232
+ async function initializeGit(projectDir, useGit) {
2233
+ if (!useGit) return;
2234
+ const gitVersionResult = await $({
2235
+ cwd: projectDir,
2236
+ reject: false,
2237
+ stderr: "pipe"
2238
+ })`git --version`;
2239
+ if (gitVersionResult.exitCode !== 0) {
2240
+ log.warn(pc.yellow("Git is not installed"));
2241
+ return;
2242
+ }
2243
+ const result = await $({
2244
+ cwd: projectDir,
2245
+ reject: false,
2246
+ stderr: "pipe"
2247
+ })`git init`;
2248
+ if (result.exitCode !== 0) throw new Error(`Git initialization failed: ${result.stderr}`);
2249
+ }
2250
+
2251
+ //#endregion
2252
+ //#region src/utils/template-processor.ts
2253
+ /**
2254
+ * Processes a Handlebars template file and writes the output to the destination.
2255
+ * @param srcPath Path to the source .hbs template file.
2256
+ * @param destPath Path to write the processed file.
2257
+ * @param context Data to be passed to the Handlebars template.
2258
+ */
2259
+ async function processTemplate(srcPath, destPath, context) {
2260
+ try {
2261
+ const templateContent = await fs.readFile(srcPath, "utf-8");
2262
+ const template = handlebars.compile(templateContent);
2263
+ const processedContent = template(context);
2264
+ await fs.ensureDir(path.dirname(destPath));
2265
+ await fs.writeFile(destPath, processedContent);
2266
+ } catch (error) {
2267
+ consola.error(`Error processing template ${srcPath}:`, error);
2268
+ throw new Error(`Failed to process template ${srcPath}`);
2269
+ }
2270
+ }
2271
+ handlebars.registerHelper("or", (a, b) => a || b);
2272
+ handlebars.registerHelper("eq", (a, b) => a === b);
2273
+ handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
2274
+
2275
+ //#endregion
2276
+ //#region src/helpers/project-generation/template-manager.ts
2277
+ async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true) {
2278
+ const sourceFiles = await globby(sourcePattern, {
2279
+ cwd: baseSourceDir,
2280
+ dot: true,
2281
+ onlyFiles: true,
2282
+ absolute: false
2283
+ });
2284
+ for (const relativeSrcPath of sourceFiles) {
2285
+ const srcPath = path.join(baseSourceDir, relativeSrcPath);
2286
+ let relativeDestPath = relativeSrcPath;
2287
+ if (relativeSrcPath.endsWith(".hbs")) relativeDestPath = relativeSrcPath.slice(0, -4);
2288
+ const basename = path.basename(relativeSrcPath);
2289
+ if (basename === "_gitignore") relativeDestPath = path.join(path.dirname(relativeSrcPath), ".gitignore");
2290
+ else if (basename === "_npmrc") relativeDestPath = path.join(path.dirname(relativeSrcPath), ".npmrc");
2291
+ const destPath = path.join(destDir, relativeDestPath);
2292
+ try {
2293
+ await fs.ensureDir(path.dirname(destPath));
2294
+ if (!overwrite && await fs.pathExists(destPath)) continue;
2295
+ if (srcPath.endsWith(".hbs")) await processTemplate(srcPath, destPath, context);
2296
+ else await fs.copy(srcPath, destPath, { overwrite: true });
2297
+ } catch (_error) {}
2298
+ }
2299
+ }
2300
+ async function copyBaseTemplate(projectDir, context) {
2301
+ const templateDir = path.join(PKG_ROOT, "templates/base");
2302
+ await processAndCopyFiles(["**/*"], templateDir, projectDir, context);
2303
+ await fs.ensureDir(path.join(projectDir, "packages"));
2304
+ }
2305
+ async function setupFrontendTemplates(projectDir, context) {
2306
+ const hasReactWeb = context.frontend.some((f) => [
2307
+ "tanstack-router",
2308
+ "react-router",
2309
+ "tanstack-start",
2310
+ "next"
2311
+ ].includes(f));
2312
+ const hasNuxtWeb = context.frontend.includes("nuxt");
2313
+ const hasSvelteWeb = context.frontend.includes("svelte");
2314
+ const hasSolidWeb = context.frontend.includes("solid");
2315
+ const hasNativeWind = context.frontend.includes("native-nativewind");
2316
+ const hasUnistyles = context.frontend.includes("native-unistyles");
2317
+ const _hasNative = hasNativeWind || hasUnistyles;
2318
+ const isConvex = context.backend === "convex";
2319
+ if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) {
2320
+ const webAppDir = path.join(projectDir, "apps/web");
2321
+ await fs.ensureDir(webAppDir);
2322
+ if (hasReactWeb) {
2323
+ const webBaseDir = path.join(PKG_ROOT, "templates/frontend/react/web-base");
2324
+ if (await fs.pathExists(webBaseDir)) await processAndCopyFiles("**/*", webBaseDir, webAppDir, context);
2325
+ const reactFramework = context.frontend.find((f) => [
2326
+ "tanstack-router",
2327
+ "react-router",
2328
+ "tanstack-start",
2329
+ "next"
2330
+ ].includes(f));
2331
+ if (reactFramework) {
2332
+ const frameworkSrcDir = path.join(PKG_ROOT, `templates/frontend/react/${reactFramework}`);
2333
+ if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, webAppDir, context);
2334
+ if (!isConvex && context.api !== "none") {
2335
+ const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
2336
+ if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
2337
+ }
2338
+ }
2339
+ } else if (hasNuxtWeb) {
2340
+ const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
2341
+ if (await fs.pathExists(nuxtBaseDir)) await processAndCopyFiles("**/*", nuxtBaseDir, webAppDir, context);
2342
+ if (!isConvex && context.api === "orpc") {
2343
+ const apiWebNuxtDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/nuxt`);
2344
+ if (await fs.pathExists(apiWebNuxtDir)) await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
2345
+ }
2346
+ } else if (hasSvelteWeb) {
2347
+ const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
2348
+ if (await fs.pathExists(svelteBaseDir)) await processAndCopyFiles("**/*", svelteBaseDir, webAppDir, context);
2349
+ if (!isConvex && context.api === "orpc") {
2350
+ const apiWebSvelteDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/svelte`);
2351
+ if (await fs.pathExists(apiWebSvelteDir)) await processAndCopyFiles("**/*", apiWebSvelteDir, webAppDir, context);
2352
+ }
2353
+ } else if (hasSolidWeb) {
2354
+ const solidBaseDir = path.join(PKG_ROOT, "templates/frontend/solid");
2355
+ if (await fs.pathExists(solidBaseDir)) await processAndCopyFiles("**/*", solidBaseDir, webAppDir, context);
2356
+ if (!isConvex && context.api === "orpc") {
2357
+ const apiWebSolidDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/solid`);
2358
+ if (await fs.pathExists(apiWebSolidDir)) await processAndCopyFiles("**/*", apiWebSolidDir, webAppDir, context);
2359
+ }
2360
+ }
2361
+ }
2362
+ if (hasNativeWind || hasUnistyles) {
2363
+ const nativeAppDir = path.join(projectDir, "apps/native");
2364
+ await fs.ensureDir(nativeAppDir);
2365
+ const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/native-base");
2366
+ if (await fs.pathExists(nativeBaseCommonDir)) await processAndCopyFiles("**/*", nativeBaseCommonDir, nativeAppDir, context);
2367
+ let nativeFrameworkPath = "";
2368
+ if (hasNativeWind) nativeFrameworkPath = "nativewind";
2369
+ else if (hasUnistyles) nativeFrameworkPath = "unistyles";
2370
+ const nativeSpecificDir = path.join(PKG_ROOT, `templates/frontend/native/${nativeFrameworkPath}`);
2371
+ if (await fs.pathExists(nativeSpecificDir)) await processAndCopyFiles("**/*", nativeSpecificDir, nativeAppDir, context, true);
2372
+ if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
2373
+ const apiNativeSrcDir = path.join(PKG_ROOT, `templates/api/${context.api}/native`);
2374
+ if (await fs.pathExists(apiNativeSrcDir)) await processAndCopyFiles("**/*", apiNativeSrcDir, nativeAppDir, context);
2375
+ }
2376
+ }
2377
+ }
2378
+ async function setupBackendFramework(projectDir, context) {
2379
+ if (context.backend === "none") return;
2380
+ const serverAppDir = path.join(projectDir, "apps/server");
2381
+ if (context.backend === "convex") {
2382
+ if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
2383
+ const convexBackendDestDir = path.join(projectDir, "packages/backend");
2384
+ const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
2385
+ await fs.ensureDir(convexBackendDestDir);
2386
+ if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
2387
+ return;
2388
+ }
2389
+ await fs.ensureDir(serverAppDir);
2390
+ const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/server-base");
2391
+ if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
2392
+ const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
2393
+ if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
2394
+ if (context.api !== "none") {
2395
+ const apiServerBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/base`);
2396
+ if (await fs.pathExists(apiServerBaseDir)) await processAndCopyFiles("**/*", apiServerBaseDir, serverAppDir, context, true);
2397
+ const apiServerFrameworkDir = path.join(PKG_ROOT, `templates/api/${context.api}/server/${context.backend}`);
2398
+ if (await fs.pathExists(apiServerFrameworkDir)) await processAndCopyFiles("**/*", apiServerFrameworkDir, serverAppDir, context, true);
2399
+ }
2400
+ }
2401
+ async function setupDbOrmTemplates(projectDir, context) {
2402
+ if (context.backend === "convex" || context.orm === "none" || context.database === "none") return;
2403
+ const serverAppDir = path.join(projectDir, "apps/server");
2404
+ await fs.ensureDir(serverAppDir);
2405
+ const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
2406
+ if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, serverAppDir, context);
2407
+ }
2408
+ async function setupAuthTemplate(projectDir, context) {
2409
+ if (context.backend === "convex" || !context.auth) return;
2410
+ const serverAppDir = path.join(projectDir, "apps/server");
2411
+ const webAppDir = path.join(projectDir, "apps/web");
2412
+ const nativeAppDir = path.join(projectDir, "apps/native");
2413
+ const serverAppDirExists = await fs.pathExists(serverAppDir);
2414
+ const webAppDirExists = await fs.pathExists(webAppDir);
2415
+ const nativeAppDirExists = await fs.pathExists(nativeAppDir);
2416
+ const hasReactWeb = context.frontend.some((f) => [
2417
+ "tanstack-router",
2418
+ "react-router",
2419
+ "tanstack-start",
2420
+ "next"
2421
+ ].includes(f));
2422
+ const hasNuxtWeb = context.frontend.includes("nuxt");
2423
+ const hasSvelteWeb = context.frontend.includes("svelte");
2424
+ const hasSolidWeb = context.frontend.includes("solid");
2425
+ const hasNativeWind = context.frontend.includes("native-nativewind");
2426
+ const hasUnistyles = context.frontend.includes("native-unistyles");
2427
+ const hasNative = hasNativeWind || hasUnistyles;
2428
+ if (serverAppDirExists) {
2429
+ const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base");
2430
+ if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
2431
+ if (context.backend === "next") {
2432
+ const authServerNextSrc = path.join(PKG_ROOT, "templates/auth/server/next");
2433
+ if (await fs.pathExists(authServerNextSrc)) await processAndCopyFiles("**/*", authServerNextSrc, serverAppDir, context);
2434
+ }
2435
+ if (context.orm !== "none" && context.database !== "none") {
2436
+ const orm = context.orm;
2437
+ const db = context.database;
2438
+ let authDbSrc = "";
2439
+ if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/drizzle/${db}`);
2440
+ else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/prisma/${db}`);
2441
+ else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/server/db/mongoose/${db}`);
2442
+ if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context);
2443
+ else if (authDbSrc) {}
2444
+ }
2445
+ }
2446
+ if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
2447
+ if (hasReactWeb) {
2448
+ const authWebBaseSrc = path.join(PKG_ROOT, "templates/auth/web/react/base");
2449
+ if (await fs.pathExists(authWebBaseSrc)) await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
2450
+ const reactFramework = context.frontend.find((f) => [
2451
+ "tanstack-router",
2452
+ "react-router",
2453
+ "tanstack-start",
2454
+ "next"
2455
+ ].includes(f));
2456
+ if (reactFramework) {
2457
+ const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/web/react/${reactFramework}`);
2458
+ if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
2459
+ }
2460
+ } else if (hasNuxtWeb) {
2461
+ const authWebNuxtSrc = path.join(PKG_ROOT, "templates/auth/web/nuxt");
2462
+ if (await fs.pathExists(authWebNuxtSrc)) await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
2463
+ } else if (hasSvelteWeb) {
2464
+ if (context.api === "orpc") {
2465
+ const authWebSvelteSrc = path.join(PKG_ROOT, "templates/auth/web/svelte");
2466
+ if (await fs.pathExists(authWebSvelteSrc)) await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context);
2467
+ }
2468
+ } else if (hasSolidWeb) {
2469
+ if (context.api === "orpc") {
2470
+ const authWebSolidSrc = path.join(PKG_ROOT, "templates/auth/web/solid");
2471
+ if (await fs.pathExists(authWebSolidSrc)) await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context);
2472
+ }
2473
+ }
2474
+ }
2475
+ if (hasNative && nativeAppDirExists) {
2476
+ const authNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/native/native-base");
2477
+ if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
2478
+ let nativeFrameworkAuthPath = "";
2479
+ if (hasNativeWind) nativeFrameworkAuthPath = "nativewind";
2480
+ else if (hasUnistyles) nativeFrameworkAuthPath = "unistyles";
2481
+ if (nativeFrameworkAuthPath) {
2482
+ const authNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/native/${nativeFrameworkAuthPath}`);
2483
+ if (await fs.pathExists(authNativeFrameworkSrc)) await processAndCopyFiles("**/*", authNativeFrameworkSrc, nativeAppDir, context);
2484
+ }
2485
+ }
2486
+ }
2487
+ async function setupAddonsTemplate(projectDir, context) {
2488
+ if (!context.addons || context.addons.length === 0) return;
2489
+ for (const addon of context.addons) {
2490
+ if (addon === "none") continue;
2491
+ let addonSrcDir = path.join(PKG_ROOT, `templates/addons/${addon}`);
2492
+ let addonDestDir = projectDir;
2493
+ if (addon === "pwa") {
2494
+ const webAppDir = path.join(projectDir, "apps/web");
2495
+ if (!await fs.pathExists(webAppDir)) continue;
2496
+ addonDestDir = webAppDir;
2497
+ if (context.frontend.includes("next")) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/next");
2498
+ else if (context.frontend.some((f) => [
2499
+ "tanstack-router",
2500
+ "react-router",
2501
+ "solid"
2502
+ ].includes(f))) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
2503
+ else continue;
2504
+ }
2505
+ if (await fs.pathExists(addonSrcDir)) await processAndCopyFiles("**/*", addonSrcDir, addonDestDir, context);
2506
+ }
2507
+ }
2508
+ async function setupExamplesTemplate(projectDir, context) {
2509
+ if (!context.examples || context.examples.length === 0 || context.examples[0] === "none") return;
2510
+ const serverAppDir = path.join(projectDir, "apps/server");
2511
+ const webAppDir = path.join(projectDir, "apps/web");
2512
+ const serverAppDirExists = await fs.pathExists(serverAppDir);
2513
+ const webAppDirExists = await fs.pathExists(webAppDir);
2514
+ const hasReactWeb = context.frontend.some((f) => [
2515
+ "tanstack-router",
2516
+ "react-router",
2517
+ "tanstack-start",
2518
+ "next"
2519
+ ].includes(f));
2520
+ const hasNuxtWeb = context.frontend.includes("nuxt");
2521
+ const hasSvelteWeb = context.frontend.includes("svelte");
2522
+ const hasSolidWeb = context.frontend.includes("solid");
2523
+ for (const example of context.examples) {
2524
+ if (example === "none") continue;
2525
+ const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
2526
+ if (serverAppDirExists && context.backend !== "convex" && context.backend !== "none") {
2527
+ const exampleServerSrc = path.join(exampleBaseDir, "server");
2528
+ if (example === "ai" && context.backend === "next") {
2529
+ const aiNextServerSrc = path.join(exampleServerSrc, "next");
2530
+ if (await fs.pathExists(aiNextServerSrc)) await processAndCopyFiles("**/*", aiNextServerSrc, serverAppDir, context, false);
2531
+ }
2532
+ if (context.orm !== "none" && context.database !== "none") {
2533
+ const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
2534
+ if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, serverAppDir, context, false);
2535
+ const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
2536
+ if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, serverAppDir, context, false);
2537
+ }
2538
+ const ignorePatterns = [`${context.orm}/**`];
2539
+ if (example === "ai" && context.backend === "next") ignorePatterns.push("next/**");
2540
+ const generalServerFiles = await globby(["**/*.ts", "**/*.hbs"], {
2541
+ cwd: exampleServerSrc,
2542
+ onlyFiles: true,
2543
+ deep: 1,
2544
+ ignore: ignorePatterns
2545
+ });
2546
+ for (const file of generalServerFiles) {
2547
+ const srcPath = path.join(exampleServerSrc, file);
2548
+ const destPath = path.join(serverAppDir, file.replace(".hbs", ""));
2549
+ try {
2550
+ if (srcPath.endsWith(".hbs")) await processTemplate(srcPath, destPath, context);
2551
+ else if (!await fs.pathExists(destPath)) await fs.copy(srcPath, destPath, { overwrite: false });
2552
+ } catch (_error) {}
2553
+ }
2554
+ }
2555
+ if (webAppDirExists) {
2556
+ if (hasReactWeb) {
2557
+ const exampleWebSrc = path.join(exampleBaseDir, "web/react");
2558
+ if (await fs.pathExists(exampleWebSrc)) {
2559
+ const reactFramework = context.frontend.find((f) => [
2560
+ "next",
2561
+ "react-router",
2562
+ "tanstack-router",
2563
+ "tanstack-start"
2564
+ ].includes(f));
2565
+ if (reactFramework) {
2566
+ const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
2567
+ if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
2568
+ }
2569
+ }
2570
+ } else if (hasNuxtWeb) {
2571
+ const exampleWebNuxtSrc = path.join(exampleBaseDir, "web/nuxt");
2572
+ if (await fs.pathExists(exampleWebNuxtSrc)) await processAndCopyFiles("**/*", exampleWebNuxtSrc, webAppDir, context, false);
2573
+ } else if (hasSvelteWeb) {
2574
+ const exampleWebSvelteSrc = path.join(exampleBaseDir, "web/svelte");
2575
+ if (await fs.pathExists(exampleWebSvelteSrc)) await processAndCopyFiles("**/*", exampleWebSvelteSrc, webAppDir, context, false);
2576
+ } else if (hasSolidWeb) {
2577
+ const exampleWebSolidSrc = path.join(exampleBaseDir, "web/solid");
2578
+ if (await fs.pathExists(exampleWebSolidSrc)) await processAndCopyFiles("**/*", exampleWebSolidSrc, webAppDir, context, false);
2579
+ }
2580
+ }
2581
+ }
2582
+ }
2583
+ async function handleExtras(projectDir, context) {
2584
+ const extrasDir = path.join(PKG_ROOT, "templates/extras");
2585
+ const hasNativeWind = context.frontend.includes("native-nativewind");
2586
+ const hasUnistyles = context.frontend.includes("native-unistyles");
2587
+ const hasNative = hasNativeWind || hasUnistyles;
2588
+ if (context.packageManager === "pnpm") {
2589
+ const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml");
2590
+ const pnpmWorkspaceDest = path.join(projectDir, "pnpm-workspace.yaml");
2591
+ if (await fs.pathExists(pnpmWorkspaceSrc)) await fs.copy(pnpmWorkspaceSrc, pnpmWorkspaceDest);
2592
+ }
2593
+ if (context.packageManager === "pnpm" && (hasNative || context.frontend.includes("nuxt"))) {
2594
+ const npmrcTemplateSrc = path.join(extrasDir, "_npmrc.hbs");
2595
+ const npmrcDest = path.join(projectDir, ".npmrc");
2596
+ if (await fs.pathExists(npmrcTemplateSrc)) await processTemplate(npmrcTemplateSrc, npmrcDest, context);
2597
+ }
2598
+ }
2599
+
2600
+ //#endregion
2601
+ //#region src/helpers/project-generation/create-project.ts
2602
+ async function createProject(options) {
2603
+ const projectDir = options.projectDir;
2604
+ const isConvex = options.backend === "convex";
2605
+ try {
2606
+ await fs.ensureDir(projectDir);
2607
+ await copyBaseTemplate(projectDir, options);
2608
+ await setupFrontendTemplates(projectDir, options);
2609
+ await setupBackendFramework(projectDir, options);
2610
+ if (!isConvex) {
2611
+ await setupDbOrmTemplates(projectDir, options);
2612
+ await setupAuthTemplate(projectDir, options);
2613
+ }
2614
+ if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
2615
+ await setupAddonsTemplate(projectDir, options);
2616
+ await setupApi(options);
2617
+ if (!isConvex) {
2618
+ await setupBackendDependencies(options);
2619
+ await setupDatabase(options);
2620
+ await setupRuntime(options);
2621
+ if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
2622
+ }
2623
+ if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
2624
+ if (!isConvex && options.auth) await setupAuth(options);
2625
+ await handleExtras(projectDir, options);
2626
+ await setupEnvironmentVariables(options);
2627
+ await updatePackageConfigurations(projectDir, options);
2628
+ await createReadme(projectDir, options);
2629
+ await initializeGit(projectDir, options.git);
2630
+ log.success("Project template successfully scaffolded!");
2631
+ if (options.install) await installDependencies({
2632
+ projectDir,
2633
+ packageManager: options.packageManager,
2634
+ addons: options.addons
2635
+ });
2636
+ displayPostInstallInstructions({
2637
+ ...options,
2638
+ depsInstalled: options.install
2639
+ });
2640
+ return projectDir;
2641
+ } catch (error) {
2642
+ if (error instanceof Error) {
2643
+ cancel(pc.red(`Error during project creation: ${error.message}`));
2644
+ console.error(error.stack);
2645
+ process.exit(1);
2646
+ } else {
2647
+ cancel(pc.red(`An unexpected error occurred: ${String(error)}`));
2648
+ console.error(error);
2649
+ process.exit(1);
2650
+ }
2651
+ }
2652
+ }
2653
+
2654
+ //#endregion
2655
+ //#region src/prompts/addons.ts
2656
+ async function getAddonsChoice(addons, frontends) {
2657
+ if (addons !== void 0) return addons;
2658
+ const hasCompatiblePwaFrontend = frontends?.includes("react-router") || frontends?.includes("tanstack-router") || frontends?.includes("solid") || frontends?.includes("next");
2659
+ const hasCompatibleTauriFrontend = frontends?.includes("react-router") || frontends?.includes("tanstack-router") || frontends?.includes("nuxt") || frontends?.includes("svelte") || frontends?.includes("solid") || frontends?.includes("next");
2660
+ const allPossibleOptions = [
2661
+ {
2662
+ value: "turborepo",
2663
+ label: "Turborepo (Recommended)",
2664
+ hint: "Optimize builds for monorepos"
2665
+ },
2666
+ {
2667
+ value: "starlight",
2668
+ label: "Starlight",
2669
+ hint: "Add Astro Starlight documentation site"
2670
+ },
2671
+ {
2672
+ value: "biome",
2673
+ label: "Biome",
2674
+ hint: "Add Biome for linting and formatting"
2675
+ },
2676
+ {
2677
+ value: "husky",
2678
+ label: "Husky",
2679
+ hint: "Add Git hooks with Husky, lint-staged (requires Biome)"
2680
+ },
2681
+ {
2682
+ value: "pwa",
2683
+ label: "PWA (Progressive Web App)",
2684
+ hint: "Make your app installable and work offline"
2685
+ },
2686
+ {
2687
+ value: "tauri",
2688
+ label: "Tauri Desktop App",
2689
+ hint: "Build native desktop apps from your web frontend"
2690
+ }
2691
+ ];
2692
+ const options = allPossibleOptions.filter((option) => {
2693
+ if (option.value === "pwa") return hasCompatiblePwaFrontend;
2694
+ if (option.value === "tauri") return hasCompatibleTauriFrontend;
2695
+ return true;
2696
+ });
2697
+ const initialValues = DEFAULT_CONFIG.addons.filter((addonValue) => options.some((opt) => opt.value === addonValue));
2698
+ const response = await multiselect({
2699
+ message: "Select addons",
2700
+ options,
2701
+ initialValues,
2702
+ required: false
2703
+ });
2704
+ if (isCancel(response)) {
2705
+ cancel(pc.red("Operation cancelled"));
2706
+ process.exit(0);
2707
+ }
2708
+ if (response.includes("husky") && !response.includes("biome")) response.push("biome");
2709
+ return response;
2710
+ }
2711
+
2712
+ //#endregion
2713
+ //#region src/prompts/api.ts
2714
+ async function getApiChoice(Api, frontend, backend) {
2715
+ if (backend === "convex" || backend === "none") return "none";
2716
+ if (Api) return Api;
2717
+ const includesNuxt = frontend?.includes("nuxt");
2718
+ const includesSvelte = frontend?.includes("svelte");
2719
+ const includesSolid = frontend?.includes("solid");
2720
+ let apiOptions = [
2721
+ {
2722
+ value: "trpc",
2723
+ label: "tRPC",
2724
+ hint: "End-to-end typesafe APIs made easy"
2725
+ },
2726
+ {
2727
+ value: "orpc",
2728
+ label: "oRPC",
2729
+ hint: "End-to-end type-safe APIs that adhere to OpenAPI standards"
2730
+ },
2731
+ {
2732
+ value: "none",
2733
+ label: "None",
2734
+ hint: "No API layer (e.g. for full-stack frameworks like Next.js with Route Handlers)"
2735
+ }
2736
+ ];
2737
+ if (includesNuxt || includesSvelte || includesSolid) apiOptions = [{
2738
+ value: "orpc",
2739
+ label: "oRPC",
2740
+ hint: `End-to-end type-safe APIs (Recommended for ${includesNuxt ? "Nuxt" : includesSvelte ? "Svelte" : "Solid"} frontend)`
2741
+ }, {
2742
+ value: "none",
2743
+ label: "None",
2744
+ hint: "No API layer"
2745
+ }];
2746
+ const apiType = await select({
2747
+ message: "Select API type",
2748
+ options: apiOptions,
2749
+ initialValue: apiOptions[0].value
2750
+ });
2751
+ if (isCancel(apiType)) {
2752
+ cancel(pc.red("Operation cancelled"));
2753
+ process.exit(0);
2754
+ }
2755
+ return apiType;
2756
+ }
2757
+
2758
+ //#endregion
2759
+ //#region src/prompts/auth.ts
2760
+ async function getAuthChoice(auth, hasDatabase, backend) {
2761
+ if (backend === "convex") return false;
2762
+ if (!hasDatabase) return false;
2763
+ if (auth !== void 0) return auth;
2764
+ const response = await confirm({
2765
+ message: "Add authentication with Better-Auth?",
2766
+ initialValue: DEFAULT_CONFIG.auth
2767
+ });
2768
+ if (isCancel(response)) {
2769
+ cancel(pc.red("Operation cancelled"));
2770
+ process.exit(0);
2771
+ }
2772
+ return response;
2773
+ }
2774
+
2775
+ //#endregion
2776
+ //#region src/prompts/backend.ts
2777
+ async function getBackendFrameworkChoice(backendFramework, frontends) {
2778
+ if (backendFramework !== void 0) return backendFramework;
2779
+ const hasIncompatibleFrontend = frontends?.some((f) => f === "nuxt" || f === "solid");
2780
+ const backendOptions = [
2781
+ {
2782
+ value: "hono",
2783
+ label: "Hono",
2784
+ hint: "Lightweight, ultrafast web framework"
2785
+ },
2786
+ {
2787
+ value: "next",
2788
+ label: "Next.js",
2789
+ hint: "Nextjs API routes"
2790
+ },
2791
+ {
2792
+ value: "express",
2793
+ label: "Express",
2794
+ hint: "Fast, unopinionated, minimalist web framework for Node.js"
2795
+ },
2796
+ {
2797
+ value: "fastify",
2798
+ label: "Fastify",
2799
+ hint: "Fast, low-overhead web framework for Node.js"
2800
+ },
2801
+ {
2802
+ value: "elysia",
2803
+ label: "Elysia",
2804
+ hint: "Ergonomic web framework for building backend servers"
2805
+ }
2806
+ ];
2807
+ if (!hasIncompatibleFrontend) backendOptions.push({
2808
+ value: "convex",
2809
+ label: "Convex",
2810
+ hint: "Reactive backend-as-a-service platform"
2811
+ });
2812
+ backendOptions.push({
2813
+ value: "none",
2814
+ label: "None",
2815
+ hint: "No backend server"
2816
+ });
2817
+ let initialValue = DEFAULT_CONFIG.backend;
2818
+ if (hasIncompatibleFrontend && initialValue === "convex") initialValue = "hono";
2819
+ const response = await select({
2820
+ message: "Select backend",
2821
+ options: backendOptions,
2822
+ initialValue
2823
+ });
2824
+ if (isCancel(response)) {
2825
+ cancel(pc.red("Operation cancelled"));
2826
+ process.exit(0);
2827
+ }
2828
+ return response;
2829
+ }
2830
+
2831
+ //#endregion
2832
+ //#region src/prompts/database.ts
2833
+ async function getDatabaseChoice(database, backend) {
2834
+ if (backend === "convex" || backend === "none") return "none";
2835
+ if (database !== void 0) return database;
2836
+ const response = await select({
2837
+ message: "Select database",
2838
+ options: [
2839
+ {
2840
+ value: "none",
2841
+ label: "None",
2842
+ hint: "No database setup"
2843
+ },
2844
+ {
2845
+ value: "sqlite",
2846
+ label: "SQLite",
2847
+ hint: "lightweight, server-less, embedded relational database"
2848
+ },
2849
+ {
2850
+ value: "postgres",
2851
+ label: "PostgreSQL",
2852
+ hint: "powerful, open source object-relational database system"
2853
+ },
2854
+ {
2855
+ value: "mysql",
2856
+ label: "MySQL",
2857
+ hint: "popular open-source relational database system"
2858
+ },
2859
+ {
2860
+ value: "mongodb",
2861
+ label: "MongoDB",
2862
+ hint: "open-source NoSQL database that stores data in JSON-like documents called BSON"
2863
+ }
2864
+ ],
2865
+ initialValue: DEFAULT_CONFIG.database
2866
+ });
2867
+ if (isCancel(response)) {
2868
+ cancel(pc.red("Operation cancelled"));
2869
+ process.exit(0);
2870
+ }
2871
+ return response;
2872
+ }
2873
+
2874
+ //#endregion
2875
+ //#region src/prompts/database-setup.ts
2876
+ async function getDBSetupChoice(databaseType, dbSetup, orm, backend) {
2877
+ if (backend === "convex") return "none";
2878
+ if (dbSetup !== void 0) return dbSetup;
2879
+ if (databaseType === "none") return "none";
2880
+ if (databaseType === "sqlite" && orm === "prisma") return "none";
2881
+ let options = [];
2882
+ if (databaseType === "sqlite") options = [{
2883
+ value: "turso",
2884
+ label: "Turso",
2885
+ hint: "SQLite for Production. Powered by libSQL"
2886
+ }, {
2887
+ value: "none",
2888
+ label: "None",
2889
+ hint: "Manual setup"
2890
+ }];
2891
+ else if (databaseType === "postgres") options = [
2892
+ {
2893
+ value: "neon",
2894
+ label: "Neon Postgres",
2895
+ hint: "Serverless Postgres with branching capability"
2896
+ },
2897
+ {
2898
+ value: "supabase",
2899
+ label: "Supabase",
2900
+ hint: "Local Supabase stack (requires Docker)"
2901
+ },
2902
+ ...orm === "prisma" ? [{
2903
+ value: "prisma-postgres",
2904
+ label: "Prisma Postgres",
2905
+ hint: "Instant Postgres for Global Applications"
2906
+ }] : [],
2907
+ {
2908
+ value: "none",
2909
+ label: "None",
2910
+ hint: "Manual setup"
2911
+ }
2912
+ ];
2913
+ else if (databaseType === "mongodb") options = [{
2914
+ value: "mongodb-atlas",
2915
+ label: "MongoDB Atlas",
2916
+ hint: "The most effective way to deploy MongoDB"
2917
+ }, {
2918
+ value: "none",
2919
+ label: "None",
2920
+ hint: "Manual setup"
2921
+ }];
2922
+ else return "none";
2923
+ const response = await select({
2924
+ message: `Select ${databaseType} setup option`,
2925
+ options,
2926
+ initialValue: "none"
2927
+ });
2928
+ if (isCancel(response)) {
2929
+ cancel(pc.red("Operation cancelled"));
2930
+ process.exit(0);
2931
+ }
2932
+ return response;
2933
+ }
2934
+
2935
+ //#endregion
2936
+ //#region src/prompts/examples.ts
2937
+ async function getExamplesChoice(examples, database, frontends, backend, api) {
2938
+ if (api === "none") return [];
2939
+ if (examples !== void 0) return examples;
2940
+ if (backend === "convex") return ["todo"];
2941
+ if (backend === "none") return [];
2942
+ if (database === "none") return [];
2943
+ const onlyNative = frontends && frontends.length === 1 && (frontends[0] === "native-nativewind" || frontends[0] === "native-unistyles");
2944
+ if (onlyNative) return [];
2945
+ const hasWebFrontend = frontends?.some((f) => [
2946
+ "react-router",
2947
+ "tanstack-router",
2948
+ "tanstack-start",
2949
+ "next",
2950
+ "nuxt",
2951
+ "svelte",
2952
+ "solid"
2953
+ ].includes(f)) ?? false;
2954
+ const noFrontendSelected = !frontends || frontends.length === 0;
2955
+ if (!hasWebFrontend && !noFrontendSelected) return [];
2956
+ let response = [];
2957
+ const options = [{
2958
+ value: "todo",
2959
+ label: "Todo App",
2960
+ hint: "A simple CRUD example app"
2961
+ }];
2962
+ if (backend !== "elysia" && !frontends?.includes("solid")) options.push({
2963
+ value: "ai",
2964
+ label: "AI Chat",
2965
+ hint: "A simple AI chat interface using AI SDK"
2966
+ });
2967
+ response = await multiselect({
2968
+ message: "Include examples",
2969
+ options,
2970
+ required: false,
2971
+ initialValues: DEFAULT_CONFIG.examples
2972
+ });
2973
+ if (isCancel(response)) {
2974
+ cancel(pc.red("Operation cancelled"));
2975
+ process.exit(0);
2976
+ }
2977
+ return response;
2978
+ }
2979
+
2980
+ //#endregion
2981
+ //#region src/prompts/frontend.ts
2982
+ async function getFrontendChoice(frontendOptions, backend) {
2983
+ if (frontendOptions !== void 0) return frontendOptions;
2984
+ const frontendTypes = await multiselect({
2985
+ message: "Select platforms to develop for",
2986
+ options: [{
2987
+ value: "web",
2988
+ label: "Web",
2989
+ hint: "React, Vue or Svelte Web Application"
2990
+ }, {
2991
+ value: "native",
2992
+ label: "Native",
2993
+ hint: "Create a React Native/Expo app"
2994
+ }],
2995
+ required: false,
2996
+ initialValues: ["web"]
2997
+ });
2998
+ if (isCancel(frontendTypes)) {
2999
+ cancel(pc.red("Operation cancelled"));
3000
+ process.exit(0);
3001
+ }
3002
+ const result = [];
3003
+ if (frontendTypes.includes("web")) {
3004
+ const allWebOptions = [
3005
+ {
3006
+ value: "tanstack-router",
3007
+ label: "TanStack Router",
3008
+ hint: "Modern and scalable routing for React Applications"
3009
+ },
3010
+ {
3011
+ value: "react-router",
3012
+ label: "React Router",
3013
+ hint: "A user‑obsessed, standards‑focused, multi‑strategy router"
3014
+ },
3015
+ {
3016
+ value: "next",
3017
+ label: "Next.js",
3018
+ hint: "The React Framework for the Web"
3019
+ },
3020
+ {
3021
+ value: "nuxt",
3022
+ label: "Nuxt",
3023
+ hint: "The Progressive Web Framework for Vue.js"
3024
+ },
3025
+ {
3026
+ value: "svelte",
3027
+ label: "Svelte",
3028
+ hint: "web development for the rest of us"
3029
+ },
3030
+ {
3031
+ value: "solid",
3032
+ label: "Solid",
3033
+ hint: "Simple and performant reactivity for building user interfaces"
3034
+ },
3035
+ {
3036
+ value: "tanstack-start",
3037
+ label: "TanStack Start (beta)",
3038
+ hint: "SSR, Server Functions, API Routes and more with TanStack Router"
3039
+ }
3040
+ ];
3041
+ const webOptions = allWebOptions.filter((option) => {
3042
+ if (backend === "convex") return option.value !== "nuxt" && option.value !== "solid";
3043
+ return true;
3044
+ });
3045
+ const webFramework = await select({
3046
+ message: "Choose frontend",
3047
+ options: webOptions,
3048
+ initialValue: DEFAULT_CONFIG.frontend[0]
3049
+ });
3050
+ if (isCancel(webFramework)) {
3051
+ cancel(pc.red("Operation cancelled"));
3052
+ process.exit(0);
3053
+ }
3054
+ result.push(webFramework);
3055
+ }
3056
+ if (frontendTypes.includes("native")) {
3057
+ const nativeFramework = await select({
3058
+ message: "Choose native framework",
3059
+ options: [{
3060
+ value: "native-nativewind",
3061
+ label: "NativeWind",
3062
+ hint: "Use Tailwind CSS for React Native"
3063
+ }, {
3064
+ value: "native-unistyles",
3065
+ label: "Unistyles",
3066
+ hint: "Consistent styling for React Native"
3067
+ }],
3068
+ initialValue: "native-nativewind"
3069
+ });
3070
+ if (isCancel(nativeFramework)) {
3071
+ cancel(pc.red("Operation cancelled"));
3072
+ process.exit(0);
3073
+ }
3074
+ result.push(nativeFramework);
3075
+ }
3076
+ return result;
3077
+ }
3078
+
3079
+ //#endregion
3080
+ //#region src/prompts/git.ts
3081
+ async function getGitChoice(git) {
3082
+ if (git !== void 0) return git;
3083
+ const response = await confirm({
3084
+ message: "Initialize git repository?",
3085
+ initialValue: DEFAULT_CONFIG.git
3086
+ });
3087
+ if (isCancel(response)) {
3088
+ cancel(pc.red("Operation cancelled"));
3089
+ process.exit(0);
3090
+ }
3091
+ return response;
3092
+ }
3093
+
3094
+ //#endregion
3095
+ //#region src/prompts/install.ts
3096
+ async function getinstallChoice(install) {
3097
+ if (install !== void 0) return install;
3098
+ const response = await confirm({
3099
+ message: "Install dependencies?",
3100
+ initialValue: DEFAULT_CONFIG.install
3101
+ });
3102
+ if (isCancel(response)) {
3103
+ cancel(pc.red("Operation cancelled"));
3104
+ process.exit(0);
3105
+ }
3106
+ return response;
3107
+ }
3108
+
3109
+ //#endregion
3110
+ //#region src/prompts/orm.ts
3111
+ const ormOptions = {
3112
+ prisma: {
3113
+ value: "prisma",
3114
+ label: "Prisma",
3115
+ hint: "Powerful, feature-rich ORM"
3116
+ },
3117
+ mongoose: {
3118
+ value: "mongoose",
3119
+ label: "Mongoose",
3120
+ hint: "Elegant object modeling tool"
3121
+ },
3122
+ drizzle: {
3123
+ value: "drizzle",
3124
+ label: "Drizzle",
3125
+ hint: "Lightweight and performant TypeScript ORM"
3126
+ }
3127
+ };
3128
+ async function getORMChoice(orm, hasDatabase, database, backend) {
3129
+ if (backend === "convex") return "none";
3130
+ if (!hasDatabase) return "none";
3131
+ if (orm !== void 0) return orm;
3132
+ const options = [...database === "mongodb" ? [ormOptions.prisma, ormOptions.mongoose] : [ormOptions.drizzle, ormOptions.prisma]];
3133
+ const response = await select({
3134
+ message: "Select ORM",
3135
+ options,
3136
+ initialValue: database === "mongodb" ? "prisma" : DEFAULT_CONFIG.orm
3137
+ });
3138
+ if (isCancel(response)) {
3139
+ cancel(pc.red("Operation cancelled"));
3140
+ process.exit(0);
3141
+ }
3142
+ return response;
3143
+ }
3144
+
3145
+ //#endregion
3146
+ //#region src/prompts/package-manager.ts
3147
+ async function getPackageManagerChoice(packageManager) {
3148
+ if (packageManager !== void 0) return packageManager;
3149
+ const detectedPackageManager = getUserPkgManager();
3150
+ const response = await select({
3151
+ message: "Choose package manager",
3152
+ options: [
3153
+ {
3154
+ value: "npm",
3155
+ label: "npm",
3156
+ hint: "Node Package Manager"
3157
+ },
3158
+ {
3159
+ value: "pnpm",
3160
+ label: "pnpm",
3161
+ hint: "Fast, disk space efficient package manager"
3162
+ },
3163
+ {
3164
+ value: "bun",
3165
+ label: "bun",
3166
+ hint: "All-in-one JavaScript runtime & toolkit"
3167
+ }
3168
+ ],
3169
+ initialValue: detectedPackageManager
3170
+ });
3171
+ if (isCancel(response)) {
3172
+ cancel(pc.red("Operation cancelled"));
3173
+ process.exit(0);
3174
+ }
3175
+ return response;
3176
+ }
3177
+
3178
+ //#endregion
3179
+ //#region src/prompts/runtime.ts
3180
+ async function getRuntimeChoice(runtime, backend) {
3181
+ if (backend === "convex" || backend === "none") return "none";
3182
+ if (runtime !== void 0) return runtime;
3183
+ if (backend === "next") return "node";
3184
+ const response = await select({
3185
+ message: "Select runtime",
3186
+ options: [{
3187
+ value: "bun",
3188
+ label: "Bun",
3189
+ hint: "Fast all-in-one JavaScript runtime"
3190
+ }, {
3191
+ value: "node",
3192
+ label: "Node.js",
3193
+ hint: "Traditional Node.js runtime"
3194
+ }],
3195
+ initialValue: DEFAULT_CONFIG.runtime
3196
+ });
3197
+ if (isCancel(response)) {
3198
+ cancel(pc.red("Operation cancelled"));
3199
+ process.exit(0);
3200
+ }
3201
+ return response;
3202
+ }
3203
+
3204
+ //#endregion
3205
+ //#region src/prompts/config-prompts.ts
3206
+ async function gatherConfig(flags, projectName, projectDir, relativePath) {
3207
+ const result = await group({
3208
+ frontend: () => getFrontendChoice(flags.frontend, flags.backend),
3209
+ backend: ({ results }) => getBackendFrameworkChoice(flags.backend, results.frontend),
3210
+ runtime: ({ results }) => getRuntimeChoice(flags.runtime, results.backend),
3211
+ database: ({ results }) => getDatabaseChoice(flags.database, results.backend),
3212
+ orm: ({ results }) => getORMChoice(flags.orm, results.database !== "none", results.database, results.backend),
3213
+ api: ({ results }) => getApiChoice(flags.api, results.frontend, results.backend),
3214
+ auth: ({ results }) => getAuthChoice(flags.auth, results.database !== "none", results.backend),
3215
+ addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend),
3216
+ examples: ({ results }) => getExamplesChoice(flags.examples, results.database, results.frontend, results.backend, results.api),
3217
+ dbSetup: ({ results }) => getDBSetupChoice(results.database ?? "none", flags.dbSetup, results.orm, results.backend),
3218
+ git: () => getGitChoice(flags.git),
3219
+ packageManager: () => getPackageManagerChoice(flags.packageManager),
3220
+ install: () => getinstallChoice(flags.install)
3221
+ }, { onCancel: () => {
3222
+ cancel(pc.red("Operation cancelled"));
3223
+ process.exit(0);
3224
+ } });
3225
+ if (result.backend === "convex") {
3226
+ result.runtime = "none";
3227
+ result.database = "none";
3228
+ result.orm = "none";
3229
+ result.api = "none";
3230
+ result.auth = false;
3231
+ result.dbSetup = "none";
3232
+ result.examples = ["todo"];
3233
+ }
3234
+ if (result.backend === "none") {
3235
+ result.runtime = "none";
3236
+ result.database = "none";
3237
+ result.orm = "none";
3238
+ result.api = "none";
3239
+ result.auth = false;
3240
+ result.dbSetup = "none";
3241
+ result.examples = [];
3242
+ }
3243
+ return {
3244
+ projectName,
3245
+ projectDir,
3246
+ relativePath,
3247
+ frontend: result.frontend,
3248
+ backend: result.backend,
3249
+ runtime: result.runtime,
3250
+ database: result.database,
3251
+ orm: result.orm,
3252
+ auth: result.auth,
3253
+ addons: result.addons,
3254
+ examples: result.examples,
3255
+ git: result.git,
3256
+ packageManager: result.packageManager,
3257
+ install: result.install,
3258
+ dbSetup: result.dbSetup,
3259
+ api: result.api
3260
+ };
3261
+ }
3262
+
3263
+ //#endregion
3264
+ //#region src/prompts/project-name.ts
3265
+ const INVALID_CHARS = [
3266
+ "<",
3267
+ ">",
3268
+ ":",
3269
+ "\"",
3270
+ "|",
3271
+ "?",
3272
+ "*"
3273
+ ];
3274
+ const MAX_LENGTH = 255;
3275
+ function validateDirectoryName(name) {
3276
+ if (name === ".") return void 0;
3277
+ if (!name) return "Project name cannot be empty";
3278
+ if (name.length > MAX_LENGTH) return `Project name must be less than ${MAX_LENGTH} characters`;
3279
+ if (INVALID_CHARS.some((char) => name.includes(char))) return "Project name contains invalid characters";
3280
+ if (name.startsWith(".") || name.startsWith("-")) return "Project name cannot start with a dot or dash";
3281
+ if (name.toLowerCase() === "node_modules") return "Project name is reserved";
3282
+ return void 0;
3283
+ }
3284
+ async function getProjectName(initialName) {
3285
+ if (initialName) {
3286
+ if (initialName === ".") return initialName;
3287
+ const finalDirName = path.basename(initialName);
3288
+ const validationError = validateDirectoryName(finalDirName);
3289
+ if (!validationError) return initialName;
3290
+ }
3291
+ let isValid = false;
3292
+ let projectPath = "";
3293
+ let defaultName = DEFAULT_CONFIG.projectName;
3294
+ let counter = 1;
3295
+ while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
3296
+ defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
3297
+ counter++;
3298
+ }
3299
+ while (!isValid) {
3300
+ const response = await text({
3301
+ message: "Enter your project name or path (relative to current directory)",
3302
+ placeholder: defaultName,
3303
+ initialValue: initialName,
3304
+ defaultValue: defaultName,
3305
+ validate: (value) => {
3306
+ const nameToUse = value.trim() || defaultName;
3307
+ const finalDirName = path.basename(nameToUse);
3308
+ const validationError = validateDirectoryName(finalDirName);
3309
+ if (validationError) return validationError;
3310
+ if (nameToUse !== ".") {
3311
+ const projectDir = path.resolve(process.cwd(), nameToUse);
3312
+ if (!projectDir.startsWith(process.cwd())) return "Project path must be within current directory";
3313
+ }
3314
+ return void 0;
3315
+ }
3316
+ });
3317
+ if (isCancel(response)) {
3318
+ cancel(pc.red("Operation cancelled."));
3319
+ process.exit(0);
3320
+ }
3321
+ projectPath = response || defaultName;
3322
+ isValid = true;
3323
+ }
3324
+ return projectPath;
3325
+ }
3326
+
3327
+ //#endregion
3328
+ //#region src/utils/analytics.ts
3329
+ const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
3330
+ const POSTHOG_HOST = "https://us.i.posthog.com";
3331
+ async function trackProjectCreation(config) {
3332
+ const posthog = new PostHog(POSTHOG_API_KEY, {
3333
+ host: POSTHOG_HOST,
3334
+ flushAt: 1,
3335
+ flushInterval: 0,
3336
+ privacyMode: true,
3337
+ disableGeoip: true
3338
+ });
3339
+ try {
3340
+ const sessionId = `cli_${Date.now()}_${crypto.randomUUID().replace(/-/g, "")}`;
3341
+ const { projectName, projectDir, relativePath,...safeConfig } = config;
3342
+ posthog.capture({
3343
+ distinctId: sessionId,
3344
+ event: "project_created",
3345
+ properties: {
3346
+ ...safeConfig,
3347
+ cli_version: getLatestCLIVersion(),
3348
+ node_version: process.version,
3349
+ platform: process.platform,
3350
+ $ip: null
3351
+ }
3352
+ });
3353
+ } catch (error) {
3354
+ consola.debug("Analytics tracking failed:", error);
3355
+ } finally {
3356
+ await posthog.shutdown();
3357
+ }
3358
+ }
3359
+
3360
+ //#endregion
3361
+ //#region src/utils/display-config.ts
3362
+ function displayConfig(config) {
3363
+ const configDisplay = [];
3364
+ if (config.projectName) configDisplay.push(`${pc.blue("Project Name:")} ${config.projectName}`);
3365
+ if (config.frontend !== void 0) {
3366
+ const frontend = Array.isArray(config.frontend) ? config.frontend : [config.frontend];
3367
+ const frontendText = frontend.length > 0 && frontend[0] !== void 0 ? frontend.join(", ") : "none";
3368
+ configDisplay.push(`${pc.blue("Frontend:")} ${frontendText}`);
3369
+ }
3370
+ if (config.backend !== void 0) configDisplay.push(`${pc.blue("Backend:")} ${String(config.backend)}`);
3371
+ if (config.runtime !== void 0) configDisplay.push(`${pc.blue("Runtime:")} ${String(config.runtime)}`);
3372
+ if (config.api !== void 0) configDisplay.push(`${pc.blue("API:")} ${String(config.api)}`);
3373
+ if (config.database !== void 0) configDisplay.push(`${pc.blue("Database:")} ${String(config.database)}`);
3374
+ if (config.orm !== void 0) configDisplay.push(`${pc.blue("ORM:")} ${String(config.orm)}`);
3375
+ if (config.auth !== void 0) {
3376
+ const authText = typeof config.auth === "boolean" ? config.auth ? "Yes" : "No" : String(config.auth);
3377
+ configDisplay.push(`${pc.blue("Authentication:")} ${authText}`);
3378
+ }
3379
+ if (config.addons !== void 0) {
3380
+ const addons = Array.isArray(config.addons) ? config.addons : [config.addons];
3381
+ const addonsText = addons.length > 0 && addons[0] !== void 0 ? addons.join(", ") : "none";
3382
+ configDisplay.push(`${pc.blue("Addons:")} ${addonsText}`);
3383
+ }
3384
+ if (config.examples !== void 0) {
3385
+ const examples = Array.isArray(config.examples) ? config.examples : [config.examples];
3386
+ const examplesText = examples.length > 0 && examples[0] !== void 0 ? examples.join(", ") : "none";
3387
+ configDisplay.push(`${pc.blue("Examples:")} ${examplesText}`);
3388
+ }
3389
+ if (config.git !== void 0) {
3390
+ const gitText = typeof config.git === "boolean" ? config.git ? "Yes" : "No" : String(config.git);
3391
+ configDisplay.push(`${pc.blue("Git Init:")} ${gitText}`);
3392
+ }
3393
+ if (config.packageManager !== void 0) configDisplay.push(`${pc.blue("Package Manager:")} ${String(config.packageManager)}`);
3394
+ if (config.install !== void 0) {
3395
+ const installText = typeof config.install === "boolean" ? config.install ? "Yes" : "No" : String(config.install);
3396
+ configDisplay.push(`${pc.blue("Install Dependencies:")} ${installText}`);
3397
+ }
3398
+ if (config.dbSetup !== void 0) configDisplay.push(`${pc.blue("Database Setup:")} ${String(config.dbSetup)}`);
3399
+ if (configDisplay.length === 0) return pc.yellow("No configuration selected.");
3400
+ return configDisplay.join("\n");
3401
+ }
3402
+
3403
+ //#endregion
3404
+ //#region src/utils/generate-reproducible-command.ts
3405
+ function generateReproducibleCommand(config) {
3406
+ const flags = [];
3407
+ if (config.frontend && config.frontend.length > 0) flags.push(`--frontend ${config.frontend.join(" ")}`);
3408
+ else flags.push("--frontend none");
3409
+ flags.push(`--backend ${config.backend}`);
3410
+ flags.push(`--runtime ${config.runtime}`);
3411
+ flags.push(`--database ${config.database}`);
3412
+ flags.push(`--orm ${config.orm}`);
3413
+ flags.push(`--api ${config.api}`);
3414
+ flags.push(config.auth ? "--auth" : "--no-auth");
3415
+ if (config.addons && config.addons.length > 0) flags.push(`--addons ${config.addons.join(" ")}`);
3416
+ else flags.push("--addons none");
3417
+ if (config.examples && config.examples.length > 0) flags.push(`--examples ${config.examples.join(" ")}`);
3418
+ else flags.push("--examples none");
3419
+ flags.push(`--db-setup ${config.dbSetup}`);
3420
+ flags.push(config.git ? "--git" : "--no-git");
3421
+ flags.push(`--package-manager ${config.packageManager}`);
3422
+ flags.push(config.install ? "--install" : "--no-install");
3423
+ let baseCommand = "";
3424
+ const pkgManager = config.packageManager;
3425
+ if (pkgManager === "npm") baseCommand = "npx create-better-t-stack@latest";
3426
+ else if (pkgManager === "pnpm") baseCommand = "pnpm create better-t-stack@latest";
3427
+ else if (pkgManager === "bun") baseCommand = "bun create better-t-stack@latest";
3428
+ const projectPathArg = config.relativePath ? ` ${config.relativePath}` : "";
3429
+ return `${baseCommand}${projectPathArg} ${flags.join(" ")}`;
3430
+ }
3431
+
3432
+ //#endregion
3433
+ //#region src/utils/render-title.ts
3434
+ const TITLE_TEXT = `
169
3435
  ██████╗ ███████╗████████╗████████╗███████╗██████╗
170
3436
  ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
171
3437
  ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
@@ -179,9 +3445,399 @@ ${n} db:push
179
3445
  ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
180
3446
  ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
181
3447
  ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
182
- `,Ft={pink:`#F5C2E7`,mauve:`#CBA6F7`,red:`#F38BA8`,maroon:`#E78284`,peach:`#FAB387`,yellow:`#F9E2AF`,green:`#A6E3A1`,teal:`#94E2D5`,sky:`#89DCEB`,sapphire:`#74C7EC`,lavender:`#B4BEFE`},It=()=>{let e=process.stdout.columns||80,t=$.split(`
183
- `),n=Math.max(...t.map(e=>e.length));if(e<n){let e=`
3448
+ `;
3449
+ const catppuccinTheme = {
3450
+ pink: "#F5C2E7",
3451
+ mauve: "#CBA6F7",
3452
+ red: "#F38BA8",
3453
+ maroon: "#E78284",
3454
+ peach: "#FAB387",
3455
+ yellow: "#F9E2AF",
3456
+ green: "#A6E3A1",
3457
+ teal: "#94E2D5",
3458
+ sky: "#89DCEB",
3459
+ sapphire: "#74C7EC",
3460
+ lavender: "#B4BEFE"
3461
+ };
3462
+ const renderTitle = () => {
3463
+ const terminalWidth = process.stdout.columns || 80;
3464
+ const titleLines = TITLE_TEXT.split("\n");
3465
+ const titleWidth = Math.max(...titleLines.map((line) => line.length));
3466
+ if (terminalWidth < titleWidth) {
3467
+ const simplifiedTitle = `
184
3468
  ╔══════════════════╗
185
3469
  ║ Better T Stack ║
186
3470
  ╚══════════════════╝
187
- `;console.log(T(Object.values(Ft)).multiline(e))}else console.log(T(Object.values(Ft)).multiline($))};function Lt(t,n){let r={},i=new Set(Object.keys(t).filter(e=>e!==`_`&&e!==`$0`));if(t.api&&(r.api=t.api,t.api===`none`&&t.examples&&!(t.examples.length===1&&t.examples[0]===`none`)&&(m.fatal(`Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.`),process.exit(1))),t.backend&&(r.backend=t.backend),i.has(`backend`)&&r.backend&&r.backend!==`convex`&&r.backend!==`none`&&i.has(`runtime`)&&t.runtime===`none`&&(m.fatal(`'--runtime none' is only supported with '--backend convex' or '--backend none'. Please choose 'bun', 'node', or remove the --runtime flag.`),process.exit(1)),t.database&&(r.database=t.database),t.orm&&(r.orm=t.orm),t.auth!==void 0&&(r.auth=t.auth),t.git!==void 0&&(r.git=t.git),t.install!==void 0&&(r.install=t.install),t.runtime&&(r.runtime=t.runtime),t.dbSetup&&(r.dbSetup=t.dbSetup),t.packageManager&&(r.packageManager=t.packageManager),n?r.projectName=n:t.projectDirectory&&(r.projectName=e.basename(e.resolve(process.cwd(),t.projectDirectory))),t.frontend&&t.frontend.length>0)if(t.frontend.includes(`none`))t.frontend.length>1&&(m.fatal(`Cannot combine 'none' with other frontend options.`),process.exit(1)),r.frontend=[];else{let e=t.frontend.filter(e=>e!==`none`),n=e.filter(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`||e===`solid`),i=e.filter(e=>e===`native-nativewind`||e===`native-unistyles`);n.length>1&&(m.fatal(`Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid`),process.exit(1)),i.length>1&&(m.fatal(`Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles`),process.exit(1)),r.frontend=e}if(t.addons&&t.addons.length>0&&(t.addons.includes(`none`)?(t.addons.length>1&&(m.fatal(`Cannot combine 'none' with other addons.`),process.exit(1)),r.addons=[]):r.addons=t.addons.filter(e=>e!==`none`)),t.examples&&t.examples.length>0&&(t.examples.includes(`none`)?(t.examples.length>1&&(m.fatal(`Cannot combine 'none' with other examples.`),process.exit(1)),r.examples=[]):(r.examples=t.examples.filter(e=>e!==`none`),t.examples.includes(`none`)&&r.backend!==`convex`&&(r.examples=[]))),r.backend===`convex`){let e=[];if(i.has(`auth`)&&t.auth===!0&&e.push(`--auth`),i.has(`database`)&&t.database!==`none`&&e.push(`--database ${t.database}`),i.has(`orm`)&&t.orm!==`none`&&e.push(`--orm ${t.orm}`),i.has(`api`)&&t.api!==`none`&&e.push(`--api ${t.api}`),i.has(`runtime`)&&t.runtime!==`none`&&e.push(`--runtime ${t.runtime}`),i.has(`dbSetup`)&&t.dbSetup!==`none`&&e.push(`--db-setup ${t.dbSetup}`),e.length>0&&(m.fatal(`The following flags are incompatible with '--backend convex': ${e.join(`, `)}. Please remove them.`),process.exit(1)),i.has(`frontend`)&&t.frontend){let e=t.frontend.filter(e=>e===`nuxt`||e===`solid`);e.length>0&&(m.fatal(`The following frontends are not compatible with '--backend convex': ${e.join(`, `)}. Please choose a different frontend or backend.`),process.exit(1))}r.auth=!1,r.database=`none`,r.orm=`none`,r.api=`none`,r.runtime=`none`,r.dbSetup=`none`,r.examples=[`todo`]}else if(r.backend===`none`){let e=[];i.has(`auth`)&&t.auth===!0&&e.push(`--auth`),i.has(`database`)&&t.database!==`none`&&e.push(`--database ${t.database}`),i.has(`orm`)&&t.orm!==`none`&&e.push(`--orm ${t.orm}`),i.has(`api`)&&t.api!==`none`&&e.push(`--api ${t.api}`),i.has(`runtime`)&&t.runtime!==`none`&&e.push(`--runtime ${t.runtime}`),i.has(`dbSetup`)&&t.dbSetup!==`none`&&e.push(`--db-setup ${t.dbSetup}`),e.length>0&&(m.fatal(`The following flags are incompatible with '--backend none': ${e.join(`, `)}. Please remove them.`),process.exit(1)),r.auth=!1,r.database=`none`,r.orm=`none`,r.api=`none`,r.runtime=`none`,r.dbSetup=`none`,r.examples=[]}else r.database===`none`&&(i.has(`orm`)&&t.orm!==`none`&&(m.fatal(`'--orm ${t.orm}' is incompatible with '--database none'. Please use '--orm none' or choose a database.`),process.exit(1)),i.has(`auth`)&&t.auth===!0&&(m.fatal(`'--auth' requires a database. Cannot use '--auth' with '--database none'.`),process.exit(1)),i.has(`dbSetup`)&&t.dbSetup!==`none`&&(m.fatal(`'--db-setup ${t.dbSetup}' requires a database. Cannot use with '--database none'.`),process.exit(1)),r.orm=`none`,r.auth=!1,r.dbSetup=`none`,o.info(`Due to '--database none', '--orm' has been automatically set to 'none'.`),o.info(`Due to '--database none', '--auth' has been automatically set to 'false'.`),o.info(`Due to '--database none', '--db-setup' has been automatically set to 'none'.`)),r.orm===`mongoose`&&(i.has(`database`)?r.database!==`mongodb`&&(m.fatal(`'--orm mongoose' requires '--database mongodb'. Cannot use '--orm mongoose' with '--database ${r.database}'.`),process.exit(1)):(r.database=`mongodb`,o.info(`Due to '--orm mongoose', '--database' has been automatically set to 'mongodb'.`))),r.dbSetup&&(r.dbSetup===`turso`?(i.has(`database`)?r.database!==`sqlite`&&(m.fatal(`'--db-setup turso' requires '--database sqlite'. Cannot use with '--database ${r.database}'.`),process.exit(1)):(r.database=`sqlite`,o.info(`Due to '--db-setup turso', '--database' has been automatically set to 'sqlite'.`)),i.has(`orm`)?r.orm!==`drizzle`&&(m.fatal(`'--db-setup turso' requires '--orm drizzle'. Cannot use with '--orm ${r.orm}'.`),process.exit(1)):(r.orm=`drizzle`,o.info(`Due to '--db-setup turso', '--orm' has been automatically set to 'drizzle'.`))):r.dbSetup===`prisma-postgres`?(i.has(`database`)?r.database!==`postgres`&&(m.fatal(`'--db-setup prisma-postgres' requires '--database postgres'. Cannot use with '--database ${r.database}'.`),process.exit(1)):(r.database=`postgres`,o.info(`Due to '--db-setup prisma-postgres', '--database' has been automatically set to 'postgres'.`)),i.has(`orm`)?r.orm!==`prisma`&&(m.fatal(`'--db-setup prisma-postgres' requires '--orm prisma'. Cannot use with '--orm ${r.orm}'.`),process.exit(1)):(r.orm=`prisma`,o.info(`Due to '--db-setup prisma-postgres', '--orm' has been automatically set to 'prisma'.`))):r.dbSetup===`supabase`?i.has(`database`)?r.database!==`postgres`&&(m.fatal(`'--db-setup supabase' requires '--database postgres'. Cannot use with '--database ${r.database}'.`),process.exit(1)):(r.database=`postgres`,o.info(`Due to '--db-setup supabase', '--database' has been automatically set to 'postgres'.`)):r.dbSetup===`neon`?i.has(`database`)?r.database!==`postgres`&&(m.fatal(`'--db-setup neon' requires '--database postgres'. Cannot use with '--database ${r.database}'.`),process.exit(1)):(r.database=`postgres`,o.info(`Due to '--db-setup neon', '--database' has been automatically set to 'postgres'.`)):r.dbSetup===`mongodb-atlas`&&(i.has(`database`)?r.database!==`mongodb`&&(m.fatal(`'--db-setup mongodb-atlas' requires '--database mongodb'. Cannot use with '--database ${r.database}'.`),process.exit(1)):(r.database=`mongodb`,o.info(`Due to '--db-setup mongodb-atlas', '--database' has been automatically set to 'mongodb'.`)))),r.database===`mongodb`&&r.orm===`drizzle`&&(m.fatal(`'--database mongodb' is incompatible with '--orm drizzle'. Use '--orm mongoose' or '--orm prisma' with MongoDB.`),process.exit(1));return r}const Rt=()=>process.exit(0);process.on(`SIGINT`,Rt),process.on(`SIGTERM`,Rt);async function zt(){let n=Date.now();try{let r=await te(),s=r.projectDirectory;It(),i(g.magenta(`Creating a new Better-T-Stack project`));let l,f,p,_,v=!1;if(r.yes&&s)l=s;else if(r.yes){let t=A.relativePath,n=1;for(;h.pathExistsSync(e.resolve(process.cwd(),t))&&h.readdirSync(e.resolve(process.cwd(),t)).length>0;)t=`${A.projectName}-${n}`,n++;l=t}else l=await Z(s);for(;;){let n=e.resolve(process.cwd(),l),r=h.pathExistsSync(n),i=r&&h.readdirSync(n).length>0;if(!i){f=l,v=!1;break}o.warn(`Directory "${g.yellow(l)}" already exists and is not empty.`);let s=await u({message:`What would you like to do?`,options:[{value:`overwrite`,label:`Overwrite`,hint:`Empty the directory and create the project`},{value:`merge`,label:`Merge`,hint:`Create project files inside, potentially overwriting conflicts`},{value:`rename`,label:`Choose a different name/path`,hint:`Keep the existing directory and create a new one`},{value:`cancel`,label:`Cancel`,hint:`Abort the process`}],initialValue:`rename`});if(a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),s===`overwrite`){f=l,v=!0;break}if(s===`merge`){f=l,v=!1,o.info(`Proceeding into existing directory "${g.yellow(l)}". Files may be overwritten.`);break}s===`rename`?(o.info(`Please choose a different project name or path.`),l=await Z(void 0)):s===`cancel`&&(t(g.red(`Operation cancelled.`)),process.exit(0))}if(f===`.`?(p=process.cwd(),_=e.basename(p)):(p=e.resolve(process.cwd(),f),_=e.basename(p)),v){let e=d();e.start(`Clearing directory "${p}"...`);try{await h.emptyDir(p),e.stop(`Directory "${p}" cleared.`)}catch(t){e.stop(g.red(`Failed to clear directory "${p}".`)),m.error(t),process.exit(1)}}else await h.ensureDir(p);let y=Lt(r,_),{projectName:b,...x}=y;!r.yes&&Object.keys(x).length>0&&(o.info(g.yellow(`Using these pre-selected options:`)),o.message(Q(x)),o.message(``));let S;r.yes?(S={...A,...y,projectName:_,projectDir:p,relativePath:f},S.backend===`convex`?o.info(`Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo`):S.backend===`none`&&o.info(`Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none`),o.info(g.yellow(`Using default/flag options (config prompts skipped):`)),o.message(Q(S)),o.message(``)):S=await Mt(y,_,p,f),await vt(S),o.success(g.blue(`You can reproduce this setup with the following command:\n${Pt(S)}`));let C=((Date.now()-n)/1e3).toFixed(2);c(g.magenta(`Project created successfully in ${g.bold(C)} seconds!`))}catch(e){e instanceof Error?(e.name===`YError`?t(g.red(`Invalid arguments: ${e.message}`)):(m.error(`An unexpected error occurred: ${e.message}`),e.message.includes(`is only supported with`)||m.error(e.stack)),process.exit(1)):(m.error(`An unexpected error occurred.`),console.error(e),process.exit(1))}}zt().catch(e=>{m.error(`Aborting installation due to unexpected error...`),e instanceof Error?!e.message.includes(`is only supported with`)&&!e.message.includes(`incompatible with`)&&!e.message.includes(`requires`)&&!e.message.includes(`Cannot use`)&&!e.message.includes(`Cannot select multiple`)&&!e.message.includes(`Cannot combine`)&&!e.message.includes(`not supported`)&&(m.error(e.message),m.error(e.stack)):console.error(e),process.exit(1)});
3471
+ `;
3472
+ console.log(gradient(Object.values(catppuccinTheme)).multiline(simplifiedTitle));
3473
+ } else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
3474
+ };
3475
+
3476
+ //#endregion
3477
+ //#region src/validation.ts
3478
+ function processAndValidateFlags(options, projectName) {
3479
+ const config = {};
3480
+ const providedFlags = new Set(Object.keys(options).filter((key) => key !== "_" && key !== "$0"));
3481
+ if (options.api) {
3482
+ config.api = options.api;
3483
+ if (options.api === "none") {
3484
+ if (options.examples && !(options.examples.length === 1 && options.examples[0] === "none")) {
3485
+ consola$1.fatal("Cannot use '--examples' when '--api' is set to 'none'. Please remove the --examples flag or choose an API type.");
3486
+ process.exit(1);
3487
+ }
3488
+ }
3489
+ }
3490
+ if (options.backend) config.backend = options.backend;
3491
+ if (providedFlags.has("backend") && config.backend && config.backend !== "convex" && config.backend !== "none") {
3492
+ if (providedFlags.has("runtime") && options.runtime === "none") {
3493
+ consola$1.fatal(`'--runtime none' is only supported with '--backend convex' or '--backend none'. Please choose 'bun', 'node', or remove the --runtime flag.`);
3494
+ process.exit(1);
3495
+ }
3496
+ }
3497
+ if (options.database) config.database = options.database;
3498
+ if (options.orm) config.orm = options.orm;
3499
+ if (options.auth !== void 0) config.auth = options.auth;
3500
+ if (options.git !== void 0) config.git = options.git;
3501
+ if (options.install !== void 0) config.install = options.install;
3502
+ if (options.runtime) config.runtime = options.runtime;
3503
+ if (options.dbSetup) config.dbSetup = options.dbSetup;
3504
+ if (options.packageManager) config.packageManager = options.packageManager;
3505
+ if (projectName) config.projectName = projectName;
3506
+ else if (options.projectDirectory) config.projectName = path.basename(path.resolve(process.cwd(), options.projectDirectory));
3507
+ if (options.frontend && options.frontend.length > 0) if (options.frontend.includes("none")) {
3508
+ if (options.frontend.length > 1) {
3509
+ consola$1.fatal(`Cannot combine 'none' with other frontend options.`);
3510
+ process.exit(1);
3511
+ }
3512
+ config.frontend = [];
3513
+ } else {
3514
+ const validOptions = options.frontend.filter((f) => f !== "none");
3515
+ const webFrontends = validOptions.filter((f) => f === "tanstack-router" || f === "react-router" || f === "tanstack-start" || f === "next" || f === "nuxt" || f === "svelte" || f === "solid");
3516
+ const nativeFrontends = validOptions.filter((f) => f === "native-nativewind" || f === "native-unistyles");
3517
+ if (webFrontends.length > 1) {
3518
+ consola$1.fatal("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid");
3519
+ process.exit(1);
3520
+ }
3521
+ if (nativeFrontends.length > 1) {
3522
+ consola$1.fatal("Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles");
3523
+ process.exit(1);
3524
+ }
3525
+ config.frontend = validOptions;
3526
+ }
3527
+ if (options.addons && options.addons.length > 0) if (options.addons.includes("none")) {
3528
+ if (options.addons.length > 1) {
3529
+ consola$1.fatal(`Cannot combine 'none' with other addons.`);
3530
+ process.exit(1);
3531
+ }
3532
+ config.addons = [];
3533
+ } else config.addons = options.addons.filter((addon) => addon !== "none");
3534
+ if (options.examples && options.examples.length > 0) if (options.examples.includes("none")) {
3535
+ if (options.examples.length > 1) {
3536
+ consola$1.fatal("Cannot combine 'none' with other examples.");
3537
+ process.exit(1);
3538
+ }
3539
+ config.examples = [];
3540
+ } else {
3541
+ config.examples = options.examples.filter((ex) => ex !== "none");
3542
+ if (options.examples.includes("none") && config.backend !== "convex") config.examples = [];
3543
+ }
3544
+ if (config.backend === "convex") {
3545
+ const incompatibleFlags = [];
3546
+ if (providedFlags.has("auth") && options.auth === true) incompatibleFlags.push("--auth");
3547
+ if (providedFlags.has("database") && options.database !== "none") incompatibleFlags.push(`--database ${options.database}`);
3548
+ if (providedFlags.has("orm") && options.orm !== "none") incompatibleFlags.push(`--orm ${options.orm}`);
3549
+ if (providedFlags.has("api") && options.api !== "none") incompatibleFlags.push(`--api ${options.api}`);
3550
+ if (providedFlags.has("runtime") && options.runtime !== "none") incompatibleFlags.push(`--runtime ${options.runtime}`);
3551
+ if (providedFlags.has("dbSetup") && options.dbSetup !== "none") incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
3552
+ if (incompatibleFlags.length > 0) {
3553
+ consola$1.fatal(`The following flags are incompatible with '--backend convex': ${incompatibleFlags.join(", ")}. Please remove them.`);
3554
+ process.exit(1);
3555
+ }
3556
+ if (providedFlags.has("frontend") && options.frontend) {
3557
+ const incompatibleFrontends = options.frontend.filter((f) => f === "nuxt" || f === "solid");
3558
+ if (incompatibleFrontends.length > 0) {
3559
+ consola$1.fatal(`The following frontends are not compatible with '--backend convex': ${incompatibleFrontends.join(", ")}. Please choose a different frontend or backend.`);
3560
+ process.exit(1);
3561
+ }
3562
+ }
3563
+ config.auth = false;
3564
+ config.database = "none";
3565
+ config.orm = "none";
3566
+ config.api = "none";
3567
+ config.runtime = "none";
3568
+ config.dbSetup = "none";
3569
+ config.examples = ["todo"];
3570
+ } else if (config.backend === "none") {
3571
+ const incompatibleFlags = [];
3572
+ if (providedFlags.has("auth") && options.auth === true) incompatibleFlags.push("--auth");
3573
+ if (providedFlags.has("database") && options.database !== "none") incompatibleFlags.push(`--database ${options.database}`);
3574
+ if (providedFlags.has("orm") && options.orm !== "none") incompatibleFlags.push(`--orm ${options.orm}`);
3575
+ if (providedFlags.has("api") && options.api !== "none") incompatibleFlags.push(`--api ${options.api}`);
3576
+ if (providedFlags.has("runtime") && options.runtime !== "none") incompatibleFlags.push(`--runtime ${options.runtime}`);
3577
+ if (providedFlags.has("dbSetup") && options.dbSetup !== "none") incompatibleFlags.push(`--db-setup ${options.dbSetup}`);
3578
+ if (incompatibleFlags.length > 0) {
3579
+ consola$1.fatal(`The following flags are incompatible with '--backend none': ${incompatibleFlags.join(", ")}. Please remove them.`);
3580
+ process.exit(1);
3581
+ }
3582
+ config.auth = false;
3583
+ config.database = "none";
3584
+ config.orm = "none";
3585
+ config.api = "none";
3586
+ config.runtime = "none";
3587
+ config.dbSetup = "none";
3588
+ config.examples = [];
3589
+ } else {
3590
+ if (config.database === "none") {
3591
+ if (providedFlags.has("orm") && options.orm !== "none") {
3592
+ consola$1.fatal(`'--orm ${options.orm}' is incompatible with '--database none'. Please use '--orm none' or choose a database.`);
3593
+ process.exit(1);
3594
+ }
3595
+ if (providedFlags.has("auth") && options.auth === true) {
3596
+ consola$1.fatal(`'--auth' requires a database. Cannot use '--auth' with '--database none'.`);
3597
+ process.exit(1);
3598
+ }
3599
+ if (providedFlags.has("dbSetup") && options.dbSetup !== "none") {
3600
+ consola$1.fatal(`'--db-setup ${options.dbSetup}' requires a database. Cannot use with '--database none'.`);
3601
+ process.exit(1);
3602
+ }
3603
+ config.orm = "none";
3604
+ config.auth = false;
3605
+ config.dbSetup = "none";
3606
+ log.info("Due to '--database none', '--orm' has been automatically set to 'none'.");
3607
+ log.info("Due to '--database none', '--auth' has been automatically set to 'false'.");
3608
+ log.info("Due to '--database none', '--db-setup' has been automatically set to 'none'.");
3609
+ }
3610
+ if (config.orm === "mongoose") {
3611
+ if (!providedFlags.has("database")) {
3612
+ config.database = "mongodb";
3613
+ log.info("Due to '--orm mongoose', '--database' has been automatically set to 'mongodb'.");
3614
+ } else if (config.database !== "mongodb") {
3615
+ consola$1.fatal(`'--orm mongoose' requires '--database mongodb'. Cannot use '--orm mongoose' with '--database ${config.database}'.`);
3616
+ process.exit(1);
3617
+ }
3618
+ }
3619
+ if (config.dbSetup) {
3620
+ if (config.dbSetup === "turso") {
3621
+ if (!providedFlags.has("database")) {
3622
+ config.database = "sqlite";
3623
+ log.info("Due to '--db-setup turso', '--database' has been automatically set to 'sqlite'.");
3624
+ } else if (config.database !== "sqlite") {
3625
+ consola$1.fatal(`'--db-setup turso' requires '--database sqlite'. Cannot use with '--database ${config.database}'.`);
3626
+ process.exit(1);
3627
+ }
3628
+ if (!providedFlags.has("orm")) {
3629
+ config.orm = "drizzle";
3630
+ log.info("Due to '--db-setup turso', '--orm' has been automatically set to 'drizzle'.");
3631
+ } else if (config.orm !== "drizzle") {
3632
+ consola$1.fatal(`'--db-setup turso' requires '--orm drizzle'. Cannot use with '--orm ${config.orm}'.`);
3633
+ process.exit(1);
3634
+ }
3635
+ } else if (config.dbSetup === "prisma-postgres") {
3636
+ if (!providedFlags.has("database")) {
3637
+ config.database = "postgres";
3638
+ log.info("Due to '--db-setup prisma-postgres', '--database' has been automatically set to 'postgres'.");
3639
+ } else if (config.database !== "postgres") {
3640
+ consola$1.fatal(`'--db-setup prisma-postgres' requires '--database postgres'. Cannot use with '--database ${config.database}'.`);
3641
+ process.exit(1);
3642
+ }
3643
+ if (!providedFlags.has("orm")) {
3644
+ config.orm = "prisma";
3645
+ log.info("Due to '--db-setup prisma-postgres', '--orm' has been automatically set to 'prisma'.");
3646
+ } else if (config.orm !== "prisma") {
3647
+ consola$1.fatal(`'--db-setup prisma-postgres' requires '--orm prisma'. Cannot use with '--orm ${config.orm}'.`);
3648
+ process.exit(1);
3649
+ }
3650
+ } else if (config.dbSetup === "supabase") {
3651
+ if (!providedFlags.has("database")) {
3652
+ config.database = "postgres";
3653
+ log.info("Due to '--db-setup supabase', '--database' has been automatically set to 'postgres'.");
3654
+ } else if (config.database !== "postgres") {
3655
+ consola$1.fatal(`'--db-setup supabase' requires '--database postgres'. Cannot use with '--database ${config.database}'.`);
3656
+ process.exit(1);
3657
+ }
3658
+ } else if (config.dbSetup === "neon") {
3659
+ if (!providedFlags.has("database")) {
3660
+ config.database = "postgres";
3661
+ log.info("Due to '--db-setup neon', '--database' has been automatically set to 'postgres'.");
3662
+ } else if (config.database !== "postgres") {
3663
+ consola$1.fatal(`'--db-setup neon' requires '--database postgres'. Cannot use with '--database ${config.database}'.`);
3664
+ process.exit(1);
3665
+ }
3666
+ } else if (config.dbSetup === "mongodb-atlas") {
3667
+ if (!providedFlags.has("database")) {
3668
+ config.database = "mongodb";
3669
+ log.info("Due to '--db-setup mongodb-atlas', '--database' has been automatically set to 'mongodb'.");
3670
+ } else if (config.database !== "mongodb") {
3671
+ consola$1.fatal(`'--db-setup mongodb-atlas' requires '--database mongodb'. Cannot use with '--database ${config.database}'.`);
3672
+ process.exit(1);
3673
+ }
3674
+ }
3675
+ }
3676
+ if (config.database === "mongodb" && config.orm === "drizzle") {
3677
+ consola$1.fatal(`'--database mongodb' is incompatible with '--orm drizzle'. Use '--orm mongoose' or '--orm prisma' with MongoDB.`);
3678
+ process.exit(1);
3679
+ }
3680
+ }
3681
+ return config;
3682
+ }
3683
+
3684
+ //#endregion
3685
+ //#region src/index.ts
3686
+ const exit = () => process.exit(0);
3687
+ process.on("SIGINT", exit);
3688
+ process.on("SIGTERM", exit);
3689
+ async function main() {
3690
+ const startTime = Date.now();
3691
+ try {
3692
+ const options = await parseCliArguments();
3693
+ const cliProjectNameArg = options.projectDirectory;
3694
+ renderTitle();
3695
+ intro(pc.magenta("Creating a new Better-T-Stack project"));
3696
+ let currentPathInput;
3697
+ let finalPathInput;
3698
+ let finalResolvedPath;
3699
+ let finalBaseName;
3700
+ let shouldClearDirectory = false;
3701
+ if (options.yes && cliProjectNameArg) currentPathInput = cliProjectNameArg;
3702
+ else if (options.yes) {
3703
+ let defaultName = DEFAULT_CONFIG.relativePath;
3704
+ let counter = 1;
3705
+ while (fs.pathExistsSync(path.resolve(process.cwd(), defaultName)) && fs.readdirSync(path.resolve(process.cwd(), defaultName)).length > 0) {
3706
+ defaultName = `${DEFAULT_CONFIG.projectName}-${counter}`;
3707
+ counter++;
3708
+ }
3709
+ currentPathInput = defaultName;
3710
+ } else currentPathInput = await getProjectName(cliProjectNameArg);
3711
+ while (true) {
3712
+ const resolvedPath = path.resolve(process.cwd(), currentPathInput);
3713
+ const dirExists = fs.pathExistsSync(resolvedPath);
3714
+ const dirIsNotEmpty = dirExists && fs.readdirSync(resolvedPath).length > 0;
3715
+ if (!dirIsNotEmpty) {
3716
+ finalPathInput = currentPathInput;
3717
+ shouldClearDirectory = false;
3718
+ break;
3719
+ }
3720
+ log.warn(`Directory "${pc.yellow(currentPathInput)}" already exists and is not empty.`);
3721
+ const action = await select({
3722
+ message: "What would you like to do?",
3723
+ options: [
3724
+ {
3725
+ value: "overwrite",
3726
+ label: "Overwrite",
3727
+ hint: "Empty the directory and create the project"
3728
+ },
3729
+ {
3730
+ value: "merge",
3731
+ label: "Merge",
3732
+ hint: "Create project files inside, potentially overwriting conflicts"
3733
+ },
3734
+ {
3735
+ value: "rename",
3736
+ label: "Choose a different name/path",
3737
+ hint: "Keep the existing directory and create a new one"
3738
+ },
3739
+ {
3740
+ value: "cancel",
3741
+ label: "Cancel",
3742
+ hint: "Abort the process"
3743
+ }
3744
+ ],
3745
+ initialValue: "rename"
3746
+ });
3747
+ if (isCancel(action)) {
3748
+ cancel(pc.red("Operation cancelled."));
3749
+ process.exit(0);
3750
+ }
3751
+ if (action === "overwrite") {
3752
+ finalPathInput = currentPathInput;
3753
+ shouldClearDirectory = true;
3754
+ break;
3755
+ }
3756
+ if (action === "merge") {
3757
+ finalPathInput = currentPathInput;
3758
+ shouldClearDirectory = false;
3759
+ log.info(`Proceeding into existing directory "${pc.yellow(currentPathInput)}". Files may be overwritten.`);
3760
+ break;
3761
+ }
3762
+ if (action === "rename") {
3763
+ log.info("Please choose a different project name or path.");
3764
+ currentPathInput = await getProjectName(void 0);
3765
+ } else if (action === "cancel") {
3766
+ cancel(pc.red("Operation cancelled."));
3767
+ process.exit(0);
3768
+ }
3769
+ }
3770
+ if (finalPathInput === ".") {
3771
+ finalResolvedPath = process.cwd();
3772
+ finalBaseName = path.basename(finalResolvedPath);
3773
+ } else {
3774
+ finalResolvedPath = path.resolve(process.cwd(), finalPathInput);
3775
+ finalBaseName = path.basename(finalResolvedPath);
3776
+ }
3777
+ if (shouldClearDirectory) {
3778
+ const s = spinner();
3779
+ s.start(`Clearing directory "${finalResolvedPath}"...`);
3780
+ try {
3781
+ await fs.emptyDir(finalResolvedPath);
3782
+ s.stop(`Directory "${finalResolvedPath}" cleared.`);
3783
+ } catch (error) {
3784
+ s.stop(pc.red(`Failed to clear directory "${finalResolvedPath}".`));
3785
+ consola$1.error(error);
3786
+ process.exit(1);
3787
+ }
3788
+ } else await fs.ensureDir(finalResolvedPath);
3789
+ const flagConfig = processAndValidateFlags(options, finalBaseName);
3790
+ const { projectName: _projectNameFromFlags,...otherFlags } = flagConfig;
3791
+ if (!options.yes && Object.keys(otherFlags).length > 0) {
3792
+ log.info(pc.yellow("Using these pre-selected options:"));
3793
+ log.message(displayConfig(otherFlags));
3794
+ log.message("");
3795
+ }
3796
+ let config;
3797
+ if (options.yes) {
3798
+ config = {
3799
+ ...DEFAULT_CONFIG,
3800
+ ...flagConfig,
3801
+ projectName: finalBaseName,
3802
+ projectDir: finalResolvedPath,
3803
+ relativePath: finalPathInput
3804
+ };
3805
+ if (config.backend === "convex") log.info("Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo");
3806
+ else if (config.backend === "none") log.info("Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none");
3807
+ log.info(pc.yellow("Using default/flag options (config prompts skipped):"));
3808
+ log.message(displayConfig(config));
3809
+ log.message("");
3810
+ } else config = await gatherConfig(flagConfig, finalBaseName, finalResolvedPath, finalPathInput);
3811
+ await createProject(config);
3812
+ await trackProjectCreation(config);
3813
+ const reproducibleCommand = generateReproducibleCommand(config);
3814
+ log.success(pc.blue(`You can reproduce this setup with the following command:\n${reproducibleCommand}`));
3815
+ const elapsedTimeInSeconds = ((Date.now() - startTime) / 1e3).toFixed(2);
3816
+ outro(pc.magenta(`Project created successfully in ${pc.bold(elapsedTimeInSeconds)} seconds!`));
3817
+ } catch (error) {
3818
+ if (error instanceof Error) {
3819
+ if (error.name === "YError") cancel(pc.red(`Invalid arguments: ${error.message}`));
3820
+ else {
3821
+ consola$1.error(`An unexpected error occurred: ${error.message}`);
3822
+ if (!error.message.includes("is only supported with")) consola$1.error(error.stack);
3823
+ }
3824
+ process.exit(1);
3825
+ } else {
3826
+ consola$1.error("An unexpected error occurred.");
3827
+ console.error(error);
3828
+ process.exit(1);
3829
+ }
3830
+ }
3831
+ }
3832
+ main().catch((err) => {
3833
+ consola$1.error("Aborting installation due to unexpected error...");
3834
+ if (err instanceof Error) {
3835
+ if (!err.message.includes("is only supported with") && !err.message.includes("incompatible with") && !err.message.includes("requires") && !err.message.includes("Cannot use") && !err.message.includes("Cannot select multiple") && !err.message.includes("Cannot combine") && !err.message.includes("not supported")) {
3836
+ consola$1.error(err.message);
3837
+ consola$1.error(err.stack);
3838
+ }
3839
+ } else console.error(err);
3840
+ process.exit(1);
3841
+ });
3842
+
3843
+ //#endregion