create-better-t-stack 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,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`,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.6.0`,prisma:`^6.6.0`,"vite-plugin-pwa":`^0.21.2`,"@vite-pwa/assets-generator":`^0.2.6`,"@tauri-apps/cli":`^2.4.0`,"@biomejs/biome":`1.9.4`,husky:`^9.1.7`,"lint-staged":`^15.5.0`,"@hono/node-server":`^1.14.0`,tsx:`^4.19.2`,"@types/node":`^22.13.11`,"@types/bun":`^1.2.6`,"@elysiajs/node":`^1.2.6`,"@elysiajs/cors":`^1.2.0`,"@elysiajs/trpc":`^1.1.0`,elysia:`^1.2.25`,"@hono/trpc-server":`^0.3.4`,hono:`^4.7.6`,cors:`^2.8.5`,express:`^5.1.0`,"@types/express":`^5.0.1`,"@types/cors":`^2.8.17`,turbo:`^2.4.2`,ai:`^4.2.8`,"@ai-sdk/google":`^1.2.3`,"@ai-sdk/vue":`^1.2.8`,"@ai-sdk/svelte":`^2.1.9`,"@prisma/extension-accelerate":`^1.3.0`,"@orpc/server":`^1.1.0`,"@orpc/client":`^1.1.0`,"@orpc/react-query":`^1.1.0`,"@orpc/vue-query":`^1.1.0`,"@orpc/svelte-query":`^1.1.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`,"@tanstack/react-query-devtools":`^5.69.0`,"@tanstack/react-query":`^5.69.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}=t,i=e.resolve(process.cwd(),n),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}=t,a=e.resolve(process.cwd(),n),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=c||u?`http://localhost:5173`:`http://localhost:3001`,f=l?`../.output/public`:u?`../build`:`../dist`,p=[`init`,`--app-name=${e.basename(a)}`,`--window-title=${e.basename(a)}`,`--frontend-dist=${f}`,`--dev-url=${d}`,`--before-dev-command=\"${r} run dev\"`,`--before-build-command=\"${r} run build\"`],m=p.join(` `),g=`@tauri-apps/cli@latest ${m}`,_=N(r,g);await x(_,{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(t){let{projectName:n,addons:r,frontend:i}=t,a=e.resolve(process.cwd(),n),o=i.includes(`react-router`)||i.includes(`tanstack-router`),s=i.includes(`nuxt`),c=i.includes(`svelte`);r.includes(`turborepo`)&&await M({devDependencies:[`turbo`],projectDir:a}),r.includes(`pwa`)&&o&&await oe(a,i),r.includes(`tauri`)&&(o||s||c)&&await te(t),r.includes(`biome`)&&await ie(a),r.includes(`husky`)&&await ae(a),r.includes(`starlight`)&&await ee(t)}function re(t,n){return n.some(e=>[`react-router`,`tanstack-router`,`nuxt`,`svelte`].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`].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}=t,s=e.resolve(process.cwd(),r),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);if(!c&&n!==`none`){let r=e.join(s,`apps/server`),a=await h.pathExists(r),o=i.some(e=>[`tanstack-router`,`react-router`,`tanstack-start`,`next`].includes(e)),c=i.includes(`nuxt`),p=i.includes(`svelte`);a&&(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&&(o?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}):c?n===`orpc`&&await M({dependencies:[`@orpc/vue-query`,`@orpc/client`,`@orpc/server`],projectDir:l}):p&&n===`orpc`&&await M({dependencies:[`@orpc/svelte-query`,`@orpc/client`,`@orpc/server`],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 p=[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`native`],m=i.some(e=>p.includes(e));if(m&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native`&&p.includes(e)),a=i.includes(`native`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(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`),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}=t;if(a===`convex`||!r)return;let o=e.resolve(process.cwd(),n),s=e.join(o,`apps/server`),c=e.join(o,`apps/web`),l=e.join(o,`apps/native`),u=await h.pathExists(c),d=await h.pathExists(l),f=await h.pathExists(s);try{f&&await M({dependencies:[`better-auth`],projectDir:s});let e=i.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].includes(e));e&&u&&await M({dependencies:[`better-auth`],projectDir:c}),i.includes(`native`)&&d&&(await M({dependencies:[`better-auth`,`@better-auth/expo`],projectDir:l}),f&&await M({dependencies:[`@better-auth/expo`],projectDir:s}))}catch(e){p.error(g.red(`Failed to configure authentication dependencies`)),e instanceof Error&&p.error(g.red(e.message))}}function le(e=32){let t=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`,n=``,r=t.length;for(let i=0;i<e;i++)n+=t.charAt(Math.floor(Math.random()*r));return n}async function ue(t){let{projectName:n,backend:r,runtime:i,api:a}=t;if(r===`convex`)return;let o=e.resolve(process.cwd(),n),s=r,c=e.join(o,`apps/server`),l=[],u=[];s===`hono`?(l.push(`hono`),a===`trpc`&&l.push(`@hono/trpc-server`),i===`node`&&(l.push(`@hono/node-server`),u.push(`tsx`,`@types/node`))):s===`elysia`?(l.push(`elysia`,`@elysiajs/cors`),a===`trpc`&&l.push(`@elysiajs/trpc`),i===`node`&&(l.push(`@elysiajs/node`),u.push(`tsx`,`@types/node`))):s===`express`&&(l.push(`express`,`cors`),u.push(`@types/express`,`@types/cors`),i===`node`&&u.push(`tsx`,`@types/node`)),i===`bun`&&u.push(`@types/bun`),(l.length>0||u.length>0)&&await M({dependencies:l,devDependencies:u,projectDir:c})}async function de(t,n){let r=e.join(t,`README.md`),i=fe(n);try{await h.writeFile(r,i)}catch(e){p.error(`Failed to create README.md file:`,e)}}function fe(e){let{projectName:t,packageManager:n,database:r,auth:i,addons:a=[],orm:o=`drizzle`,runtime:s=`bun`,frontend:c=[`tanstack-router`],backend:l=`hono`}=e,u=c.includes(`react-router`),d=c.includes(`tanstack-router`),f=c.includes(`native`),p=c.includes(`next`),m=c.includes(`tanstack-start`),h=c.includes(`svelte`),g=c.includes(`nuxt`),_=n===`npm`?`npm run`:n,v=`3001`;return(u||h)&&(v=`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`,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.6.0`,prisma:`^6.6.0`,"vite-plugin-pwa":`^0.21.2`,"@vite-pwa/assets-generator":`^0.2.6`,"@tauri-apps/cli":`^2.4.0`,"@biomejs/biome":`1.9.4`,husky:`^9.1.7`,"lint-staged":`^15.5.0`,"@hono/node-server":`^1.14.0`,tsx:`^4.19.2`,"@types/node":`^22.13.11`,"@types/bun":`^1.2.6`,"@elysiajs/node":`^1.2.6`,"@elysiajs/cors":`^1.2.0`,"@elysiajs/trpc":`^1.1.0`,elysia:`^1.2.25`,"@hono/trpc-server":`^0.3.4`,hono:`^4.7.6`,cors:`^2.8.5`,express:`^5.1.0`,"@types/express":`^5.0.1`,"@types/cors":`^2.8.17`,turbo:`^2.4.2`,ai:`^4.2.8`,"@ai-sdk/google":`^1.2.3`,"@ai-sdk/vue":`^1.2.8`,"@ai-sdk/svelte":`^2.1.9`,"@prisma/extension-accelerate":`^1.3.0`,"@orpc/server":`^1.1.0`,"@orpc/client":`^1.1.0`,"@orpc/react-query":`^1.1.0`,"@orpc/vue-query":`^1.1.0`,"@orpc/svelte-query":`^1.1.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`},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}=t,i=e.resolve(process.cwd(),n),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}=t,a=e.resolve(process.cwd(),n),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=c||u?`http://localhost:5173`:`http://localhost:3001`,f=l?`../.output/public`:u?`../build`:`../dist`,p=[`init`,`--app-name=${e.basename(a)}`,`--window-title=${e.basename(a)}`,`--frontend-dist=${f}`,`--dev-url=${d}`,`--before-dev-command=\"${r} run dev\"`,`--before-build-command=\"${r} run build\"`],m=p.join(` `),g=`@tauri-apps/cli@latest ${m}`,_=N(r,g);await x(_,{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(t){let{projectName:n,addons:r,frontend:i}=t,a=e.resolve(process.cwd(),n),o=i.includes(`react-router`)||i.includes(`tanstack-router`),s=i.includes(`nuxt`),c=i.includes(`svelte`);r.includes(`turborepo`)&&await M({devDependencies:[`turbo`],projectDir:a}),r.includes(`pwa`)&&o&&await oe(a,i),r.includes(`tauri`)&&(o||s||c)&&await te(t),r.includes(`biome`)&&await ie(a),r.includes(`husky`)&&await ae(a),r.includes(`starlight`)&&await ee(t)}function re(t,n){return n.some(e=>[`react-router`,`tanstack-router`,`nuxt`,`svelte`].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`].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}=t,s=e.resolve(process.cwd(),r),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`);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})),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 _=[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`native`],v=i.some(e=>_.includes(e));if(v&&!c){let t=[`@tanstack/react-query`],n=[`@tanstack/react-query-devtools`],r=i.some(e=>e!==`native`&&_.includes(e)),a=i.includes(`native`);if(r&&d){let r=e.join(l,`package.json`);if(await h.pathExists(r))try{await M({dependencies:t,devDependencies:n,projectDir:l})}catch{}}if(a&&f){let n=e.join(u,`package.json`);if(await h.pathExists(n))try{await M({dependencies:t,projectDir:u})}catch{}}}if(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}=t;if(a===`convex`||!r)return;let o=e.resolve(process.cwd(),n),s=e.join(o,`apps/server`),c=e.join(o,`apps/web`),l=e.join(o,`apps/native`),u=await h.pathExists(c),d=await h.pathExists(l),f=await h.pathExists(s);try{f&&await M({dependencies:[`better-auth`],projectDir:s});let e=i.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].includes(e));e&&u&&await M({dependencies:[`better-auth`],projectDir:c}),i.includes(`native`)&&d&&(await M({dependencies:[`better-auth`,`@better-auth/expo`],projectDir:l}),f&&await M({dependencies:[`@better-auth/expo`],projectDir:s}))}catch(e){p.error(g.red(`Failed to configure authentication dependencies`)),e instanceof Error&&p.error(g.red(e.message))}}function le(e=32){let t=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`,n=``,r=t.length;for(let i=0;i<e;i++)n+=t.charAt(Math.floor(Math.random()*r));return n}async function ue(t){let{projectName:n,backend:r,runtime:i,api:a}=t;if(r===`convex`)return;let o=e.resolve(process.cwd(),n),s=r,c=e.join(o,`apps/server`),l=[],u=[];s===`hono`?(l.push(`hono`),a===`trpc`&&l.push(`@hono/trpc-server`),i===`node`&&(l.push(`@hono/node-server`),u.push(`tsx`,`@types/node`))):s===`elysia`?(l.push(`elysia`,`@elysiajs/cors`),a===`trpc`&&l.push(`@elysiajs/trpc`),i===`node`&&(l.push(`@elysiajs/node`),u.push(`tsx`,`@types/node`))):s===`express`&&(l.push(`express`,`cors`),u.push(`@types/express`,`@types/cors`),i===`node`&&u.push(`tsx`,`@types/node`)),i===`bun`&&u.push(`@types/bun`),(l.length>0||u.length>0)&&await M({dependencies:l,devDependencies:u,projectDir:c})}async function de(t,n){let r=e.join(t,`README.md`),i=fe(n);try{await h.writeFile(r,i)}catch(e){p.error(`Failed to create README.md file:`,e)}}function fe(e){let{projectName:t,packageManager:n,database:r,auth:i,addons:a=[],orm:o=`drizzle`,runtime:s=`bun`,frontend:c=[`tanstack-router`],backend:l=`hono`}=e,u=c.includes(`react-router`),d=c.includes(`tanstack-router`),f=c.includes(`native`),p=c.includes(`next`),m=c.includes(`tanstack-start`),h=c.includes(`svelte`),g=c.includes(`nuxt`),_=n===`npm`?`npm run`:n,v=`3001`;return(u||h)&&(v=`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 React, ${d?`TanStack Router`:u?`React Router`:p?`Next.js`:m?`TanStack Start`:h?`SvelteKit`:g?`Nuxt`:``}, ${l[0].toUpperCase()+l.slice(1)}, tRPC, and more.
5
5
 
@@ -133,7 +133,7 @@ DATABASE_AUTH_TOKEN=`;await h.ensureDir(e.dirname(r)),await h.writeFile(r,i)}fun
133
133
  4. Add these credentials to the .env file in apps/server/.env
134
134
 
135
135
  DATABASE_URL=your_database_url
136
- DATABASE_AUTH_TOKEN=your_auth_token`)}async function ke(r){let{projectName:i,orm:s}=r,c=e.resolve(process.cwd(),i),l=s===`drizzle`,u=d();u.start(`Setting up Turso database`);try{let r=S.platform(),i=r===`darwin`,s=r===`linux`,l=r===`win32`;if(l){u.stop(g.yellow(`Turso setup not supported on Windows`)),o.warn(g.yellow(`Automatic Turso setup is not supported on Windows.`)),await z(c),B();return}u.stop(`Checking Turso CLI`);let p=await Se();if(!p){let e=await n({message:`Would you like to install Turso CLI?`,initialValue:!0});if(a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),!e){await z(c),B();return}await Te(i)}let m=await Ce();m||await we();let h=await De(),_=!1,v=``,y=e.basename(c);for(;!_;){let e=await f({message:`Enter a name for your database:`,defaultValue:y,initialValue:y,placeholder:y});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),v=e;try{let e=await Oe(v,h),t=d();t.start(`Writing configuration to .env file`),await z(c,e),t.stop(`Turso database configured successfully!`),_=!0}catch(e){e instanceof Error&&e.message===`DATABASE_EXISTS`&&(o.warn(g.yellow(`Database "${g.red(v)}" already exists`)),y=`${v}-${Math.floor(Math.random()*1e3)}`)}}}catch(e){u.stop(g.red(`Failed to set up Turso database`)),p.error(g.red(`Error during Turso setup: ${e instanceof Error?e.message:String(e)}`)),await z(c),B(),o.success(`Setup completed with manual configuration required.`)}}async function V(e,t,n){let r=d();try{let i=N(e,t);r&&r.start(n);let a=await x(i,{shell:!0});return r&&r.stop(n),a}catch(e){throw r&&r.stop(g.red(`Failed: ${n}`)),e}}async function Ae(e){try{let t=`neonctl projects list`,n=await V(e,t);return!n.stdout.includes(`not authenticated`)&&!n.stdout.includes(`error`)}catch{return!1}}async function je(e){try{return await V(e,`neonctl auth`,`Authenticating with Neon...`),o.success(`Authenticated with Neon successfully!`),!0}catch{m.error(g.red(`Failed to authenticate with Neon`))}}async function Me(e,t){try{let n=`neonctl projects create --name "${e}" --output json`,{stdout:r}=await V(t,n,`Creating Neon project "${e}"...`),i=JSON.parse(r);if(i.project&&i.connection_uris&&i.connection_uris.length>0){let e=i.project.id,t=i.connection_uris[0].connection_uri,n=i.connection_uris[0].connection_parameters;return{connectionString:t,projectId:e,dbName:n.database,roleName:n.role}}return m.error(g.red(`Failed to extract connection information from response`)),null}catch{m.error(g.red(`Failed to create Neon project`))}}async function H(t,n){let r=e.join(t,`apps/server`,`.env`),i=n?`DATABASE_URL="${n.connectionString}"`:`DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;return await h.ensureDir(e.dirname(r)),await h.writeFile(r,i),!0}function Ne(){o.info(`Manual Neon PostgreSQL Setup Instructions:
136
+ DATABASE_AUTH_TOKEN=your_auth_token`)}async function ke(r){let{projectName:i,orm:s}=r,c=e.resolve(process.cwd(),i),l=s===`drizzle`,u=d();u.start(`Setting up Turso database`);try{let r=S.platform(),i=r===`darwin`,s=r===`linux`,l=r===`win32`;if(l){u.stop(g.yellow(`Turso setup not supported on Windows`)),o.warn(g.yellow(`Automatic Turso setup is not supported on Windows.`)),await z(c),B();return}u.stop(`Checking Turso CLI`);let p=await Se();if(!p){let e=await n({message:`Would you like to install Turso CLI?`,initialValue:!0});if(a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),!e){await z(c),B();return}await Te(i)}let m=await Ce();m||await we();let h=await De(),_=!1,v=``,y=e.basename(c);for(;!_;){let e=await f({message:`Enter a name for your database:`,defaultValue:y,initialValue:y,placeholder:y});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),v=e;try{let e=await Oe(v,h),t=d();t.start(`Writing configuration to .env file`),await z(c,e),t.stop(`Turso database configured successfully!`),_=!0}catch(e){e instanceof Error&&e.message===`DATABASE_EXISTS`&&(o.warn(g.yellow(`Database "${g.red(v)}" already exists`)),y=`${v}-${Math.floor(Math.random()*1e3)}`)}}}catch(e){u.stop(g.red(`Failed to set up Turso database`)),p.error(g.red(`Error during Turso setup: ${e instanceof Error?e.message:String(e)}`)),await z(c),B(),o.success(`Setup completed with manual configuration required.`)}}async function V(e,t,n){let r=d();try{let i=N(e,t);n&&r.start(n);let a=await x(i,{shell:!0});return n&&r.stop(g.green(`Completed`)),a}catch(e){throw r&&r.stop(g.red(`Failed: ${n}`)),e}}async function Ae(e){try{let t=`neonctl projects list`,n=await V(e,t);return!n.stdout.includes(`not authenticated`)&&!n.stdout.includes(`error`)}catch{return!1}}async function je(e){try{return await V(e,`neonctl auth`,`Authenticating with Neon...`),o.success(`Authenticated with Neon successfully!`),!0}catch{m.error(g.red(`Failed to authenticate with Neon`))}}async function Me(e,t){try{let n=`neonctl projects create --name "${e}" --output json`,{stdout:r}=await V(t,n,`Creating Neon project "${e}"...`),i=JSON.parse(r);if(i.project&&i.connection_uris&&i.connection_uris.length>0){let e=i.project.id,t=i.connection_uris[0].connection_uri,n=i.connection_uris[0].connection_parameters;return{connectionString:t,projectId:e,dbName:n.database,roleName:n.role}}return m.error(g.red(`Failed to extract connection information from response`)),null}catch{m.error(g.red(`Failed to create Neon project`))}}async function H(t,n){let r=e.join(t,`apps/server`,`.env`),i=n?`DATABASE_URL="${n.connectionString}"`:`DATABASE_URL="postgresql://postgres:postgres@localhost:5432/mydb?schema=public"`;return await h.ensureDir(e.dirname(r)),await h.writeFile(r,i),!0}function Ne(){o.info(`Manual Neon PostgreSQL Setup Instructions:
137
137
 
138
138
  1. Visit https://neon.tech and create an account
139
139
  2. Create a new project from the dashboard
@@ -144,7 +144,7 @@ DATABASE_URL="your_connection_string"`)}async function Pe(n){let{projectName:r,p
144
144
  `)&&(r+=`
145
145
  `),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}=t,u=e.resolve(process.cwd(),n),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=d||f||p||m||g||_;if(v){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`)){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 y=e.join(u,`apps/server`);if(!await h.pathExists(y))return;let b=e.join(y,`.env`),x=`http://localhost:3001`;(d||_)&&(x=`http://localhost:5173`);let S=null,C=l===`turso`||l===`prisma-postgres`||l===`mongodb-atlas`||l===`neon`;if(a!==`none`&&!C)switch(a){case`postgres`:S=`postgresql://postgres:postgres@localhost:5432/mydb?schema=public`;break;case`mysql`:S=`mysql://root:password@localhost:3306/mydb`;break;case`mongodb`:S=`mongodb://localhost:27017/mydatabase`;break;case`sqlite`:S=`file:./local.db`;break}let w=[{key:`CORS_ORIGIN`,value:x,condition:!0},{key:`BETTER_AUTH_SECRET`,value:le(),condition:!!s},{key:`BETTER_AUTH_URL`,value:`http://localhost:3000`,condition:!!s},{key:`DATABASE_URL`,value:S,condition:a!==`none`&&!C},{key:`GOOGLE_GENERATIVE_AI_API_KEY`,value:``,condition:c?.includes(`ai`)||!1}];await U(b,w)}async function Le(t){let{projectName:n,examples:r,frontend:i,backend:a}=t;if(a===`convex`||!r||r.length===0||r[0]===`none`)return;let o=e.resolve(process.cwd(),n);if(r.includes(`ai`)){let t=e.join(o,`apps/web`),n=e.join(o,`apps/server`),r=await h.pathExists(t),a=await h.pathExists(n),s=i.includes(`nuxt`),c=i.includes(`svelte`);if(r){let e=[`ai`];s?e.push(`@ai-sdk/vue`):c&&e.push(`@ai-sdk/svelte`),await M({dependencies:e,projectDir:t})}a&&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,packageManager:r,depsInstalled:i,orm:a,addons:o,runtime:s,frontend:c,backend:l}=e,u=l===`convex`,d=r===`npm`?`npm run`:r,f=`cd ${n}`,p=o?.includes(`husky`)||o?.includes(`biome`),h=!u&&t!==`none`?Ue(t,a,d,s):``,_=o?.includes(`tauri`)?We(d):``,v=p?He(d):``,y=c?.includes(`native`)?Ve(u):``,b=o?.includes(`pwa`)&&(c?.includes(`react-router`)||c?.includes(`tanstack-router`))?Ge():``,x=o?.includes(`starlight`)?Ke(d):``,S=c?.some(e=>[`tanstack-router`,`react-router`,`next`,`tanstack-start`,`nuxt`,`svelte`].includes(e)),C=c?.includes(`native`),w=r===`bun`&&C&&S?Je():``,T=!u&&t!==`none`&&a===`none`?qe():``,E=c?.includes(`react-router`),D=c?.includes(`svelte`),O=E||D?`5173`:`3001`,k=N(r,`taze -r`),A=`${g.bold(`Next steps`)}\n${g.cyan(`1.`)} ${f}\n`,j=2;i||(A+=`${g.cyan(`${j++}.`)} ${r} install\n`),u&&(A+=`${g.cyan(`${j++}.`)} ${d} dev:setup ${g.dim(`(this will guide you through Convex project setup)`)}\n`),A+=`${g.cyan(`${j++}.`)} ${d} dev\n\n`,A+=`${g.bold(`Your project will be available at:`)}\n`,S?A+=`${g.cyan(`•`)} Frontend: http://localhost:${O}\n`:!C&&!o?.includes(`starlight`)&&(A+=`${g.yellow(`NOTE:`)} You are creating a backend-only app (no frontend selected)\n`),u||(A+=`${g.cyan(`•`)} Backend API: http://localhost:3000\n`),o?.includes(`starlight`)&&(A+=`${g.cyan(`•`)} Docs: http://localhost:4321\n`),y&&(A+=`\n${y.trim()}\n`),h&&(A+=`\n${h.trim()}\n`),_&&(A+=`\n${_.trim()}\n`),v&&(A+=`\n${v.trim()}\n`),b&&(A+=`\n${b.trim()}\n`),x&&(A+=`\n${x.trim()}\n`),T&&(A+=`\n${T.trim()}\n`),w&&(A+=`\n${w.trim()}\n`),A+=`\n${g.bold(`Update all dependencies:
146
146
  `)}${g.cyan(k)}\n\n`,A+=`${g.bold(`Like Better-T Stack?`)} Please consider giving us a star on GitHub:\n`,A+=g.cyan(`https://github.com/AmanVarshney01/create-better-t-stack`),m.box(A)}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(`
147
- `)}`:``}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 W(e,t){await Ye(e,t),t.backend===`convex`?await Ze(e,t):await Xe(e,t)}async function Ye(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.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.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.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.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.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 Xe(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.prisma`,a[`db:studio`]=`prisma studio`):n.orm===`drizzle`&&(a[`db:push`]=`drizzle-kit push`,a[`db:studio`]=`drizzle-kit studio`)),await h.writeJson(r,i,{spaces:2})}async function Ze(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 Qe(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 $e(t){let{projectName:n,runtime:r,backend:i}=t;if(i===`convex`||i===`next`||r===`none`)return;let a=e.resolve(process.cwd(),n),o=e.join(a,`apps/server`);await h.pathExists(o)&&(r===`bun`?await et(o,i):r===`node`&&await tt(o,i))}async function et(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 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:`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 G(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 K(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 G(o,l,i):await h.copy(o,l,{overwrite:!0})}catch{}}}async function nt(t,n){let r=e.join(k,`templates/base`);await K([`**/*`],r,t,n),await h.ensureDir(e.join(t,`packages`))}async function rt(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(`native`),s=n.backend===`convex`;if(r||i||a){let o=e.join(t,`apps/web`);if(await h.ensureDir(o),r){let t=e.join(k,`templates/frontend/react/web-base`);await h.pathExists(t)&&await K(`**/*`,t,o,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 K(`**/*`,t,o,n),!s&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/react/base`);await h.pathExists(t)&&await K(`**/*`,t,o,n)}}}else if(i){let t=e.join(k,`templates/frontend/nuxt`);if(await h.pathExists(t)&&await K(`**/*`,t,o,n),!s&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/nuxt`);await h.pathExists(t)&&await K(`**/*`,t,o,n)}}else if(a){let t=e.join(k,`templates/frontend/svelte`);if(await h.pathExists(t)&&await K(`**/*`,t,o,n),!s&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/svelte`);await h.pathExists(t)&&await K(`**/*`,t,o,n)}}}if(o){let r=e.join(t,`apps/native`);await h.ensureDir(r);let i=e.join(k,`templates/frontend/native`);if(await h.pathExists(i)&&await K(`**/*`,i,r,n),!s&&(n.api===`trpc`||n.api===`orpc`)){let t=e.join(k,`templates/api/${n.api}/native`);await h.pathExists(t)&&await K(`**/*`,t,r,n)}}}async function it(t,n){if(n.backend===`convex`){let r=e.join(t,`packages/backend`),i=e.join(k,`templates/backend/convex/packages/backend`);await h.ensureDir(r),await h.pathExists(i)&&await K(`**/*`,i,r,n);let a=e.join(t,`apps/server`);await h.pathExists(a)&&await h.remove(a);return}let r=e.join(t,`apps/server`);await h.ensureDir(r);let i=e.join(k,`templates/backend/server/server-base`);await h.pathExists(i)&&await K(`**/*`,i,r,n);let a=e.join(k,`templates/backend/server/${n.backend}`);if(await h.pathExists(a)&&await K(`**/*`,a,r,n,!0),n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/server/base`);await h.pathExists(t)&&await K(`**/*`,t,r,n,!0);let i=e.join(k,`templates/api/${n.api}/server/${n.backend}`);await h.pathExists(i)&&await K(`**/*`,i,r,n,!0)}}async function at(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 K(`**/*`,i,r,n)}async function ot(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(`native`);if(o){let t=e.join(k,`templates/auth/server/base`);if(await h.pathExists(t)&&await K(`**/*`,t,r,n),n.backend===`next`){let t=e.join(k,`templates/auth/server/next`);await h.pathExists(t)&&await K(`**/*`,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}`)),a&&await h.pathExists(a)&&await K(`**/*`,a,r,n)}}if((l||u||d)&&s){if(l){let t=e.join(k,`templates/auth/web/react/base`);await h.pathExists(t)&&await K(`**/*`,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 K(`**/*`,t,i,n)}}else if(u){let t=e.join(k,`templates/auth/web/nuxt`);await h.pathExists(t)&&await K(`**/*`,t,i,n)}else if(d&&n.api===`orpc`){let t=e.join(k,`templates/auth/web/svelte`);await h.pathExists(t)&&await K(`**/*`,t,i,n)}}if(f&&c){let t=e.join(k,`templates/auth/native`);await h.pathExists(t)&&await K(`**/*`,t,a,n)}}async function st(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 K(`**/*`,i,a,n)}}async function ct(t,n){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`);for(let t of n.examples){if(!n.examples||n.examples.length===0||n.examples[0]===`none`||t===`none`)continue;let u=e.join(k,`templates/examples/${t}`);if(a){let t=e.join(u,`server`);if(await h.pathExists(t)&&n.backend!==`convex`){if(n.orm!==`none`&&n.database!==`none`){let i=e.join(t,n.orm,`base`);await h.pathExists(i)&&await K(`**/*`,i,r,n,!1);let a=e.join(t,n.orm,n.database);await h.pathExists(a)&&await K(`**/*`,a,r,n,!1)}let i=await C([`*.ts`,`*.hbs`],{cwd:t,onlyFiles:!0,deep:1,ignore:[`${n.orm}/**`]});for(let a of i){let i=e.join(t,a),o=e.join(r,a.replace(`.hbs`,``));i.endsWith(`.hbs`)?await G(i,o,n):await h.copy(i,o,{overwrite:!1})}}}if(o){if(s){let t=e.join(u,`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 K(`**/*`,a,i,n,!1)}}}else if(c){if(n.api===`orpc`){let t=e.join(u,`web/nuxt`);await h.pathExists(t)&&await K(`**/*`,t,i,n,!1)}}else if(l&&n.api===`orpc`){let t=e.join(u,`web/svelte`);await h.pathExists(t)&&await K(`**/*`,t,i,n,!1)}}}}async function lt(t,n){let r=e.join(k,`templates/extras`);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`&&(n.frontend.includes(`native`)||n.frontend.includes(`nuxt`))){let i=e.join(r,`_npmrc.hbs`),a=e.join(t,`.npmrc`);await h.pathExists(i)&&await G(i,a,n)}}async function ut(n){let r=e.resolve(process.cwd(),n.projectName),i=n.backend===`convex`;try{return await h.ensureDir(r),await nt(r,n),await rt(r,n),await it(r,n),i||(await at(r,n),await ot(r,n)),n.examples.length>0&&n.examples[0]!==`none`&&await ct(r,n),await st(r,n),await se(n),i||(await ue(n),await Fe(n),await $e(n),n.examples.length>0&&n.examples[0]!==`none`&&await Le(n)),n.addons.length>0&&n.addons[0]!==`none`&&await ne(n),!i&&n.auth&&await ce(n),await lt(r,n),await Ie(n),await W(r,n),await de(r,n),await Qe(r,n.git),o.success(`Project template successfully scaffolded!`),n.install&&await Re({projectDir:r,packageManager:n.packageManager,addons:n.addons}),Be({...n,depsInstalled:n.install}),r}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 dt(e,n){if(e!==void 0)return e;let r=n?.includes(`react-router`)||n?.includes(`tanstack-router`),i=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`nuxt`)||n?.includes(`svelte`),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 ft(e,n,r){if(r===`convex`)return`none`;if(e)return e;let i=n?.includes(`nuxt`),o=n?.includes(`svelte`),s=[{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=[{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs (Required for ${i?`Nuxt`:`Svelte`} frontend)`}]);let c=await u({message:`Select API type`,options:s,initialValue:i||o?`orpc`:A.api});return a(c)&&(t(g.red(`Operation cancelled`)),process.exit(0)),(i||o)&&c!==`orpc`?`orpc`:c}async function pt(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 mt(e){if(e!==void 0)return e;let n=await u({message:`Select backend framework`,options:[{value:`hono`,label:`Hono`,hint:`Lightweight, ultrafast web framework`},{value:`next`,label:`Next.js`,hint:`Full-stack framework with API routes`},{value:`express`,label:`Express`,hint:`Fast, unopinionated, minimalist web framework for Node.js`},{value:`elysia`,label:`Elysia`,hint:`Ergonomic web framework for building backend servers`},{value:`convex`,label:`Convex`,hint:`Reactive backend-as-a-service platform`}],initialValue:A.backend});return a(n)&&(t(g.red(`Operation cancelled`)),process.exit(0)),n}async function ht(e,n){if(n===`convex`)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 gt(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 _t(e,n,r,i){if(e!==void 0)return e;if(i===`convex`)return[`todo`];if(n===`none`)return[];let o=r&&r.length===1&&r[0]===`native`;if(o)return[];let c=r?.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].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`&&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 vt(e){if(e!==void 0)return e;let n=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:A.frontend.some(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`)?[`web`]:[]});a(n)&&(t(g.red(`Operation cancelled`)),process.exit(0));let r=[];if(n.includes(`web`)){let e=await u({message:`Choose frontend framework`,options:[{value:`tanstack-router`,label:`TanStack Router`,hint:`Modern and scalable routing for React Applications`},{value:`react-router`,label:`React Router`,hint:`A user‑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:`tanstack-start`,label:`TanStack Start (beta)`,hint:`SSR, Server Functions, API Routes and more with TanStack Router`}],initialValue:A.frontend.find(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`)||`tanstack-router`});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r.push(e)}return n.includes(`native`)&&r.push(`native`),r}async function yt(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 bt(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}async function xt(e,n,r,i){if(i===`convex`||!n)return`none`;if(e!==void 0)return e;if(r===`mongodb`)return o.info(`Only Prisma is supported with MongoDB.`),`prisma`;let s=await u({message:`Select ORM`,options:[{value:`drizzle`,label:`Drizzle`,hint:`lightweight and performant TypeScript ORM`},{value:`prisma`,label:`Prisma`,hint:`Powerful, feature-rich ORM`}],initialValue:A.orm});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function St(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}const Ct=[`<`,`>`,`:`,`"`,`|`,`?`,`*`],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(Ct.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===`.`){let e=process.cwd();if(h.readdirSync(e).length===0)return n}else{let t=e.basename(n),r=J(t);if(!r){let t=e.resolve(process.cwd(),n);if(!h.pathExistsSync(t)||h.readdirSync(t).length===0)return n}}let r=!1,i=``,o=A.projectName,s=1;for(;h.pathExistsSync(e.resolve(process.cwd(),o));)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;if(n===`.`){let e=h.readdirSync(process.cwd());if(e.length>0)return`Current directory is not empty. Please choose a different directory.`;r=!0;return}let i=e.resolve(process.cwd(),n),a=e.basename(i),s=J(a);if(s)return s;if(!i.startsWith(process.cwd()))return`Project path must be within current directory`;if(h.pathExistsSync(i)){let e=h.readdirSync(i);if(e.length>0)return`Directory "${n}" already exists and is not empty. Please choose a different name or path.`}r=!0}});a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),i=s||o}return i}async function wt(e,n){if(n===`convex`)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){let n=await r({projectName:async()=>Y(e.projectName),frontend:()=>vt(e.frontend),backend:()=>mt(e.backend),runtime:({results:t})=>wt(e.runtime,t.backend),database:({results:t})=>ht(e.database,t.backend),orm:({results:t})=>xt(e.orm,t.database!==`none`,t.database,t.backend),api:({results:t})=>ft(e.api,t.frontend,t.backend),auth:({results:t})=>pt(e.auth,t.database!==`none`,t.backend),addons:({results:t})=>dt(e.addons,t.frontend),examples:({results:t})=>_t(e.examples,t.database,t.frontend,t.backend),dbSetup:({results:t})=>gt(t.database??`none`,e.dbSetup,t.orm,t.backend),git:()=>yt(e.git),packageManager:()=>St(e.packageManager),install:()=>bt(e.install)},{onCancel:()=>{t(g.red(`Operation cancelled`)),process.exit(0)}});return n.backend===`convex`&&(n.runtime=`none`,n.database=`none`,n.orm=`none`,n.api=`none`,n.auth=!1,n.dbSetup=`none`),{projectName:n.projectName,frontend:n.frontend,backend:n.backend,runtime:n.runtime,database:n.database,orm:n.orm,auth:n.auth,addons:n.addons,examples:n.examples,git:n.git,packageManager:n.packageManager,install:n.install,dbSetup:n.dbSetup,api:n.api}}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(`
147
+ `)}`:``}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 W(e,t){await Ye(e,t),t.backend===`convex`?await Ze(e,t):await Xe(e,t)}async function Ye(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.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.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.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.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.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 Xe(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.prisma`,a[`db:studio`]=`prisma studio`):n.orm===`drizzle`&&(a[`db:push`]=`drizzle-kit push`,a[`db:studio`]=`drizzle-kit studio`)),await h.writeJson(r,i,{spaces:2})}async function Ze(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 Qe(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 $e(t){let{projectName:n,runtime:r,backend:i}=t;if(i===`convex`||i===`next`||r===`none`)return;let a=e.resolve(process.cwd(),n),o=e.join(a,`apps/server`);await h.pathExists(o)&&(r===`bun`?await et(o,i):r===`node`&&await tt(o,i))}async function et(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 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:`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 G(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 K(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 G(o,l,i):await h.copy(o,l,{overwrite:!0})}catch{}}}async function nt(t,n){let r=e.join(k,`templates/base`);await K([`**/*`],r,t,n),await h.ensureDir(e.join(t,`packages`))}async function rt(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(`native`),s=n.backend===`convex`;if(r||i||a){let o=e.join(t,`apps/web`);if(await h.ensureDir(o),r){let t=e.join(k,`templates/frontend/react/web-base`);await h.pathExists(t)&&await K(`**/*`,t,o,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 K(`**/*`,t,o,n),!s&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/react/base`);await h.pathExists(t)&&await K(`**/*`,t,o,n)}}}else if(i){let t=e.join(k,`templates/frontend/nuxt`);if(await h.pathExists(t)&&await K(`**/*`,t,o,n),!s&&n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/web/nuxt`);await h.pathExists(t)&&await K(`**/*`,t,o,n)}}else if(a){let t=e.join(k,`templates/frontend/svelte`);if(await h.pathExists(t)&&await K(`**/*`,t,o,n),!s&&n.api===`orpc`){let t=e.join(k,`templates/api/${n.api}/web/svelte`);await h.pathExists(t)&&await K(`**/*`,t,o,n)}}}if(o){let r=e.join(t,`apps/native`);await h.ensureDir(r);let i=e.join(k,`templates/frontend/native`);if(await h.pathExists(i)&&await K(`**/*`,i,r,n),!s&&(n.api===`trpc`||n.api===`orpc`)){let t=e.join(k,`templates/api/${n.api}/native`);await h.pathExists(t)&&await K(`**/*`,t,r,n)}}}async function it(t,n){if(n.backend===`convex`){let r=e.join(t,`packages/backend`),i=e.join(k,`templates/backend/convex/packages/backend`);await h.ensureDir(r),await h.pathExists(i)&&await K(`**/*`,i,r,n);let a=e.join(t,`apps/server`);await h.pathExists(a)&&await h.remove(a);return}let r=e.join(t,`apps/server`);await h.ensureDir(r);let i=e.join(k,`templates/backend/server/server-base`);await h.pathExists(i)&&await K(`**/*`,i,r,n);let a=e.join(k,`templates/backend/server/${n.backend}`);if(await h.pathExists(a)&&await K(`**/*`,a,r,n,!0),n.api!==`none`){let t=e.join(k,`templates/api/${n.api}/server/base`);await h.pathExists(t)&&await K(`**/*`,t,r,n,!0);let i=e.join(k,`templates/api/${n.api}/server/${n.backend}`);await h.pathExists(i)&&await K(`**/*`,i,r,n,!0)}}async function at(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 K(`**/*`,i,r,n)}async function ot(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(`native`);if(o){let t=e.join(k,`templates/auth/server/base`);if(await h.pathExists(t)&&await K(`**/*`,t,r,n),n.backend===`next`){let t=e.join(k,`templates/auth/server/next`);await h.pathExists(t)&&await K(`**/*`,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}`)),a&&await h.pathExists(a)&&await K(`**/*`,a,r,n)}}if((l||u||d)&&s){if(l){let t=e.join(k,`templates/auth/web/react/base`);await h.pathExists(t)&&await K(`**/*`,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 K(`**/*`,t,i,n)}}else if(u){let t=e.join(k,`templates/auth/web/nuxt`);await h.pathExists(t)&&await K(`**/*`,t,i,n)}else if(d&&n.api===`orpc`){let t=e.join(k,`templates/auth/web/svelte`);await h.pathExists(t)&&await K(`**/*`,t,i,n)}}if(f&&c){let t=e.join(k,`templates/auth/native`);await h.pathExists(t)&&await K(`**/*`,t,a,n)}}async function st(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 K(`**/*`,i,a,n)}}async function ct(t,n){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`);for(let t of n.examples){if(!n.examples||n.examples.length===0||n.examples[0]===`none`||t===`none`)continue;let u=e.join(k,`templates/examples/${t}`);if(a){let t=e.join(u,`server`);if(await h.pathExists(t)&&n.backend!==`convex`){if(n.orm!==`none`&&n.database!==`none`){let i=e.join(t,n.orm,`base`);await h.pathExists(i)&&await K(`**/*`,i,r,n,!1);let a=e.join(t,n.orm,n.database);await h.pathExists(a)&&await K(`**/*`,a,r,n,!1)}let i=await C([`*.ts`,`*.hbs`],{cwd:t,onlyFiles:!0,deep:1,ignore:[`${n.orm}/**`]});for(let a of i){let i=e.join(t,a),o=e.join(r,a.replace(`.hbs`,``));i.endsWith(`.hbs`)?await G(i,o,n):await h.copy(i,o,{overwrite:!1})}}}if(o){if(s){let t=e.join(u,`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 K(`**/*`,a,i,n,!1)}}}else if(c){let t=e.join(u,`web/nuxt`);await h.pathExists(t)&&await K(`**/*`,t,i,n,!1)}else if(l){let t=e.join(u,`web/svelte`);await h.pathExists(t)&&await K(`**/*`,t,i,n,!1)}}}}async function lt(t,n){let r=e.join(k,`templates/extras`);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`&&(n.frontend.includes(`native`)||n.frontend.includes(`nuxt`))){let i=e.join(r,`_npmrc.hbs`),a=e.join(t,`.npmrc`);await h.pathExists(i)&&await G(i,a,n)}}async function ut(n){let r=e.resolve(process.cwd(),n.projectName),i=n.backend===`convex`;try{return await h.ensureDir(r),await nt(r,n),await rt(r,n),await it(r,n),i||(await at(r,n),await ot(r,n)),n.examples.length>0&&n.examples[0]!==`none`&&await ct(r,n),await st(r,n),await se(n),i||(await ue(n),await Fe(n),await $e(n),n.examples.length>0&&n.examples[0]!==`none`&&await Le(n)),n.addons.length>0&&n.addons[0]!==`none`&&await ne(n),!i&&n.auth&&await ce(n),await lt(r,n),await Ie(n),await W(r,n),await de(r,n),await Qe(r,n.git),o.success(`Project template successfully scaffolded!`),n.install&&await Re({projectDir:r,packageManager:n.packageManager,addons:n.addons}),Be({...n,depsInstalled:n.install}),r}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 dt(e,n){if(e!==void 0)return e;let r=n?.includes(`react-router`)||n?.includes(`tanstack-router`),i=n?.includes(`react-router`)||n?.includes(`tanstack-router`)||n?.includes(`nuxt`)||n?.includes(`svelte`),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 ft(e,n,r){if(r===`convex`)return`none`;if(e)return e;let i=n?.includes(`nuxt`),o=n?.includes(`svelte`),s=[{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=[{value:`orpc`,label:`oRPC`,hint:`End-to-end type-safe APIs (Required for ${i?`Nuxt`:`Svelte`} frontend)`}]);let c=await u({message:`Select API type`,options:s,initialValue:i||o?`orpc`:A.api});return a(c)&&(t(g.red(`Operation cancelled`)),process.exit(0)),(i||o)&&c!==`orpc`?`orpc`:c}async function pt(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 mt(e){if(e!==void 0)return e;let n=await u({message:`Select backend framework`,options:[{value:`hono`,label:`Hono`,hint:`Lightweight, ultrafast web framework`},{value:`next`,label:`Next.js`,hint:`Full-stack framework with API routes`},{value:`express`,label:`Express`,hint:`Fast, unopinionated, minimalist web framework for Node.js`},{value:`elysia`,label:`Elysia`,hint:`Ergonomic web framework for building backend servers`},{value:`convex`,label:`Convex`,hint:`Reactive backend-as-a-service platform`}],initialValue:A.backend});return a(n)&&(t(g.red(`Operation cancelled`)),process.exit(0)),n}async function ht(e,n){if(n===`convex`)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 gt(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 _t(e,n,r,i){if(e!==void 0)return e;if(i===`convex`)return[`todo`];if(n===`none`)return[];let o=r&&r.length===1&&r[0]===`native`;if(o)return[];let c=r?.some(e=>[`react-router`,`tanstack-router`,`tanstack-start`,`next`,`nuxt`,`svelte`].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`&&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 vt(e){if(e!==void 0)return e;let n=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:A.frontend.some(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`)?[`web`]:[]});a(n)&&(t(g.red(`Operation cancelled`)),process.exit(0));let r=[];if(n.includes(`web`)){let e=await u({message:`Choose frontend framework`,options:[{value:`tanstack-router`,label:`TanStack Router`,hint:`Modern and scalable routing for React Applications`},{value:`react-router`,label:`React Router`,hint:`A user‑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:`tanstack-start`,label:`TanStack Start (beta)`,hint:`SSR, Server Functions, API Routes and more with TanStack Router`}],initialValue:A.frontend.find(e=>e===`tanstack-router`||e===`react-router`||e===`tanstack-start`||e===`next`||e===`nuxt`||e===`svelte`)||`tanstack-router`});a(e)&&(t(g.red(`Operation cancelled`)),process.exit(0)),r.push(e)}return n.includes(`native`)&&r.push(`native`),r}async function yt(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 bt(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}async function xt(e,n,r,i){if(i===`convex`||!n)return`none`;if(e!==void 0)return e;if(r===`mongodb`)return o.info(`Only Prisma is supported with MongoDB.`),`prisma`;let s=await u({message:`Select ORM`,options:[{value:`drizzle`,label:`Drizzle`,hint:`lightweight and performant TypeScript ORM`},{value:`prisma`,label:`Prisma`,hint:`Powerful, feature-rich ORM`}],initialValue:A.orm});return a(s)&&(t(g.red(`Operation cancelled`)),process.exit(0)),s}async function St(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}const Ct=[`<`,`>`,`:`,`"`,`|`,`?`,`*`],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(Ct.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===`.`){let e=process.cwd();if(h.readdirSync(e).length===0)return n}else{let t=e.basename(n),r=J(t);if(!r){let t=e.resolve(process.cwd(),n);if(!h.pathExistsSync(t)||h.readdirSync(t).length===0)return n}}let r=!1,i=``,o=A.projectName,s=1;for(;h.pathExistsSync(e.resolve(process.cwd(),o));)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;if(n===`.`){let e=h.readdirSync(process.cwd());if(e.length>0)return`Current directory is not empty. Please choose a different directory.`;r=!0;return}let i=e.resolve(process.cwd(),n),a=e.basename(i),s=J(a);if(s)return s;if(!i.startsWith(process.cwd()))return`Project path must be within current directory`;if(h.pathExistsSync(i)){let e=h.readdirSync(i);if(e.length>0)return`Directory "${n}" already exists and is not empty. Please choose a different name or path.`}r=!0}});a(s)&&(t(g.red(`Operation cancelled.`)),process.exit(0)),i=s||o}return i}async function wt(e,n){if(n===`convex`)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){let n=await r({projectName:async()=>Y(e.projectName),frontend:()=>vt(e.frontend),backend:()=>mt(e.backend),runtime:({results:t})=>wt(e.runtime,t.backend),database:({results:t})=>ht(e.database,t.backend),orm:({results:t})=>xt(e.orm,t.database!==`none`,t.database,t.backend),api:({results:t})=>ft(e.api,t.frontend,t.backend),auth:({results:t})=>pt(e.auth,t.database!==`none`,t.backend),addons:({results:t})=>dt(e.addons,t.frontend),examples:({results:t})=>_t(e.examples,t.database,t.frontend,t.backend),dbSetup:({results:t})=>gt(t.database??`none`,e.dbSetup,t.orm,t.backend),git:()=>yt(e.git),packageManager:()=>St(e.packageManager),install:()=>bt(e.install)},{onCancel:()=>{t(g.red(`Operation cancelled`)),process.exit(0)}});return n.backend===`convex`&&(n.runtime=`none`,n.database=`none`,n.orm=`none`,n.api=`none`,n.auth=!1,n.dbSetup=`none`),{projectName:n.projectName,frontend:n.frontend,backend:n.backend,runtime:n.runtime,database:n.database,orm:n.orm,auth:n.auth,addons:n.addons,examples:n.examples,git:n.git,packageManager:n.packageManager,install:n.install,dbSetup:n.dbSetup,api:n.api}}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(`
148
148
  `)}function Et(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.projectName?` ${e.projectName}`:``;return`${n}${i} ${t.join(` `)}`}const Dt=()=>{let t=e.join(k,`package.json`),n=h.readJSONSync(t);return n.version??`1.0.0`},Z=`
149
149
  ██████╗ ███████╗████████╗████████╗███████╗██████╗
150
150
  ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.3.0",
3
+ "version": "2.4.1",
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",
@@ -0,0 +1,357 @@
1
+ {{#if (eq backend "convex")}}
2
+ <script lang="ts">
3
+ import { useQuery, useConvexClient } from 'convex-svelte';
4
+ import { api } from '@{{projectName}}/backend/convex/_generated/api.js';
5
+ import type { Id } from '@{{projectName}}/backend/convex/_generated/dataModel.js';
6
+
7
+ let newTodoText = $state('');
8
+ let isAdding = $state(false);
9
+ let addError = $state<Error | null>(null);
10
+ let togglingId = $state<Id<'todos'> | null>(null);
11
+ let toggleError = $state<Error | null>(null);
12
+ let deletingId = $state<Id<'todos'> | null>(null);
13
+ let deleteError = $state<Error | null>(null);
14
+
15
+ const client = useConvexClient();
16
+
17
+ const todosQuery = useQuery(api.todos.getAll, {});
18
+
19
+ async function handleAddTodo(event: SubmitEvent) {
20
+ event.preventDefault();
21
+ const text = newTodoText.trim();
22
+ if (!text || isAdding) return;
23
+
24
+ isAdding = true;
25
+ addError = null;
26
+ try {
27
+ await client.mutation(api.todos.create, { text });
28
+ newTodoText = '';
29
+ } catch (err) {
30
+ console.error('Failed to add todo:', err);
31
+ addError = err instanceof Error ? err : new Error(String(err));
32
+ } finally {
33
+ isAdding = false;
34
+ }
35
+ }
36
+
37
+ async function handleToggleTodo(id: Id<'todos'>, completed: boolean) {
38
+ if (togglingId === id || deletingId === id) return;
39
+
40
+ togglingId = id;
41
+ toggleError = null;
42
+ try {
43
+ await client.mutation(api.todos.toggle, { id, completed: !completed });
44
+ } catch (err) {
45
+ console.error('Failed to toggle todo:', err);
46
+ toggleError = err instanceof Error ? err : new Error(String(err));
47
+ } finally {
48
+ if (togglingId === id) {
49
+ togglingId = null;
50
+ }
51
+ }
52
+ }
53
+
54
+ async function handleDeleteTodo(id: Id<'todos'>) {
55
+ if (togglingId === id || deletingId === id) return;
56
+
57
+ deletingId = id;
58
+ deleteError = null;
59
+ try {
60
+ await client.mutation(api.todos.deleteTodo, { id });
61
+ } catch (err) {
62
+ console.error('Failed to delete todo:', err);
63
+ deleteError = err instanceof Error ? err : new Error(String(err));
64
+ } finally {
65
+ if (deletingId === id) {
66
+ deletingId = null;
67
+ }
68
+ }
69
+ }
70
+
71
+ const canAdd = $derived(!isAdding && newTodoText.trim().length > 0);
72
+ const isLoadingTodos = $derived(todosQuery.isLoading);
73
+ const todos = $derived(todosQuery.data ?? []);
74
+ const hasTodos = $derived(todos.length > 0);
75
+
76
+ </script>
77
+
78
+ <div class="p-4">
79
+ <h1 class="text-xl mb-4">Todos (Convex)</h1>
80
+
81
+ <form onsubmit={handleAddTodo} class="flex gap-2 mb-4">
82
+ <input
83
+ type="text"
84
+ bind:value={newTodoText}
85
+ placeholder="New task..."
86
+ disabled={isAdding}
87
+ class="p-1 flex-grow"
88
+ />
89
+ <button
90
+ type="submit"
91
+ disabled={!canAdd}
92
+ class="bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50"
93
+ >
94
+ {#if isAdding}Adding...{:else}Add{/if}
95
+ </button>
96
+ </form>
97
+
98
+ {#if isLoadingTodos}
99
+ <p>Loading...</p>
100
+ {:else if !hasTodos}
101
+ <p>No todos yet.</p>
102
+ {:else}
103
+ <ul class="space-y-1">
104
+ {#each todos as todo (todo._id)}
105
+ {@const isTogglingThis = togglingId === todo._id}
106
+ {@const isDeletingThis = deletingId === todo._id}
107
+ {@const isDisabled = isTogglingThis || isDeletingThis}
108
+ <li
109
+ class="flex items-center justify-between p-2"
110
+ class:opacity-50={isDisabled}
111
+ >
112
+ <div class="flex items-center gap-2">
113
+ <input
114
+ type="checkbox"
115
+ id={`todo-${todo._id}`}
116
+ checked={todo.completed}
117
+ onchange={() => handleToggleTodo(todo._id, todo.completed)}
118
+ disabled={isDisabled}
119
+ />
120
+ <label
121
+ for={`todo-${todo._id}`}
122
+ class:line-through={todo.completed}
123
+ >
124
+ {todo.text}
125
+ </label>
126
+ </div>
127
+ <button
128
+ type="button"
129
+ onclick={() => handleDeleteTodo(todo._id)}
130
+ disabled={isDisabled}
131
+ aria-label="Delete todo"
132
+ class="text-red-500 px-1 disabled:opacity-50"
133
+ >
134
+ {#if isDeletingThis}Deleting...{:else}X{/if}
135
+ </button>
136
+ </li>
137
+ {/each}
138
+ </ul>
139
+ {/if}
140
+
141
+ {#if todosQuery.error}
142
+ <p class="mt-4 text-red-500">
143
+ Error loading: {todosQuery.error?.message ?? 'Unknown error'}
144
+ </p>
145
+ {/if}
146
+ {#if addError}
147
+ <p class="mt-4 text-red-500">
148
+ Error adding: {addError.message ?? 'Unknown error'}
149
+ </p>
150
+ {/if}
151
+ {#if toggleError}
152
+ <p class="mt-4 text-red-500">
153
+ Error updating: {toggleError.message ?? 'Unknown error'}
154
+ </p>
155
+ {/if}
156
+ {#if deleteError}
157
+ <p class="mt-4 text-red-500">
158
+ Error deleting: {deleteError.message ?? 'Unknown error'}
159
+ </p>
160
+ {/if}
161
+ </div>
162
+ {{else}}
163
+ <script lang="ts">
164
+ {{#if (eq api "orpc")}}
165
+ import { orpc } from '$lib/orpc';
166
+ {{/if}}
167
+ {{#if (eq api "trpc")}}
168
+ import { trpc } from '$lib/trpc';
169
+ {{/if}}
170
+ import { createQuery, createMutation } from '@tanstack/svelte-query';
171
+
172
+ let newTodoText = $state('');
173
+
174
+ {{#if (eq api "orpc")}}
175
+ const todosQuery = createQuery(orpc.todo.getAll.queryOptions());
176
+
177
+ const addMutation = createMutation(
178
+ orpc.todo.create.mutationOptions({
179
+ onSuccess: () => {
180
+ $todosQuery.refetch();
181
+ newTodoText = '';
182
+ },
183
+ onError: (error) => {
184
+ console.error('Failed to create todo:', error?.message ?? error);
185
+ },
186
+ })
187
+ );
188
+
189
+ const toggleMutation = createMutation(
190
+ orpc.todo.toggle.mutationOptions({
191
+ onSuccess: () => {
192
+ $todosQuery.refetch();
193
+ },
194
+ onError: (error) => {
195
+ console.error('Failed to toggle todo:', error?.message ?? error);
196
+ },
197
+ })
198
+ );
199
+
200
+ const deleteMutation = createMutation(
201
+ orpc.todo.delete.mutationOptions({
202
+ onSuccess: () => {
203
+ $todosQuery.refetch();
204
+ },
205
+ onError: (error) => {
206
+ console.error('Failed to delete todo:', error?.message ?? error);
207
+ },
208
+ })
209
+ );
210
+ {{/if}}
211
+ {{#if (eq api "trpc")}}
212
+ const todosQuery = createQuery(trpc.todo.getAll.queryOptions());
213
+
214
+ const addMutation = createMutation(
215
+ trpc.todo.create.mutationOptions({
216
+ onSuccess: () => {
217
+ $todosQuery.refetch();
218
+ newTodoText = '';
219
+ },
220
+ onError: (error) => {
221
+ console.error('Failed to create todo:', error?.message ?? error);
222
+ },
223
+ })
224
+ );
225
+
226
+ const toggleMutation = createMutation(
227
+ trpc.todo.toggle.mutationOptions({
228
+ onSuccess: () => {
229
+ $todosQuery.refetch();
230
+ },
231
+ onError: (error) => {
232
+ console.error('Failed to toggle todo:', error?.message ?? error);
233
+ },
234
+ })
235
+ );
236
+
237
+ const deleteMutation = createMutation(
238
+ trpc.todo.delete.mutationOptions({
239
+ onSuccess: () => {
240
+ $todosQuery.refetch();
241
+ },
242
+ onError: (error) => {
243
+ console.error('Failed to delete todo:', error?.message ?? error);
244
+ },
245
+ })
246
+ );
247
+ {{/if}}
248
+
249
+ function handleAddTodo(event: SubmitEvent) {
250
+ event.preventDefault();
251
+ const text = newTodoText.trim();
252
+ if (text) {
253
+ $addMutation.mutate({ text });
254
+ }
255
+ }
256
+
257
+ function handleToggleTodo(id: number, completed: boolean) {
258
+ $toggleMutation.mutate({ id, completed: !completed });
259
+ }
260
+
261
+ function handleDeleteTodo(id: number) {
262
+ $deleteMutation.mutate({ id });
263
+ }
264
+
265
+ const isAdding = $derived($addMutation.isPending);
266
+ const canAdd = $derived(!isAdding && newTodoText.trim().length > 0);
267
+ const isLoadingTodos = $derived($todosQuery.isLoading);
268
+ const todos = $derived($todosQuery.data ?? []);
269
+ const hasTodos = $derived(todos.length > 0);
270
+
271
+ </script>
272
+
273
+ <div class="p-4">
274
+ <h1 class="text-xl mb-4">Todos{{#if (eq api "trpc")}} (tRPC){{/if}}{{#if (eq api "orpc")}} (oRPC){{/if}}</h1>
275
+
276
+ <form onsubmit={handleAddTodo} class="flex gap-2 mb-4">
277
+ <input
278
+ type="text"
279
+ bind:value={newTodoText}
280
+ placeholder="New task..."
281
+ disabled={isAdding}
282
+ class=" p-1 flex-grow"
283
+ />
284
+ <button
285
+ type="submit"
286
+ disabled={!canAdd}
287
+ class="bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50"
288
+ >
289
+ {#if isAdding}Adding...{:else}Add{/if}
290
+ </button>
291
+ </form>
292
+
293
+ {#if isLoadingTodos}
294
+ <p>Loading...</p>
295
+ {:else if !hasTodos}
296
+ <p>No todos yet.</p>
297
+ {:else}
298
+ <ul class="space-y-1">
299
+ {#each todos as todo (todo.id)}
300
+ {@const isToggling = $toggleMutation.isPending && $toggleMutation.variables?.id === todo.id}
301
+ {@const isDeleting = $deleteMutation.isPending && $deleteMutation.variables?.id === todo.id}
302
+ {@const isDisabled = isToggling || isDeleting}
303
+ <li
304
+ class="flex items-center justify-between p-2 "
305
+ class:opacity-50={isDisabled}
306
+ >
307
+ <div class="flex items-center gap-2">
308
+ <input
309
+ type="checkbox"
310
+ id={`todo-${todo.id}`}
311
+ checked={todo.completed}
312
+ onchange={() => handleToggleTodo(todo.id, todo.completed)}
313
+ disabled={isDisabled}
314
+ />
315
+ <label
316
+ for={`todo-${todo.id}`}
317
+ class:line-through={todo.completed}
318
+ >
319
+ {todo.text}
320
+ </label>
321
+ </div>
322
+ <button
323
+ type="button"
324
+ onclick={() => handleDeleteTodo(todo.id)}
325
+ disabled={isDisabled}
326
+ aria-label="Delete todo"
327
+ class="text-red-500 px-1 disabled:opacity-50"
328
+ >
329
+ {#if isDeleting}Deleting...{:else}X{/if}
330
+ </button>
331
+ </li>
332
+ {/each}
333
+ </ul>
334
+ {/if}
335
+
336
+ {#if $todosQuery.isError}
337
+ <p class="mt-4 text-red-500">
338
+ Error loading: {$todosQuery.error?.message ?? 'Unknown error'}
339
+ </p>
340
+ {/if}
341
+ {#if $addMutation.isError}
342
+ <p class="mt-4 text-red-500">
343
+ Error adding: {$addMutation.error?.message ?? 'Unknown error'}
344
+ </p>
345
+ {/if}
346
+ {#if $toggleMutation.isError}
347
+ <p class="mt-4 text-red-500">
348
+ Error updating: {$toggleMutation.error?.message ?? 'Unknown error'}
349
+ </p>
350
+ {/if}
351
+ {#if $deleteMutation.isError}
352
+ <p class="mt-4 text-red-500">
353
+ Error deleting: {$deleteMutation.error?.message ?? 'Unknown error'}
354
+ </p>
355
+ {/if}
356
+ </div>
357
+ {{/if}}
@@ -1,8 +1,31 @@
1
+ {{#if (eq backend "convex")}}
2
+ <script lang="ts">
3
+ import '../app.css';
4
+ import Header from '../components/Header.svelte';
5
+ import { PUBLIC_CONVEX_URL } from '$env/static/public';
6
+ import { setupConvex } from 'convex-svelte';
7
+
8
+ const { children } = $props();
9
+ setupConvex(PUBLIC_CONVEX_URL);
10
+ </script>
11
+
12
+ <div class="grid h-svh grid-rows-[auto_1fr]">
13
+ <Header />
14
+ <main class="overflow-y-auto">
15
+ {@render children()}
16
+ </main>
17
+ </div>
18
+ {{else}}
1
19
  <script lang="ts">
2
20
  import { QueryClientProvider } from '@tanstack/svelte-query';
3
21
  import { SvelteQueryDevtools } from '@tanstack/svelte-query-devtools'
4
22
  import '../app.css';
23
+ {{#if (eq api "orpc")}}
5
24
  import { queryClient } from '$lib/orpc';
25
+ {{/if}}
26
+ {{#if (eq api "trpc")}}
27
+ import { queryClient } from '$lib/trpc';
28
+ {{/if}}
6
29
  import Header from '../components/Header.svelte';
7
30
 
8
31
  let { children } = $props();
@@ -17,3 +40,4 @@
17
40
  </div>
18
41
  <SvelteQueryDevtools />
19
42
  </QueryClientProvider>
43
+ {{/if}}
@@ -1,8 +1,65 @@
1
+ {{#if (eq backend "convex")}}
1
2
  <script lang="ts">
3
+ import { useQuery } from 'convex-svelte';
4
+ import { api } from "@{{projectName}}/backend/convex/_generated/api.js";
5
+
6
+
7
+ const healthCheck = useQuery(api.healthCheck.get, {});
8
+
9
+
10
+ const TITLE_TEXT = `
11
+ ██████╗ ███████╗████████╗████████╗███████╗██████╗
12
+ ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
13
+ ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
14
+ ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
15
+ ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
16
+ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
17
+
18
+ ████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
19
+ ╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
20
+ ██║ ███████╗ ██║ ███████║██║ █████╔╝
21
+ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
22
+ ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
23
+ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
24
+ `;
25
+ </script>
26
+
27
+ <div class="container mx-auto max-w-3xl px-4 py-2">
28
+ <pre class="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
29
+ <div class="grid gap-6">
30
+ <section class="rounded-lg border p-4">
31
+ <h2 class="mb-2 font-medium">API Status (Convex)</h2>
32
+ <div class="flex items-center gap-2">
33
+ <div
34
+ class={`h-2 w-2 rounded-full ${healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
35
+ ></div>
36
+ <span class="text-muted-foreground text-sm">
37
+ {healthCheck.isLoading
38
+ ? "Checking..."
39
+ : healthCheck.data
40
+ ? "Connected"
41
+ : "Disconnected"}
42
+ </span>
43
+ </div>
44
+ </section>
45
+ </div>
46
+ </div>
47
+ {{else}}
48
+ <script lang="ts">
49
+ {{#if (eq api "orpc")}}
2
50
  import { orpc } from "$lib/orpc";
51
+ {{/if}}
52
+ {{#if (eq api "trpc")}}
53
+ import { trpc } from "$lib/trpc";
54
+ {{/if}}
3
55
  import { createQuery } from "@tanstack/svelte-query";
4
56
 
57
+ {{#if (eq api "orpc")}}
5
58
  const healthCheck = createQuery(orpc.healthCheck.queryOptions());
59
+ {{/if}}
60
+ {{#if (eq api "trpc")}}
61
+ const healthCheck = createQuery(trpc.healthCheck.queryOptions());
62
+ {{/if}}
6
63
 
7
64
 
8
65
  const TITLE_TEXT = `
@@ -26,7 +83,7 @@ const TITLE_TEXT = `
26
83
  <pre class="overflow-x-auto font-mono text-sm">{TITLE_TEXT}</pre>
27
84
  <div class="grid gap-6">
28
85
  <section class="rounded-lg border p-4">
29
- <h2 class="mb-2 font-medium">API Status</h2>
86
+ <h2 class="mb-2 font-medium">API Status{{#if (eq api "trpc")}} (tRPC){{/if}}{{#if (eq api "orpc")}} (oRPC){{/if}}</h2>
30
87
  <div class="flex items-center gap-2">
31
88
  <div
32
89
  class={`h-2 w-2 rounded-full ${$healthCheck.data ? "bg-green-500" : "bg-red-500"}`}
@@ -42,3 +99,4 @@ const TITLE_TEXT = `
42
99
  </section>
43
100
  </div>
44
101
  </div>
102
+ {{/if}}
@@ -1,150 +0,0 @@
1
- <script lang="ts">
2
- import { orpc } from '$lib/orpc';
3
- import { createQuery, createMutation } from '@tanstack/svelte-query';
4
-
5
- let newTodoText = $state('');
6
-
7
- const todosQuery = createQuery(orpc.todo.getAll.queryOptions());
8
-
9
- const addMutation = createMutation(
10
- orpc.todo.create.mutationOptions({
11
- onSuccess: () => {
12
- $todosQuery.refetch();
13
- newTodoText = '';
14
- },
15
- onError: (error) => {
16
- console.error('Failed to create todo:', error?.message ?? error);
17
- },
18
- })
19
- );
20
-
21
- const toggleMutation = createMutation(
22
- orpc.todo.toggle.mutationOptions({
23
- onSuccess: () => {
24
- $todosQuery.refetch();
25
- },
26
- onError: (error) => {
27
- console.error('Failed to toggle todo:', error?.message ?? error);
28
- },
29
- })
30
- );
31
-
32
- const deleteMutation = createMutation(
33
- orpc.todo.delete.mutationOptions({
34
- onSuccess: () => {
35
- $todosQuery.refetch();
36
- },
37
- onError: (error) => {
38
- console.error('Failed to delete todo:', error?.message ?? error);
39
- },
40
- })
41
- );
42
-
43
- function handleAddTodo(event: SubmitEvent) {
44
- event.preventDefault();
45
- const text = newTodoText.trim();
46
- if (text) {
47
- $addMutation.mutate({ text });
48
- }
49
- }
50
-
51
- function handleToggleTodo(id: number, completed: boolean) {
52
- $toggleMutation.mutate({ id, completed: !completed });
53
- }
54
-
55
- function handleDeleteTodo(id: number) {
56
- $deleteMutation.mutate({ id });
57
- }
58
-
59
- const isAdding = $derived($addMutation.isPending);
60
- const canAdd = $derived(!isAdding && newTodoText.trim().length > 0);
61
- const isLoadingTodos = $derived($todosQuery.isLoading);
62
- const todos = $derived($todosQuery.data ?? []);
63
- const hasTodos = $derived(todos.length > 0);
64
-
65
- </script>
66
-
67
- <div class="p-4">
68
- <h1 class="text-xl mb-4">Todos</h1>
69
-
70
- <form onsubmit={handleAddTodo} class="flex gap-2 mb-4">
71
- <input
72
- type="text"
73
- bind:value={newTodoText}
74
- placeholder="New task..."
75
- disabled={isAdding}
76
- class=" p-1 flex-grow"
77
- />
78
- <button
79
- type="submit"
80
- disabled={!canAdd}
81
- class="bg-blue-500 text-white px-3 py-1 rounded disabled:opacity-50"
82
- >
83
- {#if isAdding}Adding...{:else}Add{/if}
84
- </button>
85
- </form>
86
-
87
- {#if isLoadingTodos}
88
- <p>Loading...</p>
89
- {:else if !hasTodos}
90
- <p>No todos yet.</p>
91
- {:else}
92
- <ul class="space-y-1">
93
- {#each todos as todo (todo.id)}
94
- {@const isToggling = $toggleMutation.isPending && $toggleMutation.variables?.id === todo.id}
95
- {@const isDeleting = $deleteMutation.isPending && $deleteMutation.variables?.id === todo.id}
96
- {@const isDisabled = isToggling || isDeleting}
97
- <li
98
- class="flex items-center justify-between p-2 "
99
- class:opacity-50={isDisabled}
100
- >
101
- <div class="flex items-center gap-2">
102
- <input
103
- type="checkbox"
104
- id={`todo-${todo.id}`}
105
- checked={todo.completed}
106
- onchange={() => handleToggleTodo(todo.id, todo.completed)}
107
- disabled={isDisabled}
108
- />
109
- <label
110
- for={`todo-${todo.id}`}
111
- class:line-through={todo.completed}
112
- >
113
- {todo.text}
114
- </label>
115
- </div>
116
- <button
117
- type="button"
118
- onclick={() => handleDeleteTodo(todo.id)}
119
- disabled={isDisabled}
120
- aria-label="Delete todo"
121
- class="text-red-500 px-1 disabled:opacity-50"
122
- >
123
- {#if isDeleting}Deleting...{:else}X{/if}
124
- </button>
125
- </li>
126
- {/each}
127
- </ul>
128
- {/if}
129
-
130
- {#if $todosQuery.isError}
131
- <p class="mt-4 text-red-500">
132
- Error loading: {$todosQuery.error?.message ?? 'Unknown error'}
133
- </p>
134
- {/if}
135
- {#if $addMutation.isError}
136
- <p class="mt-4 text-red-500">
137
- Error adding: {$addMutation.error?.message ?? 'Unknown error'}
138
- </p>
139
- {/if}
140
- {#if $toggleMutation.isError}
141
- <p class="mt-4 text-red-500">
142
- Error updating: {$toggleMutation.error?.message ?? 'Unknown error'}
143
- </p>
144
- {/if}
145
- {#if $deleteMutation.isError}
146
- <p class="mt-4 text-red-500">
147
- Error deleting: {$deleteMutation.error?.message ?? 'Unknown error'}
148
- </p>
149
- {/if}
150
- </div>