create-better-t-stack 2.8.1 → 2.8.3

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/README.md CHANGED
@@ -2,11 +2,7 @@
2
2
 
3
3
  A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations
4
4
 
5
- ## Sponsors
6
-
7
- <p align="center">
8
- <img src="https://cdn.jsdelivr.net/gh/amanvarshney01/sponsors@master/sponsorkit/sponsors.svg" alt="Sponsors" width="300">
9
- </p>
5
+ ![demo](https://cdn.jsdelivr.net/gh/amanvarshney01/create-better-t-stack@master/demo.gif)
10
6
 
11
7
  ## Quick Start
12
8
 
@@ -27,35 +23,21 @@ Follow the prompts to configure your project or use the `--yes` flag for default
27
23
 
28
24
  ## Features
29
25
 
30
- - **TypeScript**: End-to-end type safety.
31
- - **Monorepo Structure**: Choose between Turborepo for optimized builds or standard pnpm/npm/bun workspaces.
32
- - **Frontend Options**:
33
- - React with Vite: TanStack Router, React Router, or TanStack Start.
34
- - Next.js (Full-stack or frontend-only).
35
- - Nuxt (Vue framework).
36
- - SvelteKit.
37
- - React Native with Expo for mobile apps.
38
- - None.
39
- - **UI**: Tailwind CSS with shadcn/ui components pre-configured.
40
- - **Backend Frameworks**: Choose between Hono, Express, Elysia, or use Next.js API routes.
41
- - **API Layer**: End-to-end type safety with tRPC or oRPC.
42
- - **Runtime Options**: Choose between Bun or Node.js for your server.
43
- - **Database Options**: SQLite, PostgreSQL, MySQL, MongoDB, or no database.
44
- - **ORM Selection**: Choose between Drizzle ORM (TypeScript-first), Prisma (feature-rich), or no ORM.
45
- - **Database Setup**: Optional automated setup for Turso (SQLite), Neon (Postgres), Prisma Postgres (Supabase), or MongoDB Atlas.
46
- - **Authentication**: Optional auth setup using Better-Auth (email/password, OAuth coming soon).
47
- - **Addons**:
48
- - **PWA**: Add Progressive Web App support.
49
- - **Tauri**: Build native desktop applications.
50
- - **Starlight**: Add an Astro-based documentation site.
51
- - **Biome**: Integrated linting and formatting.
52
- - **Husky**: Git hooks for code quality checks (lint-staged).
53
- - **Turborepo**: Optimized monorepo build system.
54
- - **Examples**: Include pre-built examples like a Todo app or an AI Chat interface (using Vercel AI SDK).
55
- - **Developer Experience**:
56
- - Automatic Git initialization.
57
- - Choice of package manager (npm, pnpm, bun).
58
- - Optional automatic dependency installation.
26
+ | Category | Options |
27
+ |----------|---------|
28
+ | **TypeScript** | End-to-end type safety across all parts of your application |
29
+ | **Frontend** | • React with TanStack Router<br>• React with React Router<br>• React with TanStack Start (SSR)<br>• Next.js<br>• SvelteKit<br>• Nuxt (Vue)<br>• SolidJS<br>• React Native with Expo<br>• None |
30
+ | **Backend** | • Hono<br>• Express<br>• Elysia<br>• Next.js API routes<br>• Convex |
31
+ | **API Layer** | • tRPC (type-safe APIs)<br>• oRPC (OpenAPI-compatible type-safe APIs) |
32
+ | **Runtime** | • Bun<br>• Node.js |
33
+ | **Database** | SQLite<br>• PostgreSQL<br>• MySQL<br>• MongoDB<br>• None |
34
+ | **ORM** | • Drizzle (TypeScript-first)<br>• Prisma (feature-rich)<br>• Mongoose (for MongoDB)<br>• None |
35
+ | **Database Setup** | Turso (SQLite)<br>• Neon (PostgreSQL)<br>• Prisma Postgres (via Prisma Accelerate)<br>• MongoDB Atlas<br>• None (manual setup) |
36
+ | **Authentication** | Better-Auth (email/password, with more options coming soon) |
37
+ | **Styling** | Tailwind CSS with shadcn/ui components |
38
+ | **Addons** | PWA support<br>• Tauri (desktop applications)<br>• Starlight (documentation site)<br>• Biome (linting and formatting)<br>• Husky (Git hooks)<br>• Turborepo (optimized builds) |
39
+ | **Examples** | Todo app<br>• AI Chat interface (using Vercel AI SDK) |
40
+ | **Developer Experience** | Automatic Git initialization<br>• Package manager choice (npm, pnpm, bun)<br>• Automatic dependency installation |
59
41
 
60
42
  ## Usage
61
43
 
@@ -66,10 +48,10 @@ Options:
66
48
  -V, --version Output the version number
67
49
  -y, --yes Use default configuration
68
50
  --database <type> Database type (none, sqlite, postgres, mysql, mongodb)
69
- --orm <type> ORM type (none, drizzle, prisma)
51
+ --orm <type> ORM type (none, drizzle, prisma, mongoose)
70
52
  --auth Include authentication
71
53
  --no-auth Exclude authentication
72
- --frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, native, none)
54
+ --frontend <types...> Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native, none)
73
55
  --addons <types...> Additional addons (pwa, tauri, starlight, biome, husky, turborepo, none)
74
56
  --examples <types...> Examples to include (todo, ai, none)
75
57
  --git Initialize git repository
@@ -78,9 +60,9 @@ Options:
78
60
  --install Install dependencies
79
61
  --no-install Skip installing dependencies
80
62
  --db-setup <setup> Database setup (turso, neon, prisma-postgres, mongodb-atlas, none)
81
- --backend <framework> Backend framework (hono, express, elysia)
82
- --runtime <runtime> Runtime (bun, node)
83
- --api <type> API type (trpc, orpc)
63
+ --backend <framework> Backend framework (hono, express, elysia, next, convex)
64
+ --runtime <runtime> Runtime (bun, node, none)
65
+ --api <type> API type (trpc, orpc, none)
84
66
  -h, --help Display help
85
67
  ```
86
68
 
@@ -98,13 +80,13 @@ Create a project with specific options:
98
80
  npx create-better-t-stack my-app --database postgres --orm drizzle --auth --addons pwa biome
99
81
  ```
100
82
 
101
- Create a project with Elysia and Node.js runtime:
83
+ Create a project with Elysia backend and Node.js runtime:
102
84
 
103
85
  ```bash
104
86
  npx create-better-t-stack my-app --backend elysia --runtime node
105
87
  ```
106
88
 
107
- Create a project with specific frontend options:
89
+ Create a project with multiple frontend options:
108
90
 
109
91
  ```bash
110
92
  npx create-better-t-stack my-app --frontend tanstack-router native
@@ -119,7 +101,13 @@ npx create-better-t-stack my-app --examples todo ai
119
101
  Create a project with Turso database setup:
120
102
 
121
103
  ```bash
122
- npx create-better-t-stack my-app --db-setup turso
104
+ npx create-better-t-stack my-app --database sqlite --orm drizzle --db-setup turso
105
+ ```
106
+
107
+ Create a project with Convex backend:
108
+
109
+ ```bash
110
+ npx create-better-t-stack my-app --backend convex --frontend tanstack-router
123
111
  ```
124
112
 
125
113
  Create a project with documentation site:
@@ -127,3 +115,34 @@ Create a project with documentation site:
127
115
  ```bash
128
116
  npx create-better-t-stack my-app --addons starlight
129
117
  ```
118
+
119
+ ## Compatibility Notes
120
+
121
+ - **Convex backend**: Automatically disables authentication, database, ORM, and API options
122
+ - **SvelteKit, Nuxt, and SolidJS** frontends are only compatible with oRPC API layer
123
+ - **PWA support** requires React with TanStack Router, React Router, or SolidJS
124
+ - **Tauri desktop app** requires React (TanStack Router/React Router), Nuxt, SvelteKit, or SolidJS
125
+ - **AI example** is not compatible with Elysia backend or SolidJS frontend
126
+
127
+ ## Project Structure
128
+
129
+ The created project follows a clean monorepo structure:
130
+
131
+ ```
132
+ my-better-t-app/
133
+ ├── apps/
134
+ │ ├── web/ # Frontend application
135
+ │ ├── server/ # Backend API
136
+ │ ├── native/ # (optional) Mobile application
137
+ │ └── docs/ # (optional) Documentation site
138
+ ├── packages/ # Shared packages
139
+ └── README.md # Auto-generated project documentation
140
+ ```
141
+
142
+ After project creation, you'll receive detailed instructions for next steps and additional setup requirements.
143
+
144
+ ## Sponsors
145
+
146
+ <p align="center">
147
+ <img src="https://cdn.jsdelivr.net/gh/amanvarshney01/sponsors@master/sponsorkit/sponsors.svg" alt="Sponsors" width="300">
148
+ </p>
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import e from"node:path";import{cancel as t,confirm as n,group as r,intro as i,isCancel as a,log as o,multiselect as s,outro as c,password as l,select as u,spinner as d,text as f}from"@clack/prompts";import p,{consola as m}from"consola";import h from"fs-extra";import g from"picocolors";import _ from"yargs";import{hideBin as v}from"yargs/helpers";import{fileURLToPath as y}from"node:url";import{$ as b,execa as x}from"execa";import S from"node:os";import{globby as C}from"globby";import w from"handlebars";import T from"gradient-string";const E=()=>{let e=process.env.npm_config_user_agent;return e?.startsWith(`pnpm`)?`pnpm`:e?.startsWith(`bun`)?`bun`:`npm`},D=y(import.meta.url),O=e.dirname(D),k=e.join(O,`../`),A={projectName:`my-better-t-app`,projectDir:e.resolve(process.cwd(),`my-better-t-app`),relativePath:`my-better-t-app`,frontend:[`tanstack-router`],database:`sqlite`,orm:`drizzle`,auth:!0,addons:[],examples:[],git:!0,packageManager:E(),install:!0,dbSetup:`none`,backend:`hono`,runtime:`bun`,api:`trpc`},j={"better-auth":`^1.2.7`,"@better-auth/expo":`^1.2.7`,"drizzle-orm":`^0.38.4`,"drizzle-kit":`^0.30.5`,"@libsql/client":`^0.14.0`,pg:`^8.14.1`,"@types/pg":`^8.11.11`,mysql2:`^3.14.0`,"@prisma/client":`^6.7.0`,prisma:`^6.7.0`,mongoose:`^8.14.0`,"vite-plugin-pwa":`^0.21.2`,"@vite-pwa/assets-generator":`^0.2.6`,"@tauri-apps/cli":`^2.4.0`,"@biomejs/biome":`1.9.4`,husky:`^9.1.7`,"lint-staged":`^15.5.0`,"@hono/node-server":`^1.14.0`,tsx:`^4.19.2`,"@types/node":`^22.13.11`,"@types/bun":`^1.2.6`,"@elysiajs/node":`^1.2.6`,"@elysiajs/cors":`^1.2.0`,"@elysiajs/trpc":`^1.1.0`,elysia:`^1.2.25`,"@hono/trpc-server":`^0.3.4`,hono:`^4.7.6`,cors:`^2.8.5`,express:`^5.1.0`,"@types/express":`^5.0.1`,"@types/cors":`^2.8.17`,turbo:`^2.4.2`,ai:`^4.2.8`,"@ai-sdk/google":`^1.2.3`,"@ai-sdk/vue":`^1.2.8`,"@ai-sdk/svelte":`^2.1.9`,"@prisma/extension-accelerate":`^1.3.0`,"@orpc/server":`^1.1.1`,"@orpc/client":`^1.1.1`,"@orpc/react-query":`^1.1.1`,"@orpc/solid-query":`^1.1.1`,"@orpc/vue-query":`^1.1.1`,"@orpc/svelte-query":`^1.1.1`,"@trpc/tanstack-react-query":`^11.0.0`,"@trpc/server":`^11.0.0`,"@trpc/client":`^11.0.0`,convex:`^1.23.0`,"@convex-dev/react-query":`^0.0.0-alpha.8`,"convex-svelte":`^0.0.11`,"@tanstack/svelte-query":`^5.74.4`,"@tanstack/react-query-devtools":`^5.69.0`,"@tanstack/react-query":`^5.69.0`,"@tanstack/solid-query":`^5.75.0`,"@tanstack/solid-query-devtools":`^5.75.0`},M=async t=>{let{dependencies:n=[],devDependencies:r=[],projectDir:i}=t,a=e.join(i,`package.json`),o=await h.readJson(a);o.dependencies||={},o.devDependencies||={};for(let e of n){let t=j[e];t?o.dependencies[e]=t:console.warn(`Warning: Dependency ${e} not found in version map.`)}for(let e of r){let t=j[e];t?o.devDependencies[e]=t:console.warn(`Warning: Dev dependency ${e} not found in version map.`)}await h.writeJson(a,o,{spaces:2})};function N(e,t){switch(e){case`pnpm`:return`pnpm dlx ${t}`;case`bun`:return`bunx ${t}`;default:return`npx ${t}`}}async function ee(t){let{projectName:n,packageManager:r,projectDir:i}=t,a=d();try{a.start(`Setting up Starlight docs...`);let t=[`docs`,`--template`,`starlight`,`--no-install`,`--add`,`tailwind`,`--no-git`,`--skip-houston`],n=t.join(` `),o=`create-astro@latest ${n}`,s=N(r,o);await x(s,{cwd:e.join(i,`apps`),env:{CI:`true`},shell:!0}),a.stop(`Starlight docs setup successfully!`)}catch(e){a.stop(g.red(`Failed to set up Starlight docs`)),e instanceof Error&&p.error(g.red(e.message))}}async function te(t){let{projectName:n,packageManager:r,frontend:i,projectDir:a}=t,o=d(),s=e.join(a,`apps/web`);if(await h.pathExists(s))try{o.start(`Setting up Tauri desktop app support...`),await M({devDependencies:[`@tauri-apps/cli`],projectDir:s});let t=e.join(s,`package.json`);if(await h.pathExists(t)){let e=await h.readJson(t);e.scripts={...e.scripts,tauri:`tauri`,"desktop:dev":`tauri dev`,"desktop:build":`tauri build`},await h.writeJson(t,e,{spaces:2})}let n=i.includes(`tanstack-router`),c=i.includes(`react-router`),l=i.includes(`nuxt`),u=i.includes(`svelte`),d=i.includes(`solid`),f=c||u?`http://localhost:5173`:`http://localhost:3001`,p=l?`../.output/public`:u?`../build`:`../dist`,m=[`init`,`--app-name=${e.basename(a)}`,`--window-title=${e.basename(a)}`,`--frontend-dist=${p}`,`--dev-url=${f}`,`--before-dev-command=\"${r} run dev\"`,`--before-build-command=\"${r} run build\"`],g=m.join(` `),_=`@tauri-apps/cli@latest ${g}`,v=N(r,_);await x(v,{cwd:s,env:{CI:`true`},shell:!0}),o.stop(`Tauri desktop app support configured successfully!`)}catch(e){o.stop(g.red(`Failed to set up Tauri`)),e instanceof Error&&m.error(g.red(e.message))}}async function ne(e){let{projectName:t,addons:n,frontend:r,projectDir:i}=e,a=r.includes(`react-router`)||r.includes(`tanstack-router`),o=r.includes(`nuxt`),s=r.includes(`svelte`),c=r.includes(`solid`);n.includes(`turborepo`)&&await M({devDependencies:[`turbo`],projectDir:i}),n.includes(`pwa`)&&(a||c)&&await oe(i,r),n.includes(`tauri`)&&(a||o||s||c)&&await te(e),n.includes(`biome`)&&await ie(i),n.includes(`husky`)&&await ae(i),n.includes(`starlight`)&&await ee(e)}function re(t,n){return n.some(e=>[`react-router`,`tanstack-router`,`nuxt`,`svelte`,`solid`].includes(e)),e.join(t,`apps/web`)}async function ie(t){await M({devDependencies:[`@biomejs/biome`],projectDir:t});let n=e.join(t,`package.json`);if(await h.pathExists(n)){let e=await h.readJson(n);e.scripts={...e.scripts,check:`biome check --write .`},await h.writeJson(n,e,{spaces:2})}}async function ae(t){await M({devDependencies:[`husky`,`lint-staged`],projectDir:t});let n=e.join(t,`package.json`);if(await h.pathExists(n)){let e=await h.readJson(n);e.scripts={...e.scripts,prepare:`husky`},e[`lint-staged`]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":[`biome check --write .`]},await h.writeJson(n,e,{spaces:2})}}async function oe(t,n){let r=n.some(e=>[`react-router`,`tanstack-router`,`solid`].includes(e));if(!r)return;let i=re(t,n);if(!await h.pathExists(i))return;await M({dependencies:[`vite-plugin-pwa`],devDependencies:[`@vite-pwa/assets-generator`],projectDir:i});let a=e.join(i,`package.json`);if(await h.pathExists(a)){let e=await h.readJson(a);e.scripts={...e.scripts,"generate-pwa-assets":`pwa-assets-generator`},await h.writeJson(a,e,{spaces:2})}}async function se(t){let{api:n,projectName:r,frontend:i,backend:a,packageManager:o,projectDir:s}=t,c=a===`convex`,l=e.join(s,`apps/web`),u=e.join(s,`apps/native`),d=await h.pathExists(l),f=await h.pathExists(u),p=i.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),m=i.includes(`nuxt`),g=i.includes(`svelte`),_=i.includes(`solid`);if(!c&&n!==`none`){let r=e.join(s,`apps/server`),i=await h.pathExists(r);i&&(n===`orpc`?await M({dependencies:[`@orpc/server`,`@orpc/client`],projectDir:r}):n===`trpc`&&(await M({dependencies:[`@trpc/server`,`@trpc/client`],projectDir:r}),t.backend===`hono`?await M({dependencies:[`@hono/trpc-server`],projectDir:r}):t.backend===`elysia`&&await M({dependencies:[`@elysiajs/trpc`],projectDir:r}))),d&&(p?n===`orpc`?await M({dependencies:[`@orpc/react-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):n===`trpc`&&await M({dependencies:[`@trpc/tanstack-react-query`,`@trpc/client`,`@trpc/server`],projectDir:l}):m?n===`orpc`&&await M({dependencies:[`@orpc/vue-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):g?n===`orpc`&&await M({dependencies:[`@orpc/svelte-query`,`@orpc/client`,`@orpc/server`,`@tanstack/svelte-query`],projectDir:l}):_&&n===`orpc`&&await M({dependencies:[`@orpc/solid-query`,`@orpc/client`,`@orpc/server`,`@tanstack/solid-query`],projectDir:l})),f&&(n===`trpc`?await M({dependencies:[`@trpc/tanstack-react-query`,`@trpc/client`,`@trpc/server`],projectDir:u}):n===`orpc`&&await M({dependencies:[`@orpc/react-query`,`@orpc/client`,`@orpc/server`],projectDir:u}))}let v=[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`native`],y=i.includes(`solid`),b=i.some(e=>v.includes(e));if(b&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native`&&v.includes(e)),a=i.includes(`native`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(y&&!c){let t=[`@tanstack/solid-query`],n=[`@tanstack/solid-query-devtools`];if(d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}}if(c){if(d){let t=e.join(l,`package.json`);if(await h.pathExists(t))try{let e=[`convex`];i.includes(`tanstack-start`)&&e.push(`@convex-dev/react-query`),g&&e.push(`convex-svelte`),await M({dependencies:e,projectDir:l})}catch{}}if(f){let t=e.join(u,`package.json`);if(await h.pathExists(t))try{await M({dependencies:[`convex`],projectDir:u})}catch{}}let t=`@${r}/backend`,n=o===`npm`?`*`:`workspace:*`,a=async(e,t,n)=>{try{let r=await h.readJson(e);r.dependencies||={},r.dependencies[t]!==n&&(r.dependencies[t]=n,await h.writeJson(e,r,{spaces:2}))}catch{}};if(d){let r=e.join(l,`package.json`);await h.pathExists(r)&&await a(r,t,n)}if(f){let r=e.join(u,`package.json`);await h.pathExists(r)&&await a(r,t,n)}}}async function ce(t){let{projectName:n,auth:r,frontend:i,backend:a,projectDir:o}=t;if(a===`convex`||!r)return;let s=e.join(o,`apps/server`),c=e.join(o,`apps/web`),l=e.join(o,`apps/native`),u=await h.pathExists(c),d=await h.pathExists(l),f=await h.pathExists(s);try{f&&await M({dependencies:[`better-auth`],projectDir:s});let e=i.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].includes(e));e&&u&&await M({dependencies:[`better-auth`],projectDir:c}),i.includes(`native`)&&d&&(await M({dependencies:[`better-auth`,`@better-auth/expo`],projectDir:l}),f&&await M({dependencies:[`@better-auth/expo`],projectDir:s}))}catch(e){p.error(g.red(`Failed to configure authentication dependencies`)),e instanceof Error&&p.error(g.red(e.message))}}function le(e=32){let t=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`,n=``,r=t.length;for(let i=0;i<e;i++)n+=t.charAt(Math.floor(Math.random()*r));return n}async function ue(t){let{projectName:n,backend:r,runtime:i,api:a,projectDir:o}=t;if(r===`convex`)return;let s=r,c=e.join(o,`apps/server`),l=[],u=[];s===`hono`?(l.push(`hono`),a===`trpc`&&l.push(`@hono/trpc-server`),i===`node`&&(l.push(`@hono/node-server`),u.push(`tsx`,`@types/node`))):s===`elysia`?(l.push(`elysia`,`@elysiajs/cors`),a===`trpc`&&l.push(`@elysiajs/trpc`),i===`node`&&(l.push(`@elysiajs/node`),u.push(`tsx`,`@types/node`))):s===`express`&&(l.push(`express`,`cors`),u.push(`@types/express`,`@types/cors`),i===`node`&&u.push(`tsx`,`@types/node`)),i===`bun`&&u.push(`@types/bun`),(l.length>0||u.length>0)&&await M({dependencies:l,devDependencies:u,projectDir:c})}async function de(t,n){let r=e.join(t,`README.md`),i=fe(n);try{await h.writeFile(r,i)}catch(e){p.error(`Failed to create README.md file:`,e)}}function fe(e){let{projectName:t,packageManager:n,database:r,auth:i,addons:a=[],orm:o=`drizzle`,runtime:s=`bun`,frontend:c=[`tanstack-router`],backend:l=`hono`}=e,u=c.includes(`react-router`),d=c.includes(`tanstack-router`),f=c.includes(`native`),p=c.includes(`next`),m=c.includes(`tanstack-start`),h=c.includes(`svelte`),g=c.includes(`solid`),_=c.includes(`nuxt`),v=n===`npm`?`npm run`:n,y=`3001`;return(u||h)&&(y=`5173`),`# ${t}
2
+ import e from"node:path";import{cancel as t,confirm as n,group as r,intro as i,isCancel as a,log as o,multiselect as s,outro as c,password as l,select as u,spinner as d,text as f}from"@clack/prompts";import p,{consola as m}from"consola";import h from"fs-extra";import g from"picocolors";import _ from"yargs";import{hideBin as v}from"yargs/helpers";import{fileURLToPath as y}from"node:url";import{$ as b,execa as x}from"execa";import S from"node:os";import{globby as C}from"globby";import w from"handlebars";import T from"gradient-string";const E=()=>{let e=process.env.npm_config_user_agent;return e?.startsWith(`pnpm`)?`pnpm`:e?.startsWith(`bun`)?`bun`:`npm`},D=y(import.meta.url),O=e.dirname(D),k=e.join(O,`../`),A={projectName:`my-better-t-app`,projectDir:e.resolve(process.cwd(),`my-better-t-app`),relativePath:`my-better-t-app`,frontend:[`tanstack-router`],database:`sqlite`,orm:`drizzle`,auth:!0,addons:[],examples:[],git:!0,packageManager:E(),install:!0,dbSetup:`none`,backend:`hono`,runtime:`bun`,api:`trpc`},j={"better-auth":`^1.2.7`,"@better-auth/expo":`^1.2.7`,"drizzle-orm":`^0.38.4`,"drizzle-kit":`^0.30.5`,"@libsql/client":`^0.14.0`,pg:`^8.14.1`,"@types/pg":`^8.11.11`,mysql2:`^3.14.0`,"@prisma/client":`^6.7.0`,prisma:`^6.7.0`,mongoose:`^8.14.0`,"vite-plugin-pwa":`^0.21.2`,"@vite-pwa/assets-generator":`^0.2.6`,"@tauri-apps/cli":`^2.4.0`,"@biomejs/biome":`1.9.4`,husky:`^9.1.7`,"lint-staged":`^15.5.0`,"@hono/node-server":`^1.14.0`,tsx:`^4.19.2`,"@types/node":`^22.13.11`,"@types/bun":`^1.2.6`,"@elysiajs/node":`^1.2.6`,"@elysiajs/cors":`^1.2.0`,"@elysiajs/trpc":`^1.1.0`,elysia:`^1.2.25`,"@hono/trpc-server":`^0.3.4`,hono:`^4.7.6`,cors:`^2.8.5`,express:`^5.1.0`,"@types/express":`^5.0.1`,"@types/cors":`^2.8.17`,turbo:`^2.4.2`,ai:`^4.2.8`,"@ai-sdk/google":`^1.2.3`,"@ai-sdk/vue":`^1.2.8`,"@ai-sdk/svelte":`^2.1.9`,"@prisma/extension-accelerate":`^1.3.0`,"@orpc/server":`^1.1.1`,"@orpc/client":`^1.1.1`,"@orpc/react-query":`^1.1.1`,"@orpc/solid-query":`^1.1.1`,"@orpc/vue-query":`^1.1.1`,"@orpc/svelte-query":`^1.1.1`,"@trpc/tanstack-react-query":`^11.0.0`,"@trpc/server":`^11.0.0`,"@trpc/client":`^11.0.0`,convex:`^1.23.0`,"@convex-dev/react-query":`^0.0.0-alpha.8`,"convex-svelte":`^0.0.11`,"@tanstack/svelte-query":`^5.74.4`,"@tanstack/react-query-devtools":`^5.69.0`,"@tanstack/react-query":`^5.69.0`,"@tanstack/solid-query":`^5.75.0`,"@tanstack/solid-query-devtools":`^5.75.0`},M=async t=>{let{dependencies:n=[],devDependencies:r=[],projectDir:i}=t,a=e.join(i,`package.json`),o=await h.readJson(a);o.dependencies||={},o.devDependencies||={};for(let e of n){let t=j[e];t?o.dependencies[e]=t:console.warn(`Warning: Dependency ${e} not found in version map.`)}for(let e of r){let t=j[e];t?o.devDependencies[e]=t:console.warn(`Warning: Dev dependency ${e} not found in version map.`)}await h.writeJson(a,o,{spaces:2})};function N(e,t){switch(e){case`pnpm`:return`pnpm dlx ${t}`;case`bun`:return`bunx ${t}`;default:return`npx ${t}`}}async function ee(t){let{projectName:n,packageManager:r,projectDir:i}=t,a=d();try{a.start(`Setting up Starlight docs...`);let t=[`docs`,`--template`,`starlight`,`--no-install`,`--add`,`tailwind`,`--no-git`,`--skip-houston`],n=t.join(` `),o=`create-astro@latest ${n}`,s=N(r,o);await x(s,{cwd:e.join(i,`apps`),env:{CI:`true`},shell:!0}),a.stop(`Starlight docs setup successfully!`)}catch(e){a.stop(g.red(`Failed to set up Starlight docs`)),e instanceof Error&&p.error(g.red(e.message))}}async function te(t){let{projectName:n,packageManager:r,frontend:i,projectDir:a}=t,o=d(),s=e.join(a,`apps/web`);if(await h.pathExists(s))try{o.start(`Setting up Tauri desktop app support...`),await M({devDependencies:[`@tauri-apps/cli`],projectDir:s});let t=e.join(s,`package.json`);if(await h.pathExists(t)){let e=await h.readJson(t);e.scripts={...e.scripts,tauri:`tauri`,"desktop:dev":`tauri dev`,"desktop:build":`tauri build`},await h.writeJson(t,e,{spaces:2})}let n=i.includes(`tanstack-router`),c=i.includes(`react-router`),l=i.includes(`nuxt`),u=i.includes(`svelte`),d=i.includes(`solid`),f=c||u?`http://localhost:5173`:`http://localhost:3001`,p=l?`../.output/public`:u?`../build`:`../dist`,m=[`init`,`--app-name=${e.basename(a)}`,`--window-title=${e.basename(a)}`,`--frontend-dist=${p}`,`--dev-url=${f}`,`--before-dev-command=\"${r} run dev\"`,`--before-build-command=\"${r} run build\"`],g=m.join(` `),_=`@tauri-apps/cli@latest ${g}`,v=N(r,_);await x(v,{cwd:s,env:{CI:`true`},shell:!0}),o.stop(`Tauri desktop app support configured successfully!`)}catch(e){o.stop(g.red(`Failed to set up Tauri`)),e instanceof Error&&m.error(g.red(e.message))}}async function ne(e){let{projectName:t,addons:n,frontend:r,projectDir:i}=e,a=r.includes(`react-router`)||r.includes(`tanstack-router`),o=r.includes(`nuxt`),s=r.includes(`svelte`),c=r.includes(`solid`);n.includes(`turborepo`)&&await M({devDependencies:[`turbo`],projectDir:i}),n.includes(`pwa`)&&(a||c)&&await oe(i,r),n.includes(`tauri`)&&(a||o||s||c)&&await te(e),n.includes(`biome`)&&await ie(i),n.includes(`husky`)&&await ae(i),n.includes(`starlight`)&&await ee(e)}function re(t,n){return n.some(e=>[`react-router`,`tanstack-router`,`nuxt`,`svelte`,`solid`].includes(e)),e.join(t,`apps/web`)}async function ie(t){await M({devDependencies:[`@biomejs/biome`],projectDir:t});let n=e.join(t,`package.json`);if(await h.pathExists(n)){let e=await h.readJson(n);e.scripts={...e.scripts,check:`biome check --write .`},await h.writeJson(n,e,{spaces:2})}}async function ae(t){await M({devDependencies:[`husky`,`lint-staged`],projectDir:t});let n=e.join(t,`package.json`);if(await h.pathExists(n)){let e=await h.readJson(n);e.scripts={...e.scripts,prepare:`husky`},e[`lint-staged`]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":[`biome check --write .`]},await h.writeJson(n,e,{spaces:2})}}async function oe(t,n){let r=n.some(e=>[`react-router`,`tanstack-router`,`solid`].includes(e));if(!r)return;let i=re(t,n);if(!await h.pathExists(i))return;await M({dependencies:[`vite-plugin-pwa`],devDependencies:[`@vite-pwa/assets-generator`],projectDir:i});let a=e.join(i,`package.json`);if(await h.pathExists(a)){let e=await h.readJson(a);e.scripts={...e.scripts,"generate-pwa-assets":`pwa-assets-generator`},await h.writeJson(a,e,{spaces:2})}}async function se(t){let{api:n,projectName:r,frontend:i,backend:a,packageManager:o,projectDir:s}=t,c=a===`convex`,l=e.join(s,`apps/web`),u=e.join(s,`apps/native`),d=await h.pathExists(l),f=await h.pathExists(u),p=i.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),m=i.includes(`nuxt`),g=i.includes(`svelte`),_=i.includes(`solid`);if(!c&&n!==`none`){let r=e.join(s,`apps/server`),i=await h.pathExists(r);i&&(n===`orpc`?await M({dependencies:[`@orpc/server`,`@orpc/client`],projectDir:r}):n===`trpc`&&(await M({dependencies:[`@trpc/server`,`@trpc/client`],projectDir:r}),t.backend===`hono`?await M({dependencies:[`@hono/trpc-server`],projectDir:r}):t.backend===`elysia`&&await M({dependencies:[`@elysiajs/trpc`],projectDir:r}))),d&&(p?n===`orpc`?await M({dependencies:[`@orpc/react-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):n===`trpc`&&await M({dependencies:[`@trpc/tanstack-react-query`,`@trpc/client`,`@trpc/server`],projectDir:l}):m?n===`orpc`&&await M({dependencies:[`@orpc/vue-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):g?n===`orpc`&&await M({dependencies:[`@orpc/svelte-query`,`@orpc/client`,`@orpc/server`,`@tanstack/svelte-query`],projectDir:l}):_&&n===`orpc`&&await M({dependencies:[`@orpc/solid-query`,`@orpc/client`,`@orpc/server`,`@tanstack/solid-query`],projectDir:l})),f&&(n===`trpc`?await M({dependencies:[`@trpc/tanstack-react-query`,`@trpc/client`,`@trpc/server`],projectDir:u}):n===`orpc`&&await M({dependencies:[`@orpc/react-query`,`@orpc/client`,`@orpc/server`],projectDir:u}))}let v=[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`native`],y=i.includes(`solid`),b=i.some(e=>v.includes(e));if(b&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native`&&v.includes(e)),a=i.includes(`native`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(y&&!c){let t=[`@tanstack/solid-query`],n=[`@tanstack/solid-query-devtools`];if(d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}}if(c){if(d){let t=e.join(l,`package.json`);if(await h.pathExists(t))try{let e=[`convex`];i.includes(`tanstack-start`)&&e.push(`@convex-dev/react-query`),g&&e.push(`convex-svelte`),await M({dependencies:e,projectDir:l})}catch{}}if(f){let t=e.join(u,`package.json`);if(await h.pathExists(t))try{await M({dependencies:[`convex`],projectDir:u})}catch{}}let t=`@${r}/backend`,n=o===`npm`?`*`:`workspace:*`,a=async(e,t,n)=>{try{let r=await h.readJson(e);r.dependencies||={},r.dependencies[t]!==n&&(r.dependencies[t]=n,await h.writeJson(e,r,{spaces:2}))}catch{}};if(d){let r=e.join(l,`package.json`);await h.pathExists(r)&&await a(r,t,n)}if(f){let r=e.join(u,`package.json`);await h.pathExists(r)&&await a(r,t,n)}}}async function ce(t){let{projectName:n,auth:r,frontend:i,backend:a,projectDir:o}=t;if(a===`convex`||!r)return;let s=e.join(o,`apps/server`),c=e.join(o,`apps/web`),l=e.join(o,`apps/native`),u=await h.pathExists(c),d=await h.pathExists(l),f=await h.pathExists(s);try{f&&await M({dependencies:[`better-auth`],projectDir:s});let e=i.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].includes(e));e&&u&&await M({dependencies:[`better-auth`],projectDir:c}),i.includes(`native`)&&d&&(await M({dependencies:[`better-auth`,`@better-auth/expo`],projectDir:l}),f&&await M({dependencies:[`@better-auth/expo`],projectDir:s}))}catch(e){p.error(g.red(`Failed to configure authentication dependencies`)),e instanceof Error&&p.error(g.red(e.message))}}function le(e=32){let t=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`,n=``,r=t.length;for(let i=0;i<e;i++)n+=t.charAt(Math.floor(Math.random()*r));return n}async function ue(t){let{projectName:n,backend:r,runtime:i,api:a,projectDir:o}=t;if(r===`convex`)return;let s=r,c=e.join(o,`apps/server`),l=[],u=[];s===`hono`?(l.push(`hono`),a===`trpc`&&l.push(`@hono/trpc-server`),i===`node`&&(l.push(`@hono/node-server`),u.push(`tsx`,`@types/node`))):s===`elysia`?(l.push(`elysia`,`@elysiajs/cors`),a===`trpc`&&l.push(`@elysiajs/trpc`),i===`node`&&(l.push(`@elysiajs/node`),u.push(`tsx`,`@types/node`))):s===`express`&&(l.push(`express`,`cors`),u.push(`@types/express`,`@types/cors`),i===`node`&&u.push(`tsx`,`@types/node`)),i===`bun`&&u.push(`@types/bun`),(l.length>0||u.length>0)&&await M({dependencies:l,devDependencies:u,projectDir:c})}async function de(t,n){let r=e.join(t,`README.md`),i=fe(n);try{await h.writeFile(r,i)}catch(e){p.error(`Failed to create README.md file:`,e)}}function fe(e){let{projectName:t,packageManager:n,database:r,auth:i,addons:a=[],orm:o=`drizzle`,runtime:s=`bun`,frontend:c=[`tanstack-router`],backend:l=`hono`,api:u=`trpc`}=e,d=l===`convex`,f=c.includes(`react-router`),p=c.includes(`tanstack-router`),m=c.includes(`native`),h=c.includes(`next`),g=c.includes(`tanstack-start`),_=c.includes(`svelte`),v=c.includes(`solid`),y=c.includes(`nuxt`),b=n===`npm`?`npm run`:n,x=`3001`;return(f||_)&&(x=`5173`),`# ${t}
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, ${d?`TanStack Router`:u?`React Router`:p?`Next.js`:m?`TanStack Start`:h?`SvelteKit`:_?`Nuxt`:g?`SolidJS`:``}, ${l[0].toUpperCase()+l.slice(1)}, tRPC, and more.
4
+ This project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack that combines ${p?`React, TanStack Router`:f?`React, React Router`:h?`Next.js`:g?`React, TanStack Start`:_?`SvelteKit`:y?`Nuxt`:v?`SolidJS`:``}, ${l[0].toUpperCase()+l.slice(1)}${d?``:`, ${u.toUpperCase()}`}, and more.
5
5
 
6
6
  ## Features
7
7
 
8
- ${pe(r,i,a,o,s,c,l)}
8
+ ${pe(r,i,a,o,s,c,l,u)}
9
9
 
10
10
  ## Getting Started
11
11
 
@@ -14,21 +14,29 @@ First, install the dependencies:
14
14
  \`\`\`bash
15
15
  ${n} install
16
16
  \`\`\`
17
+ ${d?`
18
+ ## Convex Setup
17
19
 
18
- ${me(r,i,v,o)}
20
+ This project uses Convex as a backend. You'll need to set up Convex before running the app:
21
+
22
+ \`\`\`bash
23
+ ${b} dev:setup
24
+ \`\`\`
25
+
26
+ Follow the prompts to create a new Convex project and connect it to your application.`:me(r,i,b,o)}
19
27
 
20
28
  Then, run the development server:
21
29
 
22
30
  \`\`\`bash
23
- ${v} dev
31
+ ${b} dev
24
32
  \`\`\`
25
33
 
26
- ${d||u||p||m||h||_||g?`Open [http://localhost:${y}](http://localhost:${y}) in your browser to see the web application.`:``}
27
- ${f?`Use the Expo Go app to run the mobile application.
34
+ ${p||f||h||g||_||y||v?`Open [http://localhost:${x}](http://localhost:${x}) in your browser to see the web application.`:``}
35
+ ${m?`Use the Expo Go app to run the mobile application.
28
36
  `:``}
29
- The API is running at [http://localhost:3000](http://localhost:3000).
37
+ ${d?`Your app will connect to the Convex cloud backend automatically.`:`The API is running at [http://localhost:3000](http://localhost:3000).`}
30
38
 
31
- ${a.includes(`pwa`)&&u?`
39
+ ${a.includes(`pwa`)&&f?`
32
40
  ## PWA Support with React Router v7
33
41
 
34
42
  There is a known compatibility issue between VitePWA and React Router v7.
@@ -40,15 +48,17 @@ See: https://github.com/vite-pwa/vite-plugin-pwa/issues/809
40
48
  \`\`\`
41
49
  ${t}/
42
50
  ├── apps/
43
- ${d||u||p||m||h||_||g?`│ ├── web/ # Frontend application (${d?`React + TanStack Router`:u?`React + React Router`:p?`Next.js`:m?`React + TanStack Start`:h?`SvelteKit`:_?`Nuxt`:g?`SolidJS`:``})\n`:``}${f?`│ ├── native/ # Mobile application (React Native, Expo)
51
+ ${p||f||h||g||_||y||v?`│ ├── web/ # Frontend application (${p?`React + TanStack Router`:f?`React + React Router`:h?`Next.js`:g?`React + TanStack Start`:_?`SvelteKit`:y?`Nuxt`:v?`SolidJS`:``})\n`:``}${m?`│ ├── native/ # Mobile application (React Native, Expo)
44
52
  `:``}${a.includes(`starlight`)?`│ ├── docs/ # Documentation site (Astro Starlight)
45
- `:``}│ └── server/ # Backend API (${l[0].toUpperCase()+l.slice(1)}, tRPC)
53
+ `:``}${d?`├── packages/
54
+ │ └── backend/ # Convex backend functions and schema
55
+ `:`│ └── server/ # Backend API (${l[0].toUpperCase()+l.slice(1)}, ${u.toUpperCase()})`}
46
56
  \`\`\`
47
57
 
48
58
  ## Available Scripts
49
59
 
50
- ${he(v,r,o,i,f,a,l)}
51
- `}function pe(e,t,n,r,i,a,o){let s=a.includes(`tanstack-router`),c=a.includes(`react-router`),l=a.includes(`native`),u=a.includes(`next`),d=a.includes(`tanstack-start`),f=a.includes(`svelte`),p=a.includes(`nuxt`),m=a.includes(`solid`),h=[`- **TypeScript** - For type safety and improved developer experience`];s?h.push(`- **TanStack Router** - File-based routing with full type safety`):c?h.push(`- **React Router** - Declarative routing for React`):u?h.push(`- **Next.js** - Full-stack React framework`):d?h.push(`- **TanStack Start** - SSR framework with TanStack Router`):f?h.push(`- **SvelteKit** - Web framework for building Svelte apps`):p?h.push(`- **Nuxt** - The Intuitive Vue Framework`):m&&h.push(`- **SolidJS** - Simple and performant reactivity`),l&&(h.push(`- **React Native** - Build mobile apps using React`),h.push(`- **Expo** - Tools for React Native development`)),h.push(`- **TailwindCSS** - Utility-first CSS for rapid UI development`,`- **shadcn/ui** - Reusable UI components`),o===`hono`?h.push(`- **Hono** - Lightweight, performant server framework`):o===`express`?h.push(`- **Express** - Fast, unopinionated web framework`):o===`elysia`?h.push(`- **Elysia** - Type-safe, high-performance framework`):o===`next`&&h.push(`- **Next.js** - Full-stack React framework`),h.push(`- **tRPC** - End-to-end type-safe APIs`,`- **${i===`bun`?`Bun`:`Node.js`}** - Runtime environment`),e!==`none`&&h.push(`- **${r===`drizzle`?`Drizzle`:`Prisma`}** - TypeScript-first ORM`,`- **${e===`sqlite`?`SQLite/Turso`:e===`postgres`?`PostgreSQL`:e===`mysql`?`MySQL`:`MongoDB`}** - Database engine`),t&&h.push(`- **Authentication** - Email & password authentication with Better Auth`);for(let e of n)e===`pwa`?h.push(`- **PWA** - Progressive Web App support`):e===`tauri`?h.push(`- **Tauri** - Build native desktop applications`):e===`biome`?h.push(`- **Biome** - Linting and formatting`):e===`husky`?h.push(`- **Husky** - Git hooks for code quality`):e===`starlight`&&h.push(`- **Starlight** - Documentation site with Astro`);return h.join(`
60
+ ${he(b,r,o,i,m,a,l)}
61
+ `}function pe(e,t,n,r,i,a,o,s){let c=o===`convex`,l=a.includes(`tanstack-router`),u=a.includes(`react-router`),d=a.includes(`native`),f=a.includes(`next`),p=a.includes(`tanstack-start`),m=a.includes(`svelte`),h=a.includes(`nuxt`),g=a.includes(`solid`),_=[`- **TypeScript** - For type safety and improved developer experience`];l?_.push(`- **TanStack Router** - File-based routing with full type safety`):u?_.push(`- **React Router** - Declarative routing for React`):f?_.push(`- **Next.js** - Full-stack React framework`):p?_.push(`- **TanStack Start** - SSR framework with TanStack Router`):m?_.push(`- **SvelteKit** - Web framework for building Svelte apps`):h?_.push(`- **Nuxt** - The Intuitive Vue Framework`):g&&_.push(`- **SolidJS** - Simple and performant reactivity`),d&&(_.push(`- **React Native** - Build mobile apps using React`),_.push(`- **Expo** - Tools for React Native development`)),_.push(`- **TailwindCSS** - Utility-first CSS for rapid UI development`,`- **shadcn/ui** - Reusable UI components`),c?_.push(`- **Convex** - Reactive backend-as-a-service platform`):(o===`hono`?_.push(`- **Hono** - Lightweight, performant server framework`):o===`express`?_.push(`- **Express** - Fast, unopinionated web framework`):o===`elysia`?_.push(`- **Elysia** - Type-safe, high-performance framework`):o===`next`&&_.push(`- **Next.js** - Full-stack React framework`),s===`trpc`?_.push(`- **tRPC** - End-to-end type-safe APIs`):s===`orpc`&&_.push(`- **oRPC** - End-to-end type-safe APIs with OpenAPI integration`),_.push(`- **${i===`bun`?`Bun`:`Node.js`}** - Runtime environment`)),e!==`none`&&!c&&_.push(`- **${r===`drizzle`?`Drizzle`:r===`prisma`?`Prisma`:`Mongoose`}** - TypeScript-first ORM`,`- **${e===`sqlite`?`SQLite/Turso`:e===`postgres`?`PostgreSQL`:e===`mysql`?`MySQL`:`MongoDB`}** - Database engine`),t&&!c&&_.push(`- **Authentication** - Email & password authentication with Better Auth`);for(let e of n)e===`pwa`?_.push(`- **PWA** - Progressive Web App support`):e===`tauri`?_.push(`- **Tauri** - Build native desktop applications`):e===`biome`?_.push(`- **Biome** - Linting and formatting`):e===`husky`?_.push(`- **Husky** - Git hooks for code quality`):e===`starlight`?_.push(`- **Starlight** - Documentation site with Astro`):e===`turborepo`&&_.push(`- **Turborepo** - Optimized monorepo build system`);return _.join(`
52
62
  `)}function me(e,t,n,r){if(e===`none`)return``;let i=`## Database Setup
53
63
 
54
64
  `;return e===`sqlite`?i+=`This project uses SQLite${r===`drizzle`?` with Drizzle ORM`:` with Prisma`}.
@@ -67,7 +77,7 @@ cd apps/server && ${n} db:local
67
77
 
68
78
  1. Make sure you have a MySQL database set up.
69
79
  2. Update your \`apps/server/.env\` file with your MySQL connection details.
70
- `:e===`mongodb`&&(i+=`This project uses MongoDB with Prisma ORM.
80
+ `:e===`mongodb`&&(i+=`This project uses MongoDB ${r===`mongoose`?`with Mongoose`:`with Prisma ORM`}.
71
81
 
72
82
  1. Make sure you have MongoDB set up.
73
83
  2. Update your \`apps/server/.env\` file with your MongoDB connection URI.
@@ -79,21 +89,22 @@ ${n} db:push
79
89
  \`\`\`bash
80
90
  ${n} db:push
81
91
  \`\`\``}
82
- `,i}function he(e,t,n,r,i,a,o){let s=`- \`${e} dev\`: Start all applications in development mode
92
+ `,i}function he(e,t,n,r,i,a,o){let s=o===`convex`,c=`- \`${e} dev\`: Start all applications in development mode
83
93
  - \`${e} build\`: Build all applications
84
- - \`${e} dev:web\`: Start only the web application
85
- - \`${e} dev:server\`: Start only the server
86
- - \`${e} check-types\`: Check TypeScript types across all apps`;return i&&(s+=`
87
- - \`${e} dev:native\`: Start the React Native/Expo development server`),t!==`none`&&(s+=`
94
+ - \`${e} dev:web\`: Start only the web application`;return s?c+=`
95
+ - \`${e} dev:setup\`: Setup and configure your Convex project`:c+=`
96
+ - \`${e} dev:server\`: Start only the server`,c+=`
97
+ - \`${e} check-types\`: Check TypeScript types across all apps`,i&&(c+=`
98
+ - \`${e} dev:native\`: Start the React Native/Expo development server`),t!==`none`&&!s&&(c+=`
88
99
  - \`${e} db:push\`: Push schema changes to database
89
- - \`${e} db:studio\`: Open database studio UI`,t===`sqlite`&&n===`drizzle`&&(s+=`
90
- - \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),a.includes(`biome`)&&(s+=`
91
- - \`${e} check\`: Run Biome formatting and linting`),a.includes(`pwa`)&&(s+=`
92
- - \`cd apps/web && ${e} generate-pwa-assets\`: Generate PWA assets`),a.includes(`tauri`)&&(s+=`
100
+ - \`${e} db:studio\`: Open database studio UI`,t===`sqlite`&&n===`drizzle`&&(c+=`
101
+ - \`cd apps/server && ${e} db:local\`: Start the local SQLite database`)),a.includes(`biome`)&&(c+=`
102
+ - \`${e} check\`: Run Biome formatting and linting`),a.includes(`pwa`)&&(c+=`
103
+ - \`cd apps/web && ${e} generate-pwa-assets\`: Generate PWA assets`),a.includes(`tauri`)&&(c+=`
93
104
  - \`cd apps/web && ${e} desktop:dev\`: Start Tauri desktop app in development
94
- - \`cd apps/web && ${e} desktop:build\`: Build Tauri desktop app`),a.includes(`starlight`)&&(s+=`
105
+ - \`cd apps/web && ${e} desktop:build\`: Build Tauri desktop app`),a.includes(`starlight`)&&(c+=`
95
106
  - \`cd apps/docs && ${e} dev\`: Start documentation site
96
- - \`cd apps/docs && ${e} build\`: Build documentation site`),s}async function P(e){try{let t=process.platform===`win32`;if(t){let t=await x(`where`,[e]);return t.exitCode===0}let n=await x(`which`,[e]);return n.exitCode===0}catch{return!1}}async function ge(){let e=d();e.start(`Checking for MongoDB Atlas CLI`);try{let t=await P(`atlas`);return e.stop(t?`MongoDB Atlas CLI found`:g.yellow(`MongoDB Atlas CLI not found`)),t}catch{return e.stop(g.red(`Error checking for MongoDB Atlas CLI`)),!1}}async function _e(e){try{let n=await ge();if(!n)return p.error(g.red(`MongoDB Atlas CLI not found.`)),o.info(g.yellow(`Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/`)),null;o.info(g.blue(`Running MongoDB Atlas setup...`)),await x(`atlas`,[`deployments`,`setup`],{cwd:e,stdio:`inherit`}),o.info(g.green(`Atlas setup complete!`));let r=await f({message:`Enter your MongoDB connection string:`,placeholder:`mongodb+srv://username:password@cluster.mongodb.net/database`,validate(e){if(!e)return`Please enter a connection string`;if(!e.startsWith(`mongodb`))return`URL should start with mongodb:// or mongodb+srv://`}});return a(r)?(t(`MongoDB setup cancelled`),null):{connectionString:r}}catch(e){return e instanceof Error&&p.error(g.red(e.message)),null}}async function F(t,n){try{let r=e.join(t,`apps/server`,`.env`);await h.ensureDir(e.dirname(r));let i=``;await h.pathExists(r)&&(i=await h.readFile(r,`utf8`));let a=n?`DATABASE_URL="${n.connectionString}"`:`DATABASE_URL="mongodb://localhost:27017/mydb"`;i.includes(`DATABASE_URL=`)?i=i.replace(/DATABASE_URL=.*(\r?\n|$)/,`${a}$1`):i+=`\n${a}`,await h.writeFile(r,i.trim())}catch{p.error(`Failed to update environment configuration`)}}function I(){o.info(`
107
+ - \`cd apps/docs && ${e} build\`: Build documentation site`),c}async function P(e){try{let t=process.platform===`win32`;if(t){let t=await x(`where`,[e]);return t.exitCode===0}let n=await x(`which`,[e]);return n.exitCode===0}catch{return!1}}async function ge(){let e=d();e.start(`Checking for MongoDB Atlas CLI`);try{let t=await P(`atlas`);return e.stop(t?`MongoDB Atlas CLI found`:g.yellow(`MongoDB Atlas CLI not found`)),t}catch{return e.stop(g.red(`Error checking for MongoDB Atlas CLI`)),!1}}async function _e(e){try{let n=await ge();if(!n)return p.error(g.red(`MongoDB Atlas CLI not found.`)),o.info(g.yellow(`Please install it from: https://www.mongodb.com/docs/atlas/cli/current/install-atlas-cli/`)),null;o.info(g.blue(`Running MongoDB Atlas setup...`)),await x(`atlas`,[`deployments`,`setup`],{cwd:e,stdio:`inherit`}),o.info(g.green(`Atlas setup complete!`));let r=await f({message:`Enter your MongoDB connection string:`,placeholder:`mongodb+srv://username:password@cluster.mongodb.net/database`,validate(e){if(!e)return`Please enter a connection string`;if(!e.startsWith(`mongodb`))return`URL should start with mongodb:// or mongodb+srv://`}});return a(r)?(t(`MongoDB setup cancelled`),null):{connectionString:r}}catch(e){return e instanceof Error&&p.error(g.red(e.message)),null}}async function F(t,n){try{let r=e.join(t,`apps/server`,`.env`);await h.ensureDir(e.dirname(r));let i=``;await h.pathExists(r)&&(i=await h.readFile(r,`utf8`));let a=n?`DATABASE_URL="${n.connectionString}"`:`DATABASE_URL="mongodb://localhost:27017/mydb"`;i.includes(`DATABASE_URL=`)?i=i.replace(/DATABASE_URL=.*(\r?\n|$)/,`${a}$1`):i+=`\n${a}`,await h.writeFile(r,i.trim())}catch{p.error(`Failed to update environment configuration`)}}function I(){o.info(`
97
108
  ${g.green(`MongoDB Atlas Manual Setup Instructions:`)}
98
109
 
99
110
  1. Install Atlas CLI:
@@ -164,4 +175,4 @@ DATABASE_URL="your_connection_string"`)}async function Pe(n){let{projectName:r,p
164
175
  ╔══════════════════╗
165
176
  ║ Better T-Stack ║
166
177
  ╚══════════════════╝
167
- `;console.log(T(Object.values(Q)).multiline(e))}else console.log(T(Object.values(Q)).multiline(Z))},$=()=>process.exit(0);process.on(`SIGINT`,$),process.on(`SIGTERM`,$);async function At(){let r=Date.now();try{let s=await _(v(process.argv)).scriptName(`create-better-t-stack`).usage(`$0 [project-directory] [options]`,`Create a new Better-T Stack project`).positional(`project-directory`,{describe:`Project name/directory`,type:`string`}).option(`yes`,{alias:`y`,type:`boolean`,describe:`Use default configuration and skip prompts`,default:!1}).option(`database`,{type:`string`,describe:`Database type`,choices:[`none`,`sqlite`,`postgres`,`mysql`,`mongodb`]}).option(`orm`,{type:`string`,describe:`ORM type`,choices:[`drizzle`,`prisma`,`mongoose`,`none`]}).option(`auth`,{type:`boolean`,describe:`Include authentication (use --no-auth to exclude)`}).option(`frontend`,{type:`array`,string:!0,describe:`Frontend types`,choices:[`tanstack-router`,`react-router`,`tanstack-start`,`next`,`nuxt`,`native`,`svelte`,`solid`,`none`]}).option(`addons`,{type:`array`,string:!0,describe:`Additional addons`,choices:[`pwa`,`tauri`,`starlight`,`biome`,`husky`,`turborepo`,`none`]}).option(`examples`,{type:`array`,string:!0,describe:`Examples to include`,choices:[`todo`,`ai`,`none`]}).option(`git`,{type:`boolean`,describe:`Initialize git repository (use --no-git to skip)`}).option(`package-manager`,{alias:`pm`,type:`string`,describe:`Package manager`,choices:[`npm`,`pnpm`,`bun`]}).option(`install`,{type:`boolean`,describe:`Install dependencies (use --no-install to skip)`}).option(`db-setup`,{type:`string`,describe:`Database setup`,choices:[`turso`,`neon`,`prisma-postgres`,`mongodb-atlas`,`none`]}).option(`backend`,{type:`string`,describe:`Backend framework`,choices:[`hono`,`express`,`next`,`elysia`,`convex`]}).option(`runtime`,{type:`string`,describe:`Runtime`,choices:[`bun`,`node`,`none`]}).option(`api`,{type:`string`,describe:`API type`,choices:[`trpc`,`orpc`,`none`]}).completion().recommendCommands().version(Ot()).alias(`version`,`v`).help().alias(`help`,`h`).strict().wrap(null).parse(),l=s,u=l.projectDirectory;kt(),i(g.magenta(`Creating a new Better-T-Stack project`));let f,p,y,b,x=!1;if(l.yes&&u)f=u;else if(l.yes){let t=A.relativePath,n=1;for(;h.pathExistsSync(e.resolve(process.cwd(),t))&&h.readdirSync(e.resolve(process.cwd(),t)).length>0;)t=`${A.projectName}-${n}`,n++;f=t}else f=await Y(u);for(;;){let r=e.resolve(process.cwd(),f),i=h.pathExistsSync(r),s=i&&h.readdirSync(r).length>0;if(!s){p=f,x=!1;break}let c=await n({message:`Directory "${g.yellow(f)}" already exists and is not empty. Overwrite and replace all existing files?`,initialValue:!1});if(a(c)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),c){p=f,x=!0;break}o.info(`Please choose a different project name or path.`),f=await Y(void 0)}if(p===`.`?(y=process.cwd(),b=e.basename(y)):(y=e.resolve(process.cwd(),p),b=e.basename(y)),x){let e=d();e.start(`Clearing directory "${y}"...`);try{await h.emptyDir(y),e.stop(`Directory "${y}" cleared.`)}catch(t){e.stop(g.red(`Failed to clear directory "${y}".`)),m.error(t),process.exit(1)}}let S=jt(l,b),{projectName:C,...w}=S;!l.yes&&Object.keys(w).length>0&&(o.info(g.yellow(`Using these pre-selected options:`)),o.message(X(w)),o.message(``));let T;l.yes?(T={...A,...S,projectName:b,projectDir:y,relativePath:p},T.backend===`convex`?(T.auth=!1,T.database=`none`,T.orm=`none`,T.api=`none`,T.runtime=`none`,T.dbSetup=`none`,T.examples=[`todo`]):T.database===`none`&&(T.orm=`none`,T.auth=!1,T.dbSetup=`none`),o.info(g.yellow(`Using default/flag options (config prompts skipped):`)),o.message(X(T)),o.message(``)):T=await Tt(S,b,y,p),await dt(T),o.success(g.blue(`You can reproduce this setup with the following command:\n${Dt(T)}`));let E=((Date.now()-r)/1e3).toFixed(2);c(g.magenta(`Project created successfully in ${g.bold(E)} seconds!`))}catch(e){e instanceof Error?(e.name===`YError`?t(g.red(`Invalid arguments: ${e.message}`)):(m.error(`An unexpected error occurred: ${e.message}`),e.message.includes(`is only supported with`)||m.error(e.stack)),process.exit(1)):(m.error(`An unexpected error occurred.`),console.error(e),process.exit(1))}}function jt(t,n){let r={},i=new Set(Object.keys(t).filter(e=>e!==`_`&&e!==`$0`));if(t.api&&(r.api=t.api,t.api===`none`&&(t.backend&&t.backend!==`convex`&&(m.fatal(`'--api none' is only supported with '--backend convex'. Please choose a different API setting or use '--backend convex'.`),process.exit(1)),r.backend=`convex`)),t.backend&&(r.backend=t.backend),i.has(`backend`)&&r.backend&&r.backend!==`convex`&&(i.has(`api`)&&t.api===`none`&&(m.fatal(`'--api none' is only supported with '--backend convex'. Please choose 'trpc', 'orpc', or remove the --api flag.`),process.exit(1)),i.has(`runtime`)&&t.runtime===`none`&&(m.fatal(`'--runtime none' is only supported with '--backend convex'. Please choose 'bun', 'node', or remove the --runtime flag.`),process.exit(1))),t.database&&(r.database=t.database),t.orm&&(r.orm=t.orm),t.auth!==void 0&&(r.auth=t.auth),t.git!==void 0&&(r.git=t.git),t.install!==void 0&&(r.install=t.install),t.runtime&&(r.runtime=t.runtime),t.dbSetup&&(r.dbSetup=t.dbSetup),t.packageManager&&(r.packageManager=t.packageManager),n?r.projectName=n:t.projectDirectory&&(r.projectName=e.basename(e.resolve(process.cwd(),t.projectDirectory))),t.frontend&&t.frontend.length>0)if(t.frontend.includes(`none`))t.frontend.length>1&&(m.fatal(`Cannot combine 'none' with other frontend options.`),process.exit(1)),r.frontend=[];else{let e=t.frontend.filter(e=>e!==`none`),n=e.filter(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`||e===`solid`);n.length>1&&(m.fatal(`Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid`),process.exit(1)),r.frontend=e}if(t.addons&&t.addons.length>0&&(t.addons.includes(`none`)?(t.addons.length>1&&(m.fatal(`Cannot combine 'none' with other addons.`),process.exit(1)),r.addons=[]):r.addons=t.addons.filter(e=>e!==`none`)),t.examples&&t.examples.length>0&&(t.examples.includes(`none`)?(t.examples.length>1&&(m.fatal(`Cannot combine 'none' with other examples.`),process.exit(1)),r.examples=[]):(r.examples=t.examples.filter(e=>e!==`none`),t.examples.includes(`none`)&&r.backend!==`convex`&&(r.examples=[]))),r.backend===`convex`){let e=[];if(i.has(`auth`)&&t.auth===!0&&e.push(`--auth`),i.has(`database`)&&t.database!==`none`&&e.push(`--database ${t.database}`),i.has(`orm`)&&t.orm!==`none`&&e.push(`--orm ${t.orm}`),i.has(`api`)&&t.api!==`none`&&e.push(`--api ${t.api}`),i.has(`runtime`)&&t.runtime!==`none`&&e.push(`--runtime ${t.runtime}`),i.has(`dbSetup`)&&t.dbSetup!==`none`&&e.push(`--db-setup ${t.dbSetup}`),e.length>0&&(m.fatal(`The following flags are incompatible with '--backend convex': ${e.join(`, `)}. Please remove them.`),process.exit(1)),i.has(`frontend`)&&t.frontend){let e=t.frontend.filter(e=>e===`nuxt`||e===`solid`);e.length>0&&(m.fatal(`The following frontends are not compatible with '--backend convex': ${e.join(`, `)}. Please choose a different frontend or backend.`),process.exit(1))}r.auth=!1,r.database=`none`,r.orm=`none`,r.api=`none`,r.runtime=`none`,r.dbSetup=`none`,r.examples=[`todo`]}else{let e=r.database??(t.yes?A.database:void 0),n=r.orm??(t.yes?A.orm:void 0),a=r.auth??(t.yes?A.auth:void 0),o=r.dbSetup??(t.yes?A.dbSetup:void 0),s=r.examples??(t.yes?A.examples:void 0),c=r.frontend??(t.yes?A.frontend:void 0),l=r.api??(t.yes?A.api:void 0),u=r.backend??(t.yes?A.backend:void 0);if(e===`none`&&(i.has(`orm`)&&t.orm!==`none`&&(m.fatal(`Cannot use ORM '--orm ${t.orm}' when database is 'none'.`),process.exit(1)),r.orm=`none`,i.has(`auth`)&&t.auth===!0&&(m.fatal(`Authentication requires a database. Cannot use --auth when database is 'none'.`),process.exit(1)),r.auth=!1,i.has(`dbSetup`)&&t.dbSetup!==`none`&&(m.fatal(`Database setup '--db-setup ${t.dbSetup}' requires a database. Cannot use when database is 'none'.`),process.exit(1)),r.dbSetup=`none`),r.orm===`mongoose`&&!i.has(`database`)&&(e&&e!==`mongodb`&&(m.fatal(`Mongoose ORM requires MongoDB. Cannot use --orm mongoose with --database ${e}.`),process.exit(1)),r.database=`mongodb`),e===`mongodb`&&n===`drizzle`&&(m.fatal(`Drizzle ORM is not compatible with MongoDB. Please use --orm prisma or --orm mongoose.`),process.exit(1)),n===`mongoose`&&e&&e!==`mongodb`&&(m.fatal(`Mongoose ORM requires MongoDB. Cannot use --orm mongoose with --database ${e}.`),process.exit(1)),r.dbSetup&&r.dbSetup!==`none`){let t=r.dbSetup;(!e||e===`none`)&&(m.fatal(`Database setup '--db-setup ${t}' requires a database. Cannot use when database is 'none'.`),process.exit(1)),t===`turso`?(e&&e!==`sqlite`&&(m.fatal(`Turso setup requires SQLite. Cannot use --db-setup turso with --database ${e}`),process.exit(1)),n!==`drizzle`&&(m.fatal(`Turso setup requires Drizzle ORM. Cannot use --db-setup turso with --orm ${n??`none`}.`),process.exit(1))):t===`prisma-postgres`?(e!==`postgres`&&(m.fatal(`Prisma PostgreSQL setup requires PostgreSQL. Cannot use --db-setup prisma-postgres with --database ${e}.`),process.exit(1)),n!==`prisma`&&(m.fatal(`Prisma PostgreSQL setup requires Prisma ORM. Cannot use --db-setup prisma-postgres with --orm ${n}.`),process.exit(1))):t===`mongodb-atlas`?(e!==`mongodb`&&(m.fatal(`MongoDB Atlas setup requires MongoDB. Cannot use --db-setup mongodb-atlas with --database ${e}.`),process.exit(1)),n!==`prisma`&&n!==`mongoose`&&(m.fatal(`MongoDB Atlas setup requires Prisma or Mongoose ORM. Cannot use --db-setup mongodb-atlas with --orm ${n}.`),process.exit(1))):t===`neon`&&e!==`postgres`&&(m.fatal(`Neon PostgreSQL setup requires PostgreSQL. Cannot use --db-setup neon with --database ${e}.`),process.exit(1))}let d=c?.includes(`nuxt`),f=c?.includes(`svelte`),p=c?.includes(`solid`);if((d||f||p)&&l===`trpc`&&(m.fatal(`tRPC API is not supported with '${d?`nuxt`:f?`svelte`:`solid`}' frontend. Please use --api orpc or remove '${d?`nuxt`:f?`svelte`:`solid`}' from --frontend.`),process.exit(1)),(d||f||p)&&l!==`orpc`&&(!t.api||t.yes&&t.api===`trpc`)&&r.api!==`none`&&(r.api=`orpc`),r.addons&&r.addons.length>0){let e=[`pwa`,`tauri`],t=r.addons.some(t=>e.includes(t)),n=c?.some(e=>{let t=e===`tanstack-router`||e===`react-router`||e===`solid`,n=e===`tanstack-router`||e===`react-router`||e===`nuxt`||e===`svelte`||e===`solid`;return r.addons?.includes(`pwa`)&&r.addons?.includes(`tauri`)?t&&n:r.addons?.includes(`pwa`)?t:r.addons?.includes(`tauri`)?n:!0});if(t&&!n){let e=`Selected frontend is not compatible.`;r.addons.includes(`pwa`)&&(e=`PWA requires tanstack-router, react-router, or solid.`),r.addons.includes(`tauri`)&&(e=`Tauri requires tanstack-router, react-router, nuxt, svelte, or solid.`),m.fatal(`Incompatible addon/frontend combination: ${e}`),process.exit(1)}r.addons.includes(`husky`)&&!r.addons.includes(`biome`)&&m.warn(`Husky addon is recommended to be used with Biome for lint-staged configuration.`),r.addons=[...new Set(r.addons)]}let h=c&&c.length===1&&c[0]===`native`;h&&r.examples&&r.examples.length>0&&!r.examples.includes(`none`)&&(m.fatal(`Examples are not supported when only the 'native' frontend is selected.`),process.exit(1)),r.examples&&r.examples.length>0&&!r.examples.includes(`none`)&&(r.examples.includes(`todo`)&&u!==`convex`&&e===`none`&&(m.fatal(`The 'todo' example requires a database (unless using Convex). Cannot use --examples todo when database is 'none'.`),process.exit(1)),r.examples.includes(`ai`)&&u===`elysia`&&(m.fatal(`The 'ai' example is not compatible with the Elysia backend.`),process.exit(1)),r.examples.includes(`ai`)&&p&&(m.fatal(`The 'ai' example is not compatible with the Solid frontend.`),process.exit(1)))}return r}At().catch(e=>{m.error(`Aborting installation due to unexpected error...`),e instanceof Error?!e.message.includes(`is only supported with`)&&!e.message.includes(`incompatible with`)&&!e.message.includes(`requires`)&&!e.message.includes(`Cannot use`)&&!e.message.includes(`Cannot select multiple`)&&!e.message.includes(`Cannot combine`)&&!e.message.includes(`not supported`)&&(m.error(e.message),m.error(e.stack)):console.error(e),process.exit(1)});
178
+ `;console.log(T(Object.values(Q)).multiline(e))}else console.log(T(Object.values(Q)).multiline(Z))},$=()=>process.exit(0);process.on(`SIGINT`,$),process.on(`SIGTERM`,$);async function At(){let n=Date.now();try{let r=await _(v(process.argv)).scriptName(`create-better-t-stack`).usage(`$0 [project-directory] [options]`,`Create a new Better-T Stack project`).positional(`project-directory`,{describe:`Project name/directory`,type:`string`}).option(`yes`,{alias:`y`,type:`boolean`,describe:`Use default configuration and skip prompts`,default:!1}).option(`database`,{type:`string`,describe:`Database type`,choices:[`none`,`sqlite`,`postgres`,`mysql`,`mongodb`]}).option(`orm`,{type:`string`,describe:`ORM type`,choices:[`drizzle`,`prisma`,`mongoose`,`none`]}).option(`auth`,{type:`boolean`,describe:`Include authentication (use --no-auth to exclude)`}).option(`frontend`,{type:`array`,string:!0,describe:`Frontend types`,choices:[`tanstack-router`,`react-router`,`tanstack-start`,`next`,`nuxt`,`native`,`svelte`,`solid`,`none`]}).option(`addons`,{type:`array`,string:!0,describe:`Additional addons`,choices:[`pwa`,`tauri`,`starlight`,`biome`,`husky`,`turborepo`,`none`]}).option(`examples`,{type:`array`,string:!0,describe:`Examples to include`,choices:[`todo`,`ai`,`none`]}).option(`git`,{type:`boolean`,describe:`Initialize git repository (use --no-git to skip)`}).option(`package-manager`,{alias:`pm`,type:`string`,describe:`Package manager`,choices:[`npm`,`pnpm`,`bun`]}).option(`install`,{type:`boolean`,describe:`Install dependencies (use --no-install to skip)`}).option(`db-setup`,{type:`string`,describe:`Database setup`,choices:[`turso`,`neon`,`prisma-postgres`,`mongodb-atlas`,`none`]}).option(`backend`,{type:`string`,describe:`Backend framework`,choices:[`hono`,`express`,`next`,`elysia`,`convex`]}).option(`runtime`,{type:`string`,describe:`Runtime`,choices:[`bun`,`node`,`none`]}).option(`api`,{type:`string`,describe:`API type`,choices:[`trpc`,`orpc`,`none`]}).completion().recommendCommands().version(Ot()).alias(`version`,`v`).help().alias(`help`,`h`).strict().wrap(null).parse(),s=r,l=s.projectDirectory;kt(),i(g.magenta(`Creating a new Better-T-Stack project`));let f,p,y,b,x=!1;if(s.yes&&l)f=l;else if(s.yes){let t=A.relativePath,n=1;for(;h.pathExistsSync(e.resolve(process.cwd(),t))&&h.readdirSync(e.resolve(process.cwd(),t)).length>0;)t=`${A.projectName}-${n}`,n++;f=t}else f=await Y(l);for(;;){let n=e.resolve(process.cwd(),f),r=h.pathExistsSync(n),i=r&&h.readdirSync(n).length>0;if(!i){p=f,x=!1;break}o.warn(`Directory "${g.yellow(f)}" already exists and is not empty.`);let s=await u({message:`What would you like to do?`,options:[{value:`overwrite`,label:`Overwrite`,hint:`Empty the directory and create the project`},{value:`merge`,label:`Merge`,hint:`Create project files inside, potentially overwriting conflicts`},{value:`rename`,label:`Choose a different name/path`,hint:`Keep the existing directory and create a new one`},{value:`cancel`,label:`Cancel`,hint:`Abort the process`}],initialValue:`rename`});if(a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),s===`overwrite`){p=f,x=!0;break}if(s===`merge`){p=f,x=!1,o.info(`Proceeding into existing directory "${g.yellow(f)}". Files may be overwritten.`);break}s===`rename`?(o.info(`Please choose a different project name or path.`),f=await Y(void 0)):s===`cancel`&&(t(g.red(`Operation cancelled.`)),process.exit(0))}if(p===`.`?(y=process.cwd(),b=e.basename(y)):(y=e.resolve(process.cwd(),p),b=e.basename(y)),x){let e=d();e.start(`Clearing directory "${y}"...`);try{await h.emptyDir(y),e.stop(`Directory "${y}" cleared.`)}catch(t){e.stop(g.red(`Failed to clear directory "${y}".`)),m.error(t),process.exit(1)}}else await h.ensureDir(y);let S=jt(s,b),{projectName:C,...w}=S;!s.yes&&Object.keys(w).length>0&&(o.info(g.yellow(`Using these pre-selected options:`)),o.message(X(w)),o.message(``));let T;s.yes?(T={...A,...S,projectName:b,projectDir:y,relativePath:p},T.backend===`convex`?(T.auth=!1,T.database=`none`,T.orm=`none`,T.api=`none`,T.runtime=`none`,T.dbSetup=`none`,T.examples=[`todo`]):T.database===`none`&&(T.orm=`none`,T.auth=!1,T.dbSetup=`none`),o.info(g.yellow(`Using default/flag options (config prompts skipped):`)),o.message(X(T)),o.message(``)):T=await Tt(S,b,y,p),await dt(T),o.success(g.blue(`You can reproduce this setup with the following command:\n${Dt(T)}`));let E=((Date.now()-n)/1e3).toFixed(2);c(g.magenta(`Project created successfully in ${g.bold(E)} seconds!`))}catch(e){e instanceof Error?(e.name===`YError`?t(g.red(`Invalid arguments: ${e.message}`)):(m.error(`An unexpected error occurred: ${e.message}`),e.message.includes(`is only supported with`)||m.error(e.stack)),process.exit(1)):(m.error(`An unexpected error occurred.`),console.error(e),process.exit(1))}}function jt(t,n){let r={},i=new Set(Object.keys(t).filter(e=>e!==`_`&&e!==`$0`));if(t.api&&(r.api=t.api,t.api===`none`&&(t.backend&&t.backend!==`convex`&&(m.fatal(`'--api none' is only supported with '--backend convex'. Please choose a different API setting or use '--backend convex'.`),process.exit(1)),r.backend=`convex`)),t.backend&&(r.backend=t.backend),i.has(`backend`)&&r.backend&&r.backend!==`convex`&&(i.has(`api`)&&t.api===`none`&&(m.fatal(`'--api none' is only supported with '--backend convex'. Please choose 'trpc', 'orpc', or remove the --api flag.`),process.exit(1)),i.has(`runtime`)&&t.runtime===`none`&&(m.fatal(`'--runtime none' is only supported with '--backend convex'. Please choose 'bun', 'node', or remove the --runtime flag.`),process.exit(1))),t.database&&(r.database=t.database),t.orm&&(r.orm=t.orm),t.auth!==void 0&&(r.auth=t.auth),t.git!==void 0&&(r.git=t.git),t.install!==void 0&&(r.install=t.install),t.runtime&&(r.runtime=t.runtime),t.dbSetup&&(r.dbSetup=t.dbSetup),t.packageManager&&(r.packageManager=t.packageManager),n?r.projectName=n:t.projectDirectory&&(r.projectName=e.basename(e.resolve(process.cwd(),t.projectDirectory))),t.frontend&&t.frontend.length>0)if(t.frontend.includes(`none`))t.frontend.length>1&&(m.fatal(`Cannot combine 'none' with other frontend options.`),process.exit(1)),r.frontend=[];else{let e=t.frontend.filter(e=>e!==`none`),n=e.filter(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`||e===`solid`);n.length>1&&(m.fatal(`Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid`),process.exit(1)),r.frontend=e}if(t.addons&&t.addons.length>0&&(t.addons.includes(`none`)?(t.addons.length>1&&(m.fatal(`Cannot combine 'none' with other addons.`),process.exit(1)),r.addons=[]):r.addons=t.addons.filter(e=>e!==`none`)),t.examples&&t.examples.length>0&&(t.examples.includes(`none`)?(t.examples.length>1&&(m.fatal(`Cannot combine 'none' with other examples.`),process.exit(1)),r.examples=[]):(r.examples=t.examples.filter(e=>e!==`none`),t.examples.includes(`none`)&&r.backend!==`convex`&&(r.examples=[]))),r.backend===`convex`){let e=[];if(i.has(`auth`)&&t.auth===!0&&e.push(`--auth`),i.has(`database`)&&t.database!==`none`&&e.push(`--database ${t.database}`),i.has(`orm`)&&t.orm!==`none`&&e.push(`--orm ${t.orm}`),i.has(`api`)&&t.api!==`none`&&e.push(`--api ${t.api}`),i.has(`runtime`)&&t.runtime!==`none`&&e.push(`--runtime ${t.runtime}`),i.has(`dbSetup`)&&t.dbSetup!==`none`&&e.push(`--db-setup ${t.dbSetup}`),e.length>0&&(m.fatal(`The following flags are incompatible with '--backend convex': ${e.join(`, `)}. Please remove them.`),process.exit(1)),i.has(`frontend`)&&t.frontend){let e=t.frontend.filter(e=>e===`nuxt`||e===`solid`);e.length>0&&(m.fatal(`The following frontends are not compatible with '--backend convex': ${e.join(`, `)}. Please choose a different frontend or backend.`),process.exit(1))}r.auth=!1,r.database=`none`,r.orm=`none`,r.api=`none`,r.runtime=`none`,r.dbSetup=`none`,r.examples=[`todo`]}else{let e=r.database??(t.yes?A.database:void 0),n=r.orm??(t.yes?A.orm:void 0),a=r.auth??(t.yes?A.auth:void 0),o=r.dbSetup??(t.yes?A.dbSetup:void 0),s=r.examples??(t.yes?A.examples:void 0),c=r.frontend??(t.yes?A.frontend:void 0),l=r.api??(t.yes?A.api:void 0),u=r.backend??(t.yes?A.backend:void 0);if(e===`none`&&(i.has(`orm`)&&t.orm!==`none`&&(m.fatal(`Cannot use ORM '--orm ${t.orm}' when database is 'none'.`),process.exit(1)),r.orm=`none`,i.has(`auth`)&&t.auth===!0&&(m.fatal(`Authentication requires a database. Cannot use --auth when database is 'none'.`),process.exit(1)),r.auth=!1,i.has(`dbSetup`)&&t.dbSetup!==`none`&&(m.fatal(`Database setup '--db-setup ${t.dbSetup}' requires a database. Cannot use when database is 'none'.`),process.exit(1)),r.dbSetup=`none`),r.orm===`mongoose`&&!i.has(`database`)&&(e&&e!==`mongodb`&&(m.fatal(`Mongoose ORM requires MongoDB. Cannot use --orm mongoose with --database ${e}.`),process.exit(1)),r.database=`mongodb`),e===`mongodb`&&n===`drizzle`&&(m.fatal(`Drizzle ORM is not compatible with MongoDB. Please use --orm prisma or --orm mongoose.`),process.exit(1)),n===`mongoose`&&e&&e!==`mongodb`&&(m.fatal(`Mongoose ORM requires MongoDB. Cannot use --orm mongoose with --database ${e}.`),process.exit(1)),r.dbSetup&&r.dbSetup!==`none`){let t=r.dbSetup;(!e||e===`none`)&&(m.fatal(`Database setup '--db-setup ${t}' requires a database. Cannot use when database is 'none'.`),process.exit(1)),t===`turso`?(e&&e!==`sqlite`&&(m.fatal(`Turso setup requires SQLite. Cannot use --db-setup turso with --database ${e}`),process.exit(1)),n!==`drizzle`&&(m.fatal(`Turso setup requires Drizzle ORM. Cannot use --db-setup turso with --orm ${n??`none`}.`),process.exit(1))):t===`prisma-postgres`?(e!==`postgres`&&(m.fatal(`Prisma PostgreSQL setup requires PostgreSQL. Cannot use --db-setup prisma-postgres with --database ${e}.`),process.exit(1)),n!==`prisma`&&(m.fatal(`Prisma PostgreSQL setup requires Prisma ORM. Cannot use --db-setup prisma-postgres with --orm ${n}.`),process.exit(1))):t===`mongodb-atlas`?(e!==`mongodb`&&(m.fatal(`MongoDB Atlas setup requires MongoDB. Cannot use --db-setup mongodb-atlas with --database ${e}.`),process.exit(1)),n!==`prisma`&&n!==`mongoose`&&(m.fatal(`MongoDB Atlas setup requires Prisma or Mongoose ORM. Cannot use --db-setup mongodb-atlas with --orm ${n}.`),process.exit(1))):t===`neon`&&e!==`postgres`&&(m.fatal(`Neon PostgreSQL setup requires PostgreSQL. Cannot use --db-setup neon with --database ${e}.`),process.exit(1))}let d=c?.includes(`nuxt`),f=c?.includes(`svelte`),p=c?.includes(`solid`);if((d||f||p)&&l===`trpc`&&(m.fatal(`tRPC API is not supported with '${d?`nuxt`:f?`svelte`:`solid`}' frontend. Please use --api orpc or remove '${d?`nuxt`:f?`svelte`:`solid`}' from --frontend.`),process.exit(1)),(d||f||p)&&l!==`orpc`&&(!t.api||t.yes&&t.api===`trpc`)&&r.api!==`none`&&(r.api=`orpc`),r.addons&&r.addons.length>0){let e=[`pwa`,`tauri`],t=r.addons.some(t=>e.includes(t)),n=c?.some(e=>{let t=e===`tanstack-router`||e===`react-router`||e===`solid`,n=e===`tanstack-router`||e===`react-router`||e===`nuxt`||e===`svelte`||e===`solid`;return r.addons?.includes(`pwa`)&&r.addons?.includes(`tauri`)?t&&n:r.addons?.includes(`pwa`)?t:r.addons?.includes(`tauri`)?n:!0});if(t&&!n){let e=`Selected frontend is not compatible.`;r.addons.includes(`pwa`)&&(e=`PWA requires tanstack-router, react-router, or solid.`),r.addons.includes(`tauri`)&&(e=`Tauri requires tanstack-router, react-router, nuxt, svelte, or solid.`),m.fatal(`Incompatible addon/frontend combination: ${e}`),process.exit(1)}r.addons.includes(`husky`)&&!r.addons.includes(`biome`)&&m.warn(`Husky addon is recommended to be used with Biome for lint-staged configuration.`),r.addons=[...new Set(r.addons)]}let h=c&&c.length===1&&c[0]===`native`;h&&r.examples&&r.examples.length>0&&!r.examples.includes(`none`)&&(m.fatal(`Examples are not supported when only the 'native' frontend is selected.`),process.exit(1)),r.examples&&r.examples.length>0&&!r.examples.includes(`none`)&&(r.examples.includes(`todo`)&&u!==`convex`&&e===`none`&&(m.fatal(`The 'todo' example requires a database (unless using Convex). Cannot use --examples todo when database is 'none'.`),process.exit(1)),r.examples.includes(`ai`)&&u===`elysia`&&(m.fatal(`The 'ai' example is not compatible with the Elysia backend.`),process.exit(1)),r.examples.includes(`ai`)&&p&&(m.fatal(`The 'ai' example is not compatible with the Solid frontend.`),process.exit(1)))}return r}At().catch(e=>{m.error(`Aborting installation due to unexpected error...`),e instanceof Error?!e.message.includes(`is only supported with`)&&!e.message.includes(`incompatible with`)&&!e.message.includes(`requires`)&&!e.message.includes(`Cannot use`)&&!e.message.includes(`Cannot select multiple`)&&!e.message.includes(`Cannot combine`)&&!e.message.includes(`not supported`)&&(m.error(e.message),m.error(e.stack)):console.error(e),process.exit(1)});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.8.1",
3
+ "version": "2.8.3",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,132 +0,0 @@
1
- import { createFileRoute } from "@tanstack/solid-router";
2
- import { Loader2, Trash2 } from "lucide-solid";
3
- import { createSignal, For, Show } from "solid-js";
4
- import { orpc } from "@/utils/orpc";
5
- import { useQuery, useMutation } from "@tanstack/solid-query";
6
-
7
- export const Route = createFileRoute("/todos")({
8
- component: TodosRoute,
9
- });
10
-
11
- function TodosRoute() {
12
- const [newTodoText, setNewTodoText] = createSignal("");
13
-
14
- const todos = useQuery(() => orpc.todo.getAll.queryOptions());
15
-
16
- const createMutation = useMutation(() =>
17
- orpc.todo.create.mutationOptions({
18
- onSuccess: () => {
19
- todos.refetch();
20
- setNewTodoText("");
21
- },
22
- }),
23
- );
24
-
25
- const toggleMutation = useMutation(() =>
26
- orpc.todo.toggle.mutationOptions({
27
- onSuccess: () => todos.refetch(),
28
- }),
29
- );
30
-
31
- const deleteMutation = useMutation(() =>
32
- orpc.todo.delete.mutationOptions({
33
- onSuccess: () => todos.refetch(),
34
- }),
35
- );
36
-
37
- const handleAddTodo = (e: Event) => {
38
- e.preventDefault();
39
- if (newTodoText().trim()) {
40
- createMutation.mutate({ text: newTodoText() });
41
- }
42
- };
43
-
44
- const handleToggleTodo = (id: number, completed: boolean) => {
45
- toggleMutation.mutate({ id, completed: !completed });
46
- };
47
-
48
- const handleDeleteTodo = (id: number) => {
49
- deleteMutation.mutate({ id });
50
- };
51
-
52
- return (
53
- <div class="mx-auto w-full max-w-md py-10">
54
- <div class="rounded-lg border p-6 shadow-sm">
55
- <div class="mb-4">
56
- <h2 class="text-xl font-semibold">Todo List</h2>
57
- <p class="text-sm">Manage your tasks efficiently</p>
58
- </div>
59
- <div>
60
- <form
61
- onSubmit={handleAddTodo}
62
- class="mb-6 flex items-center space-x-2"
63
- >
64
- <input
65
- type="text"
66
- value={newTodoText()}
67
- onInput={(e) => setNewTodoText(e.currentTarget.value)}
68
- placeholder="Add a new task..."
69
- disabled={createMutation.isPending}
70
- class="w-full rounded-md border p-2 text-sm"
71
- />
72
- <button
73
- type="submit"
74
- disabled={createMutation.isPending || !newTodoText().trim()}
75
- class="rounded-md bg-blue-600 px-4 py-2 text-sm text-white disabled:opacity-50"
76
- >
77
- <Show when={createMutation.isPending} fallback="Add">
78
- <Loader2 class="h-4 w-4 animate-spin" />
79
- </Show>
80
- </button>
81
- </form>
82
-
83
- <Show when={todos.isLoading}>
84
- <div class="flex justify-center py-4">
85
- <Loader2 class="h-6 w-6 animate-spin" />
86
- </div>
87
- </Show>
88
-
89
- <Show when={!todos.isLoading && todos.data?.length === 0}>
90
- <p class="py-4 text-center">No todos yet. Add one above!</p>
91
- </Show>
92
-
93
- <Show when={!todos.isLoading}>
94
- <ul class="space-y-2">
95
- <For each={todos.data}>
96
- {(todo) => (
97
- <li class="flex items-center justify-between rounded-md border p-2">
98
- <div class="flex items-center space-x-2">
99
- <input
100
- type="checkbox"
101
- checked={todo.completed}
102
- onChange={() =>
103
- handleToggleTodo(todo.id, todo.completed)
104
- }
105
- id={`todo-${todo.id}`}
106
- class="h-4 w-4"
107
- />
108
- <label
109
- for={`todo-${todo.id}`}
110
- class={todo.completed ? "line-through" : ""}
111
- >
112
- {todo.text}
113
- </label>
114
- </div>
115
- <button
116
- type="button"
117
- onClick={() => handleDeleteTodo(todo.id)}
118
- aria-label="Delete todo"
119
- class="ml-2 rounded-md p-1"
120
- >
121
- <Trash2 class="h-4 w-4" />
122
- </button>
123
- </li>
124
- )}
125
- </For>
126
- </ul>
127
- </Show>
128
- </div>
129
- </div>
130
- </div>
131
- );
132
- }