create-better-t-stack 2.9.1 → 2.9.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.
Files changed (27) hide show
  1. package/dist/index.js +3 -3
  2. package/package.json +3 -3
  3. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +2 -5
  4. package/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs +91 -8
  5. package/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs +0 -1
  6. package/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +28 -20
  7. package/templates/frontend/native/nativewind/app/_layout.tsx.hbs +17 -0
  8. package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +31 -15
  9. package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +24 -0
  10. package/templates/frontend/nuxt/app/pages/{index.vue → index.vue.hbs} +6 -1
  11. package/templates/frontend/react/next/src/app/page.tsx.hbs +19 -15
  12. package/templates/frontend/react/next/src/components/providers.tsx.hbs +34 -33
  13. package/templates/frontend/react/react-router/src/root.tsx.hbs +60 -44
  14. package/templates/frontend/react/react-router/src/routes/_index.tsx.hbs +21 -17
  15. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +26 -29
  16. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +14 -44
  17. package/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs +17 -14
  18. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +18 -17
  19. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +12 -2
  20. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +21 -20
  21. package/templates/frontend/solid/src/main.tsx.hbs +6 -0
  22. package/templates/frontend/solid/src/routes/__root.tsx.hbs +14 -1
  23. package/templates/frontend/solid/src/routes/index.tsx.hbs +8 -1
  24. package/templates/frontend/svelte/src/routes/+layout.svelte.hbs +16 -5
  25. package/templates/frontend/svelte/src/routes/+page.svelte.hbs +4 -14
  26. /package/templates/frontend/nuxt/app/layouts/{default.vue → default.vue.hbs} +0 -0
  27. /package/templates/frontend/nuxt/app/plugins/{vue-query.ts → vue-query.ts.hbs} +0 -0
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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.2.0`,"@orpc/client":`^1.2.0`,"@orpc/react-query":`^1.2.0`,"@orpc/solid-query":`^1.2.0`,"@orpc/vue-query":`^1.2.0`,"@orpc/svelte-query":`^1.2.0`,"@trpc/tanstack-react-query":`^11.0.0`,"@trpc/server":`^11.0.0`,"@trpc/client":`^11.0.0`,convex:`^1.23.0`,"@convex-dev/react-query":`^0.0.0-alpha.8`,"convex-svelte":`^0.0.11`,"@tanstack/svelte-query":`^5.74.4`,"@tanstack/react-query-devtools":`^5.69.0`,"@tanstack/react-query":`^5.69.0`,"@tanstack/solid-query":`^5.75.0`,"@tanstack/solid-query-devtools":`^5.75.0`},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-nativewind`,`native-unistyles`],y=i.includes(`solid`),b=i.some(e=>v.includes(e));if(b&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native-nativewind`&&e!==`native-unistyles`&&v.includes(e)),a=i.includes(`native-nativewind`)||i.includes(`native-unistyles`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(y&&!c){let t=[`@tanstack/solid-query`],n=[`@tanstack/solid-query-devtools`];if(d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}}if(c){if(d){let t=e.join(l,`package.json`);if(await h.pathExists(t))try{let e=[`convex`];i.includes(`tanstack-start`)&&e.push(`@convex-dev/react-query`),g&&e.push(`convex-svelte`),await M({dependencies:e,projectDir:l})}catch{}}if(f){let t=e.join(u,`package.json`);if(await h.pathExists(t))try{await M({dependencies:[`convex`],projectDir:u})}catch{}}let t=`@${r}/backend`,n=o===`npm`?`*`:`workspace:*`,a=async(e,t,n)=>{try{let r=await h.readJson(e);r.dependencies||={},r.dependencies[t]!==n&&(r.dependencies[t]=n,await h.writeJson(e,r,{spaces:2}))}catch{}};if(d){let r=e.join(l,`package.json`);await h.pathExists(r)&&await a(r,t,n)}if(f){let r=e.join(u,`package.json`);await h.pathExists(r)&&await a(r,t,n)}}}async function 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-nativewind`)||i.includes(`native-unistyles`))&&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-nativewind`)||c.includes(`native-unistyles`),h=c.includes(`next`),g=c.includes(`tanstack-start`),_=c.includes(`svelte`),v=c.includes(`solid`),y=c.includes(`nuxt`),b=n===`npm`?`npm run`:n,x=`3001`;return(f||_)&&(x=`5173`),`# ${t}
2
+ import e from"node:path";import{cancel as t,confirm as n,group as r,intro as i,isCancel as a,log as o,multiselect as s,outro as c,password as l,select as u,spinner as d,text as f}from"@clack/prompts";import p,{consola as m}from"consola";import h from"fs-extra";import g from"picocolors";import _ from"yargs";import{hideBin as v}from"yargs/helpers";import{fileURLToPath as y}from"node:url";import{$ as b,execa as x}from"execa";import S from"node:os";import{globby as C}from"globby";import w from"handlebars";import T from"gradient-string";const E=()=>{let e=process.env.npm_config_user_agent;return e?.startsWith(`pnpm`)?`pnpm`:e?.startsWith(`bun`)?`bun`:`npm`},D=y(import.meta.url),O=e.dirname(D),k=e.join(O,`../`),A={projectName:`my-better-t-app`,projectDir:e.resolve(process.cwd(),`my-better-t-app`),relativePath:`my-better-t-app`,frontend:[`tanstack-router`],database:`sqlite`,orm:`drizzle`,auth:!0,addons:[`turborepo`],examples:[],git:!0,packageManager:E(),install:!0,dbSetup:`none`,backend:`hono`,runtime:`bun`,api:`trpc`},j={"better-auth":`^1.2.7`,"@better-auth/expo":`^1.2.7`,"drizzle-orm":`^0.38.4`,"drizzle-kit":`^0.30.5`,"@libsql/client":`^0.14.0`,pg:`^8.14.1`,"@types/pg":`^8.11.11`,mysql2:`^3.14.0`,"@prisma/client":`^6.7.0`,prisma:`^6.7.0`,mongoose:`^8.14.0`,"vite-plugin-pwa":`^0.21.2`,"@vite-pwa/assets-generator":`^0.2.6`,"@tauri-apps/cli":`^2.4.0`,"@biomejs/biome":`1.9.4`,husky:`^9.1.7`,"lint-staged":`^15.5.0`,"@hono/node-server":`^1.14.0`,tsx:`^4.19.2`,"@types/node":`^22.13.11`,"@types/bun":`^1.2.6`,"@elysiajs/node":`^1.2.6`,"@elysiajs/cors":`^1.2.0`,"@elysiajs/trpc":`^1.1.0`,elysia:`^1.2.25`,"@hono/trpc-server":`^0.3.4`,hono:`^4.7.6`,cors:`^2.8.5`,express:`^5.1.0`,"@types/express":`^5.0.1`,"@types/cors":`^2.8.17`,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.2.0`,"@orpc/client":`^1.2.0`,"@orpc/react-query":`^1.2.0`,"@orpc/solid-query":`^1.2.0`,"@orpc/vue-query":`^1.2.0`,"@orpc/svelte-query":`^1.2.0`,"@trpc/tanstack-react-query":`^11.0.0`,"@trpc/server":`^11.0.0`,"@trpc/client":`^11.0.0`,convex:`^1.23.0`,"@convex-dev/react-query":`^0.0.0-alpha.8`,"convex-svelte":`^0.0.11`,"@tanstack/svelte-query":`^5.74.4`,"@tanstack/react-query-devtools":`^5.69.0`,"@tanstack/react-query":`^5.69.0`,"@tanstack/solid-query":`^5.75.0`,"@tanstack/solid-query-devtools":`^5.75.0`},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-nativewind`,`native-unistyles`],y=i.includes(`solid`),b=i.some(e=>v.includes(e));if(b&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native-nativewind`&&e!==`native-unistyles`&&v.includes(e)),a=i.includes(`native-nativewind`)||i.includes(`native-unistyles`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(y&&!c){let t=[`@tanstack/solid-query`],n=[`@tanstack/solid-query-devtools`];if(d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}}if(c){if(d){let t=e.join(l,`package.json`);if(await h.pathExists(t))try{let e=[`convex`];i.includes(`tanstack-start`)&&e.push(`@convex-dev/react-query`),g&&e.push(`convex-svelte`),await M({dependencies:e,projectDir:l})}catch{}}if(f){let t=e.join(u,`package.json`);if(await h.pathExists(t))try{await M({dependencies:[`convex`],projectDir:u})}catch{}}let t=`@${r}/backend`,n=o===`npm`?`*`:`workspace:*`,a=async(e,t,n)=>{try{let r=await h.readJson(e);r.dependencies||={},r.dependencies[t]!==n&&(r.dependencies[t]=n,await h.writeJson(e,r,{spaces:2}))}catch{}};if(d){let r=e.join(l,`package.json`);await h.pathExists(r)&&await a(r,t,n)}if(f){let r=e.join(u,`package.json`);await h.pathExists(r)&&await a(r,t,n)}}}async function 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-nativewind`)||i.includes(`native-unistyles`))&&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-nativewind`)||c.includes(`native-unistyles`),h=c.includes(`next`),g=c.includes(`tanstack-start`),_=c.includes(`svelte`),v=c.includes(`solid`),y=c.includes(`nuxt`),b=n===`npm`?`npm run`:n,x=`3001`;return(f||_)&&(x=`5173`),`# ${t}
3
3
 
4
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
 
@@ -155,8 +155,8 @@ DATABASE_URL="your_connection_string"`)}async function Pe(n){let{projectName:r,p
155
155
  `)&&(r+=`
156
156
  `),r+=a),i&&await h.writeFile(t,r.trimEnd())}async function Ie(t){let{projectName:n,backend:r,frontend:i,database:a,orm:o,auth:s,examples:c,dbSetup:l,projectDir:u}=t,d=i.includes(`react-router`),f=i.includes(`tanstack-router`),p=i.includes(`tanstack-start`),m=i.includes(`next`),g=i.includes(`nuxt`),_=i.includes(`svelte`),v=i.includes(`solid`),y=d||f||p||m||g||v||_;if(y){let t=e.join(u,`apps/web`);if(await h.pathExists(t)){let n=`VITE_SERVER_URL`,i=`http://localhost:3000`;m?n=`NEXT_PUBLIC_SERVER_URL`:g?n=`NUXT_PUBLIC_SERVER_URL`:_&&(n=`PUBLIC_SERVER_URL`),r===`convex`&&(n=m?`NEXT_PUBLIC_CONVEX_URL`:g?`NUXT_PUBLIC_CONVEX_URL`:_?`PUBLIC_CONVEX_URL`:`VITE_CONVEX_URL`,i=`https://<YOUR_CONVEX_URL>`);let a=[{key:n,value:i,condition:!0}];await U(e.join(t,`.env`),a)}}if(i.includes(`native-nativewind`)||i.includes(`native-unistyles`)){let t=e.join(u,`apps/native`);if(await h.pathExists(t)){let n=`EXPO_PUBLIC_SERVER_URL`,i=`http://localhost:3000`;r===`convex`&&(n=`EXPO_PUBLIC_CONVEX_URL`,i=`https://<YOUR_CONVEX_URL>`);let a=[{key:n,value:i,condition:!0}];await U(e.join(t,`.env`),a)}}if(r===`convex`)return;let b=e.join(u,`apps/server`);if(!await h.pathExists(b))return;let x=e.join(b,`.env`),S=`http://localhost:3001`;(d||_)&&(S=`http://localhost:5173`);let C=null,w=l===`turso`||l===`prisma-postgres`||l===`mongodb-atlas`||l===`neon`;if(a!==`none`&&!w)switch(a){case`postgres`:C=`postgresql://postgres:postgres@localhost:5432/mydb?schema=public`;break;case`mysql`:C=`mysql://root:password@localhost:3306/mydb`;break;case`mongodb`:C=`mongodb://localhost:27017/mydatabase`;break;case`sqlite`:C=`file:./local.db`;break}let T=[{key:`CORS_ORIGIN`,value:S,condition:!0},{key:`BETTER_AUTH_SECRET`,value:le(),condition:!!s},{key:`BETTER_AUTH_URL`,value:`http://localhost:3000`,condition:!!s},{key:`DATABASE_URL`,value:C,condition:a!==`none`&&!w},{key:`GOOGLE_GENERATIVE_AI_API_KEY`,value:``,condition:c?.includes(`ai`)||!1}];await U(x,T)}async function Le(t){let{projectName:n,examples:r,frontend:i,backend:a,projectDir:o}=t;if(!(a===`convex`||!r||r.length===0||r[0]===`none`)&&r.includes(`ai`)){let t=e.join(o,`apps/web`),n=e.join(o,`apps/server`),r=await h.pathExists(t),s=await h.pathExists(n),c=i.includes(`nuxt`),l=i.includes(`svelte`);if(r){let e=[`ai`];c?e.push(`@ai-sdk/vue`):l&&e.push(`@ai-sdk/svelte`),await M({dependencies:e,projectDir:t})}s&&a!==`none`&&await M({dependencies:[`ai`,`@ai-sdk/google`],projectDir:n})}}async function Re({projectDir:e,packageManager:t,addons:n=[]}){let r=d();try{r.start(`Running ${t} install...`),await b({cwd:e,stderr:`inherit`})`${t} install`,r.stop(`Dependencies installed successfully`),(n.includes(`biome`)||n.includes(`husky`))&&await ze(e,t)}catch(e){r.stop(g.red(`Failed to install dependencies`)),e instanceof Error&&p.error(g.red(`Installation error: ${e.message}`))}}async function ze(e,t){let n=d();try{n.start(`Running Biome format check...`),await b({cwd:e,stderr:`inherit`})`${t} biome check --write .`,n.stop(`Biome check completed successfully`)}catch{n.stop(g.yellow(`Biome check encountered issues`)),o.warn(g.yellow(`Some files may need manual formatting`))}}function Be(e){let{database:t,projectName:n,relativePath:r,packageManager:i,depsInstalled:a,orm:o,addons:s,runtime:c,frontend:l,backend:u}=e,d=u===`convex`,f=i===`npm`?`npm run`:i,p=`cd ${r}`,h=s?.includes(`husky`)||s?.includes(`biome`),_=!d&&t!==`none`?Ue(t,o,f,c):``,v=s?.includes(`tauri`)?We(f):``,y=h?He(f):``,b=l?.includes(`native-nativewind`)||l?.includes(`native-unistyles`)?Ve(d):``,x=s?.includes(`pwa`)&&(l?.includes(`react-router`)||l?.includes(`tanstack-router`))?Ge():``,S=s?.includes(`starlight`)?Ke(f):``,C=l?.some(e=>[`tanstack-router`,`react-router`,`next`,`tanstack-start`,`nuxt`,`svelte`,`solid`].includes(e)),w=l?.includes(`native-nativewind`)||l?.includes(`native-unistyles`),T=i===`bun`&&w&&C?Je():``,E=!d&&t!==`none`&&o===`none`?qe():``,D=l?.includes(`react-router`),O=l?.includes(`svelte`),k=D||O?`5173`:`3001`,A=N(i,`taze -r`),j=`${g.bold(`Next steps`)}\n${g.cyan(`1.`)} ${p}\n`,M=2;a||(j+=`${g.cyan(`${M++}.`)} ${i} install\n`),d&&(j+=`${g.cyan(`${M++}.`)} ${f} dev:setup ${g.dim(`(this will guide you through Convex project setup)`)}\n`),j+=`${g.cyan(`${M++}.`)} ${f} dev\n\n`,j+=`${g.bold(`Your project will be available at:`)}\n`,C?j+=`${g.cyan(`•`)} Frontend: http://localhost:${k}\n`:!w&&!s?.includes(`starlight`)&&(j+=`${g.yellow(`NOTE:`)} You are creating a backend-only app (no frontend selected)\n`),d||(j+=`${g.cyan(`•`)} Backend API: http://localhost:3000\n`),s?.includes(`starlight`)&&(j+=`${g.cyan(`•`)} Docs: http://localhost:4321\n`),b&&(j+=`\n${b.trim()}\n`),_&&(j+=`\n${_.trim()}\n`),v&&(j+=`\n${v.trim()}\n`),y&&(j+=`\n${y.trim()}\n`),x&&(j+=`\n${x.trim()}\n`),S&&(j+=`\n${S.trim()}\n`),E&&(j+=`\n${E.trim()}\n`),T&&(j+=`\n${T.trim()}\n`),j+=`\n${g.bold(`Update all dependencies:
157
157
  `)}${g.cyan(A)}\n\n`,j+=`${g.bold(`Like Better-T Stack?`)} Please consider giving us a star on GitHub:\n`,j+=g.cyan(`https://github.com/AmanVarshney01/create-better-t-stack`),m.box(j)}function Ve(e){let t=e?`EXPO_PUBLIC_CONVEX_URL`:`EXPO_PUBLIC_SERVER_URL`,n=e?`https://<YOUR_CONVEX_URL>`:`http://<YOUR_LOCAL_IP>:3000`,r=`.env`,i=e?`your Convex deployment URL (find after running 'dev:setup')`:`your local IP address`;return`${g.yellow(`NOTE:`)} For Expo connectivity issues, update apps/native/${r} \nwith ${i}:\n${`${t}=${n}`}\n`}function He(e){return`${g.bold(`Linting and formatting:`)}\n${g.cyan(`•`)} Format and lint fix: ${`${e} check`}\n`}function Ue(e,t,n,r){let i=[];return t===`prisma`?(e===`sqlite`&&i.push(`${g.yellow(`NOTE:`)} Turso support with Prisma is in Early Access and requires additional setup.`,`Learn more at: https://www.prisma.io/docs/orm/overview/databases/turso`),r===`bun`&&i.push(`${g.yellow(`NOTE:`)} Prisma with Bun may require additional configuration. If you encounter errors,\nfollow the guidance provided in the error messages`),i.push(`${g.cyan(`•`)} Apply schema: ${`${n} db:push`}`),i.push(`${g.cyan(`•`)} Database UI: ${`${n} db:studio`}`)):t===`drizzle`?(i.push(`${g.cyan(`•`)} Apply schema: ${`${n} db:push`}`),i.push(`${g.cyan(`•`)} Database UI: ${`${n} db:studio`}`),e===`sqlite`&&i.push(`${g.cyan(`•`)} Start local DB (if needed): ${`cd apps/server && ${n} db:local`}`)):t===`none`&&i.push(`${g.yellow(`NOTE:`)} Manual database schema setup required.`),i.length?`${g.bold(`Database commands:`)}\n${i.join(`
158
- `)}`:``}function We(e){return`\n${g.bold(`Desktop app with Tauri:`)}\n${g.cyan(`•`)} Start desktop app: ${`cd apps/web && ${e} desktop:dev`}\n${g.cyan(`•`)} Build desktop app: ${`cd apps/web && ${e} desktop:build`}\n${g.yellow(`NOTE:`)} Tauri requires Rust and platform-specific dependencies.\nSee: https://v2.tauri.app/start/prerequisites/`}function Ge(){return`\n${g.bold(`PWA with React Router v7:`)}\n${g.yellow(`NOTE:`)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`}function Ke(e){return`\n${g.bold(`Documentation with Starlight:`)}\n${g.cyan(`•`)} Start docs site: ${`cd apps/docs && ${e} dev`}\n${g.cyan(`•`)} Build docs site: ${`cd apps/docs && ${e} build`}`}function qe(){return`\n${g.yellow(`WARNING:`)} Database selected without an ORM. Features requiring database access (e.g., examples, auth) need manual setup.`}function Je(){return`\n${g.yellow(`WARNING:`)} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.`}async function Ye(e,t){await Xe(e,t),t.backend===`convex`?await Qe(e,t):await Ze(e,t)}async function Xe(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.name=n.projectName,i.scripts||={};let a=i.scripts,s=n.backend===`convex`?`@${n.projectName}/backend`:`server`,c=``;n.addons.includes(`turborepo`)?c=`turbo -F ${s} dev`:n.packageManager===`bun`?c=`bun run --filter ${s} dev`:n.packageManager===`pnpm`?c=`pnpm --filter ${s} dev`:n.packageManager===`npm`&&(c=`npm run dev --workspace ${s}`);let l=``;n.packageManager===`pnpm`?l=`pnpm -r dev`:n.packageManager===`npm`?l=`npm run dev --workspaces`:n.packageManager===`bun`&&(l=`bun run --filter '*' dev`);let u=n.backend!==`convex`&&n.database!==`none`&&n.orm!==`none`&&n.orm!==`mongoose`;n.addons.includes(`turborepo`)?(a.dev=`turbo dev`,a.build=`turbo build`,a[`check-types`]=`turbo check-types`,a[`dev:native`]=`turbo -F native dev`,a[`dev:web`]=`turbo -F web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`turbo -F ${s} setup`),u&&(a[`db:push`]=`turbo -F ${s} db:push`,a[`db:studio`]=`turbo -F ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`turbo -F ${s} db:generate`,a[`db:migrate`]=`turbo -F ${s} db:migrate`))):n.packageManager===`pnpm`?(a.dev=l,a.build=`pnpm -r build`,a[`check-types`]=`pnpm -r check-types`,a[`dev:native`]=`pnpm --filter native dev`,a[`dev:web`]=`pnpm --filter web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`pnpm --filter ${s} setup`),u&&(a[`db:push`]=`pnpm --filter ${s} db:push`,a[`db:studio`]=`pnpm --filter ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`pnpm --filter ${s} db:generate`,a[`db:migrate`]=`pnpm --filter ${s} db:migrate`))):n.packageManager===`npm`?(a.dev=l,a.build=`npm run build --workspaces`,a[`check-types`]=`npm run check-types --workspaces`,a[`dev:native`]=`npm run dev --workspace native`,a[`dev:web`]=`npm run dev --workspace web`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`npm run setup --workspace ${s}`),u&&(a[`db:push`]=`npm run db:push --workspace ${s}`,a[`db:studio`]=`npm run db:studio --workspace ${s}`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`npm run db:generate --workspace ${s}`,a[`db:migrate`]=`npm run db:migrate --workspace ${s}`))):n.packageManager===`bun`&&(a.dev=l,a.build=`bun run --filter '*' build`,a[`check-types`]=`bun run --filter '*' check-types`,a[`dev:native`]=`bun run --filter native dev`,a[`dev:web`]=`bun run --filter web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`bun run --filter ${s} setup`),u&&(a[`db:push`]=`bun run --filter ${s} db:push`,a[`db:studio`]=`bun run --filter ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`bun run --filter ${s} db:generate`,a[`db:migrate`]=`bun run --filter ${s} db:migrate`))),n.addons.includes(`biome`)&&(a.check=`biome check --write .`),n.addons.includes(`husky`)&&(a.prepare=`husky`,i[`lint-staged`]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":[`biome check --write .`]});try{let{stdout:e}=await x(n.packageManager,[`-v`],{cwd:t});i.packageManager=`${n.packageManager}@${e.trim()}`}catch{o.warn(`Could not determine ${n.packageManager} version.`)}i.workspaces||=[];let d=i.workspaces;if(n.backend===`convex`){d.includes(`packages/*`)||d.push(`packages/*`);let e=n.frontend.length>0||n.addons.includes(`starlight`);e&&!d.includes(`apps/*`)&&d.push(`apps/*`)}else d.includes(`apps/*`)||d.push(`apps/*`),d.includes(`packages/*`)||d.push(`packages/*`);await h.writeJson(r,i,{spaces:2})}async function Ze(t,n){let r=e.join(t,`apps/server/package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts||={};let a=i.scripts;n.database!==`none`&&(n.database===`sqlite`&&n.orm===`drizzle`&&(a[`db:local`]=`turso dev --db-file local.db`),n.orm===`prisma`?(a[`db:push`]=`prisma db push --schema ./prisma/schema`,a[`db:studio`]=`prisma studio`,a[`db:generate`]=`prisma generate --schema ./prisma/schema`,a[`db:migrate`]=`prisma migrate dev`):n.orm===`drizzle`&&(a[`db:push`]=`drizzle-kit push`,a[`db:studio`]=`drizzle-kit studio`,a[`db:generate`]=`drizzle-kit generate`,a[`db:migrate`]=`drizzle-kit migrate`)),await h.writeJson(r,i,{spaces:2})}async function Qe(t,n){let r=e.join(t,`packages/backend/package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.name=`@${n.projectName}/backend`,i.scripts||={},await h.writeJson(r,i,{spaces:2})}async function $e(e,t){if(!t)return;let n=await b({cwd:e,reject:!1,stderr:`pipe`})`git --version`;if(n.exitCode!==0){o.warn(g.yellow(`Git is not installed`));return}let r=await b({cwd:e,reject:!1,stderr:`pipe`})`git init`;if(r.exitCode!==0)throw Error(`Git initialization failed: ${r.stderr}`)}async function et(t){let{projectName:n,runtime:r,backend:i,projectDir:a}=t;if(i===`convex`||i===`next`||r===`none`)return;let o=e.join(a,`apps/server`);await h.pathExists(o)&&(r===`bun`?await tt(o,i):r===`node`&&await nt(o,i))}async function tt(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts={...i.scripts,dev:`bun run --hot src/index.ts`,start:`bun run dist/src/index.js`},await h.writeJson(r,i,{spaces:2}),await M({devDependencies:[`@types/bun`],projectDir:t})}async function nt(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts={...i.scripts,dev:`tsx watch src/index.ts`,start:`node dist/src/index.js`},await h.writeJson(r,i,{spaces:2}),await M({devDependencies:[`tsx`,`@types/node`],projectDir:t}),n===`hono`?await M({dependencies:[`@hono/node-server`],projectDir:t}):n===`elysia`&&await M({dependencies:[`@elysiajs/node`],projectDir:t})}async function W(t,n,r){try{let i=await h.readFile(t,`utf-8`),a=w.compile(i),o=a(r);await h.ensureDir(e.dirname(n)),await h.writeFile(n,o)}catch(e){throw p.error(`Error processing template ${t}:`,e),Error(`Failed to process template ${t}`)}}w.registerHelper(`or`,(e,t)=>e||t),w.registerHelper(`eq`,(e,t)=>e===t),w.registerHelper(`includes`,(e,t)=>Array.isArray(e)&&e.includes(t));async function G(t,n,r,i,a=!0){let o=await C(t,{cwd:n,dot:!0,onlyFiles:!0,absolute:!1});for(let t of o){let o=e.join(n,t),s=t;t.endsWith(`.hbs`)&&(s=t.slice(0,-4));let c=e.basename(t);c===`_gitignore`?s=e.join(e.dirname(t),`.gitignore`):c===`_npmrc`&&(s=e.join(e.dirname(t),`.npmrc`));let l=e.join(r,s);try{if(await h.ensureDir(e.dirname(l)),!a&&await h.pathExists(l))continue;o.endsWith(`.hbs`)?await W(o,l,i):await h.copy(o,l,{overwrite:!0})}catch{}}}async function rt(t,n){let r=e.join(k,`templates/base`);await G([`**/*`],r,t,n),await h.ensureDir(e.join(t,`packages`))}async function it(t,n){let r=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),i=n.frontend.includes(`nuxt`),a=n.frontend.includes(`svelte`),o=n.frontend.includes(`solid`),s=n.frontend.includes(`native-nativewind`),c=n.frontend.includes(`native-unistyles`),l=s||c,u=n.backend===`convex`;if(r||i||a||o){let s=e.join(t,`apps/web`);if(await h.ensureDir(s),r){let t=e.join(k,`templates/frontend/react/web-base`);await h.pathExists(t)&&await G(`**/*`,t,s,n);let r=n.frontend.find(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e));if(r){let t=e.join(k,`templates/frontend/react/${r}`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/react/base`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}}else if(i){let t=e.join(k,`templates/frontend/nuxt`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/nuxt`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}else if(a){let t=e.join(k,`templates/frontend/svelte`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/svelte`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}else if(o){let t=e.join(k,`templates/frontend/solid`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/solid`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}}if(s||c){let r=e.join(t,`apps/native`);await h.ensureDir(r);let i=e.join(k,`templates/frontend/native/native-base`);await h.pathExists(i)&&await G(`**/*`,i,r,n);let a=``;s?a=`nativewind`:c&&(a=`unistyles`);let o=e.join(k,`templates/frontend/native/${a}`);if(await h.pathExists(o)&&await G(`**/*`,o,r,n,!0),!u&&(n.api===`trpc`||n.api===`orpc`)){let t=e.join(k,`templates/api/${n.api}/native`);await h.pathExists(t)&&await G(`**/*`,t,r,n)}}}async function at(t,n){if(n.backend===`none`)return;let r=e.join(t,`apps/server`);if(n.backend===`convex`){await h.pathExists(r)&&await h.remove(r);let i=e.join(t,`packages/backend`),a=e.join(k,`templates/backend/convex/packages/backend`);await h.ensureDir(i),await h.pathExists(a)&&await G(`**/*`,a,i,n);return}await h.ensureDir(r);let i=e.join(k,`templates/backend/server/server-base`);await h.pathExists(i)&&await G(`**/*`,i,r,n);let a=e.join(k,`templates/backend/server/${n.backend}`);if(await h.pathExists(a)&&await G(`**/*`,a,r,n,!0),n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/server/base`);await h.pathExists(t)&&await G(`**/*`,t,r,n,!0);let i=e.join(k,`templates/api/${n.api}/server/${n.backend}`);await h.pathExists(i)&&await G(`**/*`,i,r,n,!0)}}async function ot(t,n){if(n.backend===`convex`||n.orm===`none`||n.database===`none`)return;let r=e.join(t,`apps/server`);await h.ensureDir(r);let i=e.join(k,`templates/db/${n.orm}/${n.database}`);await h.pathExists(i)&&await G(`**/*`,i,r,n)}async function st(t,n){if(n.backend===`convex`||!n.auth)return;let r=e.join(t,`apps/server`),i=e.join(t,`apps/web`),a=e.join(t,`apps/native`),o=await h.pathExists(r),s=await h.pathExists(i),c=await h.pathExists(a),l=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),u=n.frontend.includes(`nuxt`),d=n.frontend.includes(`svelte`),f=n.frontend.includes(`solid`),p=n.frontend.includes(`native-nativewind`),m=n.frontend.includes(`native-unistyles`),g=p||m;if(o){let t=e.join(k,`templates/auth/server/base`);if(await h.pathExists(t)&&await G(`**/*`,t,r,n),n.backend===`next`){let t=e.join(k,`templates/auth/server/next`);await h.pathExists(t)&&await G(`**/*`,t,r,n)}if(n.orm!==`none`&&n.database!==`none`){let t=n.orm,i=n.database,a=``;t===`drizzle`?a=e.join(k,`templates/auth/server/db/drizzle/${i}`):t===`prisma`?a=e.join(k,`templates/auth/server/db/prisma/${i}`):t===`mongoose`&&(a=e.join(k,`templates/auth/server/db/mongoose/${i}`)),a&&await h.pathExists(a)&&await G(`**/*`,a,r,n)}}if((l||u||d||f)&&s){if(l){let t=e.join(k,`templates/auth/web/react/base`);await h.pathExists(t)&&await G(`**/*`,t,i,n);let r=n.frontend.find(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e));if(r){let t=e.join(k,`templates/auth/web/react/${r}`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}}else if(u){let t=e.join(k,`templates/auth/web/nuxt`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}else if(d){if(n.api===`orpc`){let t=e.join(k,`templates/auth/web/svelte`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}}else if(f&&n.api===`orpc`){let t=e.join(k,`templates/auth/web/solid`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}}if(g&&c){let t=e.join(k,`templates/auth/native/native-base`);await h.pathExists(t)&&await G(`**/*`,t,a,n);let r=``;if(p?r=`nativewind`:m&&(r=`unistyles`),r){let t=e.join(k,`templates/auth/native/${r}`);await h.pathExists(t)&&await G(`**/*`,t,a,n)}}}async function ct(t,n){if(!(!n.addons||n.addons.length===0))for(let r of n.addons){if(r===`none`)continue;let i=e.join(k,`templates/addons/${r}`),a=t;if(r===`pwa`&&(i=e.join(k,`templates/addons/pwa/apps/web`),a=e.join(t,`apps/web`),!await h.pathExists(a)))continue;await h.pathExists(i)&&await G(`**/*`,i,a,n)}}async function lt(t,n){if(!n.examples||n.examples.length===0||n.examples[0]===`none`)return;let r=e.join(t,`apps/server`),i=e.join(t,`apps/web`),a=await h.pathExists(r),o=await h.pathExists(i),s=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),c=n.frontend.includes(`nuxt`),l=n.frontend.includes(`svelte`),u=n.frontend.includes(`solid`);for(let t of n.examples){if(t===`none`)continue;let d=e.join(k,`templates/examples/${t}`);if(a&&n.backend!==`convex`&&n.backend!==`none`){let i=e.join(d,`server`);if(t===`ai`&&n.backend===`next`){let t=e.join(i,`next`);await h.pathExists(t)&&await G(`**/*`,t,r,n,!1)}if(n.orm!==`none`&&n.database!==`none`){let t=e.join(i,n.orm,`base`);await h.pathExists(t)&&await G(`**/*`,t,r,n,!1);let a=e.join(i,n.orm,n.database);await h.pathExists(a)&&await G(`**/*`,a,r,n,!1)}let a=[`${n.orm}/**`];t===`ai`&&n.backend===`next`&&a.push(`next/**`);let o=await C([`**/*.ts`,`**/*.hbs`],{cwd:i,onlyFiles:!0,deep:1,ignore:a});for(let t of o){let a=e.join(i,t),o=e.join(r,t.replace(`.hbs`,``));try{a.endsWith(`.hbs`)?await W(a,o,n):await h.pathExists(o)||await h.copy(a,o,{overwrite:!1})}catch{}}}if(o){if(s){let t=e.join(d,`web/react`);if(await h.pathExists(t)){let r=n.frontend.find(e=>[`next`,`react-router`,`tanstack-router`,`tanstack-start`].includes(e));if(r){let a=e.join(t,r);await h.pathExists(a)&&await G(`**/*`,a,i,n,!1)}}}else if(c){let t=e.join(d,`web/nuxt`);await h.pathExists(t)&&await G(`**/*`,t,i,n,!1)}else if(l){let t=e.join(d,`web/svelte`);await h.pathExists(t)&&await G(`**/*`,t,i,n,!1)}else if(u){let t=e.join(d,`web/solid`);await h.pathExists(t)&&await G(`**/*`,t,i,n,!1)}}}}async function ut(t,n){let r=e.join(k,`templates/extras`),i=n.frontend.includes(`native-nativewind`),a=n.frontend.includes(`native-unistyles`),o=i||a;if(n.packageManager===`pnpm`){let n=e.join(r,`pnpm-workspace.yaml`),i=e.join(t,`pnpm-workspace.yaml`);await h.pathExists(n)&&await h.copy(n,i)}if(n.packageManager===`pnpm`&&(o||n.frontend.includes(`nuxt`))){let i=e.join(r,`_npmrc.hbs`),a=e.join(t,`.npmrc`);await h.pathExists(i)&&await W(i,a,n)}}async function dt(e){let n=e.projectDir,r=e.backend===`convex`;try{return await h.ensureDir(n),await rt(n,e),await it(n,e),await at(n,e),r||(await ot(n,e),await st(n,e)),e.examples.length>0&&e.examples[0]!==`none`&&await lt(n,e),await ct(n,e),await se(e),r||(await ue(e),await Fe(e),await et(e),e.examples.length>0&&e.examples[0]!==`none`&&await Le(e)),e.addons.length>0&&e.addons[0]!==`none`&&await ne(e),!r&&e.auth&&await ce(e),await ut(n,e),await Ie(e),await Ye(n,e),await de(n,e),await $e(n,e.git),o.success(`Project template successfully scaffolded!`),e.install&&await Re({projectDir:n,packageManager:e.packageManager,addons:e.addons}),Be({...e,depsInstalled:e.install}),n}catch(e){e instanceof Error?(t(g.red(`Error during project creation: ${e.message}`)),console.error(e.stack),process.exit(1)):(t(g.red(`An unexpected error occurred: ${String(e)}`)),console.error(e),process.exit(1))}}async function ft(e,n){if(e!==void 0)return e;let r=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`solid`),i=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`nuxt`)||n?.includes(`svelte`)||n?.includes(`solid`),o=[{value:`turborepo`,label:`Turborepo (Recommended)`,hint:`Optimize builds for monorepos`},{value:`starlight`,label:`Starlight`,hint:`Add Astro Starlight documentation site`},{value:`biome`,label:`Biome`,hint:`Add Biome for linting and formatting`},{value:`husky`,label:`Husky`,hint:`Add Git hooks with Husky, lint-staged (requires Biome)`},{value:`pwa`,label:`PWA (Progressive Web App)`,hint:`Make your app installable and work offline`},{value:`tauri`,label:`Tauri Desktop App`,hint:`Build native desktop apps from your web frontend`}],c=o.filter(e=>e.value===`pwa`?r:e.value===`tauri`?i:!0),l=A.addons.filter(e=>c.some(t=>t.value===e)),u=await s({message:`Select addons`,options:c,initialValues:l,required:!1});return a(u)&&(t(g.red(`Operation cancelled`)),process.exit(0)),u.includes(`husky`)&&!u.includes(`biome`)&&u.push(`biome`),u}async function pt(e,n,r){if(r===`convex`||r===`none`)return`none`;if(e)return e;let i=n?.includes(`nuxt`),o=n?.includes(`svelte`),s=n?.includes(`solid`),c=[{value:`trpc`,label:`tRPC`,hint:`End-to-end typesafe APIs made easy`},{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs that adhere to OpenAPI standards`}];(i||o||s)&&(c=[{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs (Required for ${i?`Nuxt`:o?`Svelte`:`Solid`} frontend)`}]);let l=await u({message:`Select API type`,options:c,initialValue:i||o||s?`orpc`:A.api});return a(l)&&(t(g.red(`Operation cancelled`)),process.exit(0)),(i||o||s)&&l!==`orpc`?`orpc`:l}async function mt(e,r,i){if(i===`convex`||!r)return!1;if(e!==void 0)return e;let o=await n({message:`Add authentication with Better-Auth?`,initialValue:A.auth});return a(o)&&(t(g.red(`Operation cancelled`)),process.exit(0)),o}async function ht(e,n){if(e!==void 0)return e;let r=n?.some(e=>e===`nuxt`||e===`solid`),i=[{value:`hono`,label:`Hono`,hint:`Lightweight, ultrafast web framework`},{value:`next`,label:`Next.js`,hint:`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`}];r||i.push({value:`convex`,label:`Convex`,hint:`Reactive backend-as-a-service platform`}),i.push({value:`none`,label:`None`,hint:`No backend server (e.g., for a static site or client-only app)`});let o=A.backend;r&&o===`convex`&&(o=`hono`);let s=await u({message:`Select backend framework`,options:i,initialValue:o});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function gt(e,n){if(n===`convex`||n===`none`)return`none`;if(e!==void 0)return e;let r=await u({message:`Select database`,options:[{value:`none`,label:`None`,hint:`No database setup`},{value:`sqlite`,label:`SQLite`,hint:`lightweight, server-less, embedded relational database`},{value:`postgres`,label:`PostgreSQL`,hint:`powerful, open source object-relational database system`},{value:`mysql`,label:`MySQL`,hint:`popular open-source relational database system`},{value:`mongodb`,label:`MongoDB`,hint:`open-source NoSQL database that stores data in JSON-like documents called BSON`}],initialValue:A.database});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function _t(e,n,r,i){if(i===`convex`)return`none`;if(n!==void 0)return n;if(e===`none`||e===`sqlite`&&r===`prisma`)return`none`;let o=[];if(e===`sqlite`)o=[{value:`turso`,label:`Turso`,hint:`SQLite for Production. Powered by libSQL`},{value:`none`,label:`None`,hint:`Manual setup`}];else if(e===`postgres`)o=[{value:`neon`,label:`Neon Postgres`,hint:`Serverless Postgres with branching capability`},...r===`prisma`?[{value:`prisma-postgres`,label:`Prisma Postgres`,hint:`Instant Postgres for Global Applications`}]:[],{value:`none`,label:`None`,hint:`Manual setup`}];else if(e===`mongodb`)o=[{value:`mongodb-atlas`,label:`MongoDB Atlas`,hint:`The most effective way to deploy MongoDB`},{value:`none`,label:`None`,hint:`Manual setup`}];else return`none`;let s=await u({message:`Select ${e} setup option`,options:o,initialValue:`none`});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function vt(e,n,r,i){if(e!==void 0)return e;if(i===`convex`)return[`todo`];if(i===`none`||n===`none`)return[];let o=r&&r.length===1&&(r[0]===`native-nativewind`||r[0]===`native-unistyles`);if(o)return[];let c=r?.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`,`solid`].includes(e))??!1,l=!r||r.length===0;if(!c&&!l)return[];let u=[],d=[{value:`todo`,label:`Todo App`,hint:`A simple CRUD example app`}];return i!==`elysia`&&!r?.includes(`solid`)&&d.push({value:`ai`,label:`AI Chat`,hint:`A simple AI chat interface using AI SDK`}),u=await s({message:`Include examples`,options:d,required:!1,initialValues:A.examples}),a(u)&&(t(g.red(`Operation cancelled`)),process.exit(0)),u}async function yt(e,n){if(e!==void 0)return e;let r=await s({message:`Select platforms to develop for`,options:[{value:`web`,label:`Web`,hint:`React, Vue or Svelte Web Application`},{value:`native`,label:`Native`,hint:`Create a React Native/Expo app`}],required:!1,initialValues:[`web`]});a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0));let i=[];if(r.includes(`web`)){let e=[{value:`tanstack-router`,label:`TanStack Router`,hint:`Modern and scalable routing for React Applications`},{value:`react-router`,label:`React Router`,hint:`A user‑obsessed, standards‑focused, multi‑strategy router`},{value:`next`,label:`Next.js`,hint:`The React Framework for the Web`},{value:`nuxt`,label:`Nuxt`,hint:`The Progressive Web Framework for Vue.js`},{value:`svelte`,label:`Svelte`,hint:`web development for the rest of us`},{value:`solid`,label:`Solid`,hint:`Simple and performant reactivity for building user interfaces`},{value:`tanstack-start`,label:`TanStack Start (beta)`,hint:`SSR, Server Functions, API Routes and more with TanStack Router`}],r=e.filter(e=>n===`convex`?e.value!==`nuxt`&&e.value!==`solid`:!0),o=await u({message:`Choose frontend framework`,options:r,initialValue:A.frontend[0]});a(o)&&(t(g.red(`Operation cancelled`)),process.exit(0)),i.push(o)}if(r.includes(`native`)){let e=await u({message:`Choose native framework`,options:[{value:`native-nativewind`,label:`NativeWind`,hint:`Use Tailwind CSS for React Native`},{value:`native-unistyles`,label:`Unistyles`,hint:`Consistent styling for React Native`}],initialValue:`native-nativewind`});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),i.push(e)}return i}async function bt(e){if(e!==void 0)return e;let r=await n({message:`Initialize git repository?`,initialValue:A.git});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function xt(e){if(e!==void 0)return e;let r=await n({message:`Install dependencies?`,initialValue:A.install});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}const K={prisma:{value:`prisma`,label:`Prisma`,hint:`Powerful, feature-rich ORM`},mongoose:{value:`mongoose`,label:`Mongoose`,hint:`Elegant object modeling tool`},drizzle:{value:`drizzle`,label:`Drizzle`,hint:`Lightweight and performant TypeScript ORM`}};async function St(e,n,r,i){if(i===`convex`||!n)return`none`;if(e!==void 0)return e;let o=[...r===`mongodb`?[K.prisma,K.mongoose]:[K.drizzle,K.prisma]],s=await u({message:`Select ORM`,options:o,initialValue:r===`mongodb`?`prisma`:A.orm});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function Ct(e){if(e!==void 0)return e;let n=E(),r=await u({message:`Choose package manager`,options:[{value:`npm`,label:`npm`,hint:`Node Package Manager`},{value:`pnpm`,label:`pnpm`,hint:`Fast, disk space efficient package manager`},{value:`bun`,label:`bun`,hint:`All-in-one JavaScript runtime & toolkit`}],initialValue:n});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function wt(e,n){if(n===`convex`||n===`none`)return`none`;if(e!==void 0)return e;if(n===`next`)return`node`;let r=await u({message:`Select runtime`,options:[{value:`bun`,label:`Bun`,hint:`Fast all-in-one JavaScript runtime`},{value:`node`,label:`Node.js`,hint:`Traditional Node.js runtime`}],initialValue:A.runtime});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function Tt(e,n,i,a){let o=await r({frontend:({results:t})=>yt(e.frontend,e.backend),backend:({results:t})=>ht(e.backend,t.frontend),runtime:({results:t})=>wt(e.runtime,t.backend),database:({results:t})=>gt(e.database,t.backend),orm:({results:t})=>St(e.orm,t.database!==`none`,t.database,t.backend),api:({results:t})=>pt(e.api,t.frontend,t.backend),auth:({results:t})=>mt(e.auth,t.database!==`none`,t.backend),addons:({results:t})=>ft(e.addons,t.frontend),examples:({results:t})=>vt(e.examples,t.database,t.frontend,t.backend),dbSetup:({results:t})=>_t(t.database??`none`,e.dbSetup,t.orm,t.backend),git:()=>bt(e.git),packageManager:()=>Ct(e.packageManager),install:()=>xt(e.install)},{onCancel:()=>{t(g.red(`Operation cancelled`)),process.exit(0)}});return o.backend===`convex`&&(o.runtime=`none`,o.database=`none`,o.orm=`none`,o.api=`none`,o.auth=!1,o.dbSetup=`none`),o.backend===`none`&&(o.runtime=`none`,o.database=`none`,o.orm=`none`,o.api=`none`,o.auth=!1,o.dbSetup=`none`,o.examples=[]),{projectName:n,projectDir:i,relativePath:a,frontend:o.frontend,backend:o.backend,runtime:o.runtime,database:o.database,orm:o.orm,auth:o.auth,addons:o.addons,examples:o.examples,git:o.git,packageManager:o.packageManager,install:o.install,dbSetup:o.dbSetup,api:o.api}}const Et=[`<`,`>`,`:`,`"`,`|`,`?`,`*`],q=255;function J(e){if(e!==`.`){if(!e)return`Project name cannot be empty`;if(e.length>q)return`Project name must be less than ${q} characters`;if(Et.some(t=>e.includes(t)))return`Project name contains invalid characters`;if(e.startsWith(`.`)||e.startsWith(`-`))return`Project name cannot start with a dot or dash`;if(e.toLowerCase()===`node_modules`)return`Project name is reserved`}}async function Y(n){if(n){if(n===`.`)return n;let t=e.basename(n),r=J(t);if(!r)return n}let r=!1,i=``,o=A.projectName,s=1;for(;h.pathExistsSync(e.resolve(process.cwd(),o))&&h.readdirSync(e.resolve(process.cwd(),o)).length>0;)o=`${A.projectName}-${s}`,s++;for(;!r;){let s=await f({message:`Enter your project name or path (relative to current directory)`,placeholder:o,initialValue:n,defaultValue:o,validate:t=>{let n=t.trim()||o,r=e.basename(n),i=J(r);if(i)return i;if(n!==`.`){let t=e.resolve(process.cwd(),n);if(!t.startsWith(process.cwd()))return`Project path must be within current directory`}}});a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),i=s||o,r=!0}return i}function X(e){let t=[];if(e.projectName&&t.push(`${g.blue(`Project Name:`)} ${e.projectName}`),e.frontend!==void 0){let n=Array.isArray(e.frontend)?e.frontend:[e.frontend],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Frontend:`)} ${r}`)}if(e.backend!==void 0&&t.push(`${g.blue(`Backend Framework:`)} ${String(e.backend)}`),e.runtime!==void 0&&t.push(`${g.blue(`Runtime:`)} ${String(e.runtime)}`),e.api!==void 0&&t.push(`${g.blue(`API:`)} ${String(e.api)}`),e.database!==void 0&&t.push(`${g.blue(`Database:`)} ${String(e.database)}`),e.orm!==void 0&&t.push(`${g.blue(`ORM:`)} ${String(e.orm)}`),e.auth!==void 0){let n=typeof e.auth==`boolean`?e.auth?`Yes`:`No`:String(e.auth);t.push(`${g.blue(`Authentication:`)} ${n}`)}if(e.addons!==void 0){let n=Array.isArray(e.addons)?e.addons:[e.addons],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Addons:`)} ${r}`)}if(e.examples!==void 0){let n=Array.isArray(e.examples)?e.examples:[e.examples],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Examples:`)} ${r}`)}if(e.git!==void 0){let n=typeof e.git==`boolean`?e.git?`Yes`:`No`:String(e.git);t.push(`${g.blue(`Git Init:`)} ${n}`)}if(e.packageManager!==void 0&&t.push(`${g.blue(`Package Manager:`)} ${String(e.packageManager)}`),e.install!==void 0){let n=typeof e.install==`boolean`?e.install?`Yes`:`No`:String(e.install);t.push(`${g.blue(`Install Dependencies:`)} ${n}`)}return e.dbSetup!==void 0&&t.push(`${g.blue(`Database Setup:`)} ${String(e.dbSetup)}`),t.length===0?g.yellow(`No configuration selected.`):t.join(`
159
- `)}function Dt(e){let t=[];e.database===`none`?t.push(`--database none`):(t.push(`--database ${e.database}`),e.orm&&t.push(`--orm ${e.orm}`),e.dbSetup&&t.push(`--db-setup ${e.dbSetup}`)),e.api&&t.push(`--api ${e.api}`),t.push(e.auth?`--auth`:`--no-auth`),t.push(e.git?`--git`:`--no-git`),t.push(e.install?`--install`:`--no-install`),e.runtime&&t.push(`--runtime ${e.runtime}`),e.backend&&t.push(`--backend ${e.backend}`),e.frontend&&e.frontend.length>0&&t.push(`--frontend ${e.frontend.join(` `)}`),e.addons&&e.addons.length>0?t.push(`--addons ${e.addons.join(` `)}`):t.push(`--addons none`),e.examples&&e.examples.length>0?t.push(`--examples ${e.examples.join(` `)}`):t.push(`--examples none`),e.packageManager&&t.push(`--package-manager ${e.packageManager}`);let n=``,r=e.packageManager;r===`npm`?n=`npx create-better-t-stack@latest`:r===`pnpm`?n=`pnpm create better-t-stack@latest`:r===`bun`&&(n=`bun create better-t-stack@latest`);let i=e.relativePath?` ${e.relativePath}`:``;return`${n}${i} ${t.join(` `)}`}const Ot=()=>{let t=e.join(k,`package.json`),n=h.readJSONSync(t);return n.version??`1.0.0`},Z=`
158
+ `)}`:``}function We(e){return`\n${g.bold(`Desktop app with Tauri:`)}\n${g.cyan(`•`)} Start desktop app: ${`cd apps/web && ${e} desktop:dev`}\n${g.cyan(`•`)} Build desktop app: ${`cd apps/web && ${e} desktop:build`}\n${g.yellow(`NOTE:`)} Tauri requires Rust and platform-specific dependencies.\nSee: https://v2.tauri.app/start/prerequisites/`}function Ge(){return`\n${g.bold(`PWA with React Router v7:`)}\n${g.yellow(`NOTE:`)} There is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809`}function Ke(e){return`\n${g.bold(`Documentation with Starlight:`)}\n${g.cyan(`•`)} Start docs site: ${`cd apps/docs && ${e} dev`}\n${g.cyan(`•`)} Build docs site: ${`cd apps/docs && ${e} build`}`}function qe(){return`\n${g.yellow(`WARNING:`)} Database selected without an ORM. Features requiring database access (e.g., examples, auth) need manual setup.`}function Je(){return`\n${g.yellow(`WARNING:`)} 'bun' might cause issues with web + native apps in a monorepo. Use 'pnpm' if problems arise.`}async function Ye(e,t){await Xe(e,t),t.backend===`convex`?await Qe(e,t):await Ze(e,t)}async function Xe(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.name=n.projectName,i.scripts||={};let a=i.scripts,s=n.backend===`convex`?`@${n.projectName}/backend`:`server`,c=``;n.addons.includes(`turborepo`)?c=`turbo -F ${s} dev`:n.packageManager===`bun`?c=`bun run --filter ${s} dev`:n.packageManager===`pnpm`?c=`pnpm --filter ${s} dev`:n.packageManager===`npm`&&(c=`npm run dev --workspace ${s}`);let l=``;n.packageManager===`pnpm`?l=`pnpm -r dev`:n.packageManager===`npm`?l=`npm run dev --workspaces`:n.packageManager===`bun`&&(l=`bun run --filter '*' dev`);let u=n.backend!==`convex`&&n.database!==`none`&&n.orm!==`none`&&n.orm!==`mongoose`;n.addons.includes(`turborepo`)?(a.dev=`turbo dev`,a.build=`turbo build`,a[`check-types`]=`turbo check-types`,a[`dev:native`]=`turbo -F native dev`,a[`dev:web`]=`turbo -F web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`turbo -F ${s} setup`),u&&(a[`db:push`]=`turbo -F ${s} db:push`,a[`db:studio`]=`turbo -F ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`turbo -F ${s} db:generate`,a[`db:migrate`]=`turbo -F ${s} db:migrate`))):n.packageManager===`pnpm`?(a.dev=l,a.build=`pnpm -r build`,a[`check-types`]=`pnpm -r check-types`,a[`dev:native`]=`pnpm --filter native dev`,a[`dev:web`]=`pnpm --filter web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`pnpm --filter ${s} setup`),u&&(a[`db:push`]=`pnpm --filter ${s} db:push`,a[`db:studio`]=`pnpm --filter ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`pnpm --filter ${s} db:generate`,a[`db:migrate`]=`pnpm --filter ${s} db:migrate`))):n.packageManager===`npm`?(a.dev=l,a.build=`npm run build --workspaces`,a[`check-types`]=`npm run check-types --workspaces`,a[`dev:native`]=`npm run dev --workspace native`,a[`dev:web`]=`npm run dev --workspace web`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`npm run setup --workspace ${s}`),u&&(a[`db:push`]=`npm run db:push --workspace ${s}`,a[`db:studio`]=`npm run db:studio --workspace ${s}`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`npm run db:generate --workspace ${s}`,a[`db:migrate`]=`npm run db:migrate --workspace ${s}`))):n.packageManager===`bun`&&(a.dev=l,a.build=`bun run --filter '*' build`,a[`check-types`]=`bun run --filter '*' check-types`,a[`dev:native`]=`bun run --filter native dev`,a[`dev:web`]=`bun run --filter web dev`,a[`dev:server`]=c,n.backend===`convex`&&(a[`dev:setup`]=`bun run --filter ${s} setup`),u&&(a[`db:push`]=`bun run --filter ${s} db:push`,a[`db:studio`]=`bun run --filter ${s} db:studio`,(n.orm===`prisma`||n.orm===`drizzle`)&&(a[`db:generate`]=`bun run --filter ${s} db:generate`,a[`db:migrate`]=`bun run --filter ${s} db:migrate`))),n.addons.includes(`biome`)&&(a.check=`biome check --write .`),n.addons.includes(`husky`)&&(a.prepare=`husky`,i[`lint-staged`]={"*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}":[`biome check --write .`]});try{let{stdout:e}=await x(n.packageManager,[`-v`],{cwd:t});i.packageManager=`${n.packageManager}@${e.trim()}`}catch{o.warn(`Could not determine ${n.packageManager} version.`)}i.workspaces||=[];let d=i.workspaces;if(n.backend===`convex`){d.includes(`packages/*`)||d.push(`packages/*`);let e=n.frontend.length>0||n.addons.includes(`starlight`);e&&!d.includes(`apps/*`)&&d.push(`apps/*`)}else d.includes(`apps/*`)||d.push(`apps/*`),d.includes(`packages/*`)||d.push(`packages/*`);await h.writeJson(r,i,{spaces:2})}async function Ze(t,n){let r=e.join(t,`apps/server/package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts||={};let a=i.scripts;n.database!==`none`&&(n.database===`sqlite`&&n.orm===`drizzle`&&(a[`db:local`]=`turso dev --db-file local.db`),n.orm===`prisma`?(a[`db:push`]=`prisma db push --schema ./prisma/schema`,a[`db:studio`]=`prisma studio`,a[`db:generate`]=`prisma generate --schema ./prisma/schema`,a[`db:migrate`]=`prisma migrate dev`):n.orm===`drizzle`&&(a[`db:push`]=`drizzle-kit push`,a[`db:studio`]=`drizzle-kit studio`,a[`db:generate`]=`drizzle-kit generate`,a[`db:migrate`]=`drizzle-kit migrate`)),await h.writeJson(r,i,{spaces:2})}async function Qe(t,n){let r=e.join(t,`packages/backend/package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.name=`@${n.projectName}/backend`,i.scripts||={},await h.writeJson(r,i,{spaces:2})}async function $e(e,t){if(!t)return;let n=await b({cwd:e,reject:!1,stderr:`pipe`})`git --version`;if(n.exitCode!==0){o.warn(g.yellow(`Git is not installed`));return}let r=await b({cwd:e,reject:!1,stderr:`pipe`})`git init`;if(r.exitCode!==0)throw Error(`Git initialization failed: ${r.stderr}`)}async function et(t){let{projectName:n,runtime:r,backend:i,projectDir:a}=t;if(i===`convex`||i===`next`||r===`none`)return;let o=e.join(a,`apps/server`);await h.pathExists(o)&&(r===`bun`?await tt(o,i):r===`node`&&await nt(o,i))}async function tt(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts={...i.scripts,dev:`bun run --hot src/index.ts`,start:`bun run dist/src/index.js`},await h.writeJson(r,i,{spaces:2}),await M({devDependencies:[`@types/bun`],projectDir:t})}async function nt(t,n){let r=e.join(t,`package.json`);if(!await h.pathExists(r))return;let i=await h.readJson(r);i.scripts={...i.scripts,dev:`tsx watch src/index.ts`,start:`node dist/src/index.js`},await h.writeJson(r,i,{spaces:2}),await M({devDependencies:[`tsx`,`@types/node`],projectDir:t}),n===`hono`?await M({dependencies:[`@hono/node-server`],projectDir:t}):n===`elysia`&&await M({dependencies:[`@elysiajs/node`],projectDir:t})}async function W(t,n,r){try{let i=await h.readFile(t,`utf-8`),a=w.compile(i),o=a(r);await h.ensureDir(e.dirname(n)),await h.writeFile(n,o)}catch(e){throw p.error(`Error processing template ${t}:`,e),Error(`Failed to process template ${t}`)}}w.registerHelper(`or`,(e,t)=>e||t),w.registerHelper(`eq`,(e,t)=>e===t),w.registerHelper(`includes`,(e,t)=>Array.isArray(e)&&e.includes(t));async function G(t,n,r,i,a=!0){let o=await C(t,{cwd:n,dot:!0,onlyFiles:!0,absolute:!1});for(let t of o){let o=e.join(n,t),s=t;t.endsWith(`.hbs`)&&(s=t.slice(0,-4));let c=e.basename(t);c===`_gitignore`?s=e.join(e.dirname(t),`.gitignore`):c===`_npmrc`&&(s=e.join(e.dirname(t),`.npmrc`));let l=e.join(r,s);try{if(await h.ensureDir(e.dirname(l)),!a&&await h.pathExists(l))continue;o.endsWith(`.hbs`)?await W(o,l,i):await h.copy(o,l,{overwrite:!0})}catch{}}}async function rt(t,n){let r=e.join(k,`templates/base`);await G([`**/*`],r,t,n),await h.ensureDir(e.join(t,`packages`))}async function it(t,n){let r=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),i=n.frontend.includes(`nuxt`),a=n.frontend.includes(`svelte`),o=n.frontend.includes(`solid`),s=n.frontend.includes(`native-nativewind`),c=n.frontend.includes(`native-unistyles`),l=s||c,u=n.backend===`convex`;if(r||i||a||o){let s=e.join(t,`apps/web`);if(await h.ensureDir(s),r){let t=e.join(k,`templates/frontend/react/web-base`);await h.pathExists(t)&&await G(`**/*`,t,s,n);let r=n.frontend.find(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e));if(r){let t=e.join(k,`templates/frontend/react/${r}`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/react/base`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}}else if(i){let t=e.join(k,`templates/frontend/nuxt`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/nuxt`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}else if(a){let t=e.join(k,`templates/frontend/svelte`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/svelte`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}else if(o){let t=e.join(k,`templates/frontend/solid`);if(await h.pathExists(t)&&await G(`**/*`,t,s,n),!u&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/solid`);await h.pathExists(t)&&await G(`**/*`,t,s,n)}}}if(s||c){let r=e.join(t,`apps/native`);await h.ensureDir(r);let i=e.join(k,`templates/frontend/native/native-base`);await h.pathExists(i)&&await G(`**/*`,i,r,n);let a=``;s?a=`nativewind`:c&&(a=`unistyles`);let o=e.join(k,`templates/frontend/native/${a}`);if(await h.pathExists(o)&&await G(`**/*`,o,r,n,!0),!u&&(n.api===`trpc`||n.api===`orpc`)){let t=e.join(k,`templates/api/${n.api}/native`);await h.pathExists(t)&&await G(`**/*`,t,r,n)}}}async function at(t,n){if(n.backend===`none`)return;let r=e.join(t,`apps/server`);if(n.backend===`convex`){await h.pathExists(r)&&await h.remove(r);let i=e.join(t,`packages/backend`),a=e.join(k,`templates/backend/convex/packages/backend`);await h.ensureDir(i),await h.pathExists(a)&&await G(`**/*`,a,i,n);return}await h.ensureDir(r);let i=e.join(k,`templates/backend/server/server-base`);await h.pathExists(i)&&await G(`**/*`,i,r,n);let a=e.join(k,`templates/backend/server/${n.backend}`);if(await h.pathExists(a)&&await G(`**/*`,a,r,n,!0),n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/server/base`);await h.pathExists(t)&&await G(`**/*`,t,r,n,!0);let i=e.join(k,`templates/api/${n.api}/server/${n.backend}`);await h.pathExists(i)&&await G(`**/*`,i,r,n,!0)}}async function ot(t,n){if(n.backend===`convex`||n.orm===`none`||n.database===`none`)return;let r=e.join(t,`apps/server`);await h.ensureDir(r);let i=e.join(k,`templates/db/${n.orm}/${n.database}`);await h.pathExists(i)&&await G(`**/*`,i,r,n)}async function st(t,n){if(n.backend===`convex`||!n.auth)return;let r=e.join(t,`apps/server`),i=e.join(t,`apps/web`),a=e.join(t,`apps/native`),o=await h.pathExists(r),s=await h.pathExists(i),c=await h.pathExists(a),l=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),u=n.frontend.includes(`nuxt`),d=n.frontend.includes(`svelte`),f=n.frontend.includes(`solid`),p=n.frontend.includes(`native-nativewind`),m=n.frontend.includes(`native-unistyles`),g=p||m;if(o){let t=e.join(k,`templates/auth/server/base`);if(await h.pathExists(t)&&await G(`**/*`,t,r,n),n.backend===`next`){let t=e.join(k,`templates/auth/server/next`);await h.pathExists(t)&&await G(`**/*`,t,r,n)}if(n.orm!==`none`&&n.database!==`none`){let t=n.orm,i=n.database,a=``;t===`drizzle`?a=e.join(k,`templates/auth/server/db/drizzle/${i}`):t===`prisma`?a=e.join(k,`templates/auth/server/db/prisma/${i}`):t===`mongoose`&&(a=e.join(k,`templates/auth/server/db/mongoose/${i}`)),a&&await h.pathExists(a)&&await G(`**/*`,a,r,n)}}if((l||u||d||f)&&s){if(l){let t=e.join(k,`templates/auth/web/react/base`);await h.pathExists(t)&&await G(`**/*`,t,i,n);let r=n.frontend.find(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e));if(r){let t=e.join(k,`templates/auth/web/react/${r}`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}}else if(u){let t=e.join(k,`templates/auth/web/nuxt`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}else if(d){if(n.api===`orpc`){let t=e.join(k,`templates/auth/web/svelte`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}}else if(f&&n.api===`orpc`){let t=e.join(k,`templates/auth/web/solid`);await h.pathExists(t)&&await G(`**/*`,t,i,n)}}if(g&&c){let t=e.join(k,`templates/auth/native/native-base`);await h.pathExists(t)&&await G(`**/*`,t,a,n);let r=``;if(p?r=`nativewind`:m&&(r=`unistyles`),r){let t=e.join(k,`templates/auth/native/${r}`);await h.pathExists(t)&&await G(`**/*`,t,a,n)}}}async function ct(t,n){if(!(!n.addons||n.addons.length===0))for(let r of n.addons){if(r===`none`)continue;let i=e.join(k,`templates/addons/${r}`),a=t;if(r===`pwa`&&(i=e.join(k,`templates/addons/pwa/apps/web`),a=e.join(t,`apps/web`),!await h.pathExists(a)))continue;await h.pathExists(i)&&await G(`**/*`,i,a,n)}}async function lt(t,n){if(!n.examples||n.examples.length===0||n.examples[0]===`none`)return;let r=e.join(t,`apps/server`),i=e.join(t,`apps/web`),a=await h.pathExists(r),o=await h.pathExists(i),s=n.frontend.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),c=n.frontend.includes(`nuxt`),l=n.frontend.includes(`svelte`),u=n.frontend.includes(`solid`);for(let t of n.examples){if(t===`none`)continue;let d=e.join(k,`templates/examples/${t}`);if(a&&n.backend!==`convex`&&n.backend!==`none`){let i=e.join(d,`server`);if(t===`ai`&&n.backend===`next`){let t=e.join(i,`next`);await h.pathExists(t)&&await G(`**/*`,t,r,n,!1)}if(n.orm!==`none`&&n.database!==`none`){let t=e.join(i,n.orm,`base`);await h.pathExists(t)&&await G(`**/*`,t,r,n,!1);let a=e.join(i,n.orm,n.database);await h.pathExists(a)&&await G(`**/*`,a,r,n,!1)}let a=[`${n.orm}/**`];t===`ai`&&n.backend===`next`&&a.push(`next/**`);let o=await C([`**/*.ts`,`**/*.hbs`],{cwd:i,onlyFiles:!0,deep:1,ignore:a});for(let t of o){let a=e.join(i,t),o=e.join(r,t.replace(`.hbs`,``));try{a.endsWith(`.hbs`)?await W(a,o,n):await h.pathExists(o)||await h.copy(a,o,{overwrite:!1})}catch{}}}if(o){if(s){let t=e.join(d,`web/react`);if(await h.pathExists(t)){let r=n.frontend.find(e=>[`next`,`react-router`,`tanstack-router`,`tanstack-start`].includes(e));if(r){let a=e.join(t,r);await h.pathExists(a)&&await G(`**/*`,a,i,n,!1)}}}else if(c){let t=e.join(d,`web/nuxt`);await h.pathExists(t)&&await G(`**/*`,t,i,n,!1)}else if(l){let t=e.join(d,`web/svelte`);await h.pathExists(t)&&await G(`**/*`,t,i,n,!1)}else if(u){let t=e.join(d,`web/solid`);await h.pathExists(t)&&await G(`**/*`,t,i,n,!1)}}}}async function ut(t,n){let r=e.join(k,`templates/extras`),i=n.frontend.includes(`native-nativewind`),a=n.frontend.includes(`native-unistyles`),o=i||a;if(n.packageManager===`pnpm`){let n=e.join(r,`pnpm-workspace.yaml`),i=e.join(t,`pnpm-workspace.yaml`);await h.pathExists(n)&&await h.copy(n,i)}if(n.packageManager===`pnpm`&&(o||n.frontend.includes(`nuxt`))){let i=e.join(r,`_npmrc.hbs`),a=e.join(t,`.npmrc`);await h.pathExists(i)&&await W(i,a,n)}}async function dt(e){let n=e.projectDir,r=e.backend===`convex`;try{return await h.ensureDir(n),await rt(n,e),await it(n,e),await at(n,e),r||(await ot(n,e),await st(n,e)),e.examples.length>0&&e.examples[0]!==`none`&&await lt(n,e),await ct(n,e),await se(e),r||(await ue(e),await Fe(e),await et(e),e.examples.length>0&&e.examples[0]!==`none`&&await Le(e)),e.addons.length>0&&e.addons[0]!==`none`&&await ne(e),!r&&e.auth&&await ce(e),await ut(n,e),await Ie(e),await Ye(n,e),await de(n,e),await $e(n,e.git),o.success(`Project template successfully scaffolded!`),e.install&&await Re({projectDir:n,packageManager:e.packageManager,addons:e.addons}),Be({...e,depsInstalled:e.install}),n}catch(e){e instanceof Error?(t(g.red(`Error during project creation: ${e.message}`)),console.error(e.stack),process.exit(1)):(t(g.red(`An unexpected error occurred: ${String(e)}`)),console.error(e),process.exit(1))}}async function ft(e,n){if(e!==void 0)return e;let r=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`solid`),i=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`nuxt`)||n?.includes(`svelte`)||n?.includes(`solid`),o=[{value:`turborepo`,label:`Turborepo (Recommended)`,hint:`Optimize builds for monorepos`},{value:`starlight`,label:`Starlight`,hint:`Add Astro Starlight documentation site`},{value:`biome`,label:`Biome`,hint:`Add Biome for linting and formatting`},{value:`husky`,label:`Husky`,hint:`Add Git hooks with Husky, lint-staged (requires Biome)`},{value:`pwa`,label:`PWA (Progressive Web App)`,hint:`Make your app installable and work offline`},{value:`tauri`,label:`Tauri Desktop App`,hint:`Build native desktop apps from your web frontend`}],c=o.filter(e=>e.value===`pwa`?r:e.value===`tauri`?i:!0),l=A.addons.filter(e=>c.some(t=>t.value===e)),u=await s({message:`Select addons`,options:c,initialValues:l,required:!1});return a(u)&&(t(g.red(`Operation cancelled`)),process.exit(0)),u.includes(`husky`)&&!u.includes(`biome`)&&u.push(`biome`),u}async function pt(e,n,r){if(r===`convex`||r===`none`)return`none`;if(e)return e;let i=n?.includes(`nuxt`),o=n?.includes(`svelte`),s=n?.includes(`solid`),c=[{value:`trpc`,label:`tRPC`,hint:`End-to-end typesafe APIs made easy`},{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs that adhere to OpenAPI standards`}];(i||o||s)&&(c=[{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs (Required for ${i?`Nuxt`:o?`Svelte`:`Solid`} frontend)`}]);let l=await u({message:`Select API type`,options:c,initialValue:i||o||s?`orpc`:A.api});return a(l)&&(t(g.red(`Operation cancelled`)),process.exit(0)),(i||o||s)&&l!==`orpc`?`orpc`:l}async function mt(e,r,i){if(i===`convex`||!r)return!1;if(e!==void 0)return e;let o=await n({message:`Add authentication with Better-Auth?`,initialValue:A.auth});return a(o)&&(t(g.red(`Operation cancelled`)),process.exit(0)),o}async function ht(e,n){if(e!==void 0)return e;let r=n?.some(e=>e===`nuxt`||e===`solid`),i=[{value:`hono`,label:`Hono`,hint:`Lightweight, ultrafast web framework`},{value:`next`,label:`Next.js`,hint:`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`}];r||i.push({value:`convex`,label:`Convex`,hint:`Reactive backend-as-a-service platform`}),i.push({value:`none`,label:`None`,hint:`No backend server (e.g., for a static site or client-only app)`});let o=A.backend;r&&o===`convex`&&(o=`hono`);let s=await u({message:`Select backend framework`,options:i,initialValue:o});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function gt(e,n){if(n===`convex`||n===`none`)return`none`;if(e!==void 0)return e;let r=await u({message:`Select database`,options:[{value:`none`,label:`None`,hint:`No database setup`},{value:`sqlite`,label:`SQLite`,hint:`lightweight, server-less, embedded relational database`},{value:`postgres`,label:`PostgreSQL`,hint:`powerful, open source object-relational database system`},{value:`mysql`,label:`MySQL`,hint:`popular open-source relational database system`},{value:`mongodb`,label:`MongoDB`,hint:`open-source NoSQL database that stores data in JSON-like documents called BSON`}],initialValue:A.database});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function _t(e,n,r,i){if(i===`convex`)return`none`;if(n!==void 0)return n;if(e===`none`||e===`sqlite`&&r===`prisma`)return`none`;let o=[];if(e===`sqlite`)o=[{value:`turso`,label:`Turso`,hint:`SQLite for Production. Powered by libSQL`},{value:`none`,label:`None`,hint:`Manual setup`}];else if(e===`postgres`)o=[{value:`neon`,label:`Neon Postgres`,hint:`Serverless Postgres with branching capability`},...r===`prisma`?[{value:`prisma-postgres`,label:`Prisma Postgres`,hint:`Instant Postgres for Global Applications`}]:[],{value:`none`,label:`None`,hint:`Manual setup`}];else if(e===`mongodb`)o=[{value:`mongodb-atlas`,label:`MongoDB Atlas`,hint:`The most effective way to deploy MongoDB`},{value:`none`,label:`None`,hint:`Manual setup`}];else return`none`;let s=await u({message:`Select ${e} setup option`,options:o,initialValue:`none`});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function vt(e,n,r,i){if(e!==void 0)return e;if(i===`convex`)return[`todo`];if(i===`none`||n===`none`)return[];let o=r&&r.length===1&&(r[0]===`native-nativewind`||r[0]===`native-unistyles`);if(o)return[];let c=r?.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`,`solid`].includes(e))??!1,l=!r||r.length===0;if(!c&&!l)return[];let u=[],d=[{value:`todo`,label:`Todo App`,hint:`A simple CRUD example app`}];return i!==`elysia`&&!r?.includes(`solid`)&&d.push({value:`ai`,label:`AI Chat`,hint:`A simple AI chat interface using AI SDK`}),u=await s({message:`Include examples`,options:d,required:!1,initialValues:A.examples}),a(u)&&(t(g.red(`Operation cancelled`)),process.exit(0)),u}async function yt(e,n){if(e!==void 0)return e;let r=await s({message:`Select platforms to develop for`,options:[{value:`web`,label:`Web`,hint:`React, Vue or Svelte Web Application`},{value:`native`,label:`Native`,hint:`Create a React Native/Expo app`}],required:!1,initialValues:[`web`]});a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0));let i=[];if(r.includes(`web`)){let e=[{value:`tanstack-router`,label:`TanStack Router`,hint:`Modern and scalable routing for React Applications`},{value:`react-router`,label:`React Router`,hint:`A user‑obsessed, standards‑focused, multi‑strategy router`},{value:`next`,label:`Next.js`,hint:`The React Framework for the Web`},{value:`nuxt`,label:`Nuxt`,hint:`The Progressive Web Framework for Vue.js`},{value:`svelte`,label:`Svelte`,hint:`web development for the rest of us`},{value:`solid`,label:`Solid`,hint:`Simple and performant reactivity for building user interfaces`},{value:`tanstack-start`,label:`TanStack Start (beta)`,hint:`SSR, Server Functions, API Routes and more with TanStack Router`}],r=e.filter(e=>n===`convex`?e.value!==`nuxt`&&e.value!==`solid`:!0),o=await u({message:`Choose frontend framework`,options:r,initialValue:A.frontend[0]});a(o)&&(t(g.red(`Operation cancelled`)),process.exit(0)),i.push(o)}if(r.includes(`native`)){let e=await u({message:`Choose native framework`,options:[{value:`native-nativewind`,label:`NativeWind`,hint:`Use Tailwind CSS for React Native`},{value:`native-unistyles`,label:`Unistyles`,hint:`Consistent styling for React Native`}],initialValue:`native-nativewind`});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),i.push(e)}return i}async function bt(e){if(e!==void 0)return e;let r=await n({message:`Initialize git repository?`,initialValue:A.git});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function xt(e){if(e!==void 0)return e;let r=await n({message:`Install dependencies?`,initialValue:A.install});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}const K={prisma:{value:`prisma`,label:`Prisma`,hint:`Powerful, feature-rich ORM`},mongoose:{value:`mongoose`,label:`Mongoose`,hint:`Elegant object modeling tool`},drizzle:{value:`drizzle`,label:`Drizzle`,hint:`Lightweight and performant TypeScript ORM`}};async function St(e,n,r,i){if(i===`convex`||!n)return`none`;if(e!==void 0)return e;let o=[...r===`mongodb`?[K.prisma,K.mongoose]:[K.drizzle,K.prisma]],s=await u({message:`Select ORM`,options:o,initialValue:r===`mongodb`?`prisma`:A.orm});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function Ct(e){if(e!==void 0)return e;let n=E(),r=await u({message:`Choose package manager`,options:[{value:`npm`,label:`npm`,hint:`Node Package Manager`},{value:`pnpm`,label:`pnpm`,hint:`Fast, disk space efficient package manager`},{value:`bun`,label:`bun`,hint:`All-in-one JavaScript runtime & toolkit`}],initialValue:n});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function wt(e,n){if(n===`convex`||n===`none`)return`none`;if(e!==void 0)return e;if(n===`next`)return`node`;let r=await u({message:`Select runtime`,options:[{value:`bun`,label:`Bun`,hint:`Fast all-in-one JavaScript runtime`},{value:`node`,label:`Node.js`,hint:`Traditional Node.js runtime`}],initialValue:A.runtime});return a(r)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r}async function Tt(e,n,i,a){let o=await r({frontend:({results:t})=>yt(e.frontend,e.backend),backend:({results:t})=>ht(e.backend,t.frontend),runtime:({results:t})=>wt(e.runtime,t.backend),database:({results:t})=>gt(e.database,t.backend),orm:({results:t})=>St(e.orm,t.database!==`none`,t.database,t.backend),api:({results:t})=>pt(e.api,t.frontend,t.backend),auth:({results:t})=>mt(e.auth,t.database!==`none`,t.backend),addons:({results:t})=>ft(e.addons,t.frontend),examples:({results:t})=>vt(e.examples,t.database,t.frontend,t.backend),dbSetup:({results:t})=>_t(t.database??`none`,e.dbSetup,t.orm,t.backend),git:()=>bt(e.git),packageManager:()=>Ct(e.packageManager),install:()=>xt(e.install)},{onCancel:()=>{t(g.red(`Operation cancelled`)),process.exit(0)}});return o.backend===`convex`&&(o.runtime=`none`,o.database=`none`,o.orm=`none`,o.api=`none`,o.auth=!1,o.dbSetup=`none`),o.backend===`none`&&(o.runtime=`none`,o.database=`none`,o.orm=`none`,o.api=`none`,o.auth=!1,o.dbSetup=`none`,o.examples=[]),{projectName:n,projectDir:i,relativePath:a,frontend:o.frontend,backend:o.backend,runtime:o.runtime,database:o.database,orm:o.orm,auth:o.auth,addons:o.addons,examples:o.examples,git:o.git,packageManager:o.packageManager,install:o.install,dbSetup:o.dbSetup,api:o.api}}const Et=[`<`,`>`,`:`,`"`,`|`,`?`,`*`],q=255;function J(e){if(e!==`.`){if(!e)return`Project name cannot be empty`;if(e.length>q)return`Project name must be less than ${q} characters`;if(Et.some(t=>e.includes(t)))return`Project name contains invalid characters`;if(e.startsWith(`.`)||e.startsWith(`-`))return`Project name cannot start with a dot or dash`;if(e.toLowerCase()===`node_modules`)return`Project name is reserved`}}async function Y(n){if(n){if(n===`.`)return n;let t=e.basename(n),r=J(t);if(!r)return n}let r=!1,i=``,o=A.projectName,s=1;for(;h.pathExistsSync(e.resolve(process.cwd(),o))&&h.readdirSync(e.resolve(process.cwd(),o)).length>0;)o=`${A.projectName}-${s}`,s++;for(;!r;){let s=await f({message:`Enter your project name or path (relative to current directory)`,placeholder:o,initialValue:n,defaultValue:o,validate:t=>{let n=t.trim()||o,r=e.basename(n),i=J(r);if(i)return i;if(n!==`.`){let t=e.resolve(process.cwd(),n);if(!t.startsWith(process.cwd()))return`Project path must be within current directory`}}});a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),i=s||o,r=!0}return i}function X(e){let t=[];if(e.projectName&&t.push(`${g.blue(`Project Name:`)} ${e.projectName}`),e.frontend!==void 0){let n=Array.isArray(e.frontend)?e.frontend:[e.frontend],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Frontend:`)} ${r}`)}if(e.backend!==void 0&&t.push(`${g.blue(`Backend:`)} ${String(e.backend)}`),e.runtime!==void 0&&t.push(`${g.blue(`Runtime:`)} ${String(e.runtime)}`),e.api!==void 0&&t.push(`${g.blue(`API:`)} ${String(e.api)}`),e.database!==void 0&&t.push(`${g.blue(`Database:`)} ${String(e.database)}`),e.orm!==void 0&&t.push(`${g.blue(`ORM:`)} ${String(e.orm)}`),e.auth!==void 0){let n=typeof e.auth==`boolean`?e.auth?`Yes`:`No`:String(e.auth);t.push(`${g.blue(`Authentication:`)} ${n}`)}if(e.addons!==void 0){let n=Array.isArray(e.addons)?e.addons:[e.addons],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Addons:`)} ${r}`)}if(e.examples!==void 0){let n=Array.isArray(e.examples)?e.examples:[e.examples],r=n.length>0&&n[0]!==void 0?n.join(`, `):`none`;t.push(`${g.blue(`Examples:`)} ${r}`)}if(e.git!==void 0){let n=typeof e.git==`boolean`?e.git?`Yes`:`No`:String(e.git);t.push(`${g.blue(`Git Init:`)} ${n}`)}if(e.packageManager!==void 0&&t.push(`${g.blue(`Package Manager:`)} ${String(e.packageManager)}`),e.install!==void 0){let n=typeof e.install==`boolean`?e.install?`Yes`:`No`:String(e.install);t.push(`${g.blue(`Install Dependencies:`)} ${n}`)}return e.dbSetup!==void 0&&t.push(`${g.blue(`Database Setup:`)} ${String(e.dbSetup)}`),t.length===0?g.yellow(`No configuration selected.`):t.join(`
159
+ `)}function Dt(e){let t=[];e.frontend&&e.frontend.length>0?t.push(`--frontend ${e.frontend.join(` `)}`):t.push(`--frontend none`),t.push(`--backend ${e.backend}`),t.push(`--runtime ${e.runtime}`),t.push(`--database ${e.database}`),t.push(`--orm ${e.orm}`),t.push(`--api ${e.api}`),t.push(e.auth?`--auth`:`--no-auth`),e.addons&&e.addons.length>0?t.push(`--addons ${e.addons.join(` `)}`):t.push(`--addons none`),e.examples&&e.examples.length>0?t.push(`--examples ${e.examples.join(` `)}`):t.push(`--examples none`),t.push(`--db-setup ${e.dbSetup}`),t.push(e.git?`--git`:`--no-git`),t.push(`--package-manager ${e.packageManager}`),t.push(e.install?`--install`:`--no-install`);let n=``,r=e.packageManager;r===`npm`?n=`npx create-better-t-stack@latest`:r===`pnpm`?n=`pnpm create better-t-stack@latest`:r===`bun`&&(n=`bun create better-t-stack@latest`);let i=e.relativePath?` ${e.relativePath}`:``;return`${n}${i} ${t.join(` `)}`}const Ot=()=>{let t=e.join(k,`package.json`),n=h.readJSONSync(t);return n.version??`1.0.0`},Z=`
160
160
  ██████╗ ███████╗████████╗████████╗███████╗██████╗
161
161
  ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
162
162
  ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.9.1",
3
+ "version": "2.9.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",
@@ -55,7 +55,7 @@
55
55
  "dependencies": {
56
56
  "@clack/prompts": "^0.10.1",
57
57
  "consola": "^3.4.2",
58
- "execa": "^9.5.2",
58
+ "execa": "^9.5.3",
59
59
  "fs-extra": "^11.3.0",
60
60
  "globby": "^14.1.0",
61
61
  "gradient-string": "^3.0.0",
@@ -66,7 +66,7 @@
66
66
  "devDependencies": {
67
67
  "@types/fs-extra": "^11.0.4",
68
68
  "@types/globby": "^9.1.0",
69
- "@types/node": "^22.14.1",
69
+ "@types/node": "^22.15.17",
70
70
  "@types/yargs": "^17.0.33",
71
71
  "tsdown": "^0.9.9",
72
72
  "typescript": "^5.8.3"
@@ -1,5 +1,4 @@
1
1
  {{#if (includes frontend 'next')}}
2
- {{!-- Next.js tRPC Client Setup --}}
3
2
  import { QueryCache, QueryClient } from '@tanstack/react-query';
4
3
  import { createTRPCClient, httpBatchLink } from '@trpc/client';
5
4
  import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
@@ -47,16 +46,14 @@ export const trpc = createTRPCOptionsProxy<AppRouter>({
47
46
  });
48
47
 
49
48
  {{else if (includes frontend 'tanstack-start')}}
50
- {{!-- TanStack Start tRPC Client Setup --}}
51
49
  import { createTRPCContext } from "@trpc/tanstack-react-query";
52
- import type { AppRouter } from "../../../server/src/routers"; {{! Adjust path if necessary }}
50
+ import type { AppRouter } from "../../../server/src/routers";
53
51
 
54
52
  export const { TRPCProvider, useTRPC, useTRPCClient } =
55
53
  createTRPCContext<AppRouter>();
56
54
 
57
55
  {{else}}
58
- {{!-- Default Web tRPC Client Setup (TanStack Router, React Router, etc.) --}}
59
- import type { AppRouter } from "../../../server/src/routers"; {{! Adjust path if necessary }}
56
+ import type { AppRouter } from "../../../server/src/routers";
60
57
  import { QueryCache, QueryClient } from "@tanstack/react-query";
61
58
  import { createTRPCClient, httpBatchLink } from "@trpc/client";
62
59
  import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
@@ -12,20 +12,48 @@ import { Checkbox } from "@/components/ui/checkbox";
12
12
  import { Input } from "@/components/ui/input";
13
13
  import { Loader2, Trash2 } from "lucide-react";
14
14
  import { useState } from "react";
15
- import { useMutation, useQuery } from "@tanstack/react-query";
16
15
 
17
- {{#if (eq api "orpc")}}
16
+ {{#if (eq backend "convex")}}
17
+ import { useMutation, useQuery } from "convex/react";
18
+ import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
19
+ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel.d.ts";
20
+ {{else}}
21
+ import { useMutation, useQuery } from "@tanstack/react-query";
22
+ {{#if (eq api "orpc")}}
18
23
  import { orpc } from "@/utils/orpc";
19
- {{/if}}
20
- {{#if (eq api "trpc")}}
24
+ {{/if}}
25
+ {{#if (eq api "trpc")}}
21
26
  import { trpc } from "@/utils/trpc";
27
+ {{/if}}
22
28
  {{/if}}
23
29
 
24
30
 
25
31
  export default function TodosPage() {
26
32
  const [newTodoText, setNewTodoText] = useState("");
27
33
 
28
- {{#if (eq api "orpc")}}
34
+ {{#if (eq backend "convex")}}
35
+ const todos = useQuery(api.todos.getAll);
36
+ const createTodoMutation = useMutation(api.todos.create);
37
+ const toggleTodoMutation = useMutation(api.todos.toggle);
38
+ const deleteTodoMutation = useMutation(api.todos.deleteTodo);
39
+
40
+ const handleAddTodo = async (e: React.FormEvent) => {
41
+ e.preventDefault();
42
+ const text = newTodoText.trim();
43
+ if (!text) return;
44
+ await createTodoMutation({ text });
45
+ setNewTodoText("");
46
+ };
47
+
48
+ const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => {
49
+ toggleTodoMutation({ id, completed: !currentCompleted });
50
+ };
51
+
52
+ const handleDeleteTodo = (id: Id<"todos">) => {
53
+ deleteTodoMutation({ id });
54
+ };
55
+ {{else}}
56
+ {{#if (eq api "orpc")}}
29
57
  const todos = useQuery(orpc.todo.getAll.queryOptions());
30
58
  const createMutation = useMutation(
31
59
  orpc.todo.create.mutationOptions({
@@ -45,8 +73,8 @@ export default function TodosPage() {
45
73
  onSuccess: () => todos.refetch(),
46
74
  }),
47
75
  );
48
- {{/if}}
49
- {{#if (eq api "trpc")}}
76
+ {{/if}}
77
+ {{#if (eq api "trpc")}}
50
78
  const todos = useQuery(trpc.todo.getAll.queryOptions());
51
79
  const createMutation = useMutation(
52
80
  trpc.todo.create.mutationOptions({
@@ -66,7 +94,7 @@ export default function TodosPage() {
66
94
  onSuccess: () => todos.refetch(),
67
95
  }),
68
96
  );
69
- {{/if}}
97
+ {{/if}}
70
98
 
71
99
  const handleAddTodo = (e: React.FormEvent) => {
72
100
  e.preventDefault();
@@ -82,6 +110,7 @@ export default function TodosPage() {
82
110
  const handleDeleteTodo = (id: number) => {
83
111
  deleteMutation.mutate({ id });
84
112
  };
113
+ {{/if}}
85
114
 
86
115
  return (
87
116
  <div className="mx-auto w-full max-w-md py-10">
@@ -99,20 +128,73 @@ export default function TodosPage() {
99
128
  value={newTodoText}
100
129
  onChange={(e) => setNewTodoText(e.target.value)}
101
130
  placeholder="Add a new task..."
131
+ {{#if (eq backend "convex")}}
132
+ {{else}}
102
133
  disabled={createMutation.isPending}
134
+ {{/if}}
103
135
  />
104
136
  <Button
105
137
  type="submit"
138
+ {{#if (eq backend "convex")}}
139
+ disabled={!newTodoText.trim()}
140
+ {{else}}
106
141
  disabled={createMutation.isPending || !newTodoText.trim()}
142
+ {{/if}}
107
143
  >
144
+ {{#if (eq backend "convex")}}
145
+ Add
146
+ {{else}}
108
147
  {createMutation.isPending ? (
109
148
  <Loader2 className="h-4 w-4 animate-spin" />
110
149
  ) : (
111
150
  "Add"
112
151
  )}
152
+ {{/if}}
113
153
  </Button>
114
154
  </form>
115
155
 
156
+ {{#if (eq backend "convex")}}
157
+ {todos === undefined ? (
158
+ <div className="flex justify-center py-4">
159
+ <Loader2 className="h-6 w-6 animate-spin" />
160
+ </div>
161
+ ) : todos.length === 0 ? (
162
+ <p className="py-4 text-center">No todos yet. Add one above!</p>
163
+ ) : (
164
+ <ul className="space-y-2">
165
+ {todos.map((todo) => (
166
+ <li
167
+ key={todo._id}
168
+ className="flex items-center justify-between rounded-md border p-2"
169
+ >
170
+ <div className="flex items-center space-x-2">
171
+ <Checkbox
172
+ checked={todo.completed}
173
+ onCheckedChange={() =>
174
+ handleToggleTodo(todo._id, todo.completed)
175
+ }
176
+ id={`todo-${todo._id}`}
177
+ />
178
+ <label
179
+ htmlFor={`todo-${todo._id}`}
180
+ className={`${todo.completed ? "line-through text-muted-foreground" : ""}`}
181
+ >
182
+ {todo.text}
183
+ </label>
184
+ </div>
185
+ <Button
186
+ variant="ghost"
187
+ size="icon"
188
+ onClick={() => handleDeleteTodo(todo._id)}
189
+ aria-label="Delete todo"
190
+ >
191
+ <Trash2 className="h-4 w-4" />
192
+ </Button>
193
+ </li>
194
+ ))}
195
+ </ul>
196
+ )}
197
+ {{else}}
116
198
  {todos.isLoading ? (
117
199
  <div className="flex justify-center py-4">
118
200
  <Loader2 className="h-6 w-6 animate-spin" />
@@ -155,6 +237,7 @@ export default function TodosPage() {
155
237
  ))}
156
238
  </ul>
157
239
  )}
240
+ {{/if}}
158
241
  </CardContent>
159
242
  </Card>
160
243
  </div>
@@ -126,7 +126,6 @@ export default function Todos() {
126
126
  onChange={(e) => setNewTodoText(e.target.value)}
127
127
  placeholder="Add a new task..."
128
128
  {{#if (eq backend "convex")}}
129
- {{!-- Convex mutations don't have an easy isPending state here, disable based on text --}}
130
129
  {{else}}
131
130
  disabled={createMutation.isPending}
132
131
  {{/if}}
@@ -33,40 +33,48 @@ export default function Home() {
33
33
 
34
34
  <View className="rounded-lg border border-foreground p-4">
35
35
  <Text className="mb-2 font-medium text-foreground">API Status</Text>
36
+ {{#if (eq backend "convex")}}
36
37
  <View className="flex-row items-center gap-2">
37
38
  <View
38
39
  className={`h-2.5 w-2.5 rounded-full ${
39
- {{#if (or (eq api "orpc") (eq api "trpc"))}}
40
- healthCheck.data ? "bg-green-500" : "bg-red-500"
41
- {{else}}
42
40
  healthCheck ? "bg-green-500" : "bg-red-500"
43
- {{/if}}
44
41
  }`}
45
42
  />
46
43
  <Text className="text-sm text-foreground">
47
- {{#if (eq api "orpc")}}
48
- {healthCheck.isLoading
49
- ? "Checking..."
50
- : healthCheck.data
51
- ? "Connected"
52
- : "Disconnected"}
53
- {{/if}}
54
- {{#if (eq api "trpc")}}
55
- {healthCheck.isLoading
56
- ? "Checking..."
57
- : healthCheck.data
58
- ? "Connected"
59
- : "Disconnected"}
60
- {{/if}}
61
- {{#if (eq backend "convex")}}
62
44
  {healthCheck === undefined
63
45
  ? "Checking..."
64
46
  : healthCheck === "OK"
65
47
  ? "Connected"
66
48
  : "Error"}
67
- {{/if}}
68
49
  </Text>
69
50
  </View>
51
+ {{else}}
52
+ {{#unless (eq api "none")}}
53
+ <View className="flex-row items-center gap-2">
54
+ <View
55
+ className={`h-2.5 w-2.5 rounded-full ${
56
+ healthCheck.data ? "bg-green-500" : "bg-red-500"
57
+ }`}
58
+ />
59
+ <Text className="text-sm text-foreground">
60
+ {{#if (eq api "orpc")}}
61
+ {healthCheck.isLoading
62
+ ? "Checking..."
63
+ : healthCheck.data
64
+ ? "Connected"
65
+ : "Disconnected"}
66
+ {{/if}}
67
+ {{#if (eq api "trpc")}}
68
+ {healthCheck.isLoading
69
+ ? "Checking..."
70
+ : healthCheck.data
71
+ ? "Connected"
72
+ : "Disconnected"}
73
+ {{/if}}
74
+ </Text>
75
+ </View>
76
+ {{/unless}}
77
+ {{/if}}
70
78
  </View>
71
79
  </ScrollView>
72
80
  </Container>
@@ -1,7 +1,9 @@
1
1
  {{#if (eq backend "convex")}}
2
2
  import { ConvexProvider, ConvexReactClient } from "convex/react";
3
3
  {{else}}
4
+ {{#unless (eq api "none")}}
4
5
  import { QueryClientProvider } from "@tanstack/react-query";
6
+ {{/unless}}
5
7
  {{/if}}
6
8
  import { Stack } from "expo-router";
7
9
  import {
@@ -82,6 +84,7 @@ export default function RootLayout() {
82
84
  </ThemeProvider>
83
85
  </ConvexProvider>
84
86
  {{else}}
87
+ {{#unless (eq api "none")}}
85
88
  <QueryClientProvider client={queryClient}>
86
89
  <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
87
90
  <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
@@ -96,6 +99,20 @@ export default function RootLayout() {
96
99
  </GestureHandlerRootView>
97
100
  </ThemeProvider>
98
101
  </QueryClientProvider>
102
+ {{else}}
103
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
104
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
105
+ <GestureHandlerRootView style=\{{ flex: 1 }}>
106
+ <Stack>
107
+ <Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
108
+ <Stack.Screen
109
+ name="modal"
110
+ options=\{{ title: "Modal", presentation: "modal" }}
111
+ />
112
+ </Stack>
113
+ </GestureHandlerRootView>
114
+ </ThemeProvider>
115
+ {{/unless}}
99
116
  {{/if}}
100
117
  );
101
118
  }
@@ -33,38 +33,54 @@ export default function Home() {
33
33
 
34
34
  <View style={styles.apiStatusCard}>
35
35
  <Text style={styles.cardTitle}>API Status</Text>
36
+ {{#if (eq backend "convex")}}
36
37
  <View style={styles.apiStatusRow}>
37
38
  <View
38
39
  style={[
39
40
  styles.statusIndicatorDot,
40
- {{#if (or (eq api "orpc") (eq api "trpc"))}}
41
- healthCheck.data
42
- ? styles.statusIndicatorGreen
43
- : styles.statusIndicatorRed,
44
- {{else}}
45
41
  healthCheck === "OK"
46
42
  ? styles.statusIndicatorGreen
47
43
  : styles.statusIndicatorRed,
48
- {{/if}}
49
44
  ]}
50
45
  />
51
46
  <Text style={styles.statusText}>
52
- {{#if (or (eq api "orpc") (eq api "trpc"))}}
53
- {healthCheck.isLoading
54
- ? "Checking..."
55
- : healthCheck.data
56
- ? "Connected"
57
- : "Disconnected"}
58
- {{/if}}
59
- {{#if (eq backend "convex")}}
60
47
  {healthCheck === undefined
61
48
  ? "Checking..."
62
49
  : healthCheck === "OK"
63
50
  ? "Connected"
64
51
  : "Error"}
65
- {{/if}}
66
52
  </Text>
67
53
  </View>
54
+ {{else}}
55
+ {{#unless (eq api "none")}}
56
+ <View style={styles.apiStatusRow}>
57
+ <View
58
+ style={[
59
+ styles.statusIndicatorDot,
60
+ healthCheck.data
61
+ ? styles.statusIndicatorGreen
62
+ : styles.statusIndicatorRed,
63
+ ]}
64
+ />
65
+ <Text style={styles.statusText}>
66
+ {{#if (eq api "orpc")}}
67
+ {healthCheck.isLoading
68
+ ? "Checking..."
69
+ : healthCheck.data
70
+ ? "Connected"
71
+ : "Disconnected"}
72
+ {{/if}}
73
+ {{#if (eq api "trpc")}}
74
+ {healthCheck.isLoading
75
+ ? "Checking..."
76
+ : healthCheck.data
77
+ ? "Connected"
78
+ : "Disconnected"}
79
+ {{/if}}
80
+ </Text>
81
+ </View>
82
+ {{/unless}}
83
+ {{/if}}
68
84
  </View>
69
85
  </ScrollView>
70
86
  </Container>