create-better-t-stack 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{cancel as
|
|
2
|
+
import Go from"node:path";import{cancel as Jo,intro as Qo,log as _,outro as Ho}from"@clack/prompts";import{consola as j}from"consola";import sr from"fs-extra";import z from"picocolors";import Yo from"yargs";import{hideBin as Ko}from"yargs/helpers";import Me 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 gr=fr(import.meta.url),hr=Me.dirname(gr),P=Me.join(hr,"../"),h={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 Ba from"node:path";import{cancel as Na,log as Ia,spinner as Ma}from"@clack/prompts";import La from"fs-extra";import Ft from"picocolors";import Y from"node:path";import B from"fs-extra";import br from"node:path";import Le from"fs-extra";var g=async e=>{let{dependencies:r=[],devDependencies:t=[],projectDir:a}=e,o=br.join(a,"package.json"),n=await Le.readJson(o);n.dependencies||(n.dependencies={}),n.devDependencies||(n.devDependencies={});for(let i of r){let s=be[i];s?n.dependencies[i]=s:console.warn(`Warning: Dependency ${i} not found in version map.`)}for(let i of t){let s=be[i];s?n.devDependencies[i]=s:console.warn(`Warning: Dev dependency ${i} not found in version map.`)}await Le.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 s=`create-astro@latest ${["docs","--template","starlight","--no-install","--add","tailwind","--no-git","--skip-houston"].join(" ")}`,c=R(t,s);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 H from"node:path";import{spinner as jr}from"@clack/prompts";import{consola as vr}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=H.resolve(process.cwd(),r),n=jr(),i=H.join(o,"apps/web");if(await ae.pathExists(i))try{n.start("Setting up Tauri desktop app support..."),await g({devDependencies:["@tauri-apps/cli"],projectDir:i});let s=H.join(i,"package.json");if(await ae.pathExists(s)){let S=await ae.readJson(s);S.scripts={...S.scripts,tauri:"tauri","desktop:dev":"tauri dev","desktop:build":"tauri build"},await ae.writeJson(s,S,{spaces:2})}let p=a.includes("react-router")?"http://localhost:5173":"http://localhost:3001",f=`@tauri-apps/cli@latest ${["init",`--app-name=${H.basename(o)}`,`--window-title=${H.basename(o)}`,"--frontend-dist=../dist",`--dev-url=${p}`,`--before-dev-command="${t} run dev"`,`--before-build-command="${t} run build"`].join(" ")}`,k=R(t,f);await kr(k,{cwd:i,env:{CI:"true"},shell:!0}),n.stop("Tauri desktop app support configured successfully!")}catch(s){throw n.stop(ze.red("Failed to set up Tauri")),s instanceof Error&&vr.error(ze.red(s.message)),s}}async function qe(e){let{projectName:r,addons:t,packageManager:a,frontend:o}=e,n=Y.resolve(process.cwd(),r),i=o.includes("react-router")||o.includes("tanstack-router");t.includes("turborepo")&&await g({devDependencies:["turbo"],projectDir:n}),t.includes("pwa")&&i&&await Dr(n,o),t.includes("tauri")&&i&&await We(e),t.includes("biome")&&await Sr(n),t.includes("husky")&&await Ar(n),t.includes("starlight")&&await _e(e)}function xr(e,r){return Y.join(e,"apps/web")}async function Sr(e){await g({devDependencies:["@biomejs/biome"],projectDir:e});let r=Y.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 Ar(e){await g({devDependencies:["husky","lint-staged"],projectDir:e});let r=Y.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 g({dependencies:["vite-plugin-pwa"],devDependencies:["@vite-pwa/assets-generator"],projectDir:t});let a=Y.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 K from"node:path";async function Ve(e){if(e.api==="none")return;let{api:r,projectName:t}=e,a=K.resolve(process.cwd(),t),o=K.join(a,"apps/web"),n=K.join(a,"apps/server");r==="orpc"&&(await g({dependencies:["@orpc/react-query","@orpc/server","@orpc/client"],projectDir:o}),await g({dependencies:["@orpc/server","@orpc/client"],projectDir:n})),r==="trpc"&&(await g({dependencies:["@trpc/tanstack-react-query","@trpc/server","@trpc/client"],projectDir:o}),await g({dependencies:["@trpc/server","@trpc/client"],projectDir:n}))}import oe from"node:path";import Ge from"consola";import Je from"picocolors";function Qe(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}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"),i=oe.join(o,"apps/web"),s=oe.join(o,"apps/native");try{await g({dependencies:["better-auth"],projectDir:n}),(a.includes("react-router")||a.includes("tanstack-router")||a.includes("tanstack-start"))&&await g({dependencies:["better-auth"],projectDir:i}),a.includes("native")&&(await g({dependencies:["better-auth","@better-auth/expo"],projectDir:s}),await g({dependencies:["@better-auth/expo"],projectDir:n}))}catch(c){throw Ge.error(Je.red("Failed to configure authentication")),c instanceof Error&&Ge.error(Je.red(c.message)),c}}import Ye from"node:path";async function Ke(e){let{projectName:r,backend:t,runtime:a}=e,o=Ye.resolve(process.cwd(),r),n=t,i=Ye.join(o,"apps/server"),s=[],c=[];n==="hono"?(s.push("hono","@hono/trpc-server"),a==="node"&&(s.push("@hono/node-server"),c.push("tsx","@types/node"))):n==="elysia"?(s.push("elysia","@elysiajs/cors","@elysiajs/trpc"),a==="node"&&(s.push("@elysiajs/node"),c.push("tsx","@types/node"))):n==="express"&&(s.push("express","cors"),c.push("@types/express","@types/cors"),a==="node"&&c.push("tsx","@types/node")),a==="bun"&&c.push("@types/bun"),await g({dependencies:s,devDependencies:c,projectDir:i})}import $r from"node:path";import Cr from"consola";import Er from"fs-extra";async function Xe(e,r){let t=$r.join(e,"README.md"),a=Tr(r);try{await Er.writeFile(t,a)}catch(o){Cr.error("Failed to create README.md file:",o)}}function Tr(e){let{projectName:r,packageManager:t,database:a,auth:o,addons:n=[],orm:i="drizzle",runtime:s="bun",frontend:c=["tanstack-router"],backend:p="hono"}=e,w=c.includes("react-router"),y=c.includes("tanstack-router"),f=c.includes("native"),k=c.includes("next"),S=c.includes("tanstack-start"),E=t==="npm"?"npm run":t,x="3001";return w?x="5173":k&&(x="3000"),`# ${r}
|
|
3
3
|
|
|
4
|
-
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":w?"React Router":k?"Next.js":
|
|
4
|
+
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":w?"React Router":k?"Next.js":S?"TanStack Start":""}, ${p[0].toUpperCase()+p.slice(1)}, tRPC, and more.
|
|
5
5
|
|
|
6
6
|
## Features
|
|
7
7
|
|
|
8
|
-
${
|
|
8
|
+
${Fr(a,o,n,i,s,c,p)}
|
|
9
9
|
|
|
10
10
|
## Getting Started
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ First, install the dependencies:
|
|
|
15
15
|
${t} install
|
|
16
16
|
\`\`\`
|
|
17
17
|
|
|
18
|
-
${
|
|
18
|
+
${Rr(a,o,E,i)}
|
|
19
19
|
|
|
20
20
|
Then, run the development server:
|
|
21
21
|
|
|
@@ -23,12 +23,12 @@ Then, run the development server:
|
|
|
23
23
|
${E} dev
|
|
24
24
|
\`\`\`
|
|
25
25
|
|
|
26
|
-
${y||w||k||
|
|
26
|
+
${y||w||k||S?`Open [http://localhost:${x}](http://localhost:${x}) in your browser to see the web application.`:""}
|
|
27
27
|
${f?`Use the Expo Go app to run the mobile application.
|
|
28
28
|
`:""}
|
|
29
29
|
The API is running at [http://localhost:3000](http://localhost:3000).
|
|
30
30
|
|
|
31
|
-
${
|
|
31
|
+
${n.includes("pwa")&&w?`
|
|
32
32
|
## PWA Support with React Router v7
|
|
33
33
|
|
|
34
34
|
There is a known compatibility issue between VitePWA and React Router v7.
|
|
@@ -40,19 +40,19 @@ See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809
|
|
|
40
40
|
\`\`\`
|
|
41
41
|
${r}/
|
|
42
42
|
\u251C\u2500\u2500 apps/
|
|
43
|
-
${y||w||k||
|
|
43
|
+
${y||w||k||S?`\u2502 \u251C\u2500\u2500 web/ # Frontend application (${y?"React + TanStack Router":w?"React + React Router":k?"Next.js":"React + TanStack Start"})
|
|
44
44
|
`:""}${f?`\u2502 \u251C\u2500\u2500 native/ # Mobile application (React Native, Expo)
|
|
45
|
-
`:""}${
|
|
45
|
+
`:""}${n.includes("starlight")?`\u2502 \u251C\u2500\u2500 docs/ # Documentation site (Astro Starlight)
|
|
46
46
|
`:""}\u2502 \u2514\u2500\u2500 server/ # Backend API (${p[0].toUpperCase()+p.slice(1)}, tRPC)
|
|
47
47
|
\`\`\`
|
|
48
48
|
|
|
49
49
|
## Available Scripts
|
|
50
50
|
|
|
51
|
-
${
|
|
52
|
-
`}function
|
|
53
|
-
`)}function
|
|
51
|
+
${Br(E,a,i,o,f,n,p)}
|
|
52
|
+
`}function Fr(e,r,t,a,o,n,i){let s=n.includes("tanstack-router"),c=n.includes("react-router"),p=n.includes("native"),w=n.includes("next"),y=n.includes("tanstack-start"),f=["- **TypeScript** - For type safety and improved developer experience"];s?f.push("- **TanStack Router** - File-based routing with full type safety"):c?f.push("- **React Router** - Declarative routing for React"):w?f.push("- **Next.js** - Full-stack React framework"):y&&f.push("- **TanStack Start** - SSR framework with TanStack Router"),p&&(f.push("- **React Native** - Build mobile apps using React"),f.push("- **Expo** - Tools for React Native development")),f.push("- **TailwindCSS** - Utility-first CSS for rapid UI development","- **shadcn/ui** - Reusable UI components"),i==="hono"?f.push("- **Hono** - Lightweight, performant server framework"):i==="express"?f.push("- **Express** - Fast, unopinionated web framework"):i==="elysia"?f.push("- **Elysia** - Type-safe, high-performance framework"):i==="next"&&f.push("- **Next.js** - Full-stack React framework"),f.push("- **tRPC** - End-to-end type-safe APIs",`- **${o==="bun"?"Bun":"Node.js"}** - Runtime environment`),e!=="none"&&f.push(`- **${a==="drizzle"?"Drizzle":"Prisma"}** - TypeScript-first ORM`,`- **${e==="sqlite"?"SQLite/Turso":e==="postgres"?"PostgreSQL":e==="mysql"?"MySQL":"MongoDB"}** - Database engine`),r&&f.push("- **Authentication** - Email & password authentication with Better Auth");for(let k of t)k==="pwa"?f.push("- **PWA** - Progressive Web App support"):k==="tauri"?f.push("- **Tauri** - Build native desktop applications"):k==="biome"?f.push("- **Biome** - Linting and formatting"):k==="husky"?f.push("- **Husky** - Git hooks for code quality"):k==="starlight"&&f.push("- **Starlight** - Documentation site with Astro");return f.join(`
|
|
53
|
+
`)}function Rr(e,r,t,a){if(e==="none")return"";let o=`## Database Setup
|
|
54
54
|
|
|
55
|
-
`;return e==="sqlite"?
|
|
55
|
+
`;return e==="sqlite"?o+=`This project uses SQLite${a==="drizzle"?" with Drizzle ORM":" with Prisma"}.
|
|
56
56
|
|
|
57
57
|
1. Start the local SQLite database:
|
|
58
58
|
\`\`\`bash
|
|
@@ -60,19 +60,19 @@ cd apps/server && ${t} db:local
|
|
|
60
60
|
\`\`\`
|
|
61
61
|
|
|
62
62
|
2. Update your \`.env\` file in the \`apps/server\` directory with the appropriate connection details if needed.
|
|
63
|
-
`:e==="postgres"?
|
|
63
|
+
`:e==="postgres"?o+=`This project uses PostgreSQL${a==="drizzle"?" with Drizzle ORM":" with Prisma"}.
|
|
64
64
|
|
|
65
65
|
1. Make sure you have a PostgreSQL database set up.
|
|
66
66
|
2. Update your \`apps/server/.env\` file with your PostgreSQL connection details.
|
|
67
|
-
`:e==="mysql"?
|
|
67
|
+
`:e==="mysql"?o+=`This project uses MySQL${a==="drizzle"?" with Drizzle ORM":" with Prisma"}.
|
|
68
68
|
|
|
69
69
|
1. Make sure you have a MySQL database set up.
|
|
70
70
|
2. Update your \`apps/server/.env\` file with your MySQL connection details.
|
|
71
|
-
`:e==="mongodb"&&(
|
|
71
|
+
`:e==="mongodb"&&(o+=`This project uses MongoDB with Prisma ORM.
|
|
72
72
|
|
|
73
73
|
1. Make sure you have MongoDB set up.
|
|
74
74
|
2. Update your \`apps/server/.env\` file with your MongoDB connection URI.
|
|
75
|
-
`),
|
|
75
|
+
`),o+=`
|
|
76
76
|
3. ${a==="prisma"?`Generate the Prisma client and push the schema:
|
|
77
77
|
\`\`\`bash
|
|
78
78
|
${t} db:push
|
|
@@ -80,22 +80,22 @@ ${t} db:push
|
|
|
80
80
|
\`\`\`bash
|
|
81
81
|
${t} db:push
|
|
82
82
|
\`\`\``}
|
|
83
|
-
`,
|
|
83
|
+
`,o}function Br(e,r,t,a,o,n,i){let s=`- \`${e} dev\`: Start all applications in development mode
|
|
84
84
|
- \`${e} build\`: Build all applications
|
|
85
85
|
- \`${e} dev:web\`: Start only the web application
|
|
86
86
|
- \`${e} dev:server\`: Start only the server
|
|
87
|
-
- \`${e} check-types\`: Check TypeScript types across all apps`;return
|
|
87
|
+
- \`${e} check-types\`: Check TypeScript types across all apps`;return o&&(s+=`
|
|
88
88
|
- \`${e} dev:native\`: Start the React Native/Expo development server`),r!=="none"&&(s+=`
|
|
89
89
|
- \`${e} db:push\`: Push schema changes to database
|
|
90
90
|
- \`${e} db:studio\`: Open database studio UI`,r==="sqlite"&&t==="drizzle"&&(s+=`
|
|
91
|
-
- \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),
|
|
92
|
-
- \`${e} check\`: Run Biome formatting and linting`),
|
|
93
|
-
- \`cd apps/web && ${e} generate-pwa-assets\`: Generate PWA assets`),
|
|
91
|
+
- \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),n.includes("biome")&&(s+=`
|
|
92
|
+
- \`${e} check\`: Run Biome formatting and linting`),n.includes("pwa")&&(s+=`
|
|
93
|
+
- \`cd apps/web && ${e} generate-pwa-assets\`: Generate PWA assets`),n.includes("tauri")&&(s+=`
|
|
94
94
|
- \`cd apps/web && ${e} desktop:dev\`: Start Tauri desktop app in development
|
|
95
|
-
- \`cd apps/web && ${e} desktop:build\`: Build Tauri desktop app`),
|
|
95
|
+
- \`cd apps/web && ${e} desktop:build\`: Build Tauri desktop app`),n.includes("starlight")&&(s+=`
|
|
96
96
|
- \`cd apps/docs && ${e} dev\`: Start documentation site
|
|
97
|
-
- \`cd apps/docs && ${e} build\`: Build documentation site`),s}import
|
|
98
|
-
${
|
|
97
|
+
- \`cd apps/docs && ${e} build\`: Build documentation site`),s}import Ce from"node:path";import{spinner as ma}from"@clack/prompts";import fa from"consola";import ga from"fs-extra";import pt from"picocolors";import se from"node:path";import{cancel as Nr,isCancel as Ir,log as W,spinner as tt,text as Mr}from"@clack/prompts";import ie from"consola";import{execa as Lr}from"execa";import X from"fs-extra";import D from"picocolors";import{execa as Ze}from"execa";async function ne(e){try{return process.platform==="win32"?(await Ze("where",[e])).exitCode===0:(await Ze("which",[e])).exitCode===0}catch{return!1}}async function Or(){let e=tt();e.start("Checking for MongoDB Atlas CLI");try{let r=await ne("atlas");return e.stop(r?"MongoDB Atlas CLI found":D.yellow("MongoDB Atlas CLI not found")),r}catch{return e.stop(D.red("Error checking for MongoDB Atlas CLI")),!1}}async function Ur(e){try{if(!await Or())return ie.error(D.red("MongoDB Atlas CLI not found.")),W.info(D.yellow("Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/")),null;W.info(D.blue("Running MongoDB Atlas setup...")),await Lr("atlas",["deployments","setup"],{cwd:e,stdio:"inherit"}),W.info(D.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 Ir(t)?(Nr("MongoDB setup cancelled"),null):{connectionString:t}}catch(r){return r instanceof Error&&ie.error(D.red(r.message)),null}}async function we(e,r){try{let t=se.join(e,"apps/server",".env");await X.ensureDir(se.dirname(t));let a="";await X.pathExists(t)&&(a=await X.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+=`
|
|
98
|
+
${o}`,await X.writeFile(t,a.trim())}catch(t){throw ie.error("Failed to update environment configuration"),t}}function et(){W.info(`
|
|
99
99
|
${D.green("MongoDB Atlas Manual Setup Instructions:")}
|
|
100
100
|
|
|
101
101
|
1. Install Atlas CLI:
|
|
@@ -109,25 +109,25 @@ ${D.green("MongoDB Atlas Manual Setup Instructions:")}
|
|
|
109
109
|
|
|
110
110
|
4. Add the connection string to your .env file:
|
|
111
111
|
${D.dim('DATABASE_URL="your_connection_string"')}
|
|
112
|
-
`)}async function
|
|
113
|
-
It looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=...`));let
|
|
114
|
-
${
|
|
112
|
+
`)}async function rt(e){let{projectName:r}=e,t=se.resolve(process.cwd(),r),a=tt();a.start("Setting up MongoDB Atlas");let o=se.join(t,"apps/server");try{await X.ensureDir(o),a.stop("Starting MongoDB Atlas setup");let n=await Ur(o);n?(await we(t,n),W.success(D.green("MongoDB Atlas setup complete! Connection saved to .env file."))):(W.warn(D.yellow("Falling back to local MongoDB configuration")),await we(t),et())}catch(n){a.stop(D.red("MongoDB Atlas setup failed")),ie.error(D.red(`Error during MongoDB Atlas setup: ${n instanceof Error?n.message:String(n)}`));try{await we(t),et()}catch{}}}import L from"node:path";import{cancel as _r,isCancel as zr,log as Z,password as Wr,spinner as Pe}from"@clack/prompts";import{consola as je}from"consola";import{execa as qr}from"execa";import N from"fs-extra";import q from"picocolors";async function Vr(e,r){let t=Pe();try{t.start("Initializing Prisma PostgreSQL");let a=L.join(e,"prisma");await N.ensureDir(a),t.stop("Initializing Prisma. Follow the prompts below:");let o=R(r,"prisma init --db");await qr(o,{cwd:e,stdio:"inherit",shell:!0}),Z.info(q.yellow(`Please copy the Prisma Postgres URL from the output above.
|
|
113
|
+
It looks like: prisma+postgres://accelerate.prisma-data.net/?api_key=...`));let n=await Wr({message:"Paste your Prisma Postgres database URL:",validate(i){if(!i)return"Please enter a database URL";if(!i.startsWith("prisma+postgres://"))return"URL should start with prisma+postgres://"}});return zr(n)?(_r("Database setup cancelled"),null):{databaseUrl:n}}catch(a){return t.stop(q.red("Failed to initialize Prisma PostgreSQL")),a instanceof Error&&je.error(a.message),null}}async function ye(e,r){try{let t=L.join(e,"apps/server",".env");await N.ensureDir(L.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+=`
|
|
114
|
+
${o}`,await N.writeFile(t,a.trim())}catch(t){throw je.error("Failed to update environment configuration"),t}}function at(){Z.info(`Manual Prisma PostgreSQL Setup Instructions:
|
|
115
115
|
|
|
116
116
|
1. Visit https://console.prisma.io and create an account
|
|
117
117
|
2. Create a new PostgreSQL database from the dashboard
|
|
118
118
|
3. Get your database URL
|
|
119
119
|
4. Add the database URL to the .env file in apps/server/.env
|
|
120
120
|
|
|
121
|
-
DATABASE_URL="your_database_url"`)}async function
|
|
121
|
+
DATABASE_URL="your_database_url"`)}async function Gr(e){try{await g({dependencies:["@prisma/extension-accelerate"],projectDir:e});let r=L.join(e,"prisma/index.ts");await N.writeFile(r,`
|
|
122
122
|
import { PrismaClient } from '@prisma/client';
|
|
123
123
|
import { withAccelerate } from "@prisma/extension-accelerate";
|
|
124
124
|
|
|
125
125
|
const prisma = new PrismaClient().$extends(withAccelerate());
|
|
126
126
|
|
|
127
127
|
export default prisma;
|
|
128
|
-
`.trim());let a=L.join(e,"src/db/index.ts");if(await N.pathExists(a)){let
|
|
129
|
-
${
|
|
130
|
-
`);if(t.length<=1)return e.stop("No Turso groups found"),[];let a=t.slice(1).map(
|
|
128
|
+
`.trim());let a=L.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";
|
|
129
|
+
${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 Z.warn(q.yellow("Could not add Prisma Accelerate extension automatically")),!1}}async function ot(e){let{projectName:r,packageManager:t}=e,a=L.resolve(process.cwd(),r),o=L.join(a,"apps/server"),n=Pe();n.start("Setting up Prisma PostgreSQL");try{await N.ensureDir(o),n.stop("Starting Prisma setup");let i=await Vr(o,t);if(i)await ye(a,i),await Gr(o),Z.success(q.green("Prisma PostgreSQL database configured successfully!"));else{let s=Pe();s.start("Setting up fallback configuration"),await ye(a),s.stop("Manual setup required"),at()}}catch(i){n.stop(q.red("Prisma PostgreSQL setup failed")),je.error(q.red(`Error during Prisma PostgreSQL setup: ${i instanceof Error?i.message:String(i)}`));try{await ye(a),at()}catch{}Z.info("Setup completed with manual configuration required.")}}import Jr from"node:os";import pe from"node:path";import{cancel as ke,confirm as Qr,isCancel as xe,log as V,select as Hr,spinner as G,text as Yr}from"@clack/prompts";import Kr from"consola";import{$ as I}from"execa";import nt from"fs-extra";import $ from"picocolors";async function Xr(){return ne("turso")}async function Zr(){try{return!(await I`turso auth whoami`).stdout.includes("You are not logged in")}catch{return!1}}async function ea(){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($.red("Failed to log in to Turso")),r}}async function ta(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($.yellow("Turso CLI installation cancelled by user")),new Error("Installation cancelled")):(r.stop($.red("Failed to install Turso CLI")),t)}}async function ra(){let e=G();try{e.start("Fetching Turso groups...");let{stdout:r}=await I`turso group list`,t=r.trim().split(`
|
|
130
|
+
`);if(t.length<=1)return e.stop("No Turso groups found"),[];let a=t.slice(1).map(o=>{let[n,i,s,c]=o.trim().split(/\s{2,}/);return{name:n,locations:i,version:s,status:c}});return e.stop(`Found ${a.length} Turso groups`),a}catch(r){return e.stop($.red("Error fetching Turso groups")),console.error("Error fetching Turso groups:",r),[]}}async function aa(){let e=await ra();if(e.length===0)return null;if(e.length===1)return V.info(`Using the only available group: ${$.blue(e[0].name)}`),e[0].name;let r=e.map(a=>({value:a.name,label:`${a.name} (${a.locations})`})),t=await Hr({message:"Select a Turso database group:",options:r});return xe(t)&&(ke($.red("Operation cancelled")),process.exit(0)),t}async function oa(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($.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($.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}"
|
|
131
131
|
DATABASE_AUTH_TOKEN="${r.authToken}"`:`DATABASE_URL=
|
|
132
132
|
DATABASE_AUTH_TOKEN=`;await nt.ensureDir(pe.dirname(t)),await nt.writeFile(t,a)}function ve(){V.info(`Manual Turso Setup Instructions:
|
|
133
133
|
|
|
@@ -137,62 +137,62 @@ DATABASE_AUTH_TOKEN=`;await nt.ensureDir(pe.dirname(t)),await nt.writeFile(t,a)}
|
|
|
137
137
|
4. Add these credentials to the .env file in apps/server/.env
|
|
138
138
|
|
|
139
139
|
DATABASE_URL=your_database_url
|
|
140
|
-
DATABASE_AUTH_TOKEN=your_auth_token`)}async function
|
|
140
|
+
DATABASE_AUTH_TOKEN=your_auth_token`)}async function st(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 i=Jr.platform(),s=i==="darwin",c=i==="linux";if(i==="win32"){n.stop($.yellow("Turso setup not supported on Windows")),V.warn($.yellow("Automatic Turso setup is not supported on Windows.")),await ce(a),ve();return}if(n.stop("Checking Turso CLI"),!await Xr()){let x=await Qr({message:"Would you like to install Turso CLI?",initialValue:!0});if(xe(x)&&(ke($.red("Operation cancelled")),process.exit(0)),!x){await ce(a),ve();return}await ta(s)}await Zr()||await ea();let f=await aa(),k=!1,S="",E=pe.basename(a);for(;!k;){let x=await Yr({message:"Enter a name for your database:",defaultValue:E,initialValue:E,placeholder:E});xe(x)&&(ke($.red("Operation cancelled")),process.exit(0)),S=x;try{let F=await oa(S,f),Q=G();Q.start("Writing configuration to .env file"),await ce(a,F),Q.stop("Turso database configured successfully!"),k=!0}catch(F){if(F instanceof Error&&F.message==="DATABASE_EXISTS")V.warn($.yellow(`Database "${$.red(S)}" already exists`)),E=`${S}-${Math.floor(Math.random()*1e3)}`;else throw F}}}catch(i){n.stop($.red("Failed to set up Turso database")),Kr.error($.red(`Error during Turso setup: ${i instanceof Error?i.message:String(i)}`)),await ce(a),ve(),V.success("Setup completed with manual configuration required.")}}import ee from"node:path";import{cancel as na,isCancel as sa,log as De,spinner as Se,text as ia}from"@clack/prompts";import{consola as le}from"consola";import{execa as ca}from"execa";import Ae from"fs-extra";import O from"picocolors";async function $e(e,r,t){let a=Se();try{let o=R(e,r);a&&a.start(t);let n=await ca(o,{shell:!0});return a&&a.stop(t),n}catch(o){throw a&&a.stop(O.red(`Failed: ${t}`)),o}}async function pa(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 la(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 da(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,i=o.connection_uris[0].connection_uri,s=o.connection_uris[0].connection_parameters;return{connectionString:i,projectId:n,dbName:s.database,roleName:s.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 it(e,r){let t=ee.join(e,"apps/server",".env"),a=r?`DATABASE_URL="${r.connectionString}"`:'DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"';return await Ae.ensureDir(ee.dirname(t)),await Ae.writeFile(t,a),!0}function ua(){De.info(`Manual Neon PostgreSQL Setup Instructions:
|
|
141
141
|
|
|
142
142
|
1. Visit https://neon.tech and create an account
|
|
143
143
|
2. Create a new project from the dashboard
|
|
144
144
|
3. Get your connection string
|
|
145
145
|
4. Add the database URL to the .env file in apps/server/.env
|
|
146
146
|
|
|
147
|
-
DATABASE_URL="your_connection_string"`)}async function
|
|
148
|
-
${
|
|
147
|
+
DATABASE_URL="your_connection_string"`)}async function ct(e){let{projectName:r,packageManager:t}=e,a=ee.resolve(process.cwd(),r),o=Se();o.start("Setting up Neon PostgreSQL");try{let n=await pa(t);o.stop("Setting up Neon PostgreSQL"),n||(De.info("Please authenticate with Neon to continue:"),await la(t));let i=ee.basename(a),s=await ia({message:"Enter a name for your Neon project:",defaultValue:i,initialValue:i});sa(s)&&(na(O.red("Operation cancelled")),process.exit(0));let c=await da(s,t);if(!c)throw new Error("Failed to create project - couldn't get connection information");let p=Se();p.start("Configuring database connection"),await Ae.ensureDir(ee.join(a,"apps/server")),await it(a,c),p.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 it(a),ua()}}async function lt(e){let{projectName:r,database:t,orm:a,packageManager:o,dbSetup:n}=e,i=Ce.resolve(process.cwd(),r),s=ma(),c=Ce.join(i,"apps/server");if(t==="none"){await ga.remove(Ce.join(c,"src/db"));return}try{a==="prisma"?await g({dependencies:["@prisma/client"],devDependencies:["prisma"],projectDir:c}):a==="drizzle"&&(t==="sqlite"?await g({dependencies:["drizzle-orm","@libsql/client"],devDependencies:["drizzle-kit"],projectDir:c}):t==="postgres"?await g({dependencies:["drizzle-orm","pg"],devDependencies:["drizzle-kit","@types/pg"],projectDir:c}):t==="mysql"&&await g({dependencies:["drizzle-orm","mysql2"],devDependencies:["drizzle-kit"],projectDir:c})),t==="sqlite"&&n==="turso"?await st(e):t==="postgres"?a==="prisma"&&n==="prisma-postgres"?await ot(e):n==="neon"&&await ct(e):t==="mongodb"&&n==="mongodb-atlas"&&await rt(e)}catch(p){throw s.stop(pt.red("Failed to set up database")),p instanceof Error&&fa.error(pt.red(p.message)),p}}import M from"node:path";import de from"fs-extra";async function Ee(e,r){await de.ensureDir(M.dirname(e));let t="";await de.pathExists(e)&&(t=await de.readFile(e,"utf8"));let a=!1;for(let{key:o,value:n,condition:i}of r)if(i){let s=new RegExp(`^${o}=.*$`,"m");s.test(t)?n&&(t=t.replace(s,`${o}=${n}`),a=!0):(t+=`
|
|
148
|
+
${o}=${n}`,a=!0)}a&&await de.writeFile(e,t.trim())}async function dt(e){let{projectName:r}=e,t=M.resolve(process.cwd(),r),a=e,o=M.join(t,"apps/server"),n=M.join(o,".env"),i=a.frontend.includes("react-router"),s=a.frontend.includes("tanstack-router"),c=a.frontend.includes("tanstack-start"),p=a.frontend.includes("next"),w=i||s||c||p,y="http://localhost:3000";i?y="http://localhost:5173":(s||c||p)&&(y="http://localhost:3001");let f="",k=a.dbSetup==="turso"||a.dbSetup==="prisma-postgres"||a.dbSetup==="mongodb-atlas"||a.dbSetup==="neon";k||(a.database==="postgres"?f="postgresql://postgres:postgres@localhost:5432/mydb?schema=public":a.database==="mysql"?f="mysql://root:password@localhost:3306/mydb":a.database==="mongodb"?f="mongodb://localhost:27017/mydatabase":a.database==="sqlite"&&(f="file:./local.db"));let S=[{key:"CORS_ORIGIN",value:y,condition:!0},{key:"BETTER_AUTH_SECRET",value:Qe(),condition:!!a.auth},{key:"BETTER_AUTH_URL",value:"http://localhost:3000",condition:!!a.auth},{key:"DATABASE_URL",value:f,condition:a.database!=="none"&&f!==""&&!k},{key:"GOOGLE_GENERATIVE_AI_API_KEY",value:"",condition:a.examples?.includes("ai")||!1}];if(await Ee(n,S),w){let E=M.join(t,"apps/web"),x="VITE_SERVER_URL";p&&(x="NEXT_PUBLIC_SERVER_URL");let F=[{key:x,value:"http://localhost:3000",condition:!0}];await Ee(M.join(E,".env"),F)}if(a.frontend.includes("native")){let E=M.join(t,"apps/native"),x=[{key:"EXPO_PUBLIC_SERVER_URL",value:"http://localhost:3000",condition:!0}];await Ee(M.join(E,".env"),x)}}import Te from"node:path";async function ut(e){let{projectName:r,examples:t,orm:a,auth:o,backend:n,frontend:i=["tanstack-router"]}=e,s=Te.resolve(process.cwd(),r);if(t.includes("ai")){let c=Te.join(s,"apps/web");await g({dependencies:["ai"],projectDir:c});let p=Te.join(s,"apps/server");await g({dependencies:["ai","@ai-sdk/google"],projectDir:p})}}import{log as ha,spinner as mt}from"@clack/prompts";import ba from"consola";import{$ as ft}from"execa";import ue from"picocolors";async function gt({projectDir:e,packageManager:r,addons:t=[]}){let a=mt();try{a.start(`Running ${r} install...`),await ft({cwd:e,stderr:"inherit"})`${r} install`,a.stop("Dependencies installed successfully"),(t.includes("biome")||t.includes("husky"))&&await wa(e,r)}catch(o){throw a.stop(ue.red("Failed to install dependencies")),o instanceof Error&&ba.error(ue.red(`Installation error: ${o.message}`)),o}}async function wa(e,r){let t=mt();try{t.start("Running Biome format check..."),await ft({cwd:e,stderr:"inherit"})`${r} biome check --write .`,t.stop("Biome check completed successfully")}catch{t.stop(ue.yellow("Biome check encountered issues")),ha.warn(ue.yellow("Some files may need manual formatting"))}}import{consola as ya}from"consola";import b from"picocolors";function ht(e){let{database:r,projectName:t,packageManager:a,depsInstalled:o,orm:n,addons:i,runtime:s,frontend:c,dbSetup:p}=e,w=a==="npm"?"npm run":a,y=`cd ${t}`,f=i?.includes("husky")||i?.includes("biome"),k=r!=="none"?va(r,n,w,s):"",S=i?.includes("tauri")?ka(w):"",E=f?ja(w):"",x=c?.includes("native")?Pa():"",F=i?.includes("pwa")&&c?.includes("react-router")?xa():"",Q=i?.includes("starlight")?Sa(w):"",cr=c?.includes("tanstack-router"),pr=c?.includes("tanstack-start"),Ne=c?.includes("react-router"),Ie=cr||Ne||pr,lr=c?.includes("native"),dr=Ie||lr,ur=Ne?"5173":"3001",mr=R(a,"taze -r");ya.box(`${b.bold("Next steps")}
|
|
149
149
|
${b.cyan("1.")} ${y}
|
|
150
|
-
${
|
|
151
|
-
`}${b.cyan(
|
|
150
|
+
${o?"":`${b.cyan("2.")} ${a} install
|
|
151
|
+
`}${b.cyan(o?"2.":"3.")} ${w} dev
|
|
152
152
|
|
|
153
153
|
${b.bold("Your project will be available at:")}
|
|
154
|
-
${
|
|
154
|
+
${dr?`${Ie?`${b.cyan("\u2022")} Frontend: http://localhost:${ur}
|
|
155
155
|
`:""}`:`${b.yellow("NOTE:")} You are creating a backend-only app (no frontend selected)
|
|
156
156
|
`}${b.cyan("\u2022")} Backend: http://localhost:3000
|
|
157
157
|
${i?.includes("starlight")?`${b.cyan("\u2022")} Docs: http://localhost:4321
|
|
158
158
|
`:""}${x?`
|
|
159
159
|
${x.trim()}`:""}${k?`
|
|
160
|
-
${k.trim()}`:""}${
|
|
161
|
-
${
|
|
160
|
+
${k.trim()}`:""}${S?`
|
|
161
|
+
${S.trim()}`:""}${E?`
|
|
162
162
|
${E.trim()}`:""}${F?`
|
|
163
163
|
${F.trim()}`:""}${Q?`
|
|
164
164
|
${Q.trim()}`:""}
|
|
165
165
|
|
|
166
166
|
${b.bold(`Update all dependencies:
|
|
167
|
-
`)}${b.cyan(
|
|
167
|
+
`)}${b.cyan(mr)}
|
|
168
168
|
|
|
169
169
|
${b.bold("Like Better-T Stack?")} Please consider giving us a star on GitHub:
|
|
170
|
-
${b.cyan("https://github.com/AmanVarshney01/create-better-t-stack")}`)}function
|
|
170
|
+
${b.cyan("https://github.com/AmanVarshney01/create-better-t-stack")}`)}function Pa(){return`${b.yellow("NOTE:")} For Expo connectivity issues, update apps/native/.env
|
|
171
171
|
with your local IP:
|
|
172
172
|
EXPO_PUBLIC_SERVER_URL=http://192.168.0.103:3000
|
|
173
|
-
`}function
|
|
173
|
+
`}function ja(e){return`${b.bold("Linting and formatting:")}
|
|
174
174
|
${b.cyan("\u2022")} Format and lint fix: ${`${e} check`}
|
|
175
175
|
|
|
176
|
-
`}function va(e,r,t,a){let
|
|
177
|
-
follow the guidance provided in the error messages`),
|
|
178
|
-
${
|
|
176
|
+
`}function va(e,r,t,a){let o=[];return r==="prisma"?(e==="sqlite"&&o.push(`${b.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(`${b.yellow("NOTE:")} Prisma with Bun may require additional configuration. If you encounter errors,
|
|
177
|
+
follow the guidance provided in the error messages`),o.push(`${b.cyan("\u2022")} Apply schema: ${`${t} db:push`}`),o.push(`${b.cyan("\u2022")} Database UI: ${`${t} db:studio`}`)):r==="drizzle"&&(o.push(`${b.cyan("\u2022")} Apply schema: ${`${t} db:push`}`),o.push(`${b.cyan("\u2022")} Database UI: ${`${t} db:studio`}`)),o.length?`${b.bold("Database commands:")}
|
|
178
|
+
${o.join(`
|
|
179
179
|
`)}
|
|
180
180
|
|
|
181
|
-
`:""}function
|
|
181
|
+
`:""}function ka(e){return`
|
|
182
182
|
${b.bold("Desktop app with Tauri:")}
|
|
183
183
|
${b.cyan("\u2022")} Start desktop app: ${`cd apps/web && ${e} desktop:dev`}
|
|
184
184
|
${b.cyan("\u2022")} Build desktop app: ${`cd apps/web && ${e} desktop:build`}
|
|
185
185
|
${b.yellow("NOTE:")} Tauri requires Rust and platform-specific dependencies.
|
|
186
186
|
See: https://v2.tauri.app/start/prerequisites/
|
|
187
187
|
|
|
188
|
-
`}function
|
|
188
|
+
`}function xa(){return`${b.bold("PWA with React Router v7:")}
|
|
189
189
|
${b.yellow("NOTE:")} There is a known compatibility issue between VitePWA and React Router v7.
|
|
190
190
|
See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809
|
|
191
|
-
`}function
|
|
191
|
+
`}function Sa(e){return`${b.bold("Documentation with Starlight:")}
|
|
192
192
|
${b.cyan("\u2022")} Start docs site: ${`cd apps/docs && ${e} dev`}
|
|
193
193
|
${b.cyan("\u2022")} Build docs site: ${`cd apps/docs && ${e} build`}
|
|
194
|
-
`}import bt from"node:path";import{log as Aa}from"@clack/prompts";import{$ as ht,execa as Sa}from"execa";import J from"fs-extra";import Da from"picocolors";async function wt(e,r){await $a(e,r),await Ca(e,r)}async function $a(e,r){let t=bt.join(e,"package.json");if(await J.pathExists(t)){let a=await J.readJson(t);a.name=r.projectName;let n={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"},o={dev:"pnpm -r --parallel 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"},i={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"},s={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=n:r.packageManager==="pnpm"?a.scripts=o:r.packageManager==="npm"?a.scripts=i:r.packageManager==="bun"?a.scripts=s:a.scripts={};let{stdout:c}=await Sa(r.packageManager,["-v"],{cwd:e});a.packageManager=`${r.packageManager}@${c.trim()}`,await J.writeJson(t,a,{spaces:2})}}async function Ca(e,r){let t=bt.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 yt(e,r){if(!r)return;if((await ht({cwd:e,reject:!1,stderr:"pipe"})`git --version`).exitCode!==0){Aa.warn(Da.yellow("Git is not installed"));return}let a=await ht({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 Pt(e){let{projectName:r,runtime:t,backend:a}=e,n=me.resolve(process.cwd(),r);if(a==="next")return;let o=me.join(n,"apps/server");t==="bun"?await Ea(o,a):t==="node"&&await Ta(o,a)}async function Ea(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 g({devDependencies:["@types/bun"],projectDir:e})}async function Ta(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 g({devDependencies:["tsx","@types/node"],projectDir:e}),r==="hono"?await g({dependencies:["@hono/node-server"],projectDir:e}):r==="elysia"&&await g({dependencies:["@elysiajs/node"],projectDir:e})}import l from"node:path";import S from"consola";import m from"fs-extra";import{globby as vt}from"globby";import C from"picocolors";import Fa from"node:path";import Te from"fs-extra";import ge from"handlebars";async function Fe(e,r,t){try{let a=await Te.readFile(e,"utf-8"),o=ge.compile(a)(t);await Te.ensureDir(Fa.dirname(r)),await Te.writeFile(r,o)}catch(a){throw console.error(`Error processing template ${e}:`,a),new Error(`Failed to process template ${e}`)}}ge.registerHelper("or",(e,r)=>e||r);ge.registerHelper("eq",(e,r)=>e===r);ge.registerHelper("includes",(e,r)=>Array.isArray(e)&&e.includes(r));async function j(e,r,t,a,n=!0){let o=await vt(e,{cwd:r,dot:!0,onlyFiles:!0,absolute:!1});for(let i of o){let s=l.join(r,i),c=i;i.endsWith(".hbs")&&(c=i.slice(0,-4));let p=l.join(t,c);if(await m.ensureDir(l.dirname(p)),s.endsWith(".hbs"))await Fe(s,p,a);else{if(!n&&await m.pathExists(p))continue;await m.copy(s,p,{overwrite:!0})}}}async function jt(e,r){let t=l.join(P,"templates/base");await j(["package.json","_gitignore"],t,e,r)}async function kt(e,r){let t=r.frontend.filter(n=>n==="tanstack-router"||n==="react-router"||n==="tanstack-start"||n==="next"),a=r.frontend.includes("native");if(t.length>0){let n=l.join(e,"apps/web");await m.ensureDir(n);let o=l.join(P,"templates/frontend/web-base");await m.pathExists(o)&&await j("**/*",o,n,r);for(let i of t){let s=l.join(P,`templates/frontend/${i}`);await m.pathExists(s)&&await j("**/*",s,n,r)}if(r.api!=="none"){let i=t[0],s=l.join(P,`templates/api/${r.api}/web/base`);await m.pathExists(s)&&await j("**/*",s,n,r);let c=l.join(P,`templates/api/${r.api}/web/${i}`);await m.pathExists(c)&&await j("**/*",c,n,r)}}if(a){let n=l.join(e,"apps/native");await m.ensureDir(n);let o=l.join(P,"templates/frontend/native");if(await m.pathExists(o)&&await j("**/*",o,n,r),r.api!=="none"){let i=l.join(P,`templates/api/${r.api}/native`);await m.pathExists(i)&&await j("**/*",i,n,r)}}}async function xt(e,r){if(r.backend==="none")return;let t=l.join(e,"apps/server");await m.ensureDir(t);let a=l.join(P,"templates/backend/server-base");await m.pathExists(a)?await j("**/*",a,t,r):S.warn(C.yellow(`Warning: server-base template not found at ${a}`));let n=l.join(P,`templates/backend/${r.backend}`);if(await m.pathExists(n)?await j("**/*",n,t,r):S.warn(C.yellow(`Warning: Backend template directory not found, skipping: ${n}`)),r.api!=="none"){let o=l.join(P,`templates/api/${r.api}/server/base`);await m.pathExists(o)&&await j("**/*",o,t,r);let i=l.join(P,`templates/api/${r.api}/server/${r.backend}`);await m.pathExists(i)&&await j("**/*",i,t,r)}}async function At(e,r){if(r.orm==="none"||r.database==="none")return;let t=l.join(e,"apps/server");await m.ensureDir(t);let a=l.join(P,`templates/db/${r.orm}/${r.database}`);await m.pathExists(a)?await j("**/*",a,t,r):S.warn(C.yellow(`Warning: Database/ORM template directory not found, skipping: ${a}`))}async function St(e,r){if(!r.auth)return;let t=l.join(e,"apps/server"),a=l.join(e,"apps/web"),n=l.join(e,"apps/native"),o=r.frontend.filter(s=>s==="tanstack-router"||s==="react-router"||s==="tanstack-start"||s==="next"),i=r.frontend.includes("native");if(await m.pathExists(t)){let s=l.join(P,"templates/auth/server/base");await m.pathExists(s)?await j("**/*",s,t,r):S.warn(C.yellow(`Warning: Base auth server template not found at ${s}`));let c=l.join(P,"templates/auth/server/next");if(await m.pathExists(c)?await j("**/*",c,t,r):S.warn(C.yellow(`Warning: Next auth server template not found at ${c}`)),r.orm!=="none"&&r.database!=="none"){let p=r.orm,w=r.database,y="";p==="drizzle"?y=l.join(P,`templates/auth/server/db/drizzle/${w}`):p==="prisma"&&(y=l.join(P,`templates/auth/server/db/prisma/${w}`)),y&&await m.pathExists(y)?await j("**/*",y,t,r):S.warn(C.yellow(`Warning: Auth template for ${p}/${w} not found at ${y}`))}}else S.warn(C.yellow("Warning: apps/server directory does not exist, skipping server-side auth setup."));if(o.length>0&&await m.pathExists(a)){let s=l.join(P,"templates/auth/web/base");await m.pathExists(s)?await j("**/*",s,a,r):S.warn(C.yellow(`Warning: Base auth web template not found at ${s}`));for(let c of o){let p=l.join(P,`templates/auth/web/${c}`);await m.pathExists(p)?await j("**/*",p,a,r):S.warn(C.yellow(`Warning: Auth web template for ${c} not found at ${p}`))}}if(i&&await m.pathExists(n)){let s=l.join(P,"templates/auth/native");await m.pathExists(s)?await j("**/*",s,n,r):S.warn(C.yellow(`Warning: Auth native template not found at ${s}`))}}async function Dt(e,r){if(r.addons.includes("turborepo")){let t=l.join(P,"templates/addons/turborepo");await m.pathExists(t)?await j("**/*",t,e,r):S.warn(C.yellow("Warning: Turborepo addon template not found."))}if(r.addons.includes("husky")){let t=l.join(P,"templates/addons/husky");await m.pathExists(t)?await j("**/*",t,e,r):S.warn(C.yellow("Warning: Husky addon template not found."))}if(r.addons.includes("biome")){let t=l.join(P,"templates/addons/biome");await m.pathExists(t)?await j("**/*",t,e,r):S.warn(C.yellow("Warning: Biome addon template not found."))}if(r.addons.includes("pwa")){let t=l.join(P,"templates/addons/pwa/apps/web"),a=l.join(e,"apps/web");await m.pathExists(t)?await m.pathExists(a)?await j("**/*",t,a,r):S.warn(C.yellow("Warning: apps/web directory not found, cannot setup PWA addon.")):S.warn(C.yellow("Warning: PWA addon template not found."))}}async function $t(e,r){if(!r.examples||r.examples.length===0)return;let t=l.join(e,"apps/server"),a=l.join(e,"apps/web");for(let n of r.examples){let o=l.join(P,`templates/examples/${n}`);if(await m.pathExists(t)){let i=l.join(o,"server");if(await m.pathExists(i)&&r.orm!=="none"){let s=l.join(i,r.orm,"base");if(await m.pathExists(s)&&await j("**/*",s,t,r,!1),r.database!=="none"){let c=l.join(i,r.orm,r.database);await m.pathExists(c)&&await j("**/*",c,t,r,!1)}}}if(await m.pathExists(a)){let i=l.join(o,"web");if(await m.pathExists(i)){let s=r.frontend.filter(c=>["next","react-router","tanstack-router","tanstack-start"].includes(c));for(let c of s){let p=l.join(i,c);await m.pathExists(p)&&await j("**/*",p,a,r,!1)}}}}}async function Ct(e,r){let t=await vt(["**/.gitignore.hbs","**/_gitignore"],{cwd:e,dot:!0,onlyFiles:!0,absolute:!0,ignore:["**/node_modules/**","**/.git/**"]});for(let a of t){let n=l.dirname(a),o=l.basename(a),i=l.join(n,".gitignore");try{o===".gitignore.hbs"?(await Fe(a,i,r),await m.remove(a)):o==="_gitignore"&&await m.move(a,i,{overwrite:!0})}catch(s){S.error(`Error processing gitignore file ${a}:`,s)}}}async function Et(e,r){if(r.packageManager==="pnpm"){let t=l.join(P,"templates/extras/pnpm-workspace.yaml"),a=l.join(e,"pnpm-workspace.yaml");await m.pathExists(t)?await m.copy(t,a):S.warn(C.yellow("Warning: pnpm-workspace.yaml template not found."))}}async function Ft(e){let r=Ia(),t=Ra.resolve(process.cwd(),e.projectName);try{return await Ma.ensureDir(t),await jt(t,e),await kt(t,e),await xt(t,e),await Ye(e),await At(t,e),await pt(e),await St(t,e),await Qe(e),await Dt(t,e),e.addons.length>0&&e.addons[0]!=="none"&&await We(e),await $t(t,e),await Et(t,e),e.examples.length>0&&e.examples[0]!=="none"&&await dt(e),await qe(e),await Pt(e),await lt(e),await wt(t,e),await Ke(t,e),await yt(t,e.git),await Ct(t,e),Na.success("Project template successfully scaffolded!"),e.install&&await ft({projectDir:t,packageManager:e.packageManager,addons:e.addons}),gt({...e,depsInstalled:e.install}),t}catch(a){throw r.stop(Tt.red("Failed")),a instanceof Error&&(Ba(Tt.red(`Error during project creation: ${a.message}`)),console.error(a.stack),process.exit(1)),a}}import{cancel as Un,group as _n}from"@clack/prompts";import zn from"picocolors";import{cancel as La,isCancel as Oa,multiselect as Ua}from"@clack/prompts";import _a from"picocolors";async function Rt(e,r){if(e!==void 0)return e;let t=r?.includes("react-router")||r?.includes("tanstack-router"),a=[{value:"starlight",label:"Starlight",hint:"Add Astro Starlight documentation site"},{value:"biome",label:"Biome",hint:"Add Biome for linting and formatting"},{value:"husky",label:"Husky",hint:"Add Git hooks with Husky, lint-staged (requires Biome)"},{value:"turborepo",label:"Turborepo",hint:"Optimize builds for monorepos"}],o=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"}],...a]:a,i=h.addons.filter(c=>t||c!=="pwa"&&c!=="tauri"),s=await Ua({message:"Select addons",options:o,initialValues:i,required:!1});return Oa(s)&&(La(_a.red("Operation cancelled")),process.exit(0)),s.includes("husky")&&!s.includes("biome")&&s.push("biome"),s}import{cancel as za,isCancel as Wa,select as qa}from"@clack/prompts";import Va from"picocolors";async function Bt(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"},{value:"none",label:"None",hint:"No API integration (skip API setup)"}];t&&(a=[{value:"trpc",label:"tRPC",hint:"End-to-end typesafe APIs made easy (Required for Native frontend)"}]);let n=await qa({message:"Select API type",options:a,initialValue:t?"trpc":h.api});return Wa(n)&&(za(Va.red("Operation cancelled")),process.exit(0)),t&&n!=="trpc"?"trpc":n}import{cancel as Ga,confirm as Ja,isCancel as Qa}from"@clack/prompts";import Ha from"picocolors";async function Nt(e,r){if(!r)return!1;if(e!==void 0)return e;let t=await Ja({message:"Add authentication with Better-Auth?",initialValue:h.auth});return Qa(t)&&(Ga(Ha.red("Operation cancelled")),process.exit(0)),t}import{cancel as Ya,isCancel as Ka,select as Xa}from"@clack/prompts";import Za from"picocolors";async function It(e){if(e!==void 0)return e;let r=await Xa({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:h.backend});return Ka(r)&&(Ya(Za.red("Operation cancelled")),process.exit(0)),r}import{cancel as en,isCancel as tn,select as rn}from"@clack/prompts";import an from"picocolors";async function Mt(e){if(e!==void 0)return e;let r=await rn({message:"Select database",options:[{value:"none",label:"None",hint:"No database setup"},{value:"sqlite",label:"SQLite",hint:"lightweight, server-less, embedded relational database management system"},{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:h.database});return tn(r)&&(en(an.red("Operation cancelled")),process.exit(0)),r}import{cancel as nn,isCancel as on,select as sn}from"@clack/prompts";import cn from"picocolors";async function Lt(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:"SQLite for Production. Powered by libSQL"},{value:"none",label:"None",hint:"Manual setup"}];else if(e==="postgres")a=[{value:"neon",label:"Neon Postgres",hint:"Serverless Postgres with branching capability"},...t==="prisma"?[{value:"prisma-postgres",label:"Prisma Postgres",hint:"Instant Postgres for Global Applications"}]:[],{value:"none",label:"None",hint:"Manual setup"}];else if(e==="mongodb")a=[{value:"mongodb-atlas",label:"MongoDB Atlas",hint:"The most effective way to deploy MongoDB"},{value:"none",label:"None",hint:"Manual setup"}];else return"none";let n=await sn({message:`Select ${e} setup option`,options:a,initialValue:"none"});return on(n)&&(nn(cn.red("Operation cancelled")),process.exit(0)),n}import{cancel as pn,isCancel as ln,multiselect as Ot}from"@clack/prompts";import dn from"picocolors";async function Ut(e,r,t,a){if(e!==void 0)return e;if(r==="none")return[];if(!(t?.includes("react-router")||t?.includes("tanstack-router")||t?.includes("tanstack-start")))return[];let o=[];return a==="elysia"&&(o=await Ot({message:"Include examples",options:[{value:"todo",label:"Todo App",hint:"A simple CRUD example app"}],required:!1,initialValues:h.examples})),(a==="hono"||a==="express")&&(o=await Ot({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:h.examples})),ln(o)&&(pn(dn.red("Operation cancelled")),process.exit(0)),o}import{cancel as _t,isCancel as zt,multiselect as un,select as mn}from"@clack/prompts";import Wt from"picocolors";async function qt(e){if(e!==void 0)return e;let r=await un({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"}],required:!1,initialValues:h.frontend.some(a=>a==="tanstack-router"||a==="react-router"||a==="tanstack-start"||a==="next")?["web"]:[]});zt(r)&&(_t(Wt.red("Operation cancelled")),process.exit(0));let t=[];if(r.includes("web")){let a=await mn({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"},{value:"next",label:"Next.js",hint:"The React Framework for the Web"},{value:"tanstack-start",label:"TanStack Start (beta)",hint:"SSR, Server Functions, API Routes and more with TanStack Router"}],initialValue:h.frontend.find(n=>n==="tanstack-router"||n==="react-router"||n==="tanstack-start"||n==="next")||"tanstack-router"});zt(a)&&(_t(Wt.red("Operation cancelled")),process.exit(0)),t.push(a)}return r.includes("native")&&t.push("native"),t}import{cancel as fn,confirm as gn,isCancel as hn}from"@clack/prompts";import bn from"picocolors";async function Vt(e){if(e!==void 0)return e;let r=await gn({message:"Initialize git repository?",initialValue:h.git});return hn(r)&&(fn(bn.red("Operation cancelled")),process.exit(0)),r}import{cancel as wn,confirm as yn,isCancel as Pn}from"@clack/prompts";import vn from"picocolors";async function Gt(e){if(e!==void 0)return e;let r=await yn({message:"Install dependencies?",initialValue:h.install});return Pn(r)&&(wn(vn.red("Operation cancelled")),process.exit(0)),r}import{cancel as jn,isCancel as kn,log as xn,select as An}from"@clack/prompts";import Sn from"picocolors";async function Jt(e,r,t){if(!r)return"none";if(e!==void 0)return e;if(t==="mongodb")return xn.info("Only Prisma is supported with MongoDB."),"prisma";let a=await An({message:"Select ORM",options:[{value:"drizzle",label:"Drizzle",hint:"lightweight and performant TypeScript ORM"},{value:"prisma",label:"Prisma",hint:"Powerful, feature-rich ORM"}],initialValue:h.orm});return kn(a)&&(jn(Sn.red("Operation cancelled")),process.exit(0)),a}import{cancel as Dn,isCancel as $n,select as Cn}from"@clack/prompts";import En from"picocolors";async function Qt(e){if(e!==void 0)return e;let r=re(),t=await Cn({message:"Choose package manager",options:[{value:"npm",label:"npm",hint:"Node Package Manager"},{value:"pnpm",label:"pnpm",hint:"Fast, disk space efficient package manager"},{value:"bun",label:"bun",hint:"All-in-one JavaScript runtime & toolkit"}],initialValue:r});return $n(t)&&(Dn(En.red("Operation cancelled")),process.exit(0)),t}import te from"node:path";import{cancel as Tn,isCancel as Fn,text as Rn}from"@clack/prompts";import U from"fs-extra";import Bn from"picocolors";var Nn=["<",">",":",'"',"|","?","*"],Ht=255;function Yt(e){if(e!=="."){if(!e)return"Project name cannot be empty";if(e.length>Ht)return`Project name must be less than ${Ht} characters`;if(Nn.some(r=>e.includes(r)))return"Project name contains invalid characters";if(e.startsWith(".")||e.startsWith("-"))return"Project name cannot start with a dot or dash";if(e.toLowerCase()==="node_modules")return"Project name is reserved"}}async function Kt(e){if(e)if(e==="."){let o=process.cwd();if(U.readdirSync(o).length===0)return e}else{let o=te.basename(e);if(!Yt(o)){let s=te.resolve(process.cwd(),e);if(!U.pathExistsSync(s)||U.readdirSync(s).length===0)return e}}let r=!1,t="",a=h.projectName,n=1;for(;U.pathExistsSync(te.resolve(process.cwd(),a));)a=`${h.projectName}-${n}`,n++;for(;!r;){let o=await Rn({message:"Enter your project name or path (relative to current directory)",placeholder:a,initialValue:e,defaultValue:a,validate:i=>{let s=i.trim()||a;if(s==="."){if(U.readdirSync(process.cwd()).length>0)return"Current directory is not empty. Please choose a different directory.";r=!0;return}let c=te.resolve(process.cwd(),s),p=te.basename(c),w=Yt(p);if(w)return w;if(!c.startsWith(process.cwd()))return"Project path must be within current directory";if(U.pathExistsSync(c)&&U.readdirSync(c).length>0)return`Directory "${s}" already exists and is not empty. Please choose a different name or path.`;r=!0}});Fn(o)&&(Tn(Bn.red("Operation cancelled.")),process.exit(0)),t=o||a}return t}import{cancel as In,isCancel as Mn,select as Ln}from"@clack/prompts";import On from"picocolors";async function Xt(e,r){if(e!==void 0)return e;if(r==="next")return"node";let t=await Ln({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:h.runtime});return Mn(t)&&(In(On.red("Operation cancelled")),process.exit(0)),t}async function Zt(e){let r=await _n({projectName:async()=>Kt(e.projectName),frontend:()=>qt(e.frontend),backend:()=>It(e.backend),runtime:({results:t})=>Xt(e.runtime,t.backend),database:()=>Mt(e.database),orm:({results:t})=>Jt(e.orm,t.database!=="none",t.database),api:({results:t})=>Bt(e.api,t.frontend),auth:({results:t})=>Nt(e.auth,t.database!=="none"),addons:({results:t})=>Rt(e.addons,t.frontend),examples:({results:t})=>Ut(e.examples,t.database,t.frontend,t.backend),dbSetup:({results:t})=>Lt(t.database??"none",e.dbSetup,t.orm),git:()=>Vt(e.git),packageManager:()=>Qt(e.packageManager),install:()=>Gt(e.install)},{onCancel:()=>{Un(zn.red("Operation cancelled")),process.exit(0)}});return{projectName:r.projectName,frontend:r.frontend,database:r.database,orm:r.orm,auth:r.auth,addons:r.addons,examples:r.examples,git:r.git,packageManager:r.packageManager,install:r.install,dbSetup:r.dbSetup,backend:r.backend,runtime:r.runtime,api:r.api}}import T from"picocolors";function Re(e){let r=[];if(e.projectName&&r.push(`${T.blue("Project Name:")} ${e.projectName}`),e.frontend!==void 0){let t=Array.isArray(e.frontend)?e.frontend:[e.frontend],a=t.length>0&&t[0]!==void 0&&t[0]!==""?t.join(", "):"none";r.push(`${T.blue("Frontend:")} ${a}`)}if(e.backend!==void 0&&r.push(`${T.blue("Backend Framework:")} ${String(e.backend)}`),e.runtime!==void 0&&r.push(`${T.blue("Runtime:")} ${String(e.runtime)}`),e.api!==void 0&&r.push(`${T.blue("API:")} ${String(e.api)}`),e.database!==void 0&&r.push(`${T.blue("Database:")} ${String(e.database)}`),e.orm!==void 0&&r.push(`${T.blue("ORM:")} ${String(e.orm)}`),e.auth!==void 0){let t=typeof e.auth=="boolean"?e.auth?"Yes":"No":String(e.auth);r.push(`${T.blue("Authentication:")} ${t}`)}if(e.addons!==void 0){let t=Array.isArray(e.addons)?e.addons:[e.addons],a=t.length>0&&t[0]!==void 0?t.join(", "):"none";r.push(`${T.blue("Addons:")} ${a}`)}if(e.examples!==void 0){let t=Array.isArray(e.examples)?e.examples:[e.examples],a=t.length>0&&t[0]!==void 0?t.join(", "):"none";r.push(`${T.blue("Examples:")} ${a}`)}if(e.git!==void 0){let t=typeof e.git=="boolean"?e.git?"Yes":"No":String(e.git);r.push(`${T.blue("Git Init:")} ${t}`)}if(e.packageManager!==void 0&&r.push(`${T.blue("Package Manager:")} ${String(e.packageManager)}`),e.install!==void 0){let t=typeof e.install=="boolean"?e.install?"Yes":"No":String(e.install);r.push(`${T.blue("Install Dependencies:")} ${t}`)}return e.dbSetup!==void 0&&r.push(`${T.blue("Database Setup:")} ${String(e.dbSetup)}`),r.length===0?T.yellow("No configuration selected."):r.join(`
|
|
195
|
-
`)}function er(e){let r=[];e.database==="none"?r.push("--database none"):(r.push(`--database ${e.database}`),e.orm&&r.push(`--orm ${e.orm}`),e.dbSetup&&r.push(`--db-setup ${e.dbSetup}`)),e.api&&r.push(`--api ${e.api}`),r.push(e.auth?"--auth":"--no-auth"),r.push(e.git?"--git":"--no-git"),r.push(e.install?"--install":"--no-install"),e.runtime&&r.push(`--runtime ${e.runtime}`),e.backend&&r.push(`--backend ${e.backend}`),e.frontend&&e.frontend.length>0&&r.push(`--frontend ${e.frontend.join(" ")}`),e.addons&&e.addons.length>0?r.push(`--addons ${e.addons.join(" ")}`):r.push("--addons none"),e.examples&&e.examples.length>0?r.push(`--examples ${e.examples.join(" ")}`):r.push("--examples none"),e.packageManager&&r.push(`--package-manager ${e.packageManager}`);let t="",a=e.packageManager;a==="npm"?t="npx create-better-t-stack@latest":a==="pnpm"?t="pnpm create better-t-stack@latest":a==="bun"&&(t="bun create better-t-stack@latest");let
|
|
194
|
+
`}import wt from"node:path";import{log as Aa}from"@clack/prompts";import{$ as bt,execa as Da}from"execa";import J from"fs-extra";import $a from"picocolors";async function yt(e,r){await Ca(e,r),await Ea(e,r)}async function Ca(e,r){let t=wt.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 --parallel 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"},i={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"},s={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=i:r.packageManager==="bun"?a.scripts=s:a.scripts={};let{stdout:c}=await Da(r.packageManager,["-v"],{cwd:e});a.packageManager=`${r.packageManager}@${c.trim()}`,await J.writeJson(t,a,{spaces:2})}}async function Ea(e,r){let t=wt.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 Pt(e,r){if(!r)return;if((await bt({cwd:e,reject:!1,stderr:"pipe"})`git --version`).exitCode!==0){Aa.warn($a.yellow("Git is not installed"));return}let a=await bt({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 Ta(n,a):t==="node"&&await Fa(n,a)}async function Ta(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 g({devDependencies:["@types/bun"],projectDir:e})}async function Fa(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 g({devDependencies:["tsx","@types/node"],projectDir:e}),r==="hono"?await g({dependencies:["@hono/node-server"],projectDir:e}):r==="elysia"&&await g({dependencies:["@elysiajs/node"],projectDir:e})}import l from"node:path";import A from"consola";import m from"fs-extra";import{globby as vt}from"globby";import C from"picocolors";import Ra from"node:path";import Fe from"fs-extra";import ge from"handlebars";async function Re(e,r,t){try{let a=await Fe.readFile(e,"utf-8"),n=ge.compile(a)(t);await Fe.ensureDir(Ra.dirname(r)),await Fe.writeFile(r,n)}catch(a){throw console.error(`Error processing template ${e}:`,a),new Error(`Failed to process template ${e}`)}}ge.registerHelper("or",(e,r)=>e||r);ge.registerHelper("eq",(e,r)=>e===r);ge.registerHelper("includes",(e,r)=>Array.isArray(e)&&e.includes(r));async function v(e,r,t,a,o=!0){let n=await vt(e,{cwd:r,dot:!0,onlyFiles:!0,absolute:!1});for(let i of n){let s=l.join(r,i),c=i;i.endsWith(".hbs")&&(c=i.slice(0,-4));let p=l.join(t,c);if(await m.ensureDir(l.dirname(p)),s.endsWith(".hbs"))await Re(s,p,a);else{if(!o&&await m.pathExists(p))continue;await m.copy(s,p,{overwrite:!0})}}}async function kt(e,r){let t=l.join(P,"templates/base");await v(["package.json","_gitignore"],t,e,r)}async function xt(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=l.join(e,"apps/web");await m.ensureDir(o);let n=l.join(P,"templates/frontend/web-base");await m.pathExists(n)&&await v("**/*",n,o,r);for(let i of t){let s=l.join(P,`templates/frontend/${i}`);await m.pathExists(s)&&await v("**/*",s,o,r)}if(r.api!=="none"){let i=t[0],s=l.join(P,`templates/api/${r.api}/web/base`);await m.pathExists(s)&&await v("**/*",s,o,r);let c=l.join(P,`templates/api/${r.api}/web/${i}`);await m.pathExists(c)&&await v("**/*",c,o,r)}}if(a){let o=l.join(e,"apps/native");await m.ensureDir(o);let n=l.join(P,"templates/frontend/native");if(await m.pathExists(n)&&await v("**/*",n,o,r),r.api!=="none"){let i=l.join(P,`templates/api/${r.api}/native`);await m.pathExists(i)&&await v("**/*",i,o,r)}}}async function St(e,r){if(r.backend==="none")return;let t=l.join(e,"apps/server");await m.ensureDir(t);let a=l.join(P,"templates/backend/server-base");await m.pathExists(a)?await v("**/*",a,t,r):A.warn(C.yellow(`Warning: server-base template not found at ${a}`));let o=l.join(P,`templates/backend/${r.backend}`);if(await m.pathExists(o)?await v("**/*",o,t,r):A.warn(C.yellow(`Warning: Backend template directory not found, skipping: ${o}`)),r.api!=="none"){let n=l.join(P,`templates/api/${r.api}/server/base`);await m.pathExists(n)&&await v("**/*",n,t,r);let i=l.join(P,`templates/api/${r.api}/server/${r.backend}`);await m.pathExists(i)&&await v("**/*",i,t,r)}}async function At(e,r){if(r.orm==="none"||r.database==="none")return;let t=l.join(e,"apps/server");await m.ensureDir(t);let a=l.join(P,`templates/db/${r.orm}/${r.database}`);await m.pathExists(a)?await v("**/*",a,t,r):A.warn(C.yellow(`Warning: Database/ORM template directory not found, skipping: ${a}`))}async function Dt(e,r){if(!r.auth)return;let t=l.join(e,"apps/server"),a=l.join(e,"apps/web"),o=l.join(e,"apps/native"),n=r.frontend.filter(s=>s==="tanstack-router"||s==="react-router"||s==="tanstack-start"||s==="next"),i=r.frontend.includes("native");if(await m.pathExists(t)){let s=l.join(P,"templates/auth/server/base");await m.pathExists(s)?await v("**/*",s,t,r):A.warn(C.yellow(`Warning: Base auth server template not found at ${s}`));let c=l.join(P,"templates/auth/server/next");if(await m.pathExists(c)?await v("**/*",c,t,r):A.warn(C.yellow(`Warning: Next auth server template not found at ${c}`)),r.orm!=="none"&&r.database!=="none"){let p=r.orm,w=r.database,y="";p==="drizzle"?y=l.join(P,`templates/auth/server/db/drizzle/${w}`):p==="prisma"&&(y=l.join(P,`templates/auth/server/db/prisma/${w}`)),y&&await m.pathExists(y)?await v("**/*",y,t,r):A.warn(C.yellow(`Warning: Auth template for ${p}/${w} not found at ${y}`))}}else A.warn(C.yellow("Warning: apps/server directory does not exist, skipping server-side auth setup."));if(n.length>0&&await m.pathExists(a)){let s=l.join(P,"templates/auth/web/base");await m.pathExists(s)?await v("**/*",s,a,r):A.warn(C.yellow(`Warning: Base auth web template not found at ${s}`));for(let c of n){let p=l.join(P,`templates/auth/web/${c}`);await m.pathExists(p)?await v("**/*",p,a,r):A.warn(C.yellow(`Warning: Auth web template for ${c} not found at ${p}`))}}if(i&&await m.pathExists(o)){let s=l.join(P,"templates/auth/native");await m.pathExists(s)?await v("**/*",s,o,r):A.warn(C.yellow(`Warning: Auth native template not found at ${s}`))}}async function $t(e,r){if(r.addons.includes("turborepo")){let t=l.join(P,"templates/addons/turborepo");await m.pathExists(t)?await v("**/*",t,e,r):A.warn(C.yellow("Warning: Turborepo addon template not found."))}if(r.addons.includes("husky")){let t=l.join(P,"templates/addons/husky");await m.pathExists(t)?await v("**/*",t,e,r):A.warn(C.yellow("Warning: Husky addon template not found."))}if(r.addons.includes("biome")){let t=l.join(P,"templates/addons/biome");await m.pathExists(t)?await v("**/*",t,e,r):A.warn(C.yellow("Warning: Biome addon template not found."))}if(r.addons.includes("pwa")){let t=l.join(P,"templates/addons/pwa/apps/web"),a=l.join(e,"apps/web");await m.pathExists(t)?await m.pathExists(a)?await v("**/*",t,a,r):A.warn(C.yellow("Warning: apps/web directory not found, cannot setup PWA addon.")):A.warn(C.yellow("Warning: PWA addon template not found."))}}async function Ct(e,r){if(!r.examples||r.examples.length===0)return;let t=l.join(e,"apps/server"),a=l.join(e,"apps/web");for(let o of r.examples){let n=l.join(P,`templates/examples/${o}`);if(await m.pathExists(t)){let i=l.join(n,"server");if(await m.pathExists(i)&&r.orm!=="none"){let s=l.join(i,r.orm,"base");if(await m.pathExists(s)&&await v("**/*",s,t,r,!1),r.database!=="none"){let c=l.join(i,r.orm,r.database);await m.pathExists(c)&&await v("**/*",c,t,r,!1)}}}if(await m.pathExists(a)){let i=l.join(n,"web");if(await m.pathExists(i)){let s=r.frontend.filter(c=>["next","react-router","tanstack-router","tanstack-start"].includes(c));for(let c of s){let p=l.join(i,c);await m.pathExists(p)&&await v("**/*",p,a,r,!1)}}}}}async function Et(e,r){let t=await vt(["**/.gitignore.hbs","**/_gitignore"],{cwd:e,dot:!0,onlyFiles:!0,absolute:!0,ignore:["**/node_modules/**","**/.git/**"]});for(let a of t){let o=l.dirname(a),n=l.basename(a),i=l.join(o,".gitignore");try{n===".gitignore.hbs"?(await Re(a,i,r),await m.remove(a)):n==="_gitignore"&&await m.move(a,i,{overwrite:!0})}catch(s){A.error(`Error processing gitignore file ${a}:`,s)}}}async function Tt(e,r){if(r.packageManager==="pnpm"){let t=l.join(P,"templates/extras/pnpm-workspace.yaml"),a=l.join(e,"pnpm-workspace.yaml");await m.pathExists(t)?await m.copy(t,a):A.warn(C.yellow("Warning: pnpm-workspace.yaml template not found."))}}async function Rt(e){let r=Ma(),t=Ba.resolve(process.cwd(),e.projectName);try{return await La.ensureDir(t),await kt(t,e),await xt(t,e),await St(t,e),await Ke(e),await At(t,e),await lt(e),await Dt(t,e),await He(e),await $t(t,e),e.addons.length>0&&e.addons[0]!=="none"&&await qe(e),await Ct(t,e),await Tt(t,e),e.examples.length>0&&e.examples[0]!=="none"&&await ut(e),await Ve(e),await jt(e),await dt(e),await yt(t,e),await Xe(t,e),await Pt(t,e.git),await Et(t,e),Ia.success("Project template successfully scaffolded!"),e.install&&await gt({projectDir:t,packageManager:e.packageManager,addons:e.addons}),ht({...e,depsInstalled:e.install}),t}catch(a){throw r.stop(Ft.red("Failed")),a instanceof Error&&(Na(Ft.red(`Error during project creation: ${a.message}`)),console.error(a.stack),process.exit(1)),a}}import{cancel as _o,group as zo}from"@clack/prompts";import Wo from"picocolors";import{cancel as Oa,isCancel as Ua,multiselect as _a}from"@clack/prompts";import za from"picocolors";async function Bt(e,r){if(e!==void 0)return e;let t=r?.includes("react-router")||r?.includes("tanstack-router"),a=[{value:"starlight",label:"Starlight",hint:"Add Astro Starlight documentation site"},{value:"biome",label:"Biome",hint:"Add Biome for linting and formatting"},{value:"husky",label:"Husky",hint:"Add Git hooks with Husky, lint-staged (requires Biome)"},{value:"turborepo",label:"Turborepo",hint:"Optimize builds for monorepos"}],n=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"}],...a]:a,i=h.addons.filter(c=>t||c!=="pwa"&&c!=="tauri"),s=await _a({message:"Select addons",options:n,initialValues:i,required:!1});return Ua(s)&&(Oa(za.red("Operation cancelled")),process.exit(0)),s.includes("husky")&&!s.includes("biome")&&s.push("biome"),s}import{cancel as Wa,isCancel as qa,select as Va}from"@clack/prompts";import Ga from"picocolors";async function Nt(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"},{value:"none",label:"None",hint:"No API integration (skip API setup)"}];t&&(a=[{value:"trpc",label:"tRPC",hint:"End-to-end typesafe APIs made easy (Required for Native frontend)"}]);let o=await Va({message:"Select API type",options:a,initialValue:t?"trpc":h.api});return qa(o)&&(Wa(Ga.red("Operation cancelled")),process.exit(0)),t&&o!=="trpc"?"trpc":o}import{cancel as Ja,confirm as Qa,isCancel as Ha}from"@clack/prompts";import Ya from"picocolors";async function It(e,r){if(!r)return!1;if(e!==void 0)return e;let t=await Qa({message:"Add authentication with Better-Auth?",initialValue:h.auth});return Ha(t)&&(Ja(Ya.red("Operation cancelled")),process.exit(0)),t}import{cancel as Ka,isCancel as Xa,select as Za}from"@clack/prompts";import eo from"picocolors";async function Mt(e){if(e!==void 0)return e;let r=await Za({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:h.backend});return Xa(r)&&(Ka(eo.red("Operation cancelled")),process.exit(0)),r}import{cancel as to,isCancel as ro,select as ao}from"@clack/prompts";import oo from"picocolors";async function Lt(e){if(e!==void 0)return e;let r=await ao({message:"Select database",options:[{value:"none",label:"None",hint:"No database setup"},{value:"sqlite",label:"SQLite",hint:"lightweight, server-less, embedded relational database management system"},{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:h.database});return ro(r)&&(to(oo.red("Operation cancelled")),process.exit(0)),r}import{cancel as no,isCancel as so,select as io}from"@clack/prompts";import co from"picocolors";async function Ot(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:"SQLite for Production. Powered by libSQL"},{value:"none",label:"None",hint:"Manual setup"}];else if(e==="postgres")a=[{value:"neon",label:"Neon Postgres",hint:"Serverless Postgres with branching capability"},...t==="prisma"?[{value:"prisma-postgres",label:"Prisma Postgres",hint:"Instant Postgres for Global Applications"}]:[],{value:"none",label:"None",hint:"Manual setup"}];else if(e==="mongodb")a=[{value:"mongodb-atlas",label:"MongoDB Atlas",hint:"The most effective way to deploy MongoDB"},{value:"none",label:"None",hint:"Manual setup"}];else return"none";let o=await io({message:`Select ${e} setup option`,options:a,initialValue:"none"});return so(o)&&(no(co.red("Operation cancelled")),process.exit(0)),o}import{cancel as po,isCancel as lo,multiselect as Ut}from"@clack/prompts";import uo from"picocolors";async function _t(e,r,t,a){if(e!==void 0)return e;if(r==="none")return[];if(!(t?.includes("react-router")||t?.includes("tanstack-router")||t?.includes("tanstack-start")))return[];let n=[];return a==="elysia"&&(n=await Ut({message:"Include examples",options:[{value:"todo",label:"Todo App",hint:"A simple CRUD example app"}],required:!1,initialValues:h.examples})),(a==="hono"||a==="express")&&(n=await Ut({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:h.examples})),lo(n)&&(po(uo.red("Operation cancelled")),process.exit(0)),n}import{cancel as zt,isCancel as Wt,multiselect as mo,select as fo}from"@clack/prompts";import qt from"picocolors";async function Vt(e){if(e!==void 0)return e;let r=await mo({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"}],required:!1,initialValues:h.frontend.some(a=>a==="tanstack-router"||a==="react-router"||a==="tanstack-start"||a==="next")?["web"]:[]});Wt(r)&&(zt(qt.red("Operation cancelled")),process.exit(0));let t=[];if(r.includes("web")){let a=await fo({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"},{value:"next",label:"Next.js",hint:"The React Framework for the Web"},{value:"tanstack-start",label:"TanStack Start (beta)",hint:"SSR, Server Functions, API Routes and more with TanStack Router"}],initialValue:h.frontend.find(o=>o==="tanstack-router"||o==="react-router"||o==="tanstack-start"||o==="next")||"tanstack-router"});Wt(a)&&(zt(qt.red("Operation cancelled")),process.exit(0)),t.push(a)}return r.includes("native")&&t.push("native"),t}import{cancel as go,confirm as ho,isCancel as bo}from"@clack/prompts";import wo from"picocolors";async function Gt(e){if(e!==void 0)return e;let r=await ho({message:"Initialize git repository?",initialValue:h.git});return bo(r)&&(go(wo.red("Operation cancelled")),process.exit(0)),r}import{cancel as yo,confirm as Po,isCancel as jo}from"@clack/prompts";import vo from"picocolors";async function Jt(e){if(e!==void 0)return e;let r=await Po({message:"Install dependencies?",initialValue:h.install});return jo(r)&&(yo(vo.red("Operation cancelled")),process.exit(0)),r}import{cancel as ko,isCancel as xo,log as So,select as Ao}from"@clack/prompts";import Do from"picocolors";async function Qt(e,r,t){if(!r)return"none";if(e!==void 0)return e;if(t==="mongodb")return So.info("Only Prisma is supported with MongoDB."),"prisma";let a=await Ao({message:"Select ORM",options:[{value:"drizzle",label:"Drizzle",hint:"lightweight and performant TypeScript ORM"},{value:"prisma",label:"Prisma",hint:"Powerful, feature-rich ORM"}],initialValue:h.orm});return xo(a)&&(ko(Do.red("Operation cancelled")),process.exit(0)),a}import{cancel as $o,isCancel as Co,select as Eo}from"@clack/prompts";import To from"picocolors";async function Ht(e){if(e!==void 0)return e;let r=re(),t=await Eo({message:"Choose package manager",options:[{value:"npm",label:"npm",hint:"Node Package Manager"},{value:"pnpm",label:"pnpm",hint:"Fast, disk space efficient package manager"},{value:"bun",label:"bun",hint:"All-in-one JavaScript runtime & toolkit"}],initialValue:r});return Co(t)&&($o(To.red("Operation cancelled")),process.exit(0)),t}import te from"node:path";import{cancel as Fo,isCancel as Ro,text as Bo}from"@clack/prompts";import U from"fs-extra";import No from"picocolors";var Io=["<",">",":",'"',"|","?","*"],Yt=255;function Kt(e){if(e!=="."){if(!e)return"Project name cannot be empty";if(e.length>Yt)return`Project name must be less than ${Yt} characters`;if(Io.some(r=>e.includes(r)))return"Project name contains invalid characters";if(e.startsWith(".")||e.startsWith("-"))return"Project name cannot start with a dot or dash";if(e.toLowerCase()==="node_modules")return"Project name is reserved"}}async function he(e){if(e)if(e==="."){let n=process.cwd();if(U.readdirSync(n).length===0)return e}else{let n=te.basename(e);if(!Kt(n)){let s=te.resolve(process.cwd(),e);if(!U.pathExistsSync(s)||U.readdirSync(s).length===0)return e}}let r=!1,t="",a=h.projectName,o=1;for(;U.pathExistsSync(te.resolve(process.cwd(),a));)a=`${h.projectName}-${o}`,o++;for(;!r;){let n=await Bo({message:"Enter your project name or path (relative to current directory)",placeholder:a,initialValue:e,defaultValue:a,validate:i=>{let s=i.trim()||a;if(s==="."){if(U.readdirSync(process.cwd()).length>0)return"Current directory is not empty. Please choose a different directory.";r=!0;return}let c=te.resolve(process.cwd(),s),p=te.basename(c),w=Kt(p);if(w)return w;if(!c.startsWith(process.cwd()))return"Project path must be within current directory";if(U.pathExistsSync(c)&&U.readdirSync(c).length>0)return`Directory "${s}" already exists and is not empty. Please choose a different name or path.`;r=!0}});Ro(n)&&(Fo(No.red("Operation cancelled.")),process.exit(0)),t=n||a}return t}import{cancel as Mo,isCancel as Lo,select as Oo}from"@clack/prompts";import Uo from"picocolors";async function Xt(e,r){if(e!==void 0)return e;if(r==="next")return"node";let t=await Oo({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:h.runtime});return Lo(t)&&(Mo(Uo.red("Operation cancelled")),process.exit(0)),t}async function Zt(e){let r=await zo({projectName:async()=>he(e.projectName),frontend:()=>Vt(e.frontend),backend:()=>Mt(e.backend),runtime:({results:t})=>Xt(e.runtime,t.backend),database:()=>Lt(e.database),orm:({results:t})=>Qt(e.orm,t.database!=="none",t.database),api:({results:t})=>Nt(e.api,t.frontend),auth:({results:t})=>It(e.auth,t.database!=="none"),addons:({results:t})=>Bt(e.addons,t.frontend),examples:({results:t})=>_t(e.examples,t.database,t.frontend,t.backend),dbSetup:({results:t})=>Ot(t.database??"none",e.dbSetup,t.orm),git:()=>Gt(e.git),packageManager:()=>Ht(e.packageManager),install:()=>Jt(e.install)},{onCancel:()=>{_o(Wo.red("Operation cancelled")),process.exit(0)}});return{projectName:r.projectName,frontend:r.frontend,database:r.database,orm:r.orm,auth:r.auth,addons:r.addons,examples:r.examples,git:r.git,packageManager:r.packageManager,install:r.install,dbSetup:r.dbSetup,backend:r.backend,runtime:r.runtime,api:r.api}}import T from"picocolors";function Be(e){let r=[];if(e.projectName&&r.push(`${T.blue("Project Name:")} ${e.projectName}`),e.frontend!==void 0){let t=Array.isArray(e.frontend)?e.frontend:[e.frontend],a=t.length>0&&t[0]!==void 0&&t[0]!==""?t.join(", "):"none";r.push(`${T.blue("Frontend:")} ${a}`)}if(e.backend!==void 0&&r.push(`${T.blue("Backend Framework:")} ${String(e.backend)}`),e.runtime!==void 0&&r.push(`${T.blue("Runtime:")} ${String(e.runtime)}`),e.api!==void 0&&r.push(`${T.blue("API:")} ${String(e.api)}`),e.database!==void 0&&r.push(`${T.blue("Database:")} ${String(e.database)}`),e.orm!==void 0&&r.push(`${T.blue("ORM:")} ${String(e.orm)}`),e.auth!==void 0){let t=typeof e.auth=="boolean"?e.auth?"Yes":"No":String(e.auth);r.push(`${T.blue("Authentication:")} ${t}`)}if(e.addons!==void 0){let t=Array.isArray(e.addons)?e.addons:[e.addons],a=t.length>0&&t[0]!==void 0?t.join(", "):"none";r.push(`${T.blue("Addons:")} ${a}`)}if(e.examples!==void 0){let t=Array.isArray(e.examples)?e.examples:[e.examples],a=t.length>0&&t[0]!==void 0?t.join(", "):"none";r.push(`${T.blue("Examples:")} ${a}`)}if(e.git!==void 0){let t=typeof e.git=="boolean"?e.git?"Yes":"No":String(e.git);r.push(`${T.blue("Git Init:")} ${t}`)}if(e.packageManager!==void 0&&r.push(`${T.blue("Package Manager:")} ${String(e.packageManager)}`),e.install!==void 0){let t=typeof e.install=="boolean"?e.install?"Yes":"No":String(e.install);r.push(`${T.blue("Install Dependencies:")} ${t}`)}return e.dbSetup!==void 0&&r.push(`${T.blue("Database Setup:")} ${String(e.dbSetup)}`),r.length===0?T.yellow("No configuration selected."):r.join(`
|
|
195
|
+
`)}function er(e){let r=[];e.database==="none"?r.push("--database none"):(r.push(`--database ${e.database}`),e.orm&&r.push(`--orm ${e.orm}`),e.dbSetup&&r.push(`--db-setup ${e.dbSetup}`)),e.api&&r.push(`--api ${e.api}`),r.push(e.auth?"--auth":"--no-auth"),r.push(e.git?"--git":"--no-git"),r.push(e.install?"--install":"--no-install"),e.runtime&&r.push(`--runtime ${e.runtime}`),e.backend&&r.push(`--backend ${e.backend}`),e.frontend&&e.frontend.length>0&&r.push(`--frontend ${e.frontend.join(" ")}`),e.addons&&e.addons.length>0?r.push(`--addons ${e.addons.join(" ")}`):r.push("--addons none"),e.examples&&e.examples.length>0?r.push(`--examples ${e.examples.join(" ")}`):r.push("--examples none"),e.packageManager&&r.push(`--package-manager ${e.packageManager}`);let t="",a=e.packageManager;a==="npm"?t="npx create-better-t-stack@latest":a==="pnpm"?t="pnpm create better-t-stack@latest":a==="bun"&&(t="bun create better-t-stack@latest");let o=e.projectName?` ${e.projectName}`:"";return`${t}${o} ${r.join(" ")}`}import qo from"node:path";import Vo from"fs-extra";var tr=()=>{let e=qo.join(P,"package.json");return Vo.readJSONSync(e).version??"1.0.0"};import rr from"gradient-string";var ar=`
|
|
196
196
|
\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
|
|
197
197
|
\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
|
|
198
198
|
\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
|
|
@@ -206,10 +206,10 @@ ${b.cyan("\u2022")} Build docs site: ${`cd apps/docs && ${e} build`}
|
|
|
206
206
|
\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
|
|
207
207
|
\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
|
|
208
208
|
\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D
|
|
209
|
-
`,
|
|
210
|
-
`),t=Math.max(...r.map(a=>a.length));e<t?console.log(rr(Object.values(
|
|
209
|
+
`,or={pink:"#F5C2E7",mauve:"#CBA6F7",red:"#F38BA8",maroon:"#E78284",peach:"#FAB387",yellow:"#F9E2AF",green:"#A6E3A1",teal:"#94E2D5",sky:"#89DCEB",sapphire:"#74C7EC",lavender:"#B4BEFE"},nr=()=>{let e=process.stdout.columns||80,r=ar.split(`
|
|
210
|
+
`),t=Math.max(...r.map(a=>a.length));e<t?console.log(rr(Object.values(or)).multiline(`
|
|
211
211
|
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
212
212
|
\u2551 Better T-Stack \u2551
|
|
213
213
|
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
214
|
-
`)):console.log(rr(Object.values(
|
|
215
|
-
${er(
|
|
214
|
+
`)):console.log(rr(Object.values(or)).multiline(ar))};var ir=()=>process.exit(0);process.on("SIGINT",ir);process.on("SIGTERM",ir);async function Xo(){let e=Date.now();try{let t=await Yo(Ko(process.argv)).scriptName("create-better-t-stack").usage("$0 [project-directory] [options]","Create a new Better-T Stack project").positional("project-directory",{describe:"Project name/directory",type:"string"}).option("yes",{alias:"y",type:"boolean",describe:"Use default configuration and skip prompts",default:!1}).option("database",{type:"string",describe:"Database type",choices:["none","sqlite","postgres","mysql","mongodb"]}).option("orm",{type:"string",describe:"ORM type",choices:["drizzle","prisma","none"]}).option("auth",{type:"boolean",describe:"Include authentication"}).option("frontend",{type:"array",string:!0,describe:"Frontend types",choices:["tanstack-router","react-router","tanstack-start","next","native","none"]}).option("addons",{type:"array",string:!0,describe:"Additional addons",choices:["pwa","tauri","starlight","biome","husky","turborepo","none"]}).option("examples",{type:"array",string:!0,describe:"Examples to include",choices:["todo","ai","none"]}).option("git",{type:"boolean",describe:"Initialize git repository"}).option("package-manager",{alias:"pm",type:"string",describe:"Package manager",choices:["npm","pnpm","bun"]}).option("install",{type:"boolean",describe:"Install dependencies"}).option("db-setup",{type:"string",describe:"Database setup",choices:["turso","neon","prisma-postgres","mongodb-atlas","none"]}).option("backend",{type:"string",describe:"Backend framework",choices:["hono","express","next","elysia"]}).option("runtime",{type:"string",describe:"Runtime",choices:["bun","node"]}).option("api",{type:"string",describe:"API type",choices:["trpc","orpc"]}).completion().recommendCommands().version(tr()).alias("version","v").help().alias("help","h").strict().wrap(null).parse(),a=t.projectDirectory;nr();let o=Zo(t,a);Qo(z.magenta("Creating a new Better-T-Stack project")),!t.yes&&Object.keys(o).length>0&&(_.info(z.yellow("Using these pre-selected options:")),_.message(Be(o)),_.message(""));let n;t.yes?(n={...h,projectName:a??h.projectName,...o},_.info(z.yellow("Using these default/flag options:")),_.message(Be(n)),_.message("")):n=await Zt(o);let i=Go.resolve(process.cwd(),n.projectName);if(sr.pathExistsSync(i)&&sr.readdirSync(i).length>0){let c=await he();n.projectName=c}await Rt(n),_.success(z.blue(`You can reproduce this setup with the following command:
|
|
215
|
+
${er(n)}`));let s=((Date.now()-e)/1e3).toFixed(2);Ho(z.magenta(`Project created successfully in ${z.bold(s)} seconds!`))}catch(r){r instanceof Error?(r.name==="YError"?Jo(z.red(`Invalid arguments: ${r.message}`)):(j.error(`An unexpected error occurred: ${r.message}`),j.error(r.stack)),process.exit(1)):(j.error("An unexpected error occurred."),console.error(r),process.exit(1))}}function Zo(e,r){let t={};if(e.database&&(t.database=e.database),e.orm&&(e.orm==="none"?t.orm="none":t.orm=e.orm),(t.database??e.database)==="mongodb"&&(t.orm??e.orm)==="drizzle"&&(j.fatal("MongoDB is only available with Prisma. Cannot use --database mongodb with --orm drizzle"),process.exit(1)),e.dbSetup){let s=e.dbSetup;s!=="none"?(t.dbSetup=s,s==="turso"?(e.database&&e.database!=="sqlite"&&(j.fatal(`Turso setup requires a SQLite database. Cannot use --db-setup turso with --database ${e.database}`),process.exit(1)),t.database="sqlite",e.orm==="prisma"&&(j.fatal("Turso setup is not compatible with Prisma. Cannot use --db-setup turso with --orm prisma"),process.exit(1)),t.orm="drizzle"):s==="prisma-postgres"?(e.database&&e.database!=="postgres"&&(j.fatal("Prisma PostgreSQL setup requires PostgreSQL database. Cannot use --db-setup prisma-postgres with a different database type."),process.exit(1)),t.database="postgres",e.orm&&e.orm!=="prisma"&&e.orm!=="none"&&(j.fatal("Prisma PostgreSQL setup requires Prisma ORM. Cannot use --db-setup prisma-postgres with a different ORM."),process.exit(1)),t.orm="prisma"):s==="mongodb-atlas"?(e.database&&e.database!=="mongodb"&&(j.fatal("MongoDB Atlas setup requires MongoDB database. Cannot use --db-setup mongodb-atlas with a different database type."),process.exit(1)),t.database="mongodb",t.orm="prisma"):s==="neon"&&(e.database&&e.database!=="postgres"&&(j.fatal("Neon PostgreSQL setup requires PostgreSQL database. Cannot use --db-setup neon with a different database type."),process.exit(1)),t.database="postgres")):t.dbSetup="none"}if((t.database??e.database)==="none"){e.auth===!0&&(j.fatal("Authentication requires a database. Cannot use --auth with --database none."),process.exit(1));let s=t.orm??e.orm;s&&s!=="none"&&(j.fatal(`Cannot use ORM with no database. Cannot use --orm ${s} with --database none.`),process.exit(1)),t.orm="none";let c=t.dbSetup??e.dbSetup;c&&c!=="none"&&(j.fatal(`Database setup requires a database. Cannot use --db-setup ${c} with --database none.`),process.exit(1)),t.dbSetup="none"}if(e.auth!==void 0&&(t.auth=e.auth),e.backend&&(t.backend=e.backend),e.runtime&&(t.runtime=e.runtime),e.frontend&&e.frontend.length>0)if(e.frontend.includes("none"))e.frontend.length>1&&(j.fatal("Cannot combine 'none' with other frontend options."),process.exit(1)),t.frontend=[];else{let s=e.frontend.filter(p=>p!=="none");s.filter(p=>p==="tanstack-router"||p==="react-router"||p==="tanstack-start"||p==="next").length>1&&(j.fatal("Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next"),process.exit(1)),t.frontend=s}e.api&&(t.api=e.api);let o=t.frontend??e.frontend?.filter(s=>s!=="none")??(e.yes?h.frontend:void 0),n=o?.includes("native"),i=t.api??(e.yes?h.api:void 0);if(n&&i==="orpc"&&(j.fatal("oRPC API is not supported when using the 'native' frontend. Please use --api trpc or remove 'native' from --frontend."),process.exit(1)),n&&i!=="trpc"&&(!e.api||e.yes&&e.api!=="orpc")&&(t.api="trpc"),e.addons&&e.addons.length>0)if(e.addons.includes("none"))e.addons.length>1&&(j.fatal("Cannot combine 'none' with other addons."),process.exit(1)),t.addons=[];else{let s=e.addons.filter(y=>y!=="none"),c=["pwa","tauri"],p=s.some(y=>c.includes(y)),w=o?.some(y=>y==="tanstack-router"||y==="react-router");p&&!w&&(e.frontend?(j.fatal("PWA and Tauri addons require tanstack-router or react-router. Cannot use these addons with your frontend selection."),process.exit(1)):e.yes&&(j.fatal("PWA and Tauri addons require tanstack-router or react-router (default frontend incompatible)."),process.exit(1))),s.includes("husky")&&!s.includes("biome")&&s.push("biome"),t.addons=[...new Set(s)]}if(e.examples&&e.examples.length>0)if(e.examples.includes("none"))e.examples.length>1&&(j.fatal("Cannot combine 'none' with other examples."),process.exit(1)),t.examples=[];else{let s=e.examples.filter(w=>w!=="none"),c=t.backend??e.backend;s.includes("ai")&&c==="elysia"&&!(e.yes&&h.backend!=="elysia")&&(j.fatal("AI example is only compatible with Hono backend. Cannot use --examples ai with --backend elysia"),process.exit(1)),o?.some(w=>["tanstack-router","react-router","tanstack-start","next"].includes(w))||(e.frontend?(j.fatal("Examples require a web frontend (tanstack-router, react-router, tanstack-start, or next). Cannot use --examples with your frontend selection."),process.exit(1)):e.yes&&(j.fatal("Examples require a web frontend (tanstack-router, react-router, tanstack-start, or next) (default frontend incompatible)."),process.exit(1))),t.examples=s}return e.packageManager&&(t.packageManager=e.packageManager),e.git!==void 0&&(t.git=e.git),e.install!==void 0&&(t.install=e.install),r&&(t.projectName=r),t}Xo().catch(e=>{j.error("Aborting installation due to unexpected error..."),e instanceof Error?(j.error(e.message),console.error(e.stack)):(j.error("An unknown error has occurred. Please open an issue on GitHub with the below:"),console.error(e)),process.exit(1)});
|
package/package.json
CHANGED
|
@@ -70,13 +70,12 @@ export async function createContext({ context }: CreateContextOptions) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
{{else if (eq backend 'express')}}
|
|
73
|
-
import type { CreateExpressContextOptions } from "@trpc/server/adapters/express";
|
|
74
73
|
{{#if auth}}
|
|
75
74
|
import { fromNodeHeaders } from "better-auth/node";
|
|
76
75
|
import { auth } from "./auth";
|
|
77
76
|
{{/if}}
|
|
78
77
|
|
|
79
|
-
export async function createContext(opts:
|
|
78
|
+
export async function createContext(opts: any) {
|
|
80
79
|
{{#if auth}}
|
|
81
80
|
const session = await auth.api.getSession({
|
|
82
81
|
headers: fromNodeHeaders(opts.req.headers),
|
|
@@ -7,6 +7,9 @@ import { appRouter } from "./routers/index";
|
|
|
7
7
|
{{#if (eq api "orpc")}}
|
|
8
8
|
import { RPCHandler } from "@orpc/server/node";
|
|
9
9
|
import { appRouter } from "./routers";
|
|
10
|
+
{{#if auth}}
|
|
11
|
+
import { createContext } from "./lib/context";
|
|
12
|
+
{{/if}}
|
|
10
13
|
{{/if}}
|
|
11
14
|
import cors from "cors";
|
|
12
15
|
import express from "express";
|
|
@@ -16,6 +19,7 @@ import { google } from "@ai-sdk/google";
|
|
|
16
19
|
{{/if}}
|
|
17
20
|
{{#if auth}}
|
|
18
21
|
import { auth } from "./lib/auth";
|
|
22
|
+
import { toNodeHandler } from "better-auth/node";
|
|
19
23
|
{{/if}}
|
|
20
24
|
|
|
21
25
|
const app = express();
|
|
@@ -50,7 +54,11 @@ const handler = new RPCHandler(appRouter);
|
|
|
50
54
|
app.use('/rpc{*path}', async (req, res, next) => {
|
|
51
55
|
const { matched } = await handler.handle(req, res, {
|
|
52
56
|
prefix: '/rpc',
|
|
57
|
+
{{#if auth}}
|
|
58
|
+
context: await createContext({ req }),
|
|
59
|
+
{{else}}
|
|
53
60
|
context: {},
|
|
61
|
+
{{/if}}
|
|
54
62
|
});
|
|
55
63
|
if (matched) return;
|
|
56
64
|
next();
|
|
@@ -58,9 +66,8 @@ app.use('/rpc{*path}', async (req, res, next) => {
|
|
|
58
66
|
{{/if}}
|
|
59
67
|
|
|
60
68
|
{{#if (includes examples "ai")}}
|
|
61
|
-
// AI chat endpoint
|
|
62
69
|
app.post("/ai", async (req, res) => {
|
|
63
|
-
const { messages = [] } = req.body;
|
|
70
|
+
const { messages = [] } = req.body || {};
|
|
64
71
|
const result = streamText({
|
|
65
72
|
model: google("gemini-1.5-flash"),
|
|
66
73
|
messages,
|
|
@@ -73,6 +80,7 @@ app.get("/", (_req, res) => {
|
|
|
73
80
|
res.status(200).send("OK");
|
|
74
81
|
});
|
|
75
82
|
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
const port = process.env.PORT || 3000;
|
|
84
|
+
app.listen(port, () => {
|
|
85
|
+
console.log(`Server is running on port ${port}`);
|
|
78
86
|
});
|