create-better-t-stack
Version:
A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
194 lines (154 loc) • 71.8 kB
JavaScript
#!/usr/bin/env node
import Ho from"node:path";import{cancel as Yo,intro as Ko,log as _,outro as Xo}from"@clack/prompts";import{consola as k}from"consola";import ir from"fs-extra";import z from"picocolors";import Zo from"yargs";import{hideBin as en}from"yargs/helpers";import Le from"node:path";import{fileURLToPath as fr}from"node:url";var re=()=>{let e=process.env.npm_config_user_agent;return e?.startsWith("pnpm")?"pnpm":e?.startsWith("bun")?"bun":"npm"};var hr=fr(import.meta.url),gr=Le.dirname(hr),v=Le.join(gr,"../"),g={projectName:"my-better-t-app",frontend:["tanstack-router"],database:"sqlite",orm:"drizzle",auth:!0,addons:[],examples:[],git:!0,packageManager:re(),install:!0,dbSetup:"none",backend:"hono",runtime:"bun",api:"trpc"},be={"better-auth":"^1.2.6","@better-auth/expo":"^1.2.6","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.6.0",prisma:"^6.6.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",turbo:"^2.4.2",ai:"^4.2.8","@ai-sdk/google":"^1.2.3","@prisma/extension-accelerate":"^1.3.0","@orpc/server":"^1.0.3","@orpc/react-query":"^1.0.3","@orpc/client":"^1.0.3","@trpc/tanstack-react-query":"^11.0.0","@trpc/server":"^11.0.0","@trpc/client":"^11.0.0"};import La from"node:path";import{cancel as Ma,log as Oa,spinner as Ua}from"@clack/prompts";import _a from"fs-extra";import Rt from"picocolors";import H from"node:path";import B from"fs-extra";import br from"node:path";import Me from"fs-extra";var w=async e=>{let{dependencies:r=[],devDependencies:t=[],projectDir:a}=e,o=br.join(a,"package.json"),n=await Me.readJson(o);n.dependencies||(n.dependencies={}),n.devDependencies||(n.devDependencies={});for(let s of r){let i=be[s];i?n.dependencies[s]=i:console.warn(`Warning: Dependency ${s} not found in version map.`)}for(let s of t){let i=be[s];i?n.devDependencies[s]=i:console.warn(`Warning: Dev dependency ${s} not found in version map.`)}await Me.writeJson(o,n,{spaces:2})};import Oe from"node:path";import{spinner as wr}from"@clack/prompts";import yr from"consola";import{execa as Pr}from"execa";import Ue from"picocolors";function R(e,r){switch(e){case"pnpm":return`pnpm dlx ${r}`;case"bun":return`bunx ${r}`;default:return`npx ${r}`}}async function _e(e){let{projectName:r,packageManager:t}=e,a=Oe.resolve(process.cwd(),r),o=wr();try{o.start("Setting up Starlight docs...");let i=`create-astro@latest ${["docs","--template","starlight","--no-install","--add","tailwind","--no-git","--skip-houston"].join(" ")}`,c=R(t,i);await Pr(c,{cwd:Oe.join(a,"apps"),env:{CI:"true"},shell:!0}),o.stop("Starlight docs setup successfully!")}catch(n){throw o.stop(Ue.red("Failed to set up Starlight docs")),n instanceof Error&&yr.error(Ue.red(n.message)),n}}import Q from"node:path";import{spinner as vr}from"@clack/prompts";import{consola as jr}from"consola";import{execa as kr}from"execa";import ae from"fs-extra";import ze from"picocolors";async function We(e){let{projectName:r,packageManager:t,frontend:a}=e,o=Q.resolve(process.cwd(),r),n=vr(),s=Q.join(o,"apps/web");if(await ae.pathExists(s))try{n.start("Setting up Tauri desktop app support..."),await w({devDependencies:["@tauri-apps/cli"],projectDir:s});let i=Q.join(s,"package.json");if(await ae.pathExists(i)){let j=await ae.readJson(i);j.scripts={...j.scripts,tauri:"tauri","desktop:dev":"tauri dev","desktop:build":"tauri build"},await ae.writeJson(i,j,{spaces:2})}let l=a.includes("react-router")?"http://localhost:5173":"http://localhost:3001",p=`@tauri-apps/cli@latest ${["init",`--app-name=${Q.basename(o)}`,`--window-title=${Q.basename(o)}`,"--frontend-dist=../dist",`--dev-url=${l}`,`--before-dev-command="${t} run dev"`,`--before-build-command="${t} run build"`].join(" ")}`,b=R(t,p);await kr(b,{cwd:s,env:{CI:"true"},shell:!0}),n.stop("Tauri desktop app support configured successfully!")}catch(i){throw n.stop(ze.red("Failed to set up Tauri")),i instanceof Error&&jr.error(ze.red(i.message)),i}}async function qe(e){let{projectName:r,addons:t,frontend:a}=e,o=H.resolve(process.cwd(),r),n=a.includes("react-router")||a.includes("tanstack-router");t.includes("turborepo")&&await w({devDependencies:["turbo"],projectDir:o}),t.includes("pwa")&&n&&await Dr(o,a),t.includes("tauri")&&n&&await We(e),t.includes("biome")&&await Ar(o),t.includes("husky")&&await Sr(o),t.includes("starlight")&&await _e(e)}function xr(e,r){return H.join(e,"apps/web")}async function Ar(e){await w({devDependencies:["@biomejs/biome"],projectDir:e});let r=H.join(e,"package.json");if(await B.pathExists(r)){let t=await B.readJson(r);t.scripts={...t.scripts,check:"biome check --write ."},await B.writeJson(r,t,{spaces:2})}}async function Sr(e){await w({devDependencies:["husky","lint-staged"],projectDir:e});let r=H.join(e,"package.json");if(await B.pathExists(r)){let t=await B.readJson(r);t.scripts={...t.scripts,prepare:"husky"},t["lint-staged"]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":["biome check --write ."]},await B.writeJson(r,t,{spaces:2})}}async function Dr(e,r){let t=xr(e,r);if(!await B.pathExists(t))return;await w({dependencies:["vite-plugin-pwa"],devDependencies:["@vite-pwa/assets-generator"],projectDir:t});let a=H.join(t,"package.json");if(await B.pathExists(a)){let o=await B.readJson(a);o.scripts={...o.scripts,"generate-pwa-assets":"pwa-assets-generator"},await B.writeJson(a,o,{spaces:2})}}import*as Y from"node:path";import $r from"fs-extra";async function Ve(e){let{api:r,projectName:t}=e,a=Y.resolve(process.cwd(),t),o=Y.join(a,"apps/web"),n=Y.join(a,"apps/server"),s=await $r.pathExists(o);r==="orpc"&&(s&&await w({dependencies:["@orpc/react-query","@orpc/server","@orpc/client"],projectDir:o}),await w({dependencies:["@orpc/server","@orpc/client"],projectDir:n})),r==="trpc"&&(s&&await w({dependencies:["@trpc/tanstack-react-query","@trpc/server","@trpc/client"],projectDir:o}),await w({dependencies:["@trpc/server","@trpc/client"],projectDir:n}))}import oe from"node:path";import Ge from"consola";import Je from"fs-extra";import Qe from"picocolors";async function He(e){let{projectName:r,auth:t,frontend:a}=e;if(!t)return;let o=oe.resolve(process.cwd(),r),n=oe.join(o,"apps/server"),s=oe.join(o,"apps/web"),i=oe.join(o,"apps/native"),c=await Je.pathExists(s),l=await Je.pathExists(i);try{await w({dependencies:["better-auth"],projectDir:n}),(a.includes("react-router")||a.includes("tanstack-router")||a.includes("tanstack-start")||a.includes("next"))&&c&&await w({dependencies:["better-auth"],projectDir:s}),a.includes("native")&&l&&(await w({dependencies:["better-auth","@better-auth/expo"],projectDir:i}),await w({dependencies:["@better-auth/expo"],projectDir:n}))}catch(d){Ge.error(Qe.red("Failed to configure authentication dependencies")),d instanceof Error&&Ge.error(Qe.red(d.message))}}function Ye(e=32){let r="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t="",a=r.length;for(let o=0;o<e;o++)t+=r.charAt(Math.floor(Math.random()*a));return t}import Ke from"node:path";async function Xe(e){let{projectName:r,backend:t,runtime:a,api:o}=e,n=Ke.resolve(process.cwd(),r),s=t,i=Ke.join(n,"apps/server"),c=[],l=[];s==="hono"?(c.push("hono"),o==="trpc"&&c.push("@hono/trpc-server"),a==="node"&&(c.push("@hono/node-server"),l.push("tsx","@types/node"))):s==="elysia"?(c.push("elysia","@elysiajs/cors"),o==="trpc"&&c.push("@elysiajs/trpc"),a==="node"&&(c.push("@elysiajs/node"),l.push("tsx","@types/node"))):s==="express"&&(c.push("express","cors"),l.push("@types/express","@types/cors"),a==="node"&&l.push("tsx","@types/node")),a==="bun"&&l.push("@types/bun"),await w({dependencies:c,devDependencies:l,projectDir:i})}import Er from"node:path";import Cr from"consola";import Tr from"fs-extra";async function Ze(e,r){let t=Er.join(e,"README.md"),a=Fr(r);try{await Tr.writeFile(t,a)}catch(o){Cr.error("Failed to create README.md file:",o)}}function Fr(e){let{projectName:r,packageManager:t,database:a,auth:o,addons:n=[],orm:s="drizzle",runtime:i="bun",frontend:c=["tanstack-router"],backend:l="hono"}=e,d=c.includes("react-router"),y=c.includes("tanstack-router"),p=c.includes("native"),b=c.includes("next"),j=c.includes("tanstack-start"),A=t==="npm"?"npm run":t,S="3001";return d?S="5173":b&&(S="3000"),`# ${r}
This project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack that combines React, ${y?"TanStack Router":d?"React Router":b?"Next.js":j?"TanStack Start":""}, ${l[0].toUpperCase()+l.slice(1)}, tRPC, and more.
## Features
${Rr(a,o,n,s,i,c,l)}
## Getting Started
First, install the dependencies:
\`\`\`bash
${t} install
\`\`\`
${Br(a,o,A,s)}
Then, run the development server:
\`\`\`bash
${A} dev
\`\`\`
${y||d||b||j?`Open [http://localhost:${S}](http://localhost:${S}) in your browser to see the web application.`:""}
${p?`Use the Expo Go app to run the mobile application.
`:""}
The API is running at [http://localhost:3000](http://localhost:3000).
${n.includes("pwa")&&d?`
## PWA Support with React Router v7
There is a known compatibility issue between VitePWA and React Router v7.
See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809
`:""}
## Project Structure
\`\`\`
${r}/
\u251C\u2500\u2500 apps/
${y||d||b||j?`\u2502 \u251C\u2500\u2500 web/ # Frontend application (${y?"React + TanStack Router":d?"React + React Router":b?"Next.js":"React + TanStack Start"})
`:""}${p?`\u2502 \u251C\u2500\u2500 native/ # Mobile application (React Native, Expo)
`:""}${n.includes("starlight")?`\u2502 \u251C\u2500\u2500 docs/ # Documentation site (Astro Starlight)
`:""}\u2502 \u2514\u2500\u2500 server/ # Backend API (${l[0].toUpperCase()+l.slice(1)}, tRPC)
\`\`\`
## Available Scripts
${Nr(A,a,s,o,p,n,l)}
`}function Rr(e,r,t,a,o,n,s){let i=n.includes("tanstack-router"),c=n.includes("react-router"),l=n.includes("native"),d=n.includes("next"),y=n.includes("tanstack-start"),p=["- **TypeScript** - For type safety and improved developer experience"];i?p.push("- **TanStack Router** - File-based routing with full type safety"):c?p.push("- **React Router** - Declarative routing for React"):d?p.push("- **Next.js** - Full-stack React framework"):y&&p.push("- **TanStack Start** - SSR framework with TanStack Router"),l&&(p.push("- **React Native** - Build mobile apps using React"),p.push("- **Expo** - Tools for React Native development")),p.push("- **TailwindCSS** - Utility-first CSS for rapid UI development","- **shadcn/ui** - Reusable UI components"),s==="hono"?p.push("- **Hono** - Lightweight, performant server framework"):s==="express"?p.push("- **Express** - Fast, unopinionated web framework"):s==="elysia"?p.push("- **Elysia** - Type-safe, high-performance framework"):s==="next"&&p.push("- **Next.js** - Full-stack React framework"),p.push("- **tRPC** - End-to-end type-safe APIs",`- **${o==="bun"?"Bun":"Node.js"}** - Runtime environment`),e!=="none"&&p.push(`- **${a==="drizzle"?"Drizzle":"Prisma"}** - TypeScript-first ORM`,`- **${e==="sqlite"?"SQLite/Turso":e==="postgres"?"PostgreSQL":e==="mysql"?"MySQL":"MongoDB"}** - Database engine`),r&&p.push("- **Authentication** - Email & password authentication with Better Auth");for(let b of t)b==="pwa"?p.push("- **PWA** - Progressive Web App support"):b==="tauri"?p.push("- **Tauri** - Build native desktop applications"):b==="biome"?p.push("- **Biome** - Linting and formatting"):b==="husky"?p.push("- **Husky** - Git hooks for code quality"):b==="starlight"&&p.push("- **Starlight** - Documentation site with Astro");return p.join(`
`)}function Br(e,r,t,a){if(e==="none")return"";let o=`## Database Setup
`;return e==="sqlite"?o+=`This project uses SQLite${a==="drizzle"?" with Drizzle ORM":" with Prisma"}.
1. Start the local SQLite database:
\`\`\`bash
cd apps/server && ${t} db:local
\`\`\`
2. Update your \`.env\` file in the \`apps/server\` directory with the appropriate connection details if needed.
`:e==="postgres"?o+=`This project uses PostgreSQL${a==="drizzle"?" with Drizzle ORM":" with Prisma"}.
1. Make sure you have a PostgreSQL database set up.
2. Update your \`apps/server/.env\` file with your PostgreSQL connection details.
`:e==="mysql"?o+=`This project uses MySQL${a==="drizzle"?" with Drizzle ORM":" with Prisma"}.
1. Make sure you have a MySQL database set up.
2. Update your \`apps/server/.env\` file with your MySQL connection details.
`:e==="mongodb"&&(o+=`This project uses MongoDB with Prisma ORM.
1. Make sure you have MongoDB set up.
2. Update your \`apps/server/.env\` file with your MongoDB connection URI.
`),o+=`
3. ${a==="prisma"?`Generate the Prisma client and push the schema:
\`\`\`bash
${t} db:push
\`\`\``:`Apply the schema to your database:
\`\`\`bash
${t} db:push
\`\`\``}
`,o}function Nr(e,r,t,a,o,n,s){let i=`- \`${e} dev\`: Start all applications in development mode
- \`${e} build\`: Build all applications
- \`${e} dev:web\`: Start only the web application
- \`${e} dev:server\`: Start only the server
- \`${e} check-types\`: Check TypeScript types across all apps`;return o&&(i+=`
- \`${e} dev:native\`: Start the React Native/Expo development server`),r!=="none"&&(i+=`
- \`${e} db:push\`: Push schema changes to database
- \`${e} db:studio\`: Open database studio UI`,r==="sqlite"&&t==="drizzle"&&(i+=`
- \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),n.includes("biome")&&(i+=`
- \`${e} check\`: Run Biome formatting and linting`),n.includes("pwa")&&(i+=`
- \`cd apps/web && ${e} generate-pwa-assets\`: Generate PWA assets`),n.includes("tauri")&&(i+=`
- \`cd apps/web && ${e} desktop:dev\`: Start Tauri desktop app in development
- \`cd apps/web && ${e} desktop:build\`: Build Tauri desktop app`),n.includes("starlight")&&(i+=`
- \`cd apps/docs && ${e} dev\`: Start documentation site
- \`cd apps/docs && ${e} build\`: Build documentation site`),i}import Ee from"node:path";import{spinner as fa}from"@clack/prompts";import ha from"consola";import ga from"fs-extra";import lt from"picocolors";import se from"node:path";import{cancel as Ir,isCancel as Lr,log as W,spinner as rt,text as Mr}from"@clack/prompts";import ie from"consola";import{execa as Or}from"execa";import K from"fs-extra";import $ from"picocolors";import{execa as et}from"execa";async function ne(e){try{return process.platform==="win32"?(await et("where",[e])).exitCode===0:(await et("which",[e])).exitCode===0}catch{return!1}}async function Ur(){let e=rt();e.start("Checking for MongoDB Atlas CLI");try{let r=await ne("atlas");return e.stop(r?"MongoDB Atlas CLI found":$.yellow("MongoDB Atlas CLI not found")),r}catch{return e.stop($.red("Error checking for MongoDB Atlas CLI")),!1}}async function _r(e){try{if(!await Ur())return ie.error($.red("MongoDB Atlas CLI not found.")),W.info($.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/")),null;W.info($.blue("Running MongoDB Atlas setup...")),await Or("atlas",["deployments","setup"],{cwd:e,stdio:"inherit"}),W.info($.green("Atlas setup complete!"));let t=await Mr({message:"Enter your MongoDB connection string:",placeholder:"mongodb+srv://username:password@cluster.mongodb.net/database",validate(a){if(!a)return"Please enter a connection string";if(!a.startsWith("mongodb"))return"URL should start with mongodb:// or mongodb+srv://"}});return Lr(t)?(Ir("MongoDB setup cancelled"),null):{connectionString:t}}catch(r){return r instanceof Error&&ie.error($.red(r.message)),null}}async function we(e,r){try{let t=se.join(e,"apps/server",".env");await K.ensureDir(se.dirname(t));let a="";await K.pathExists(t)&&(a=await K.readFile(t,"utf8"));let o=r?`DATABASE_URL="${r.connectionString}"`:'DATABASE_URL="mongodb://localhost:27017/mydb"';a.includes("DATABASE_URL=")?a=a.replace(/DATABASE_URL=.*(\r?\n|$)/,`${o}$1`):a+=`
${o}`,await K.writeFile(t,a.trim())}catch(t){throw ie.error("Failed to update environment configuration"),t}}function tt(){W.info(`
${$.green("MongoDB Atlas Manual Setup Instructions:")}
1. Install Atlas CLI:
${$.blue("https://www.mongodb.com/docs/atlas/cli/stable/install-atlas-cli/")}
2. Run the following command and follow the prompts:
${$.blue("atlas deployments setup")}
3. Get your connection string from the Atlas dashboard:
Format: ${$.dim("mongodb+srv://USERNAME:PASSWORD@CLUSTER.mongodb.net/DATABASE_NAME")}
4. Add the connection string to your .env file:
${$.dim('DATABASE_URL="your_connection_string"')}
`)}async function at(e){let{projectName:r}=e,t=se.resolve(process.cwd(),r),a=rt();a.start("Setting up MongoDB Atlas");let o=se.join(t,"apps/server");try{await K.ensureDir(o),a.stop("Starting MongoDB Atlas setup");let n=await _r(o);n?(await we(t,n),W.success($.green("MongoDB Atlas setup complete! Connection saved to .env file."))):(W.warn($.yellow("Falling back to local MongoDB configuration")),await we(t),tt())}catch(n){a.stop($.red("MongoDB Atlas setup failed")),ie.error($.red(`Error during MongoDB Atlas setup: ${n instanceof Error?n.message:String(n)}`));try{await we(t),tt()}catch{}}}import M from"node:path";import{cancel as zr,isCancel as Wr,log as X,password as qr,spinner as Pe}from"@clack/prompts";import{consola as ve}from"consola";import{execa as Vr}from"execa";import N from"fs-extra";import q from"picocolors";async function Gr(e,r){let t=Pe();try{t.start("Initializing Prisma PostgreSQL");let a=M.join(e,"prisma");await N.ensureDir(a),t.stop("Initializing Prisma. Follow the prompts below:");let o=R(r,"prisma init --db");await Vr(o,{cwd:e,stdio:"inherit",shell:!0}),X.info(q.yellow(`Please copy the Prisma Postgres URL from the output above.
It looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=...`));let n=await qr({message:"Paste your Prisma Postgres database URL:",validate(s){if(!s)return"Please enter a database URL";if(!s.startsWith("prisma+postgres://"))return"URL should start with prisma+postgres://"}});return Wr(n)?(zr("Database setup cancelled"),null):{databaseUrl:n}}catch(a){return t.stop(q.red("Failed to initialize Prisma PostgreSQL")),a instanceof Error&&ve.error(a.message),null}}async function ye(e,r){try{let t=M.join(e,"apps/server",".env");await N.ensureDir(M.dirname(t));let a="";await N.pathExists(t)&&(a=await N.readFile(t,"utf8"));let o=r?`DATABASE_URL="${r.databaseUrl}"`:'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"';a.includes("DATABASE_URL=")?a=a.replace(/DATABASE_URL=.*(\r?\n|$)/,`${o}$1`):a+=`
${o}`,await N.writeFile(t,a.trim())}catch(t){throw ve.error("Failed to update environment configuration"),t}}function ot(){X.info(`Manual Prisma PostgreSQL Setup Instructions:
1. Visit https://console.prisma.io and create an account
2. Create a new PostgreSQL database from the dashboard
3. Get your database URL
4. Add the database URL to the .env file in apps/server/.env
DATABASE_URL="your_database_url"`)}async function Jr(e){try{await w({dependencies:["@prisma/extension-accelerate"],projectDir:e});let r=M.join(e,"prisma/index.ts");await N.writeFile(r,`
import { PrismaClient } from '@prisma/client';
import { withAccelerate } from "@prisma/extension-accelerate";
const prisma = new PrismaClient().$extends(withAccelerate());
export default prisma;
`.trim());let a=M.join(e,"src/db/index.ts");if(await N.pathExists(a)){let o=await N.readFile(a,"utf8");o.includes("@prisma/extension-accelerate")||(o=`import { withAccelerate } from "@prisma/extension-accelerate";
${o}`,o=o.replace("export const db = new PrismaClient();","export const db = new PrismaClient().$extends(withAccelerate());"),await N.writeFile(a,o))}return!0}catch{return X.warn(q.yellow("Could not add Prisma Accelerate extension automatically")),!1}}async function nt(e){let{projectName:r,packageManager:t}=e,a=M.resolve(process.cwd(),r),o=M.join(a,"apps/server"),n=Pe();n.start("Setting up Prisma PostgreSQL");try{await N.ensureDir(o),n.stop("Starting Prisma setup");let s=await Gr(o,t);if(s)await ye(a,s),await Jr(o),X.success(q.green("Prisma PostgreSQL database configured successfully!"));else{let i=Pe();i.start("Setting up fallback configuration"),await ye(a),i.stop("Manual setup required"),ot()}}catch(s){n.stop(q.red("Prisma PostgreSQL setup failed")),ve.error(q.red(`Error during Prisma PostgreSQL setup: ${s instanceof Error?s.message:String(s)}`));try{await ye(a),ot()}catch{}X.info("Setup completed with manual configuration required.")}}import Qr from"node:os";import pe from"node:path";import{cancel as ke,confirm as Hr,isCancel as xe,log as V,select as Yr,spinner as G,text as Kr}from"@clack/prompts";import Xr from"consola";import{$ as I}from"execa";import st from"fs-extra";import E from"picocolors";async function Zr(){return ne("turso")}async function ea(){try{return!(await I`turso auth whoami`).stdout.includes("You are not logged in")}catch{return!1}}async function ta(){let e=G();try{return e.start("Logging in to Turso..."),await I`turso auth login`,e.stop("Logged in to Turso successfully!"),!0}catch(r){throw e.stop(E.red("Failed to log in to Turso")),r}}async function ra(e){let r=G();try{if(r.start("Installing Turso CLI..."),e)await I`brew install tursodatabase/tap/turso`;else{let{stdout:t}=await I`curl -sSfL https://get.tur.so/install.sh`;await I`bash -c '${t}'`}return r.stop("Turso CLI installed successfully!"),!0}catch(t){throw t instanceof Error&&t.message.includes("User force closed")?(r.stop("Turso CLI installation cancelled"),V.warn(E.yellow("Turso CLI installation cancelled by user")),new Error("Installation cancelled")):(r.stop(E.red("Failed to install Turso CLI")),t)}}async function aa(){let e=G();try{e.start("Fetching Turso groups...");let{stdout:r}=await I`turso group list`,t=r.trim().split(`
`);if(t.length<=1)return e.stop("No Turso groups found"),[];let a=t.slice(1).map(o=>{let[n,s,i,c]=o.trim().split(/\s{2,}/);return{name:n,locations:s,version:i,status:c}});return e.stop(`Found ${a.length} Turso groups`),a}catch(r){return e.stop(E.red("Error fetching Turso groups")),console.error("Error fetching Turso groups:",r),[]}}async function oa(){let e=await aa();if(e.length===0)return null;if(e.length===1)return V.info(`Using the only available group: ${E.blue(e[0].name)}`),e[0].name;let r=e.map(a=>({value:a.name,label:`${a.name} (${a.locations})`})),t=await Yr({message:"Select a Turso database group:",options:r});return xe(t)&&(ke(E.red("Operation cancelled")),process.exit(0)),t}async function na(e,r){let t=G();try{t.start(`Creating Turso database "${e}"${r?` in group "${r}"`:""}...`),r?await I`turso db create ${e} --group ${r}`:await I`turso db create ${e}`,t.stop(`Created database "${e}"`)}catch(a){throw t.stop(E.red(`Failed to create database "${e}"`)),a instanceof Error&&a.message.includes("already exists")?new Error("DATABASE_EXISTS"):a}t.start("Retrieving database connection details...");try{let{stdout:a}=await I`turso db show ${e} --url`,{stdout:o}=await I`turso db tokens create ${e}`;return t.stop("Retrieved database connection details"),{dbUrl:a.trim(),authToken:o.trim()}}catch(a){throw t.stop(E.red("Failed to retrieve database connection details")),a}}async function ce(e,r){let t=pe.join(e,"apps/server",".env"),a=r?`DATABASE_URL="${r.dbUrl}"
DATABASE_AUTH_TOKEN="${r.authToken}"`:`DATABASE_URL=
DATABASE_AUTH_TOKEN=`;await st.ensureDir(pe.dirname(t)),await st.writeFile(t,a)}function je(){V.info(`Manual Turso Setup Instructions:
1. Visit https://turso.tech and create an account
2. Create a new database from the dashboard
3. Get your database URL and authentication token
4. Add these credentials to the .env file in apps/server/.env
DATABASE_URL=your_database_url
DATABASE_AUTH_TOKEN=your_auth_token`)}async function it(e){let{projectName:r,orm:t}=e,a=pe.resolve(process.cwd(),r),o=t==="drizzle",n=G();n.start("Setting up Turso database");try{let s=Qr.platform(),i=s==="darwin",c=s==="linux";if(s==="win32"){n.stop(E.yellow("Turso setup not supported on Windows")),V.warn(E.yellow("Automatic Turso setup is not supported on Windows.")),await ce(a),je();return}if(n.stop("Checking Turso CLI"),!await Zr()){let S=await Hr({message:"Would you like to install Turso CLI?",initialValue:!0});if(xe(S)&&(ke(E.red("Operation cancelled")),process.exit(0)),!S){await ce(a),je();return}await ra(i)}await ea()||await ta();let p=await oa(),b=!1,j="",A=pe.basename(a);for(;!b;){let S=await Kr({message:"Enter a name for your database:",defaultValue:A,initialValue:A,placeholder:A});xe(S)&&(ke(E.red("Operation cancelled")),process.exit(0)),j=S;try{let F=await na(j,p),te=G();te.start("Writing configuration to .env file"),await ce(a,F),te.stop("Turso database configured successfully!"),b=!0}catch(F){if(F instanceof Error&&F.message==="DATABASE_EXISTS")V.warn(E.yellow(`Database "${E.red(j)}" already exists`)),A=`${j}-${Math.floor(Math.random()*1e3)}`;else throw F}}}catch(s){n.stop(E.red("Failed to set up Turso database")),Xr.error(E.red(`Error during Turso setup: ${s instanceof Error?s.message:String(s)}`)),await ce(a),je(),V.success("Setup completed with manual configuration required.")}}import Z from"node:path";import{cancel as sa,isCancel as ia,log as De,spinner as Ae,text as ca}from"@clack/prompts";import{consola as le}from"consola";import{execa as pa}from"execa";import Se from"fs-extra";import O from"picocolors";async function $e(e,r,t){let a=Ae();try{let o=R(e,r);a&&a.start(t);let n=await pa(o,{shell:!0});return a&&a.stop(t),n}catch(o){throw a&&a.stop(O.red(`Failed: ${t}`)),o}}async function la(e){try{let t=await $e(e,"neonctl projects list");return!t.stdout.includes("not authenticated")&&!t.stdout.includes("error")}catch{return!1}}async function da(e){try{return await $e(e,"neonctl auth","Authenticating with Neon..."),De.success("Authenticated with Neon successfully!"),!0}catch(r){throw le.error(O.red("Failed to authenticate with Neon")),r}}async function ua(e,r){try{let t=`neonctl projects create --name "${e}" --output json`,{stdout:a}=await $e(r,t,`Creating Neon project "${e}"...`),o=JSON.parse(a);if(o.project&&o.connection_uris&&o.connection_uris.length>0){let n=o.project.id,s=o.connection_uris[0].connection_uri,i=o.connection_uris[0].connection_parameters;return{connectionString:s,projectId:n,dbName:i.database,roleName:i.role}}return le.error(O.red("Failed to extract connection information from response")),null}catch(t){throw le.error(O.red("Failed to create Neon project")),t}}async function ct(e,r){let t=Z.join(e,"apps/server",".env"),a=r?`DATABASE_URL="${r.connectionString}"`:'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"';return await Se.ensureDir(Z.dirname(t)),await Se.writeFile(t,a),!0}function ma(){De.info(`Manual Neon PostgreSQL Setup Instructions:
1. Visit https://neon.tech and create an account
2. Create a new project from the dashboard
3. Get your connection string
4. Add the database URL to the .env file in apps/server/.env
DATABASE_URL="your_connection_string"`)}async function pt(e){let{projectName:r,packageManager:t}=e,a=Z.resolve(process.cwd(),r),o=Ae();o.start("Setting up Neon PostgreSQL");try{let n=await la(t);o.stop("Setting up Neon PostgreSQL"),n||(De.info("Please authenticate with Neon to continue:"),await da(t));let s=Z.basename(a),i=await ca({message:"Enter a name for your Neon project:",defaultValue:s,initialValue:s});ia(i)&&(sa(O.red("Operation cancelled")),process.exit(0));let c=await ua(i,t);if(!c)throw new Error("Failed to create project - couldn't get connection information");let l=Ae();l.start("Configuring database connection"),await Se.ensureDir(Z.join(a,"apps/server")),await ct(a,c),l.stop("Neon database configured successfully!")}catch(n){o.stop(O.red("Neon PostgreSQL setup failed")),n instanceof Error&&le.error(O.red(n.message)),await ct(a),ma()}}async function dt(e){let{projectName:r,database:t,orm:a,dbSetup:o}=e,n=Ee.resolve(process.cwd(),r),s=fa(),i=Ee.join(n,"apps/server");if(t==="none"){await ga.remove(Ee.join(i,"src/db"));return}try{a==="prisma"?await w({dependencies:["@prisma/client"],devDependencies:["prisma"],projectDir:i}):a==="drizzle"&&(t==="sqlite"?await w({dependencies:["drizzle-orm","@libsql/client"],devDependencies:["drizzle-kit"],projectDir:i}):t==="postgres"?await w({dependencies:["drizzle-orm","pg"],devDependencies:["drizzle-kit","@types/pg"],projectDir:i}):t==="mysql"&&await w({dependencies:["drizzle-orm","mysql2"],devDependencies:["drizzle-kit"],projectDir:i})),t==="sqlite"&&o==="turso"?await it(e):t==="postgres"?a==="prisma"&&o==="prisma-postgres"?await nt(e):o==="neon"&&await pt(e):t==="mongodb"&&o==="mongodb-atlas"&&await at(e)}catch(c){throw s.stop(lt.red("Failed to set up database")),c instanceof Error&&ha.error(lt.red(c.message)),c}}import L from"node:path";import de from"fs-extra";async function Ce(e,r){await de.ensureDir(L.dirname(e));let t="";await de.pathExists(e)&&(t=await de.readFile(e,"utf8"));let a=!1;for(let{key:o,value:n,condition:s}of r)if(s){let i=new RegExp(`^${o}=.*$`,"m");i.test(t)?n&&(t=t.replace(i,`${o}=${n}`),a=!0):(t+=`
${o}=${n}`,a=!0)}a&&await de.writeFile(e,t.trim())}async function ut(e){let{projectName:r}=e,t=L.resolve(process.cwd(),r),a=e,o=L.join(t,"apps/server"),n=L.join(o,".env"),s=a.frontend.includes("react-router"),i=a.frontend.includes("tanstack-router"),c=a.frontend.includes("tanstack-start"),l=a.frontend.includes("next"),d=s||i||c||l,y="http://localhost:3000";s?y="http://localhost:5173":(i||c||l)&&(y="http://localhost:3001");let p="",b=a.dbSetup==="turso"||a.dbSetup==="prisma-postgres"||a.dbSetup==="mongodb-atlas"||a.dbSetup==="neon";b||(a.database==="postgres"?p="postgresql://postgres:postgres@localhost:5432/mydb?schema=public":a.database==="mysql"?p="mysql://root:password@localhost:3306/mydb":a.database==="mongodb"?p="mongodb://localhost:27017/mydatabase":a.database==="sqlite"&&(p="file:./local.db"));let j=[{key:"CORS_ORIGIN",value:y,condition:!0},{key:"BETTER_AUTH_SECRET",value:Ye(),condition:!!a.auth},{key:"BETTER_AUTH_URL",value:"http://localhost:3000",condition:!!a.auth},{key:"DATABASE_URL",value:p,condition:a.database!=="none"&&p!==""&&!b},{key:"GOOGLE_GENERATIVE_AI_API_KEY",value:"",condition:a.examples?.includes("ai")||!1}];if(await Ce(n,j),d){let A=L.join(t,"apps/web"),S="VITE_SERVER_URL";l&&(S="NEXT_PUBLIC_SERVER_URL");let F=[{key:S,value:"http://localhost:3000",condition:!0}];await Ce(L.join(A,".env"),F)}if(a.frontend.includes("native")){let A=L.join(t,"apps/native"),S=[{key:"EXPO_PUBLIC_SERVER_URL",value:"http://localhost:3000",condition:!0}];await Ce(L.join(A,".env"),S)}}import Te from"node:path";import ba from"fs-extra";async function mt(e){let{projectName:r,examples:t}=e,a=Te.resolve(process.cwd(),r);if(t.includes("ai")){let o=Te.join(a,"apps/web"),n=Te.join(a,"apps/server");await ba.pathExists(o)&&await w({dependencies:["ai"],projectDir:o}),await w({dependencies:["ai","@ai-sdk/google"],projectDir:n})}}import{log as wa,spinner as ft}from"@clack/prompts";import ya from"consola";import{$ as ht}from"execa";import ue from"picocolors";async function gt({projectDir:e,packageManager:r,addons:t=[]}){let a=ft();try{a.start(`Running ${r} install...`),await ht({cwd:e,stderr:"inherit"})`${r} install`,a.stop("Dependencies installed successfully"),(t.includes("biome")||t.includes("husky"))&&await Pa(e,r)}catch(o){throw a.stop(ue.red("Failed to install dependencies")),o instanceof Error&&ya.error(ue.red(`Installation error: ${o.message}`)),o}}async function Pa(e,r){let t=ft();try{t.start("Running Biome format check..."),await ht({cwd:e,stderr:"inherit"})`${r} biome check --write .`,t.stop("Biome check completed successfully")}catch{t.stop(ue.yellow("Biome check encountered issues")),wa.warn(ue.yellow("Some files may need manual formatting"))}}import{consola as va}from"consola";import P from"picocolors";function bt(e){let{database:r,projectName:t,packageManager:a,depsInstalled:o,orm:n,addons:s,runtime:i,frontend:c}=e,l=a==="npm"?"npm run":a,d=`cd ${t}`,y=s?.includes("husky")||s?.includes("biome"),p=r!=="none"?xa(r,n,l,i):"",b=s?.includes("tauri")?Aa(l):"",j=y?ka(l):"",A=c?.includes("native")?ja():"",S=s?.includes("pwa")&&c?.includes("react-router")?Sa():"",F=s?.includes("starlight")?Da(l):"",te=c?.includes("tanstack-router"),pr=c?.includes("tanstack-start"),Ne=c?.includes("react-router"),Ie=te||Ne||pr,lr=c?.includes("native"),dr=Ie||lr,ur=Ne?"5173":"3001",mr=R(a,"taze -r");va.box(`${P.bold("Next steps")}
${P.cyan("1.")} ${d}
${o?"":`${P.cyan("2.")} ${a} install
`}${P.cyan(o?"2.":"3.")} ${l} dev
${P.bold("Your project will be available at:")}
${dr?`${Ie?`${P.cyan("\u2022")} Frontend: http://localhost:${ur}
`:""}`:`${P.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)
`}${P.cyan("\u2022")} Backend: http://localhost:3000
${s?.includes("starlight")?`${P.cyan("\u2022")} Docs: http://localhost:4321
`:""}${A?`
${A.trim()}`:""}${p?`
${p.trim()}`:""}${b?`
${b.trim()}`:""}${j?`
${j.trim()}`:""}${S?`
${S.trim()}`:""}${F?`
${F.trim()}`:""}
${P.bold(`Update all dependencies:
`)}${P.cyan(mr)}
${P.bold("Like Better-T Stack?")} Please consider giving us a star on GitHub:
${P.cyan("https://github.com/AmanVarshney01/create-better-t-stack")}`)}function ja(){return`${P.yellow("NOTE:")} For Expo connectivity issues, update apps/native/.env
with your local IP:
EXPO_PUBLIC_SERVER_URL=http://192.168.0.103:3000
`}function ka(e){return`${P.bold("Linting and formatting:")}
${P.cyan("\u2022")} Format and lint fix: ${`${e} check`}
`}function xa(e,r,t,a){let o=[];return r==="prisma"?(e==="sqlite"&&o.push(`${P.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"),a==="bun"&&o.push(`${P.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors,
follow the guidance provided in the error messages`),o.push(`${P.cyan("\u2022")} Apply schema: ${`${t} db:push`}`),o.push(`${P.cyan("\u2022")} Database UI: ${`${t} db:studio`}`)):r==="drizzle"&&(o.push(`${P.cyan("\u2022")} Apply schema: ${`${t} db:push`}`),o.push(`${P.cyan("\u2022")} Database UI: ${`${t} db:studio`}`)),o.length?`${P.bold("Database commands:")}
${o.join(`
`)}
`:""}function Aa(e){return`
${P.bold("Desktop app with Tauri:")}
${P.cyan("\u2022")} Start desktop app: ${`cd apps/web && ${e} desktop:dev`}
${P.cyan("\u2022")} Build desktop app: ${`cd apps/web && ${e} desktop:build`}
${P.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.
See: https://v2.tauri.app/start/prerequisites/
`}function Sa(){return`${P.bold("PWA with React Router v7:")}
${P.yellow("NOTE:")} There is a known compatibility issue between VitePWA and React Router v7.
See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809
`}function Da(e){return`${P.bold("Documentation with Starlight:")}
${P.cyan("\u2022")} Start docs site: ${`cd apps/docs && ${e} dev`}
${P.cyan("\u2022")} Build docs site: ${`cd apps/docs && ${e} build`}
`}import yt from"node:path";import{log as $a}from"@clack/prompts";import{$ as wt,execa as Ea}from"execa";import J from"fs-extra";import Ca from"picocolors";async function Pt(e,r){await Ta(e,r),await Fa(e,r)}async function Ta(e,r){let t=yt.join(e,"package.json");if(await J.pathExists(t)){let a=await J.readJson(t);a.name=r.projectName;let o={dev:"turbo dev",build:"turbo build","check-types":"turbo check-types","dev:native":"turbo -F native dev","dev:web":"turbo -F web dev","dev:server":"turbo -F server dev","db:push":"turbo -F server db:push","db:studio":"turbo -F server db:studio"},n={dev:"pnpm -r dev",build:"pnpm -r build","check-types":"pnpm -r check-types","dev:native":"pnpm --filter native dev","dev:web":"pnpm --filter web dev","dev:server":"pnpm --filter server dev","db:push":"pnpm --filter server db:push","db:studio":"pnpm --filter server db:studio"},s={dev:"npm run dev --workspaces",build:"npm run build --workspaces","check-types":"npm run check-types --workspaces","dev:native":"npm run dev --workspace native","dev:web":"npm run dev --workspace web","dev:server":"npm run dev --workspace server","db:push":"npm run db:push --workspace server","db:studio":"npm run db:studio --workspace server"},i={dev:"bun run --filter '*' dev",build:"bun run --filter '*' build","check-types":"bun run --filter '*' check-types","dev:native":"bun run --filter native dev","dev:web":"bun run --filter web dev","dev:server":"bun run --filter server dev","db:push":"bun run --filter server db:push","db:studio":"bun run --filter server db:studio"};r.addons.includes("turborepo")?a.scripts=o:r.packageManager==="pnpm"?a.scripts=n:r.packageManager==="npm"?a.scripts=s:r.packageManager==="bun"?a.scripts=i:a.scripts={};let{stdout:c}=await Ea(r.packageManager,["-v"],{cwd:e});a.packageManager=`${r.packageManager}@${c.trim()}`,await J.writeJson(t,a,{spaces:2})}}async function Fa(e,r){let t=yt.join(e,"apps/server/package.json");if(await J.pathExists(t)){let a=await J.readJson(t);r.database!=="none"&&(r.database==="sqlite"&&r.orm==="drizzle"&&(a.scripts["db:local"]="turso dev --db-file local.db"),r.orm==="prisma"?(a.scripts["db:push"]="prisma db push --schema ./prisma/schema",a.scripts["db:studio"]="prisma studio"):r.orm==="drizzle"&&(a.scripts["db:push"]="drizzle-kit push",a.scripts["db:studio"]="drizzle-kit studio")),await J.writeJson(t,a,{spaces:2})}}async function vt(e,r){if(!r)return;if((await wt({cwd:e,reject:!1,stderr:"pipe"})`git --version`).exitCode!==0){$a.warn(Ca.yellow("Git is not installed"));return}let a=await wt({cwd:e,reject:!1,stderr:"pipe"})`git init`;if(a.exitCode!==0)throw new Error(`Git initialization failed: ${a.stderr}`)}import me from"node:path";import fe from"fs-extra";async function jt(e){let{projectName:r,runtime:t,backend:a}=e,o=me.resolve(process.cwd(),r);if(a==="next")return;let n=me.join(o,"apps/server");t==="bun"?await Ra(n,a):t==="node"&&await Ba(n,a)}async function Ra(e,r){let t=me.join(e,"package.json"),a=await fe.readJson(t);a.scripts={...a.scripts,dev:"bun run --hot src/index.ts",start:"bun run dist/src/index.js"},await fe.writeJson(t,a,{spaces:2}),await w({devDependencies:["@types/bun"],projectDir:e})}async function Ba(e,r){let t=me.join(e,"package.json"),a=await fe.readJson(t);a.scripts={...a.scripts,dev:"tsx watch src/index.ts",start:"node dist/src/index.js"},await fe.writeJson(t,a,{spaces:2}),await w({devDependencies:["tsx","@types/node"],projectDir:e}),r==="hono"?await w({dependencies:["@hono/node-server"],projectDir:e}):r==="elysia"&&await w({dependencies:["@elysiajs/node"],projectDir:e})}import u from"node:path";import D from"consola";import h from"fs-extra";import{globby as kt}from"globby";import C from"picocolors";import Na from"node:path";import Ia from"consola";import Fe from"fs-extra";import he from"handlebars";async function Re(e,r,t){try{let a=await Fe.readFile(e,"utf-8"),n=he.compile(a)(t);await Fe.ensureDir(Na.dirname(r)),await Fe.writeFile(r,n)}catch(a){throw Ia.error(`Error processing template ${e}:`,a),new Error(`Failed to process template ${e}`)}}he.registerHelper("or",(e,r)=>e||r);he.registerHelper("eq",(e,r)=>e===r);he.registerHelper("includes",(e,r)=>Array.isArray(e)&&e.includes(r));async function x(e,r,t,a,o=!0){let n=await kt(e,{cwd:r,dot:!0,onlyFiles:!0,absolute:!1});for(let s of n){let i=u.join(r,s),c=s;s.endsWith(".hbs")&&(c=s.slice(0,-4));let l=u.join(t,c);if(await h.ensureDir(u.dirname(l)),i.endsWith(".hbs"))await Re(i,l,a);else{if(!o&&await h.pathExists(l))continue;await h.copy(i,l,{overwrite:!0})}}}async function xt(e,r){let t=u.join(v,"templates/base");await x(["package.json","_gitignore"],t,e,r)}async function At(e,r){let t=r.frontend.filter(o=>o==="tanstack-router"||o==="react-router"||o==="tanstack-start"||o==="next"),a=r.frontend.includes("native");if(t.length>0){let o=u.join(e,"apps/web");await h.ensureDir(o);let n=u.join(v,"templates/frontend/web-base");await h.pathExists(n)&&await x("**/*",n,o,r);for(let l of t){let d=u.join(v,`templates/frontend/${l}`);await h.pathExists(d)&&await x("**/*",d,o,r)}let s=t[0],i=u.join(v,`templates/api/${r.api}/web/base`);await h.pathExists(i)&&await x("**/*",i,o,r);let c=u.join(v,`templates/api/${r.api}/web/${s}`);await h.pathExists(c)&&await x("**/*",c,o,r)}if(a){let o=u.join(e,"apps/native");await h.ensureDir(o);let n=u.join(v,"templates/frontend/native");await h.pathExists(n)&&await x("**/*",n,o,r);let s=u.join(v,`templates/api/${r.api}/native`);await h.pathExists(s)&&await x("**/*",s,o,r)}}async function St(e,r){if(r.backend==="none")return;let t=u.join(e,"apps/server");await h.ensureDir(t);let a=u.join(v,"templates/backend/server-base");await h.pathExists(a)?await x("**/*",a,t,r):D.warn(C.yellow(`Warning: server-base template not found at ${a}`));let o=u.join(v,`templates/backend/${r.backend}`);await h.pathExists(o)?await x("**/*",o,t,r):D.warn(C.yellow(`Warning: Backend template directory not found, skipping: ${o}`));let n=u.join(v,`templates/api/${r.api}/server/base`);await h.pathExists(n)&&await x("**/*",n,t,r);let s=u.join(v,`templates/api/${r.api}/server/${r.backend}`);await h.pathExists(s)&&await x("**/*",s,t,r)}async function Dt(e,r){if(r.orm==="none"||r.database==="none")return;let t=u.join(e,"apps/server");await h.ensureDir(t);let a=u.join(v,`templates/db/${r.orm}/${r.database}`);await h.pathExists(a)?await x("**/*",a,t,r):D.warn(C.yellow(`Warning: Database/ORM template directory not found, skipping: ${a}`))}async function $t(e,r){if(!r.auth)return;let t=u.join(e,"apps/server"),a=u.join(e,"apps/web"),o=u.join(e,"apps/native"),n=await h.pathExists(t),s=await h.pathExists(a),i=await h.pathExists(o),c=r.frontend.filter(d=>d==="tanstack-router"||d==="react-router"||d==="tanstack-start"||d==="next"),l=r.frontend.includes("native");if(n){let d=u.join(v,"templates/auth/server/base");if(await h.pathExists(d)?await x("**/*",d,t,r):D.warn(C.yellow(`Warning: Base auth server template not found at ${d}`)),r.backend==="next"){let y=u.join(v,"templates/auth/server/next");await h.pathExists(y)?await x("**/*",y,t,r):D.warn(C.yellow(`Warning: Next auth server template not found at ${y}`))}if(r.orm!=="none"&&r.database!=="none"){let y=r.orm,p=r.database,b="";y==="drizzle"?b=u.join(v,`templates/auth/server/db/drizzle/${p}`):y==="prisma"&&(b=u.join(v,`templates/auth/server/db/prisma/${p}`)),b&&await h.pathExists(b)?await x("**/*",b,t,r):D.warn(C.yellow(`Warning: Auth template for ${y}/${p} not found at ${b}`))}}else D.warn(C.yellow("Warning: apps/server directory does not exist, skipping server-side auth template setup."));if(c.length>0&&s){let d=u.join(v,"templates/auth/web/base");await h.pathExists(d)?await x("**/*",d,a,r):D.warn(C.yellow(`Warning: Base auth web template not found at ${d}`));for(let y of c){let p=u.join(v,`templates/auth/web/${y}`);await h.pathExists(p)?await x("**/*",p,a,r):D.warn(C.yellow(`Warning: Auth web template for ${y} not found at ${p}`))}}if(l&&i){let d=u.join(v,"templates/auth/native");await h.pathExists(d)?await x("**/*",d,o,r):D.warn(C.yellow(`Warning: Auth native template not found at ${d}`))}}async function Et(e,r){if(r.addons.includes("turborepo")){let t=u.join(v,"templates/addons/turborepo");await h.pathExists(t)?await x("**/*",t,e,r):D.warn(C.yellow("Warning: Turborepo addon template not found."))}if(r.addons.includes("husky")){let t=u.join(v,"templates/addons/husky");await h.pathExists(t)?await x("**/*",t,e,r):D.warn(C.yellow("Warning: Husky addon template not found."))}if(r.addons.includes("biome")){let t=u.join(v,"templates/addons/biome");await h.pathExists(t)?await x("**/*",t,e,r):D.warn(C.yellow("Warning: Biome addon template not found."))}if(r.addons.includes("pwa")){let t=u.join(v,"templates/addons/pwa/apps/web"),a=u.join(e,"apps/web"),o=await h.pathExists(a);await h.pathExists(t)?o?await x("**/*",t,a,r):D.warn(C.yellow("Warning: apps/web directory not found, cannot setup PWA addon template.")):D.warn(C.yellow("Warning: PWA addon template not found."))}}async function Ct(e,r){if(!r.examples||r.examples.length===0)return;let t=u.join(e,"apps/server"),a=u.join(e,"apps/web"),o=await h.pathExists(t),n=await h.pathExists(a);for(let s of r.examples){let i=u.join(v,`templates/examples/${s}`);if(o){let c=u.join(i,"server");if(await h.pathExists(c)&&r.orm!=="none"){let l=u.join(c,r.orm,"base");if(await h.pathExists(l)&&await x("**/*",l,t,r,!1),r.database!=="none"){let d=u.join(c,r.orm,r.database);await h.pathExists(d)&&await x("**/*",d,t,r,!1)}}}if(n){let c=u.join(i,"web");if(await h.pathExists(c)){let l=r.frontend.filter(d=>["next","react-router","tanstack-router","tanstack-start"].includes(d));for(let d of l){let y=u.join(c,d);await h.pathExists(y)&&await x("**/*",y,a,r,!1)}}}}}async function Tt(e,r){let t=await kt(["**/.gitignore.hbs","**/_gitignore"],{cwd:e,dot:!0,onlyFiles:!0,absolute:!0,ignore:["**/node_modules/**","**/.git/**"]});for(let a of t){let o=u.dirname(a),n=u.basename(a),s=u.join(o,".gitignore");try{n===".gitignore.hbs"?(await Re(a,s,r),await h.remove(a)):n==="_gitignore"&&await h.move(a,s,{overwrite:!0})}catch(i){D.error(`Error processing gitignore file ${a}:`,i)}}}async function Ft(e,r){if(r.packageManager==="pnpm"){let t=u.join(v,"templates/extras/pnpm-workspace.yaml"),a=u.join(e,"pnpm-workspace.yaml");await h.pathExists(t)?await h.copy(t,a):D.warn(C.yellow("Warning: pnpm-workspace.yaml template not found."))}}async function Bt(e){let r=Ua(),t=La.resolve(process.cwd(),e.projectName);try{return await _a.ensureDir(t),await xt(t,e),await At(t,e),await St(t,e),await Xe(e),await Dt(t,e),await dt(e),await $t(t,e),await He(e),await Et(t,e),e.addons.length>0&&e.addons[0]!=="none"&&await qe(e),await Ct(t,e),await Ft(t,e),e.examples.length>0&&e.examples[0]!=="none"&&await mt(e),await Ve(e),await jt(e),await ut(e),await Pt(t,e),await Ze(t,e),await vt(t,e.git),await Tt(t,e),Oa.success("Project template successfully scaffolded!"),e.install&&await gt({projectDir:t,packageManager:e.packageManager,addons:e.addons}),bt({...e,depsInstalled:e.install}),t}catch(a){throw r.stop(Rt.red("Failed")),a instanceof Error&&(Ma(Rt.red(`Error during project creation: ${a.message}`)),console.error(a.stack),process.exit(1)),a}}import{cancel as qo,group as Vo}from"@clack/prompts";import Go from"picocolors";import{cancel as za,isCancel as Wa,multiselect as qa}from"@clack/prompts";import Va from"picocolors";async function Nt(e,r){if(e!==void 0)return e;let t=r?.includes("react-router")||r?.includes("tanstack-router"),a=[{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)"}],o=[{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"}],n=t?[...a,...o]:a,s=g.addons.filter(c=>t||c!=="pwa"&&c!=="tauri"),i=await qa({message:"Select addons",options:n,initialValues:s,required:!1});return Wa(i)&&(za(Va.red("Operation cancelled")),process.exit(0)),i.includes("husky")&&!i.includes("biome")&&i.push("biome"),i}import{cancel as Ga,isCancel as Ja,select as Qa}from"@clack/prompts";import Ha from"picocolors";async function It(e,r){if(e)return e;let t=r?.includes("native"),a=[{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"}];t&&(a=[{value:"trpc",label:"tRPC",hint:"End-to-end typesafe APIs made easy (Required for Native frontend)"}]);let o=await Qa({message:"Select API type",options:a,initialValue:t?"trpc":g.api});return Ja(o)&&(Ga(Ha.red("Operation cancelled")),process.exit(0)),t&&o!=="trpc"?"trpc":o}import{cancel as Ya,confirm as Ka,isCancel as Xa}from"@clack/prompts";import Za from"picocolors";async function Lt(e,r){if(!r)return!1;if(e!==void 0)return e;let t=await Ka({message:"Add authentication with Better-Auth?",initialValue:g.auth});return Xa(t)&&(Ya(Za.red("Operation cancelled")),process.exit(0)),t}import{cancel as eo,isCancel as to,select as ro}from"@clack/prompts";import ao from"picocolors";async function Mt(e){if(e!==void 0)return e;let r=await ro({message:"Select backend framework",options:[{value:"hono",label:"Hono",hint:"Lightweight, ultrafast web framework"},{value:"next",label:"Next.js",hint:"Full-stack framework with API routes"},{value:"express",label:"Express",hint:"Fast, unopinionated, minimalist web framework for Node.js"},{value:"elysia",label:"Elysia",hint:"Ergonomic web framework for building backend servers"}],initialValue:g.backend});return to(r)&&(eo(ao.red("Operation cancelled")),process.exit(0)),r}import{cancel as oo,isCancel as no,select as so}from"@clack/prompts";import io from"picocolors";async function Ot(e){if(e!==void 0)return e;let r=await so({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:g.database});return no(r)&&(oo(io.red("Operation cancelled")),process.exit(0)),r}import{cancel as co,isCancel as po,select as lo}from"@clack/prompts";import uo from"picocolors";async function Ut(e,r,t){if(r!==void 0)return r;if(e==="sqlite"&&t==="prisma")return"none";let a=[];if(e==="sqlite")a=[{value:"turso",label:"Turso",hint:"SQLi