create-better-t-stack
Version:
A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
243 lines (203 loc) • 58.8 kB
JavaScript
#!/usr/bin/env node
import{cancel as k,intro as Gr,log as R,outro as Vr,spinner as qr}from"@clack/prompts";import{Command as Jr}from"commander";import h from"picocolors";import pe from"node:path";import{fileURLToPath as xt}from"node:url";var H=()=>{let e=process.env.npm_config_user_agent;return e?.startsWith("pnpm")?"pnpm":e?.startsWith("bun")?"bun":"npm"};var $t=xt(import.meta.url),Et=pe.dirname($t),$=pe.join(Et,"../"),g={projectName:"my-better-t-app",frontend:["tanstack-router"],database:"sqlite",orm:"drizzle",auth:!0,addons:[],examples:[],git:!0,packageManager:H(),noInstall:!1,turso:!1,prismaPostgres:!1,backend:"hono",runtime:"bun"},te={"better-auth":"^1.2.4","drizzle-orm":"^0.38.4","drizzle-kit":"^0.30.5","@libsql/client":"^0.14.0",postgres:"^3.4.5","@prisma/client":"^6.5.0",prisma:"^6.5.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.5",ai:"^4.2.8","@ai-sdk/google":"^1.2.3","@prisma/extension-accelerate":"^1.3.0"};import Sa from"node:path";import{cancel as Da,spinner as La}from"@clack/prompts";import Na from"fs-extra";import Je from"picocolors";import L from"node:path";import E from"fs-extra";import Tt from"node:path";import le from"fs-extra";var P=e=>{let{dependencies:a=[],devDependencies:t=[],projectDir:r}=e,n=Tt.join(r,"package.json"),s=le.readJSONSync(n);s.dependencies||(s.dependencies={}),s.devDependencies||(s.devDependencies={});for(let i of a){let o=te[i];s.dependencies[i]=o}for(let i of t){let o=te[i];s.devDependencies[i]=o}le.writeJSONSync(n,s,{spaces:2})};import Q from"node:path";import{log as At,spinner as Ct}from"@clack/prompts";import{execa as Rt}from"execa";import K from"fs-extra";import de from"picocolors";async function ue(e,a,t){let r=Ct(),n=Q.join(e,"apps/web");if(await K.pathExists(n))try{r.start("Setting up Tauri desktop app support..."),P({devDependencies:["@tauri-apps/cli"],projectDir:n});let s=Q.join(n,"package.json");if(await K.pathExists(s)){let d=await K.readJson(s);d.scripts={...d.scripts,tauri:"tauri","desktop:dev":"tauri dev","desktop:build":"tauri build"},await K.writeJson(s,d,{spaces:2})}let i,o;switch(a){case"npm":i="npx",o=["@tauri-apps/cli@latest"];break;case"pnpm":i="pnpm",o=["dlx","@tauri-apps/cli@latest"];break;case"bun":i="bunx",o=["@tauri-apps/cli@latest"];break;default:i="npx",o=["@tauri-apps/cli@latest"]}let p=t.includes("react-router")?"http://localhost:5173":"http://localhost:3001";o=[...o,"init",`--app-name=${Q.basename(e)}`,`--window-title=${Q.basename(e)}`,"--frontend-dist=dist",`--dev-url=${p}`,`--before-dev-command=${a} run dev`,`--before-build-command=${a} run build`],await Rt(i,o,{cwd:n,env:{CI:"true"}}),r.stop("Tauri desktop app support configured successfully!")}catch(s){throw r.stop(de.red("Failed to set up Tauri")),s instanceof Error&&At.error(de.red(s.message)),s}}async function me(e,a,t,r){let n=r.includes("react-router")||r.includes("tanstack-router");a.includes("pwa")&&n&&await St(e,r),a.includes("tauri")&&n&&await ue(e,t,r),a.includes("biome")&&await It(e),a.includes("husky")&&await Ot(e)}function Ft(e,a){return L.join(e,"apps/web")}async function It(e){let a=L.join($,"template/with-biome");await E.pathExists(a)&&await E.copy(a,e,{overwrite:!0}),P({devDependencies:["@biomejs/biome"],projectDir:e});let t=L.join(e,"package.json");if(await E.pathExists(t)){let r=await E.readJson(t);r.scripts={...r.scripts,check:"biome check --write ."},await E.writeJson(t,r,{spaces:2})}}async function Ot(e){let a=L.join($,"template/with-husky");await E.pathExists(a)&&await E.copy(a,e,{overwrite:!0}),P({devDependencies:["husky","lint-staged"],projectDir:e});let t=L.join(e,"package.json");if(await E.pathExists(t)){let r=await E.readJson(t);r.scripts={...r.scripts,prepare:"husky"},r["lint-staged"]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":["biome check --write ."]},await E.writeJson(t,r,{spaces:2})}}async function St(e,a){let t=L.join($,"template/with-pwa");await E.pathExists(t)&&await E.copy(t,e,{overwrite:!0});let r=Ft(e,a);if(!await E.pathExists(r))return;P({dependencies:["vite-plugin-pwa"],devDependencies:["@vite-pwa/assets-generator"],projectDir:r});let n=L.join(r,"vite.config.ts");if(await E.pathExists(n)){let i=await E.readFile(n,"utf8");if(!i.includes("vite-plugin-pwa")){let c=i.match(/^import .* from ['"](.*)['"]/m);c?i=i.replace(c[0],`import { VitePWA } from "vite-plugin-pwa";
${c[0]}`):i=`import { VitePWA } from "vite-plugin-pwa";
${i}`}let o=`VitePWA({
registerType: "autoUpdate",
manifest: {
name: "My App",
short_name: "My App",
description: "My App",
theme_color: "#0c0c0c",
},
pwaAssets: {
disabled: false,
config: true,
},
devOptions: {
enabled: true,
},
})`;i.includes("VitePWA(")||(a.includes("react-router")?i=i.replace(/plugins: \[\s*tailwindcss\(\)/,`plugins: [
tailwindcss(),
${o}`):a.includes("tanstack-router")?i=i.replace(/plugins: \[\s*tailwindcss\(\)/,`plugins: [
tailwindcss(),
${o}`):i=i.replace(/plugins: \[/,`plugins: [
${o},`)),await E.writeFile(n,i)}let s=L.join(r,"package.json");if(await E.pathExists(s)){let i=await E.readJson(s);i.scripts={...i.scripts,"generate-pwa-assets":"pwa-assets-generator"},await E.writeJson(s,i,{spaces:2})}}import fe from"node:path";import{log as he}from"@clack/prompts";import ge from"picocolors";function we(e=32){let a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",t="",r=a.length;for(let n=0;n<e;n++)t+=a.charAt(Math.floor(Math.random()*r));return t}async function be(e,a){if(!a)return;let t=fe.join(e,"apps/server"),r=fe.join(e,"apps/web");try{P({dependencies:["better-auth"],projectDir:t}),P({dependencies:["better-auth"],projectDir:r})}catch(n){throw he.error(ge.red("Failed to configure authentication")),n instanceof Error&&he.error(ge.red(n.message)),n}}import Dt from"node:path";async function Pe(e,a,t){let r=Dt.join(e,"apps/server"),n=[],s=[];a==="hono"?(n.push("hono","@hono/trpc-server"),t==="node"&&(n.push("@hono/node-server"),s.push("tsx","@types/node"))):a==="elysia"&&(n.push("elysia","@elysiajs/cors","@elysiajs/trpc"),t==="node"&&(n.push("@elysiajs/node"),s.push("tsx","@types/node"))),t==="bun"&&s.push("@types/bun"),P({dependencies:n,devDependencies:s,projectDir:r})}import Lt from"node:path";import Nt from"fs-extra";async function ye(e,a){let t=Lt.join(e,"README.md"),r=_t(a);try{await Nt.writeFile(t,r)}catch(n){console.error("Failed to create README.md file:",n)}}function _t(e){let{projectName:a,packageManager:t,database:r,auth:n,addons:s=[],orm:i="drizzle",runtime:o="bun",frontend:c=["tanstack-router"]}=e,p=c.includes("react-router"),d=c.includes("tanstack-router"),j=c.includes("native"),A=t==="npm"?"npm run":t,b=p?"5173":"3001";return`# ${a}
This project was created with [Better-T-Stack](https://github.com/better-t-stack/Better-T-Stack), a modern TypeScript stack that combines React, ${d?"TanStack Router":"React Router"}, Hono, tRPC, and more.
## Features
${Ut(r,n,s,i,o,c)}
## Getting Started
First, install the dependencies:
\`\`\`bash
${t} install
\`\`\`
${Mt(r,n,A,i)}
Then, run the development server:
\`\`\`bash
${A} dev
\`\`\`
${d||p?`Open [http://localhost:${b}](http://localhost:${b}) in your browser to see the web application.`:""}
${j?`Use the Expo Go app to run the mobile application.
`:""}
The API is running at [http://localhost:3000](http://localhost:3000).
${s.includes("pwa")&&p?`
## 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
\`\`\`
${a}/
\u251C\u2500\u2500 apps/
${d||p?`\u2502 \u251C\u2500\u2500 web/ # Frontend application (React, ${d?"TanStack Router":"React Router"})
`:""}${j?`\u2502 \u251C\u2500\u2500 native/ # Mobile application (React Native, Expo)
`:""}\u2502 \u2514\u2500\u2500 server/ # Backend API (Hono, tRPC)
\`\`\`
## Available Scripts
${Bt(A,r,i,n,j)}
`}function Ut(e,a,t,r,n,s){let i=s.includes("tanstack-router"),o=s.includes("react-router"),c=s.includes("native"),p=["- **TypeScript** - For type safety and improved developer experience"];i?p.push("- **TanStack Router** - File-based routing with full type safety"):o&&p.push("- **React Router** - Declarative routing for React"),c&&(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","- **Hono** - Lightweight, performant server framework","- **tRPC** - End-to-end type-safe APIs",`- **${n==="bun"?"Bun":"Node.js"}** - Runtime environment`),e!=="none"&&p.push(`- **${r==="drizzle"?"Drizzle":"Prisma"}** - TypeScript-first ORM`,`- **${e==="sqlite"?"SQLite/Turso":"PostgreSQL"}** - Database engine`),a&&p.push("- **Authentication** - Email & password authentication with Better Auth");for(let d of t)d==="pwa"?p.push("- **PWA** - Progressive Web App support"):d==="tauri"?p.push("- **Tauri** - Build native desktop applications"):d==="biome"?p.push("- **Biome** - Linting and formatting"):d==="husky"&&p.push("- **Husky** - Git hooks for code quality");return p.join(`
`)}function Mt(e,a,t,r){if(e==="none")return"";let n=`## Database Setup
`;return e==="sqlite"?n+=`This project uses SQLite${r==="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"&&(n+=`This project uses PostgreSQL${r==="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.
`),n+=`
${a?"4":"3"}. ${r==="prisma"?`Generate the Prisma client and push the schema:
\`\`\`bash
${t} db:push
\`\`\``:`Apply the schema to your database:
\`\`\`bash
${t} db:push
\`\`\``}
`,n}function Bt(e,a,t,r,n){let s=`- \`${e} dev\`: Start both web and server in development mode
- \`${e} build\`: Build both web and server
- \`${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 n&&(s+=`
- \`${e} dev:native\`: Start the React Native/Expo development server`),a!=="none"&&(s+=`
- \`${e} db:push\`: Push schema changes to database
- \`${e} db:studio\`: Open database studio UI`,a==="sqlite"&&t==="drizzle"&&(s+=`
- \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),s}import $e from"node:path";import{log as ia,spinner as oa}from"@clack/prompts";import ca from"fs-extra";import Ee from"picocolors";import q from"node:path";import{cancel as zt,isCancel as Gt,log as _,password as Vt}from"@clack/prompts";import{execa as qt}from"execa";import N from"fs-extra";import z from"picocolors";async function Jt(e,a){try{_.info(z.blue("Initializing Prisma PostgreSQL"));let t=q.join(e,"prisma");await N.ensureDir(t),await qt(a==="npm"?"npx":a==="pnpm"?"pnpm dlx":"bunx",["prisma","init","--db"],{cwd:e,stdio:"inherit"}),_.info(z.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 Vt({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 Gt(n)?(zt("Database setup cancelled"),null):{databaseUrl:n}}catch(t){return t instanceof Error&&_.error(z.red(t.message)),null}}async function ae(e,a){let t=q.join(e,"apps/server",".env"),r="";await N.pathExists(t)&&(r=await N.readFile(t,"utf8"));let n=a?`DATABASE_URL="${a.databaseUrl}"`:'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"';r.includes("DATABASE_URL=")?r=r.replace(/DATABASE_URL=.*(\r?\n|$)/,`${n}$1`):r+=`
${n}`,await N.writeFile(t,r.trim())}function je(){_.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 ve(e,a="npm"){let t=q.join(e,"apps/server");try{let r=await Jt(t,a);r?(await ae(e,r),await Wt(t),_.success(z.green("Prisma PostgreSQL database configured successfully!"))):(await ae(e),je())}catch(r){_.error(z.red(`Error during Prisma PostgreSQL setup: ${r}`)),await ae(e),je(),_.info("Setup completed with manual configuration required.")}}async function Wt(e){try{P({dependencies:["@prisma/extension-accelerate"],projectDir:e});let a=q.join(e,"prisma/index.ts");await N.writeFile(a,`
import { PrismaClient } from '@prisma/client';
import { withAccelerate } from "@prisma/extension-accelerate";
const prisma = new PrismaClient().$extends(withAccelerate());
export default prisma;
`.trim());let r=q.join(e,"src/db/index.ts");if(await N.pathExists(r)){let n=await N.readFile(r,"utf8");n.includes("@prisma/extension-accelerate")||(n=`import { withAccelerate } from "@prisma/extension-accelerate";
${n}`,n=n.replace("export const db = new PrismaClient();","export const db = new PrismaClient().$extends(withAccelerate());"),await N.writeFile(r,n))}}catch{_.warn(z.yellow("Could not add Prisma Accelerate extension automatically"))}}import Ht from"node:os";import ke from"node:path";import{cancel as re,confirm as Qt,isCancel as ne,log as G,select as Kt,spinner as se,text as Xt}from"@clack/prompts";import{$ as O}from"execa";import Yt from"fs-extra";import I from"picocolors";async function Zt(){try{return(await O`turso --version`).exitCode===0}catch{return!1}}async function ea(){try{return!(await O`turso auth whoami`).stdout.includes("You are not logged in")}catch{return!1}}async function ta(){let e=se();try{return e.start("Logging in to Turso..."),await O`turso auth login`,e.stop("Logged in to Turso successfully!"),!0}catch(a){throw e.stop(I.red("Failed to log in to Turso")),a}}async function aa(e){let a=se();try{if(a.start("Installing Turso CLI..."),e)await O`brew install tursodatabase/tap/turso`;else{let{stdout:t}=await O`curl -sSfL https://get.tur.so/install.sh`;await O`bash -c '${t}'`}return a.stop("Turso CLI installed successfully!"),!0}catch(t){throw t instanceof Error&&t.message.includes("User force closed")?(a.stop(),G.warn(I.yellow("Turso CLI installation cancelled by user")),new Error("Installation cancelled")):(a.stop(I.red("Failed to install Turso CLI")),t)}}async function ra(){try{let{stdout:e}=await O`turso group list`,a=e.trim().split(`
`);return a.length<=1?[]:a.slice(1).map(r=>{let[n,s,i,o]=r.trim().split(/\s{2,}/);return{name:n,locations:s,version:i,status:o}})}catch(e){return console.error("Error fetching Turso groups:",e),[]}}async function na(){let e=await ra();if(e.length===0)return null;if(e.length===1)return e[0].name;let a=e.map(r=>({value:r.name,label:`${r.name} (${r.locations})`})),t=await Kt({message:"Select a Turso database group:",options:a});return ne(t)&&(re(I.red("Operation cancelled")),process.exit(0)),t}async function sa(e,a){try{a?await O`turso db create ${e} --group ${a}`:await O`turso db create ${e}`}catch(n){throw n instanceof Error&&n.message.includes("already exists")?new Error("DATABASE_EXISTS"):n}let{stdout:t}=await O`turso db show ${e} --url`,{stdout:r}=await O`turso db tokens create ${e}`;return{dbUrl:t.trim(),authToken:r.trim()}}async function J(e,a){let t=ke.join(e,"apps/server",".env"),r=a?`TURSO_CONNECTION_URL="${a.dbUrl}"
TURSO_AUTH_TOKEN="${a.authToken}"`:`TURSO_CONNECTION_URL=
TURSO_AUTH_TOKEN=`;await Yt.writeFile(t,r)}function X(){G.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
TURSO_CONNECTION_URL=your_database_url
TURSO_AUTH_TOKEN=your_auth_token`)}async function xe(e,a){if(!a){await J(e),G.info(I.blue("Skipping Turso setup. Setting up empty configuration.")),X();return}let t=Ht.platform(),r=t==="darwin";if(!(t!=="win32")){G.warn(I.yellow("Automatic Turso setup is not supported on Windows.")),await J(e),X();return}try{if(!await Zt()){let j=await Qt({message:"Would you like to install Turso CLI?",initialValue:!0});if(ne(j)&&(re(I.red("Operation cancelled")),process.exit(0)),!j){await J(e),X();return}await aa(r)}await ea()||await ta();let o=await na(),c=!1,p="",d=ke.basename(e);for(;!c;){let j=await Xt({message:"Enter a name for your database:",defaultValue:d,initialValue:d,placeholder:d});ne(j)&&(re(I.red("Operation cancelled")),process.exit(0)),p=j;let A=se();try{A.start(`Creating Turso database "${p}"${o?` in group "${o}"`:""}...`);let b=await sa(p,o);await J(e,b),A.stop("Turso database configured successfully!"),c=!0}catch(b){if(b instanceof Error&&b.message==="DATABASE_EXISTS")A.stop(I.yellow(`Database "${I.red(p)}" already exists`)),d=`${p}-${Math.floor(Math.random()*1e3)}`;else throw A.stop(I.red("Failed to create Turso database")),b}}}catch(s){G.error(I.red(`Error during Turso setup: ${s}`)),await J(e),X(),G.success("Setup completed with manual configuration required.")}}async function Te(e,a,t,r,n,s){let i=oa(),o=$e.join(e,"apps/server");if(a==="none"){await ca.remove($e.join(o,"src/db"));return}try{a==="sqlite"?(t==="drizzle"?P({dependencies:["drizzle-orm","@libsql/client"],devDependencies:["drizzle-kit"],projectDir:o}):t==="prisma"&&P({dependencies:["@prisma/client"],devDependencies:["prisma"],projectDir:o}),n&&await xe(e,!0)):a==="postgres"&&(t==="drizzle"?P({dependencies:["drizzle-orm","postgres"],devDependencies:["drizzle-kit"],projectDir:o}):t==="prisma"&&(P({dependencies:["@prisma/client"],devDependencies:["prisma"],projectDir:o}),a==="postgres"&&t==="prisma"&&s&&await ve(e,r)))}catch(c){throw i.stop(Ee.red("Failed to set up database")),c instanceof Error&&ia.error(Ee.red(c.message)),c}}import V from"node:path";import S from"fs-extra";async function Ae(e,a){let t=V.join(e,"apps/server"),r=V.join(t,".env"),n="";if(await S.pathExists(r)&&(n=await S.readFile(r,"utf8")),!n.includes("CORS_ORIGIN")){let o=a.frontend.includes("react-router"),c=a.frontend.includes("tanstack-router"),p="http://localhost:3000";o?p="http://localhost:5173":c&&(p="http://localhost:3001"),n+=`
CORS_ORIGIN=${p}`}if(a.auth&&(n.includes("BETTER_AUTH_SECRET")||(n+=`
BETTER_AUTH_SECRET=${we()}`),n.includes("BETTER_AUTH_URL")||(n+=`
BETTER_AUTH_URL=http://localhost:3000`)),a.database!=="none"){if(a.orm==="prisma"&&!n.includes("DATABASE_URL")){let o=a.database==="sqlite"?"":`
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;n+=o}a.database==="sqlite"&&!a.turso&&(n.includes("TURSO_CONNECTION_URL")||(n+=`
TURSO_CONNECTION_URL=http://127.0.0.1:8080`))}a.examples?.includes("ai")&&!n.includes("GOOGLE_GENERATIVE_AI_API_KEY")&&(n+=`
GOOGLE_GENERATIVE_AI_API_KEY=`),await S.writeFile(r,n.trim());let s=a.frontend.includes("react-router"),i=a.frontend.includes("tanstack-router");if(s||i){let o=V.join(e,"apps/web");await pa(o)}if(a.frontend.includes("native")){let o=V.join(e,"apps/native"),c=V.join(o,".env"),p="";await S.pathExists(c)&&(p=await S.readFile(c,"utf8")),p.includes("EXPO_PUBLIC_SERVER_URL")||(p+=`EXPO_PUBLIC_SERVER_URL=http://localhost:3000
`),await S.writeFile(c,p.trim())}}async function pa(e){let a=V.join(e,".env"),t="";await S.pathExists(a)&&(t=await S.readFile(a,"utf8")),t.includes("VITE_SERVER_URL")||(t+=`VITE_SERVER_URL=http://localhost:3000
`),await S.writeFile(a,t.trim())}import T from"node:path";import w from"fs-extra";async function Ce(e,a,t,r,n,s=["tanstack-router"]){let i=s.includes("tanstack-router"),o=s.includes("react-router"),c=i||o,p=i?"web-tanstack-router":"web-react-router",d=await w.pathExists(T.join(e,"apps/web"));a.includes("todo")&&c&&d?await ma(e,t,r,p):await ga(e,t),a.includes("ai")&&n==="hono"&&c&&d&&await la(e,p)}async function la(e,a){let t=T.join($,"template/examples/ai");if(await w.pathExists(t)){let r=T.join(t,`apps/${a}/src/routes/ai.tsx`),n=T.join(e,"apps/web/src/routes/ai.tsx");await w.pathExists(r)&&await w.copy(r,n,{overwrite:!0}),await ua(e,a);let s=T.join(e,"apps/web");P({dependencies:["ai"],projectDir:s});let i=T.join(e,"apps/server");P({dependencies:["ai","@ai-sdk/google"],projectDir:i}),await da(e)}}async function da(e){let a=T.join(e,"apps/server/src/index.ts");if(await w.pathExists(a)){let t=await w.readFile(a,"utf8");if(t.includes("hono")){let n=`import { streamText } from "ai";
import { google } from "@ai-sdk/google";
import { stream } from "hono/streaming";`,s=`
app.post("/ai", async (c) => {
const body = await c.req.json();
const messages = body.messages || [];
const result = streamText({
model: google("gemini-2.0-flash-exp"),
messages,
});
c.header("X-Vercel-AI-Data-Stream", "v1");
c.header("Content-Type", "text/plain; charset=utf-8");
return stream(c, (stream) => stream.pipe(result.toDataStream()));
});`;if(t.includes("import {")){let o=t.lastIndexOf("import"),c=t.indexOf(`
`,o);t=`${t.substring(0,c+1)}
${n}
${t.substring(c+1)}`}else t=`${n}
${t}`;let i=t.indexOf('app.use("/trpc"')||t.indexOf("app.use(trpc(");if(i!==-1)t=`${t.substring(0,i)}${s}
${t.substring(i)}`;else{let o=t.indexOf("export default");o!==-1?t=`${t.substring(0,o)}${s}
${t.substring(o)}`:t=`${t}
${s}`}await w.writeFile(a,t)}}}async function ua(e,a){let t=T.join(e,"apps/web/src/components/header.tsx");if(await w.pathExists(t)){let r=await w.readFile(t,"utf8"),n=/const links = \[\s*([^;]*?)\s*\];/s,s=r.match(n);if(s){let i=s[1];if(!i.includes('"/ai"')){let o=`const links = [
${i}${i.trim().endsWith(",")?"":","}
{ to: "/ai", label: "AI Chat" },
];`;r=r.replace(n,o),await w.writeFile(t,r)}}}}async function ma(e,a,t,r){let n=T.join($,"template/examples/todo");if(await w.pathExists(n)){let s=T.join(n,`apps/${r}/src/routes/todos.tsx`),i=T.join(e,"apps/web/src/routes/todos.tsx");if(await w.pathExists(s)&&await w.copy(s,i,{overwrite:!0}),a!=="none"){let o=T.join(n,`apps/server/src/routers/with-${a}-todo.ts`),c=T.join(e,"apps/server/src/routers/todo.ts");await w.pathExists(o)&&await w.copy(o,c,{overwrite:!0}),await fa(e)}await ha(e,r)}}async function fa(e){let a=T.join(e,"apps/server/src/routers/index.ts");if(await w.pathExists(a)){let t=await w.readFile(a,"utf8");if(!t.includes("import { todoRouter }")){let r=t.lastIndexOf("import"),n=t.indexOf(`
`,r);n!==-1?t=`${t.slice(0,n)}
import { todoRouter } from "./todo";${t.slice(n)}`:t=`import { todoRouter } from "./todo";
${t}`;let s=t.indexOf("export const appRouter = router({");if(s!==-1){let i=t.indexOf("{",s)+1;t=`${t.slice(0,i)}
todo: todoRouter,${t.slice(i)}`}await w.writeFile(a,t)}}}async function ha(e,a){let t=T.join(e,"apps/web/src/components/header.tsx");if(await w.pathExists(t)){let r=await w.readFile(t,"utf8"),n=/const links = \[\s*([^;]*?)\s*\];/s,s=r.match(n);if(s){let i=s[1];if(!i.includes('"/todos"')){let o=`const links = [
${i}${i.trim().endsWith(",")?"":","}
{ to: "/todos", label: "Todos" },
];`;r=r.replace(n,o),await w.writeFile(t,r)}}}}async function ga(e,a){if(a==="drizzle"){let r=T.join(e,"apps/server/src/db/schema/todo.ts");await w.pathExists(r)&&await w.remove(r)}else if(a==="prisma"){let r=T.join(e,"apps/server/prisma/schema/todo.prisma");await w.pathExists(r)&&await w.remove(r)}let t=T.join(e,"apps/server/src/routers/todo.ts");await w.pathExists(t)&&await w.remove(t),await wa(e)}async function wa(e){let a=T.join(e,"apps/server/src/routers/index.ts");if(await w.pathExists(a)){let t=await w.readFile(a,"utf8");t=t.replace(/import { todoRouter } from ".\/todo";/,""),t=t.replace(/todo: todoRouter,/,""),await w.writeFile(a,t)}}import{log as Re,spinner as Fe}from"@clack/prompts";import{$ as Ie}from"execa";import Y from"picocolors";async function Oe({projectDir:e,packageManager:a,addons:t=[]}){let r=Fe();try{r.start(`Running ${a} install...`),await Ie({cwd:e,stderr:"inherit"})`${a} install`,r.stop("Dependencies installed successfully"),(t.includes("biome")||t.includes("husky"))&&await ba(e,a)}catch(n){throw r.stop(Y.red("Failed to install dependencies")),n instanceof Error&&Re.error(Y.red(`Installation error: ${n.message}`)),n}}async function ba(e,a){let t=Fe();try{t.start("Running Biome format check..."),await Ie({cwd:e,stderr:"inherit"})`${a} biome check --write .`,t.stop("Biome check completed successfully")}catch{t.stop(Y.yellow("Biome check encountered issues")),Re.warn(Y.yellow("Some files may need manual formatting"))}}import{note as Pa}from"@clack/prompts";import y from"picocolors";function Se(e,a,t,r,n,s,i,o){let c=t==="npm"?"npm run":t,p=`cd ${a}`,d=s?.includes("husky")||s?.includes("biome"),j=e!=="none"?va(e,n,c,i):"",A=s?.includes("tauri")?ka(c):"",b=d?ja(c):"",v=o?.includes("native")?ya():"",x=s?.includes("pwa")&&o?.includes("react-router")?xa():"",F=o?.includes("tanstack-router"),D=o?.includes("react-router"),ce=F||D,vt=o?.includes("native"),kt=ce||vt;Pa(`${y.cyan("1.")} ${p}
${r?"":`${y.cyan("2.")} ${t} install
`}${y.cyan(r?"2.":"3.")} ${c} dev
${y.bold("Your project will be available at:")}
${kt?`${ce?`${y.cyan("\u2022")} Frontend: http://localhost:${D?"5173":"3001"}
`:""}`:`${y.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)
`}${y.cyan("\u2022")} API: http://localhost:3000
${v?`
${v.trim()}`:""}${j?`
${j.trim()}`:""}${A?`
${A.trim()}`:""}${b?`
${b.trim()}`:""}${x?`
${x.trim()}`:""}`,"Next steps")}function ya(){return`${y.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 ja(e){return`${y.bold("Linting and formatting:")}
${y.cyan("\u2022")} Format and lint fix: ${`${e} check`}
`}function va(e,a,t,r){let n=[];return a==="prisma"?(e==="sqlite"&&n.push(`${y.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"&&n.push(`${y.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors,
follow the guidance provided in the error messages`),n.push(`${y.cyan("\u2022")} Apply schema: ${`${t} db:push`}`),n.push(`${y.cyan("\u2022")} Database UI: ${`${t} db:studio`}`)):a==="drizzle"&&(e==="sqlite"&&n.push(`${y.cyan("\u2022")} Start local DB: ${`cd apps/server && ${t} db:local`}`),n.push(`${y.cyan("\u2022")} Apply schema: ${`${t} db:push`}`),n.push(`${y.cyan("\u2022")} Database UI: ${`${t} db:studio`}`)),n.length?`${y.bold("Database commands:")}
${n.join(`
`)}
`:""}function ka(e){return`
${y.bold("Desktop app with Tauri:")}
${y.cyan("\u2022")} Start desktop app: ${`cd apps/web && ${e} desktop:dev`}
${y.cyan("\u2022")} Build desktop app: ${`cd apps/web && ${e} desktop:build`}
${y.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.
See: https://v2.tauri.app/start/prerequisites/
`}function xa(){return`${y.bold("PWA with React Router v7:")}
${y.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
`}import Z from"node:path";import{log as $a}from"@clack/prompts";import{$ as De,execa as Ea}from"execa";import U from"fs-extra";import Ta from"picocolors";async function Le(e,a){await Aa(e,a),await Ca(e,a)}async function Aa(e,a){let t=Z.join(e,"package.json");if(await U.pathExists(t)){let r=await U.readJson(t);r.name=a.projectName;let{stdout:n}=await Ea(a.packageManager,["-v"],{cwd:e});if(r.packageManager=`${a.packageManager}@${n.trim()}`,await U.writeJson(t,r,{spaces:2}),a.packageManager==="pnpm"){let s=Z.join($,"template/with-pnpm/pnpm-workspace.yaml"),i=Z.join(e,"pnpm-workspace.yaml");await U.pathExists(s)&&await U.copy(s,i)}}}async function Ca(e,a){let t=Z.join(e,"apps/server/package.json");if(await U.pathExists(t)){let r=await U.readJson(t);a.database!=="none"&&(a.database==="sqlite"&&(r.scripts["db:local"]="turso dev --db-file local.db"),a.orm==="prisma"?(r.scripts["db:push"]="prisma db push --schema ./prisma/schema",r.scripts["db:studio"]="prisma studio"):a.orm==="drizzle"&&(r.scripts["db:push"]="drizzle-kit push",r.scripts["db:studio"]="drizzle-kit studio")),await U.writeJson(t,r,{spaces:2})}}async function Ne(e,a){if(!a)return;if((await De({cwd:e,reject:!1,stderr:"pipe"})`git --version`).exitCode!==0){$a.warn(Ta.yellow("Git is not installed"));return}let r=await De({cwd:e,reject:!1,stderr:"pipe"})`git init`;if(r.exitCode!==0)throw new Error(`Git initialization failed: ${r.stderr}`)}import ee from"node:path";import M from"fs-extra";async function _e(e,a,t){let r=ee.join(e,"apps/server"),n=ee.join(r,"src/index.ts"),s=await M.readFile(n,"utf-8");a==="bun"?await Ra(r,n,s,t):a==="node"&&await Fa(r,n,s,t)}async function Ra(e,a,t,r){let n=ee.join(e,"package.json"),s=await M.readJson(n);if(s.scripts={...s.scripts,dev:"bun run --hot src/index.ts",start:"bun run dist/src/index.js"},await M.writeJson(n,s,{spaces:2}),P({devDependencies:["@types/bun"],projectDir:e}),r==="hono"){let i=`${t}
export default app;
`;await M.writeFile(a,i)}}async function Fa(e,a,t,r){let n=ee.join(e,"package.json"),s=await M.readJson(n);if(s.scripts={...s.scripts,dev:"tsx watch src/index.ts",start:"node dist/src/index.js"},await M.writeJson(n,s,{spaces:2}),P({devDependencies:["tsx","@types/node"],projectDir:e}),r==="hono"){P({dependencies:["@hono/node-server"],projectDir:e});let i=`import { serve } from "@hono/node-server";
`,o=`
serve(
{
fetch: app.fetch,
port: 3000,
},
(info) => {
console.log(\`Server is running on http://localhost:\${info.port}\`);
},
);
`;if(!t.includes("@hono/node-server")){let c=t.lastIndexOf("import"),p=t.substring(0,c),d=t.substring(c),j=p+i+d+o;await M.writeFile(a,j)}}else if(r==="elysia"&&(P({dependencies:["@elysiajs/node"],projectDir:e}),!t.includes("@elysiajs/node"))){let i=`import { node } from "@elysiajs/node";
`,o=t.indexOf(`
`,t.indexOf("import")),c=t.substring(0,o+1),p=t.substring(o+1),d=c+i+p;d=d.replace(/const app = new Elysia\([^)]*\)/,"const app = new Elysia({ adapter: node() })"),await M.writeFile(a,d)}}import u from"node:path";import l from"fs-extra";async function Ue(e){let a=u.join($,"template/base");if(!await l.pathExists(a))throw new Error(`Template directory not found: ${a}`);await l.ensureDir(e);let t=await l.readdir(a);for(let s of t){let i=u.join(a,s),o=u.join(e,s);s!=="apps"&&(await l.stat(i).then(c=>c.isDirectory())?await l.copy(i,o):await l.copy(i,o))}await l.ensureDir(u.join(e,"apps"));let r=u.join(a,"apps/server"),n=u.join(e,"apps/server");await l.pathExists(r)&&await l.copy(r,n)}async function Me(e,a){let t=a.includes("tanstack-router"),r=a.includes("react-router"),n=a.includes("native");if(t||r){let s=u.join(e,"apps/web");await l.ensureDir(s);let i=u.join($,"template/base/apps/web-base");await l.pathExists(i)&&await l.copy(i,s);let o=t?"web-tanstack-router":"web-react-router",c=u.join($,`template/base/apps/${o}`);await l.pathExists(c)&&await l.copy(c,s,{overwrite:!0});let p=u.join(s,"package.json");if(await l.pathExists(p)){let d=await l.readJson(p);d.name="web",await l.writeJson(p,d,{spaces:2})}}if(n){let s=u.join($,"template/base/apps/native"),i=u.join(e,"apps/native");await l.pathExists(s)&&await l.copy(s,i),await l.writeFile(u.join(e,".npmrc"),`node-linker=hoisted
`)}}async function Be(e,a){let t=u.join($,`template/with-${a}`);await l.pathExists(t)&&await l.copy(t,e,{overwrite:!0})}async function ze(e,a,t,r){if(a==="none"||t==="none")return;let n=u.join($,Ia(a,t));if(await l.pathExists(n)&&(await l.copy(n,e,{overwrite:!0}),!r)){if(a==="prisma"){let s=u.join(e,"apps/server/prisma/schema/auth.prisma");await l.pathExists(s)&&await l.remove(s)}else if(a==="drizzle"){let s=u.join(e,"apps/server/src/db/schema/auth.ts");await l.pathExists(s)&&await l.remove(s)}}}async function Ge(e,a,t,r,n,s){if(!a)return;let i=u.join($,"template/with-auth");if(await l.pathExists(i)){let o=s.includes("react-router"),c=s.includes("tanstack-router");if(o||c){let x=u.join(e,"apps/web"),F=u.join(i,"apps/web-base");if(await l.pathExists(F)&&await l.copy(F,x,{overwrite:!0}),o){let D=u.join(i,"apps/web-react-router");await l.pathExists(D)&&await l.copy(D,x,{overwrite:!0})}if(c){let D=u.join(i,"apps/web-tanstack-router");await l.pathExists(D)&&await l.copy(D,x,{overwrite:!0})}}let p=u.join(i,"apps/server/src"),d=u.join(e,"apps/server/src");await l.copy(u.join(p,"lib/trpc.ts"),u.join(d,"lib/trpc.ts"),{overwrite:!0}),await l.copy(u.join(p,"routers/index.ts"),u.join(d,"routers/index.ts"),{overwrite:!0});let j=`with-${t}-context.ts`;await l.copy(u.join(p,"lib",j),u.join(d,"lib/context.ts"),{overwrite:!0});let A=`with-${t}-index.ts`;await l.copy(u.join(p,A),u.join(d,"index.ts"),{overwrite:!0});let b=Oa(r,n),v=u.join(p,b);if(await l.pathExists(v)){let x=await l.readdir(v);for(let F of x)await l.copy(u.join(v,F),u.join(d,"lib",F),{overwrite:!0})}if(s.includes("native")){let x=u.join(i,"apps/native"),F=u.join(e,"apps/native");await l.pathExists(x)&&await l.copy(x,F,{overwrite:!0})}}}async function Ve(e){let a=await qe(e);for(let t of a)if(await l.pathExists(t)){let r=u.join(u.dirname(t),".gitignore");await l.move(t,r,{overwrite:!0})}}async function qe(e){let a=[],t=u.join(e,"_gitignore");await l.pathExists(t)&&a.push(t);try{let r=await l.readdir(e,{withFileTypes:!0});for(let n of r)if(n.isDirectory()&&n.name!=="node_modules"){let s=u.join(e,n.name),i=await qe(s);a.push(...i)}}catch{}return a}function Ia(e,a){return e==="drizzle"?a==="sqlite"?"template/with-drizzle-sqlite":"template/with-drizzle-postgres":e==="prisma"?a==="sqlite"?"template/with-prisma-sqlite":"template/with-prisma-postgres":"template/base"}function Oa(e,a){if(e==="drizzle")return a==="sqlite"?"with-drizzle-sqlite-lib":"with-drizzle-postgres-lib";if(e==="prisma")return a==="sqlite"?"with-prisma-sqlite-lib":"with-prisma-postgres-lib";throw new Error("Invalid ORM or database configuration for auth setup")}async function We(e){let a=La(),t=Sa.resolve(process.cwd(),e.projectName);try{return await Na.ensureDir(t),await Ue(t),await Me(t,e.frontend),await Ve(t),await Be(t,e.backend),await Pe(t,e.backend,e.runtime),await ze(t,e.orm,e.database,e.auth),await Te(t,e.database,e.orm,e.packageManager,e.turso??e.database==="sqlite",e.prismaPostgres),await Ge(t,e.auth,e.backend,e.orm,e.database,e.frontend),await be(t,e.auth),await _e(t,e.runtime,e.backend),await Ce(t,e.examples,e.orm,e.auth,e.backend,e.frontend),await Ae(t,e),await Ne(t,e.git),e.addons.length>0&&await me(t,e.addons,e.packageManager,e.frontend),await Le(t,e),await ye(t,e),e.noInstall||await Oe({projectDir:t,packageManager:e.packageManager,addons:e.addons}),Se(e.database,e.projectName,e.packageManager,!e.noInstall,e.orm,e.addons,e.runtime,e.frontend),t}catch(r){throw a.message(Je.red("Failed")),r instanceof Error&&(Da(Je.red(`Error during project creation: ${r.message}`)),process.exit(1)),r}}import{cancel as _r,group as Ur}from"@clack/prompts";import Mr from"picocolors";import{cancel as _a,isCancel as Ua,multiselect as Ma}from"@clack/prompts";import Ba from"picocolors";async function He(e,a){if(e!==void 0)return e;let t=a?.includes("react-router")||a?.includes("tanstack-router"),r=[{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)"}],s=t?[...[{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"}],...r]:r,i=g.addons.filter(c=>t||c!=="pwa"&&c!=="tauri"),o=await Ma({message:"Select addons",options:s,initialValues:i,required:!1});return Ua(o)&&(_a(Ba.red("Operation cancelled")),process.exit(0)),o.includes("husky")&&!o.includes("biome")&&o.push("biome"),o}import{cancel as za,confirm as Ga,isCancel as Va,log as qa}from"@clack/prompts";import Qe from"picocolors";async function Ke(e,a,t){if(!a)return!1;let r=t?.includes("native"),n=t?.includes("tanstack-router")||t?.includes("react-router");if(r&&qa.warn(Qe.yellow("Note: Authentication is not yet available with native")),!n)return!1;if(e!==void 0)return e;let s=await Ga({message:"Add authentication with Better-Auth?",initialValue:g.auth});return Va(s)&&(za(Qe.red("Operation cancelled")),process.exit(0)),s}import{cancel as Ja,isCancel as Wa,select as Ha}from"@clack/prompts";import Qa from"picocolors";async function Xe(e){if(e!==void 0)return e;let a=await Ha({message:"Select backend framework",options:[{value:"hono",label:"Hono",hint:"Lightweight, ultrafast web framework"},{value:"elysia",label:"Elysia",hint:"Ergonomic web framework for building backend servers"}],initialValue:g.backend});return Wa(a)&&(Ja(Qa.red("Operation cancelled")),process.exit(0)),a}import{cancel as Ka,isCancel as Xa,select as Ya}from"@clack/prompts";import Za from"picocolors";async function Ye(e){if(e!==void 0)return e;let a=await Ya({message:"Select database",options:[{value:"none",label:"None",hint:"No database setup"},{value:"sqlite",label:"SQLite",hint:"by Turso"},{value:"postgres",label:"PostgreSQL",hint:"Traditional relational database"}],initialValue:g.database});return Xa(a)&&(Ka(Za.red("Operation cancelled")),process.exit(0)),a}import{cancel as er,isCancel as tr,multiselect as Ze}from"@clack/prompts";import ar from"picocolors";async function et(e,a,t,r){if(e!==void 0)return e;if(a==="none")return[];if(!(t?.includes("react-router")||t?.includes("tanstack-router")))return[];let s=[];return r==="elysia"&&(s=await Ze({message:"Include examples",options:[{value:"todo",label:"Todo App",hint:"A simple CRUD example app"}],required:!1,initialValues:g.examples})),r==="hono"&&(s=await Ze({message:"Include examples",options:[{value:"todo",label:"Todo App",hint:"A simple CRUD example app"},{value:"ai",label:"AI Chat",hint:"A simple AI chat interface using AI SDK"}],required:!1,initialValues:g.examples})),tr(s)&&(er(ar.red("Operation cancelled")),process.exit(0)),s}import{cancel as tt,isCancel as at,multiselect as rr,select as nr}from"@clack/prompts";import rt from"picocolors";async function nt(e){if(e!==void 0)return e;let a=await rr({message:"Select platforms to develop for",options:[{value:"web",label:"Web",hint:"React Web Application"},{value:"native",label:"Native",hint:"Create a React Native/Expo app"}],initialValues:g.frontend.some(r=>r==="tanstack-router"||r==="react-router")?["web"]:[]});at(a)&&(tt(rt.red("Operation cancelled")),process.exit(0));let t=[];if(a.includes("web")){let r=await nr({message:"Choose frontend framework",options:[{value:"tanstack-router",label:"TanStack Router",hint:"Modern and scalable routing for React Applications"},{value:"react-router",label:"React Router",hint:"A user\u2011obsessed, standards\u2011focused, multi\u2011strategy router you can deploy anywhere."}],initialValue:g.frontend.find(n=>n==="tanstack-router"||n==="react-router")||"tanstack-router"});at(r)&&(tt(rt.red("Operation cancelled")),process.exit(0)),t.push(r)}return a.includes("native")&&t.push("native"),t}import{cancel as sr,confirm as ir,isCancel as or}from"@clack/prompts";import cr from"picocolors";async function st(e){if(e!==void 0)return e;let a=await ir({message:"Initialize git repository?",initialValue:g.git});return or(a)&&(sr(cr.red("Operation cancelled")),process.exit(0)),a}import{cancel as pr,confirm as lr,isCancel as dr}from"@clack/prompts";import ur from"picocolors";async function it(e){if(e!==void 0)return e;let a=await lr({message:"Install dependencies?",initialValue:!g.noInstall});return dr(a)&&(pr(ur.red("Operation cancelled")),process.exit(0)),!a}import{cancel as mr,isCancel as fr,select as hr}from"@clack/prompts";import gr from"picocolors";async function ot(e,a){if(!a)return"none";if(e!==void 0)return e;let t=await hr({message:"Select ORM",options:[{value:"drizzle",label:"Drizzle",hint:"Type-safe, lightweight ORM"},{value:"prisma",label:"Prisma",hint:"Powerful, feature-rich ORM"}],initialValue:g.orm});return fr(t)&&(mr(gr.red("Operation cancelled")),process.exit(0)),t}import{cancel as wr,isCancel as br,select as Pr}from"@clack/prompts";import yr from"picocolors";async function ct(e){if(e!==void 0)return e;let a=H(),t=await Pr({message:"Choose package manager",options:[{value:"npm",label:"npm",hint:"Node Package Manager"},{value:"bun",label:"bun",hint:"All-in-one JavaScript runtime & toolkit"},{value:"pnpm",label:"pnpm",hint:"Fast, disk space efficient package manager"}],initialValue:a});return br(t)&&(wr(yr.red("Operation cancelled")),process.exit(0)),t}import{cancel as jr,confirm as vr,isCancel as kr}from"@clack/prompts";import xr from"picocolors";async function pt(e){if(e!==void 0)return e;let a=await vr({message:"Set up Prisma Postgres database?",initialValue:g.prismaPostgres});return kr(a)&&(jr(xr.red("Operation cancelled")),process.exit(0)),a}import W from"node:path";import{cancel as $r,isCancel as Er,text as Tr}from"@clack/prompts";import B from"fs-extra";import Ar from"picocolors";var Cr=["<",">",":",'"',"|","?","*"],lt=255;function dt(e){if(e!=="."){if(!e)return"Project name cannot be empty";if(e.length>lt)return`Project name must be less than ${lt} characters`;if(Cr.some(a=>e.includes(a)))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"||e.toLowerCase()==="favicon.ico")return"Project name is reserved"}}async function ut(e){if(e)if(e==="."){let s=process.cwd();if(B.readdirSync(s).length===0)return e}else{let s=W.basename(e);if(!dt(s)){let o=W.resolve(process.cwd(),e);if(!B.pathExistsSync(o)||B.readdirSync(o).length===0)return e}}let a=!1,t="",r=g.projectName,n=1;for(;B.pathExistsSync(W.resolve(process.cwd(),r));)r=`${g.projectName}-${n}`,n++;for(;!a;){let s=await Tr({message:"Enter your project name or path (relative to current directory)",placeholder:r,initialValue:e,defaultValue:r,validate:i=>{let o=i.trim()||r;if(o==="."){if(B.readdirSync(process.cwd()).length>0)return"Current directory is not empty. Please choose a different directory.";a=!0;return}let c=W.resolve(process.cwd(),o),p=W.basename(c),d=dt(p);if(d)return d;if(!c.startsWith(process.cwd()))return"Project path must be within current directory";if(B.pathExistsSync(c)&&B.readdirSync(c).length>0)return`Directory "${o}" already exists and is not empty. Please choose a different name or path.`;a=!0}});Er(s)&&($r(Ar.red("Operation cancelled.")),process.exit(0)),t=s||r}return t}import{cancel as Rr,isCancel as Fr,select as Ir}from"@clack/prompts";import Or from"picocolors";async function mt(e){if(e!==void 0)return e;let a=await Ir({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:g.runtime});return Fr(a)&&(Rr(Or.red("Operation cancelled")),process.exit(0)),a}import{cancel as Sr,confirm as Dr,isCancel as Lr}from"@clack/prompts";import Nr from"picocolors";async function ft(e){if(e!==void 0)return e;let a=await Dr({message:"Set up Turso database?",initialValue:g.turso});return Lr(a)&&(Sr(Nr.red("Operation cancelled")),process.exit(0)),a}async function ht(e){let a=await Ur({projectName:async()=>ut(e.projectName),frontend:()=>nt(e.frontend),backend:()=>Xe(e.backend),runtime:()=>mt(e.runtime),database:()=>Ye(e.database),orm:({results:t})=>ot(e.orm,t.database!=="none"),auth:({results:t})=>Ke(e.auth,t.database!=="none",t.frontend),turso:({results:t})=>t.database==="sqlite"&&t.orm!=="prisma"?ft(e.turso):Promise.resolve(!1),prismaPostgres:({results:t})=>t.database==="postgres"&&t.orm==="prisma"?pt(e.prismaPostgres):Promise.resolve(!1),addons:({results:t})=>He(e.addons,t.frontend),examples:({results:t})=>et(e.examples,t.database,t.frontend,t.backend),git:()=>st(e.git),packageManager:()=>ct(e.packageManager),noInstall:()=>it(e.noInstall)},{onCancel:()=>{_r(Mr.red("Operation cancelled")),process.exit(0)}});return{projectName:a.projectName,frontend:a.frontend,database:a.database,orm:a.orm,auth:a.auth,addons:a.addons,examples:a.examples,git:a.git,packageManager:a.packageManager,noInstall:a.noInstall,turso:a.turso,prismaPostgres:a.prismaPostgres,backend:a.backend,runtime:a.runtime}}import C from"picocolors";function ie(e){let a=[];if(e.projectName&&a.push(`${C.blue("Project Name:")} ${e.projectName}`),e.frontend!==void 0){let t=e.frontend.length>0?e.frontend.join(", "):"none";a.push(`${C.blue("Frontend:")} ${t}`)}if(e.backend!==void 0&&a.push(`${C.blue("Backend Framework:")} ${e.backend}`),e.runtime!==void 0&&a.push(`${C.blue("Runtime:")} ${e.runtime}`),e.database!==void 0&&a.push(`${C.blue("Database:")} ${e.database}`),e.orm!==void 0&&a.push(`${C.blue("ORM:")} ${e.orm}`),e.auth!==void 0&&a.push(`${C.blue("Authentication:")} ${e.auth}`),e.addons!==void 0){let t=e.addons.length>0?e.addons.join(", "):"none";a.push(`${C.blue("Addons:")} ${t}`)}if(e.examples!==void 0){let t=e.examples.length>0?e.examples.join(", "):"none";a.push(`${C.blue("Examples:")} ${t}`)}return e.git!==void 0&&a.push(`${C.blue("Git Init:")} ${e.git}`),e.packageManager!==void 0&&a.push(`${C.blue("Package Manager:")} ${e.packageManager}`),e.noInstall!==void 0&&a.push(`${C.blue("Skip Install:")} ${e.noInstall}`),e.turso!==void 0&&a.push(`${C.blue("Turso Setup:")} ${e.turso}`),e.prismaPostgres!==void 0&&a.push(`${C.blue("Prisma Postgres Setup:")} ${e.prismaPostgres?"Yes":"No"}`),a.join(`
`)}function gt(e){let a=[];e.database==="none"?a.push("--database none"):(a.push(`--database ${e.database}`),e.orm&&a.push(`--orm ${e.orm}`),e.database==="sqlite"&&a.push(e.turso?"--turso":"--no-turso")),a.push(e.auth?"--auth":"--no-auth"),a.push(e.git?"--git":"--no-git"),a.push(e.noInstall?"--no-install":"--install"),e.runtime&&a.push(`--runtime ${e.runtime}`),e.backend&&a.push(`--backend ${e.backend}`),e.frontend&&e.frontend.length>0&&a.push(`--frontend ${e.frontend.join(" ")}`),e.addons&&e.addons.length>0?a.push(`--addons ${e.addons.join(" ")}`):a.push("--addons none"),e.examples&&e.examples.length>0?a.push(`--examples ${e.examples.join(" ")}`):a.push("--no-examples"),e.packageManager&&a.push(`--package-manager ${e.packageManager}`);let t="",r=e.packageManager;r==="npm"?t="npx create-better-t-stack@latest":r==="pnpm"?t="pnpm create better-t-stack@latest":r==="bun"&&(t="bun create better-t-stack@latest");let n=e.projectName?` ${e.projectName}`:"";return`${t}${n} ${a.join(" ")}`}import Br from"node:path";import zr from"fs-extra";var wt=()=>{let e=Br.join($,"package.json");return zr.readJSONSync(e).version??"1.0.0"};import bt from"gradient-string";var Pt=`
\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D
\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557
\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551
\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557
\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2554\u255D
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2554\u255D
\u2588\u2588\u2551 \u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2588\u2588\u2557
\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2557
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2