create-better-t-stack 3.13.2 → 3.14.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.
Files changed (417) hide show
  1. package/dist/cli.mjs +1 -1
  2. package/dist/index.d.mts +29 -33
  3. package/dist/index.mjs +2 -2
  4. package/dist/{src-kvJQuDe_.mjs → src-CSz9J82Z.mjs} +432 -3588
  5. package/dist/virtual.d.mts +4 -0
  6. package/dist/virtual.mjs +4 -0
  7. package/package.json +9 -5
  8. package/templates/addons/biome/biome.json.hbs +0 -96
  9. package/templates/addons/husky/.husky/pre-commit +0 -1
  10. package/templates/addons/pwa/apps/web/next/public/favicon/apple-touch-icon.png +0 -0
  11. package/templates/addons/pwa/apps/web/next/public/favicon/favicon-96x96.png +0 -0
  12. package/templates/addons/pwa/apps/web/next/public/favicon/favicon.svg +0 -6
  13. package/templates/addons/pwa/apps/web/next/public/favicon/site.webmanifest.hbs +0 -21
  14. package/templates/addons/pwa/apps/web/next/public/favicon/web-app-manifest-192x192.png +0 -0
  15. package/templates/addons/pwa/apps/web/next/public/favicon/web-app-manifest-512x512.png +0 -0
  16. package/templates/addons/pwa/apps/web/next/src/app/manifest.ts.hbs +0 -26
  17. package/templates/addons/pwa/apps/web/vite/public/logo.png +0 -0
  18. package/templates/addons/pwa/apps/web/vite/pwa-assets.config.ts.hbs +0 -12
  19. package/templates/addons/ruler/.ruler/bts.md.hbs +0 -142
  20. package/templates/addons/ruler/.ruler/ruler.toml.hbs +0 -80
  21. package/templates/addons/turborepo/turbo.json.hbs +0 -74
  22. package/templates/addons/ultracite/biome.json.hbs +0 -26
  23. package/templates/api/orpc/fullstack/next/src/app/api/rpc/[[...rest]]/route.ts.hbs +0 -50
  24. package/templates/api/orpc/fullstack/tanstack-start/src/routes/api/rpc/$.ts.hbs +0 -58
  25. package/templates/api/orpc/native/utils/orpc.ts.hbs +0 -39
  26. package/templates/api/orpc/server/_gitignore +0 -34
  27. package/templates/api/orpc/server/package.json.hbs +0 -15
  28. package/templates/api/orpc/server/src/context.ts.hbs +0 -148
  29. package/templates/api/orpc/server/src/index.ts.hbs +0 -21
  30. package/templates/api/orpc/server/src/routers/index.ts.hbs +0 -55
  31. package/templates/api/orpc/server/tsconfig.json.hbs +0 -10
  32. package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +0 -32
  33. package/templates/api/orpc/web/nuxt/app/plugins/vue-query.ts.hbs +0 -44
  34. package/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +0 -113
  35. package/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +0 -30
  36. package/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs +0 -30
  37. package/templates/api/trpc/fullstack/next/src/app/api/trpc/[trpc]/route.ts.hbs +0 -14
  38. package/templates/api/trpc/fullstack/tanstack-start/src/routes/api/trpc/$.ts.hbs +0 -22
  39. package/templates/api/trpc/native/utils/trpc.ts.hbs +0 -37
  40. package/templates/api/trpc/server/_gitignore +0 -34
  41. package/templates/api/trpc/server/package.json.hbs +0 -14
  42. package/templates/api/trpc/server/src/context.ts.hbs +0 -148
  43. package/templates/api/trpc/server/src/index.ts.hbs +0 -26
  44. package/templates/api/trpc/server/src/routers/index.ts.hbs +0 -55
  45. package/templates/api/trpc/server/tsconfig.json.hbs +0 -10
  46. package/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +0 -97
  47. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +0 -6
  48. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +0 -68
  49. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +0 -12
  50. package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +0 -17
  51. package/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs +0 -127
  52. package/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs +0 -138
  53. package/templates/auth/better-auth/convex/native/base/lib/auth-client.ts.hbs +0 -18
  54. package/templates/auth/better-auth/convex/native/unistyles/components/sign-in.tsx.hbs +0 -127
  55. package/templates/auth/better-auth/convex/native/unistyles/components/sign-up.tsx.hbs +0 -145
  56. package/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs +0 -73
  57. package/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs +0 -85
  58. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +0 -3
  59. package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +0 -40
  60. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +0 -129
  61. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +0 -154
  62. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +0 -48
  63. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +0 -6
  64. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +0 -16
  65. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs +0 -133
  66. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs +0 -158
  67. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +0 -52
  68. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +0 -11
  69. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +0 -43
  70. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +0 -133
  71. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +0 -158
  72. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +0 -47
  73. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +0 -6
  74. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +0 -13
  75. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +0 -11
  76. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +0 -43
  77. package/templates/auth/better-auth/fullstack/next/src/app/api/auth/[...all]/route.ts.hbs +0 -4
  78. package/templates/auth/better-auth/fullstack/tanstack-start/src/routes/api/auth/$.ts.hbs +0 -15
  79. package/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs +0 -186
  80. package/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs +0 -131
  81. package/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs +0 -150
  82. package/templates/auth/better-auth/native/base/lib/auth-client.ts.hbs +0 -16
  83. package/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +0 -187
  84. package/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs +0 -139
  85. package/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs +0 -157
  86. package/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +0 -123
  87. package/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +0 -87
  88. package/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +0 -128
  89. package/templates/auth/better-auth/server/base/_gitignore +0 -34
  90. package/templates/auth/better-auth/server/base/package.json.hbs +0 -14
  91. package/templates/auth/better-auth/server/base/src/index.ts.hbs +0 -304
  92. package/templates/auth/better-auth/server/base/tsconfig.json.hbs +0 -10
  93. package/templates/auth/better-auth/server/db/drizzle/mysql/src/schema/auth.ts.hbs +0 -100
  94. package/templates/auth/better-auth/server/db/drizzle/postgres/src/schema/auth.ts.hbs +0 -93
  95. package/templates/auth/better-auth/server/db/drizzle/sqlite/src/schema/auth.ts.hbs +0 -107
  96. package/templates/auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs +0 -68
  97. package/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/auth.prisma.hbs +0 -62
  98. package/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/auth.prisma.hbs +0 -62
  99. package/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/auth.prisma.hbs +0 -62
  100. package/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/auth.prisma.hbs +0 -62
  101. package/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue.hbs +0 -82
  102. package/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue.hbs +0 -91
  103. package/templates/auth/better-auth/web/nuxt/app/components/UserMenu.vue.hbs +0 -42
  104. package/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts.hbs +0 -14
  105. package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +0 -99
  106. package/templates/auth/better-auth/web/nuxt/app/pages/login.vue.hbs +0 -27
  107. package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs +0 -21
  108. package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +0 -16
  109. package/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs +0 -60
  110. package/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +0 -42
  111. package/templates/auth/better-auth/web/react/next/src/app/login/page.tsx.hbs +0 -16
  112. package/templates/auth/better-auth/web/react/next/src/components/sign-in-form.tsx.hbs +0 -135
  113. package/templates/auth/better-auth/web/react/next/src/components/sign-up-form.tsx.hbs +0 -160
  114. package/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs +0 -62
  115. package/templates/auth/better-auth/web/react/react-router/src/components/sign-in-form.tsx.hbs +0 -135
  116. package/templates/auth/better-auth/web/react/react-router/src/components/sign-up-form.tsx.hbs +0 -160
  117. package/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs +0 -61
  118. package/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs +0 -80
  119. package/templates/auth/better-auth/web/react/react-router/src/routes/login.tsx.hbs +0 -13
  120. package/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs +0 -135
  121. package/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs +0 -160
  122. package/templates/auth/better-auth/web/react/tanstack-router/src/components/user-menu.tsx.hbs +0 -63
  123. package/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +0 -71
  124. package/templates/auth/better-auth/web/react/tanstack-router/src/routes/login.tsx.hbs +0 -18
  125. package/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +0 -135
  126. package/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +0 -160
  127. package/templates/auth/better-auth/web/react/tanstack-start/src/components/user-menu.tsx.hbs +0 -63
  128. package/templates/auth/better-auth/web/react/tanstack-start/src/functions/get-user.ts.hbs +0 -6
  129. package/templates/auth/better-auth/web/react/tanstack-start/src/middleware/auth.ts.hbs +0 -31
  130. package/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +0 -84
  131. package/templates/auth/better-auth/web/react/tanstack-start/src/routes/login.tsx.hbs +0 -18
  132. package/templates/auth/better-auth/web/solid/src/components/sign-in-form.tsx.hbs +0 -124
  133. package/templates/auth/better-auth/web/solid/src/components/sign-up-form.tsx.hbs +0 -148
  134. package/templates/auth/better-auth/web/solid/src/components/user-menu.tsx.hbs +0 -55
  135. package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs +0 -12
  136. package/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs +0 -67
  137. package/templates/auth/better-auth/web/solid/src/routes/login.tsx.hbs +0 -23
  138. package/templates/auth/better-auth/web/svelte/src/components/SignInForm.svelte.hbs +0 -109
  139. package/templates/auth/better-auth/web/svelte/src/components/SignUpForm.svelte.hbs +0 -142
  140. package/templates/auth/better-auth/web/svelte/src/components/UserMenu.svelte.hbs +0 -52
  141. package/templates/auth/better-auth/web/svelte/src/lib/auth-client.ts.hbs +0 -12
  142. package/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs +0 -59
  143. package/templates/auth/better-auth/web/svelte/src/routes/login/+page.svelte.hbs +0 -12
  144. package/templates/auth/clerk/convex/backend/convex/auth.config.ts.hbs +0 -12
  145. package/templates/auth/clerk/convex/backend/convex/privateData.ts.hbs +0 -16
  146. package/templates/auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs +0 -12
  147. package/templates/auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs +0 -67
  148. package/templates/auth/clerk/convex/native/base/app/(auth)/sign-up.tsx.hbs +0 -110
  149. package/templates/auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs +0 -27
  150. package/templates/auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs +0 -29
  151. package/templates/auth/clerk/convex/web/react/next/src/middleware.ts.hbs +0 -12
  152. package/templates/auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs +0 -32
  153. package/templates/auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +0 -37
  154. package/templates/auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +0 -37
  155. package/templates/auth/clerk/convex/web/react/tanstack-start/src/start.ts.hbs +0 -8
  156. package/templates/backend/convex/packages/backend/_gitignore +0 -2
  157. package/templates/backend/convex/packages/backend/convex/README.md +0 -90
  158. package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +0 -17
  159. package/templates/backend/convex/packages/backend/convex/healthCheck.ts.hbs +0 -7
  160. package/templates/backend/convex/packages/backend/convex/schema.ts.hbs +0 -11
  161. package/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs +0 -25
  162. package/templates/backend/convex/packages/backend/package.json.hbs +0 -15
  163. package/templates/backend/server/base/_gitignore +0 -55
  164. package/templates/backend/server/base/package.json.hbs +0 -17
  165. package/templates/backend/server/base/tsconfig.json.hbs +0 -13
  166. package/templates/backend/server/base/tsdown.config.ts.hbs +0 -9
  167. package/templates/backend/server/elysia/src/index.ts.hbs +0 -122
  168. package/templates/backend/server/express/src/index.ts.hbs +0 -126
  169. package/templates/backend/server/fastify/src/index.ts.hbs +0 -187
  170. package/templates/backend/server/hono/src/index.ts.hbs +0 -171
  171. package/templates/base/_gitignore +0 -50
  172. package/templates/base/package.json.hbs +0 -10
  173. package/templates/base/tsconfig.json.hbs +0 -3
  174. package/templates/db/base/_gitignore +0 -35
  175. package/templates/db/base/package.json.hbs +0 -14
  176. package/templates/db/base/tsconfig.json.hbs +0 -10
  177. package/templates/db/drizzle/base/src/schema/index.ts.hbs +0 -7
  178. package/templates/db/drizzle/mysql/drizzle.config.ts.hbs +0 -19
  179. package/templates/db/drizzle/mysql/src/index.ts.hbs +0 -54
  180. package/templates/db/drizzle/postgres/drizzle.config.ts.hbs +0 -19
  181. package/templates/db/drizzle/postgres/src/index.ts.hbs +0 -44
  182. package/templates/db/drizzle/sqlite/drizzle.config.ts.hbs +0 -28
  183. package/templates/db/drizzle/sqlite/src/index.ts.hbs +0 -39
  184. package/templates/db/mongoose/mongodb/src/index.ts.hbs +0 -10
  185. package/templates/db/prisma/mongodb/prisma/schema/schema.prisma.hbs +0 -19
  186. package/templates/db/prisma/mongodb/prisma.config.ts.hbs +0 -18
  187. package/templates/db/prisma/mongodb/src/index.ts.hbs +0 -5
  188. package/templates/db/prisma/mysql/prisma/schema/schema.prisma.hbs +0 -21
  189. package/templates/db/prisma/mysql/prisma.config.ts.hbs +0 -21
  190. package/templates/db/prisma/mysql/src/index.ts.hbs +0 -55
  191. package/templates/db/prisma/postgres/prisma/schema/schema.prisma.hbs +0 -21
  192. package/templates/db/prisma/postgres/prisma.config.ts.hbs +0 -21
  193. package/templates/db/prisma/postgres/src/index.ts.hbs +0 -69
  194. package/templates/db/prisma/sqlite/prisma/schema/schema.prisma.hbs +0 -18
  195. package/templates/db/prisma/sqlite/prisma.config.ts.hbs +0 -25
  196. package/templates/db/prisma/sqlite/src/index.ts.hbs +0 -25
  197. package/templates/db-setup/docker-compose/mongodb/docker-compose.yml.hbs +0 -23
  198. package/templates/db-setup/docker-compose/mysql/docker-compose.yml.hbs +0 -24
  199. package/templates/db-setup/docker-compose/postgres/docker-compose.yml.hbs +0 -23
  200. package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +0 -9
  201. package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +0 -67
  202. package/templates/examples/ai/fullstack/next/src/app/api/ai/route.ts.hbs +0 -20
  203. package/templates/examples/ai/fullstack/tanstack-start/src/routes/api/ai/$.ts.hbs +0 -36
  204. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +0 -586
  205. package/templates/examples/ai/native/bare/polyfills.js +0 -25
  206. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +0 -588
  207. package/templates/examples/ai/native/unistyles/polyfills.js +0 -22
  208. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +0 -331
  209. package/templates/examples/ai/native/uniwind/polyfills.js +0 -22
  210. package/templates/examples/ai/web/nuxt/app/pages/ai.vue.hbs +0 -54
  211. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +0 -267
  212. package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +0 -235
  213. package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +0 -242
  214. package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +0 -243
  215. package/templates/examples/ai/web/svelte/src/routes/ai/+page.svelte.hbs +0 -107
  216. package/templates/examples/todo/convex/packages/backend/convex/todos.ts.hbs +0 -42
  217. package/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs +0 -521
  218. package/templates/examples/todo/native/unistyles/app/(drawer)/todos.tsx.hbs +0 -340
  219. package/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs +0 -282
  220. package/templates/examples/todo/server/drizzle/base/src/routers/todo.ts.hbs +0 -75
  221. package/templates/examples/todo/server/drizzle/mysql/src/schema/todo.ts +0 -7
  222. package/templates/examples/todo/server/drizzle/postgres/src/schema/todo.ts +0 -7
  223. package/templates/examples/todo/server/drizzle/sqlite/src/schema/todo.ts +0 -7
  224. package/templates/examples/todo/server/mongoose/base/src/routers/todo.ts.hbs +0 -66
  225. package/templates/examples/todo/server/mongoose/mongodb/src/models/todo.model.ts.hbs +0 -24
  226. package/templates/examples/todo/server/prisma/base/src/routers/todo.ts.hbs +0 -116
  227. package/templates/examples/todo/server/prisma/mongodb/prisma/schema/todo.prisma.hbs +0 -7
  228. package/templates/examples/todo/server/prisma/mysql/prisma/schema/todo.prisma.hbs +0 -7
  229. package/templates/examples/todo/server/prisma/postgres/prisma/schema/todo.prisma.hbs +0 -7
  230. package/templates/examples/todo/server/prisma/sqlite/prisma/schema/todo.prisma.hbs +0 -7
  231. package/templates/examples/todo/web/nuxt/app/pages/todos.vue.hbs +0 -220
  232. package/templates/examples/todo/web/react/next/src/app/todos/page.tsx.hbs +0 -245
  233. package/templates/examples/todo/web/react/react-router/src/routes/todos.tsx.hbs +0 -242
  234. package/templates/examples/todo/web/react/tanstack-router/src/routes/todos.tsx.hbs +0 -247
  235. package/templates/examples/todo/web/react/tanstack-start/src/routes/todos.tsx.hbs +0 -272
  236. package/templates/examples/todo/web/solid/src/routes/todos.tsx.hbs +0 -132
  237. package/templates/examples/todo/web/svelte/src/routes/todos/+page.svelte.hbs +0 -317
  238. package/templates/extras/_npmrc.hbs +0 -5
  239. package/templates/extras/bunfig.toml.hbs +0 -6
  240. package/templates/extras/pnpm-workspace.yaml +0 -3
  241. package/templates/frontend/native/bare/_gitignore +0 -18
  242. package/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs +0 -41
  243. package/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs +0 -43
  244. package/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs +0 -43
  245. package/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs +0 -90
  246. package/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +0 -234
  247. package/templates/frontend/native/bare/app/+not-found.tsx.hbs +0 -65
  248. package/templates/frontend/native/bare/app/_layout.tsx.hbs +0 -165
  249. package/templates/frontend/native/bare/app/modal.tsx.hbs +0 -34
  250. package/templates/frontend/native/bare/app.json.hbs +0 -50
  251. package/templates/frontend/native/bare/components/container.tsx.hbs +0 -25
  252. package/templates/frontend/native/bare/components/header-button.tsx.hbs +0 -47
  253. package/templates/frontend/native/bare/components/tabbar-icon.tsx.hbs +0 -9
  254. package/templates/frontend/native/bare/lib/android-navigation-bar.tsx.hbs +0 -12
  255. package/templates/frontend/native/bare/lib/constants.ts.hbs +0 -19
  256. package/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs +0 -20
  257. package/templates/frontend/native/bare/metro.config.js.hbs +0 -9
  258. package/templates/frontend/native/bare/package.json.hbs +0 -51
  259. package/templates/frontend/native/bare/tsconfig.json.hbs +0 -11
  260. package/templates/frontend/native/base/assets/images/android-icon-background.png +0 -0
  261. package/templates/frontend/native/base/assets/images/android-icon-foreground.png +0 -0
  262. package/templates/frontend/native/base/assets/images/android-icon-monochrome.png +0 -0
  263. package/templates/frontend/native/base/assets/images/favicon.png +0 -0
  264. package/templates/frontend/native/base/assets/images/icon.png +0 -0
  265. package/templates/frontend/native/base/assets/images/partial-react-logo.png +0 -0
  266. package/templates/frontend/native/base/assets/images/react-logo.png +0 -0
  267. package/templates/frontend/native/base/assets/images/react-logo@2x.png +0 -0
  268. package/templates/frontend/native/base/assets/images/react-logo@3x.png +0 -0
  269. package/templates/frontend/native/base/assets/images/splash-icon.png +0 -0
  270. package/templates/frontend/native/unistyles/_gitignore +0 -24
  271. package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx.hbs +0 -39
  272. package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx.hbs +0 -37
  273. package/templates/frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx.hbs +0 -37
  274. package/templates/frontend/native/unistyles/app/(drawer)/_layout.tsx.hbs +0 -87
  275. package/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +0 -333
  276. package/templates/frontend/native/unistyles/app/+not-found.tsx.hbs +0 -65
  277. package/templates/frontend/native/unistyles/app/_layout.tsx.hbs +0 -169
  278. package/templates/frontend/native/unistyles/app/modal.tsx.hbs +0 -33
  279. package/templates/frontend/native/unistyles/app.json.hbs +0 -49
  280. package/templates/frontend/native/unistyles/babel.config.js.hbs +0 -21
  281. package/templates/frontend/native/unistyles/breakpoints.ts.hbs +0 -9
  282. package/templates/frontend/native/unistyles/components/container.tsx.hbs +0 -15
  283. package/templates/frontend/native/unistyles/components/header-button.tsx.hbs +0 -36
  284. package/templates/frontend/native/unistyles/components/tabbar-icon.tsx.hbs +0 -8
  285. package/templates/frontend/native/unistyles/index.js.hbs +0 -2
  286. package/templates/frontend/native/unistyles/metro.config.js.hbs +0 -5
  287. package/templates/frontend/native/unistyles/package.json.hbs +0 -51
  288. package/templates/frontend/native/unistyles/theme.ts.hbs +0 -98
  289. package/templates/frontend/native/unistyles/tsconfig.json.hbs +0 -12
  290. package/templates/frontend/native/unistyles/unistyles.ts.hbs +0 -27
  291. package/templates/frontend/native/uniwind/_gitignore +0 -21
  292. package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs +0 -46
  293. package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs +0 -15
  294. package/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs +0 -15
  295. package/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs +0 -91
  296. package/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs +0 -191
  297. package/templates/frontend/native/uniwind/app/+not-found.tsx.hbs +0 -27
  298. package/templates/frontend/native/uniwind/app/_layout.tsx.hbs +0 -132
  299. package/templates/frontend/native/uniwind/app/modal.tsx.hbs +0 -37
  300. package/templates/frontend/native/uniwind/app.json.hbs +0 -19
  301. package/templates/frontend/native/uniwind/components/container.tsx.hbs +0 -33
  302. package/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs +0 -35
  303. package/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs +0 -62
  304. package/templates/frontend/native/uniwind/global.css +0 -5
  305. package/templates/frontend/native/uniwind/metro.config.js.hbs +0 -13
  306. package/templates/frontend/native/uniwind/package.json.hbs +0 -54
  307. package/templates/frontend/native/uniwind/tsconfig.json.hbs +0 -14
  308. package/templates/frontend/nuxt/_gitignore +0 -27
  309. package/templates/frontend/nuxt/app/app.config.ts.hbs +0 -15
  310. package/templates/frontend/nuxt/app/app.vue.hbs +0 -17
  311. package/templates/frontend/nuxt/app/assets/css/main.css +0 -2
  312. package/templates/frontend/nuxt/app/components/Header.vue.hbs +0 -40
  313. package/templates/frontend/nuxt/app/layouts/default.vue.hbs +0 -10
  314. package/templates/frontend/nuxt/app/pages/index.vue.hbs +0 -97
  315. package/templates/frontend/nuxt/nuxt.config.ts.hbs +0 -29
  316. package/templates/frontend/nuxt/package.json.hbs +0 -24
  317. package/templates/frontend/nuxt/public/favicon.ico +0 -0
  318. package/templates/frontend/nuxt/public/robots.txt +0 -2
  319. package/templates/frontend/nuxt/server/tsconfig.json +0 -3
  320. package/templates/frontend/nuxt/tsconfig.json.hbs +0 -18
  321. package/templates/frontend/react/next/next-env.d.ts.hbs +0 -5
  322. package/templates/frontend/react/next/next.config.ts.hbs +0 -22
  323. package/templates/frontend/react/next/package.json.hbs +0 -34
  324. package/templates/frontend/react/next/postcss.config.mjs.hbs +0 -5
  325. package/templates/frontend/react/next/src/app/favicon.ico +0 -0
  326. package/templates/frontend/react/next/src/app/layout.tsx.hbs +0 -76
  327. package/templates/frontend/react/next/src/app/page.tsx.hbs +0 -79
  328. package/templates/frontend/react/next/src/components/mode-toggle.tsx.hbs +0 -37
  329. package/templates/frontend/react/next/src/components/providers.tsx.hbs +0 -89
  330. package/templates/frontend/react/next/src/components/theme-provider.tsx.hbs +0 -11
  331. package/templates/frontend/react/next/tsconfig.json.hbs +0 -41
  332. package/templates/frontend/react/react-router/package.json.hbs +0 -42
  333. package/templates/frontend/react/react-router/public/favicon.ico +0 -0
  334. package/templates/frontend/react/react-router/react-router.config.ts +0 -6
  335. package/templates/frontend/react/react-router/src/components/mode-toggle.tsx.hbs +0 -29
  336. package/templates/frontend/react/react-router/src/components/theme-provider.tsx.hbs +0 -11
  337. package/templates/frontend/react/react-router/src/root.tsx.hbs +0 -190
  338. package/templates/frontend/react/react-router/src/routes/_index.tsx.hbs +0 -85
  339. package/templates/frontend/react/react-router/src/routes.ts +0 -4
  340. package/templates/frontend/react/react-router/tsconfig.json.hbs +0 -27
  341. package/templates/frontend/react/react-router/vite.config.ts.hbs +0 -12
  342. package/templates/frontend/react/tanstack-router/index.html.hbs +0 -13
  343. package/templates/frontend/react/tanstack-router/package.json.hbs +0 -41
  344. package/templates/frontend/react/tanstack-router/src/components/mode-toggle.tsx.hbs +0 -29
  345. package/templates/frontend/react/tanstack-router/src/components/theme-provider.tsx.hbs +0 -11
  346. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +0 -90
  347. package/templates/frontend/react/tanstack-router/src/routes/__root.tsx.hbs +0 -103
  348. package/templates/frontend/react/tanstack-router/src/routes/index.tsx.hbs +0 -85
  349. package/templates/frontend/react/tanstack-router/tsconfig.json.hbs +0 -18
  350. package/templates/frontend/react/tanstack-router/vite.config.ts.hbs +0 -21
  351. package/templates/frontend/react/tanstack-start/package.json.hbs +0 -43
  352. package/templates/frontend/react/tanstack-start/public/robots.txt +0 -3
  353. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +0 -144
  354. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +0 -208
  355. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +0 -85
  356. package/templates/frontend/react/tanstack-start/tsconfig.json.hbs +0 -28
  357. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +0 -22
  358. package/templates/frontend/react/web-base/_gitignore +0 -60
  359. package/templates/frontend/react/web-base/components.json +0 -24
  360. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +0 -78
  361. package/templates/frontend/react/web-base/src/components/loader.tsx.hbs +0 -9
  362. package/templates/frontend/react/web-base/src/components/ui/button.tsx.hbs +0 -57
  363. package/templates/frontend/react/web-base/src/components/ui/card.tsx.hbs +0 -103
  364. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx.hbs +0 -26
  365. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx.hbs +0 -262
  366. package/templates/frontend/react/web-base/src/components/ui/input.tsx.hbs +0 -20
  367. package/templates/frontend/react/web-base/src/components/ui/label.tsx.hbs +0 -20
  368. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +0 -13
  369. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx.hbs +0 -44
  370. package/templates/frontend/react/web-base/src/index.css.hbs +0 -131
  371. package/templates/frontend/react/web-base/src/lib/utils.ts.hbs +0 -6
  372. package/templates/frontend/solid/_gitignore +0 -11
  373. package/templates/frontend/solid/index.html +0 -13
  374. package/templates/frontend/solid/package.json.hbs +0 -24
  375. package/templates/frontend/solid/public/robots.txt +0 -3
  376. package/templates/frontend/solid/src/components/header.tsx.hbs +0 -38
  377. package/templates/frontend/solid/src/components/loader.tsx +0 -9
  378. package/templates/frontend/solid/src/main.tsx.hbs +0 -41
  379. package/templates/frontend/solid/src/routes/__root.tsx.hbs +0 -34
  380. package/templates/frontend/solid/src/routes/index.tsx.hbs +0 -72
  381. package/templates/frontend/solid/src/styles.css +0 -5
  382. package/templates/frontend/solid/tsconfig.json.hbs +0 -29
  383. package/templates/frontend/solid/vite.config.ts.hbs +0 -21
  384. package/templates/frontend/svelte/_gitignore +0 -24
  385. package/templates/frontend/svelte/_npmrc +0 -1
  386. package/templates/frontend/svelte/package.json.hbs +0 -27
  387. package/templates/frontend/svelte/src/app.css +0 -5
  388. package/templates/frontend/svelte/src/app.d.ts +0 -13
  389. package/templates/frontend/svelte/src/app.html +0 -12
  390. package/templates/frontend/svelte/src/components/Header.svelte.hbs +0 -40
  391. package/templates/frontend/svelte/src/lib/index.ts +0 -2
  392. package/templates/frontend/svelte/src/routes/+layout.svelte.hbs +0 -54
  393. package/templates/frontend/svelte/src/routes/+page.svelte.hbs +0 -92
  394. package/templates/frontend/svelte/static/favicon.png +0 -0
  395. package/templates/frontend/svelte/svelte.config.js.hbs +0 -18
  396. package/templates/frontend/svelte/tsconfig.json.hbs +0 -19
  397. package/templates/frontend/svelte/vite.config.ts.hbs +0 -7
  398. package/templates/packages/config/package.json.hbs +0 -5
  399. package/templates/packages/config/tsconfig.base.json.hbs +0 -33
  400. package/templates/packages/env/env.d.ts.hbs +0 -16
  401. package/templates/packages/env/package.json.hbs +0 -7
  402. package/templates/packages/env/src/native.ts.hbs +0 -21
  403. package/templates/packages/env/src/server.ts.hbs +0 -39
  404. package/templates/packages/env/src/web.ts.hbs +0 -98
  405. package/templates/packages/env/tsconfig.json.hbs +0 -3
  406. package/templates/packages/infra/alchemy.run.ts.hbs +0 -271
  407. package/templates/packages/infra/package.json.hbs +0 -10
  408. package/templates/payments/polar/server/base/src/lib/payments.ts.hbs +0 -7
  409. package/templates/payments/polar/web/nuxt/app/pages/success.vue.hbs +0 -11
  410. package/templates/payments/polar/web/react/next/src/app/success/page.tsx.hbs +0 -15
  411. package/templates/payments/polar/web/react/react-router/src/routes/success.tsx.hbs +0 -13
  412. package/templates/payments/polar/web/react/tanstack-router/src/routes/success.tsx.hbs +0 -19
  413. package/templates/payments/polar/web/react/tanstack-start/src/functions/get-payment.ts.hbs +0 -15
  414. package/templates/payments/polar/web/react/tanstack-start/src/routes/success.tsx.hbs +0 -19
  415. package/templates/payments/polar/web/solid/src/routes/success.tsx.hbs +0 -23
  416. package/templates/payments/polar/web/svelte/src/routes/success/+page.svelte.hbs +0 -12
  417. /package/dist/{chunk-Dt3mZKp0.mjs → chunk-DPg_XC7m.mjs} +0 -0
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as __reExport } from "./chunk-Dt3mZKp0.mjs";
2
+ import { t as __reExport } from "./chunk-DPg_XC7m.mjs";
3
3
  import { autocompleteMultiselect, cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
4
4
  import { createRouterClient, os } from "@orpc/server";
5
5
  import pc from "picocolors";
@@ -9,16 +9,14 @@ import consola, { consola as consola$1 } from "consola";
9
9
  import fs from "fs-extra";
10
10
  import path from "node:path";
11
11
  import { fileURLToPath } from "node:url";
12
- import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
12
+ import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generateVirtualProject, generateVirtualProject as generateVirtualProject$1 } from "@better-t-stack/template-generator";
13
13
  import { AsyncLocalStorage } from "node:async_hooks";
14
+ import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
14
15
  import gradient from "gradient-string";
15
- import * as JSONC from "jsonc-parser";
16
+ import { writeTreeToFilesystem } from "@better-t-stack/template-generator/fs-writer";
16
17
  import { $, execa } from "execa";
17
- import { IndentationText, Node, Project, QuoteKind, SyntaxKind } from "ts-morph";
18
- import { glob } from "tinyglobby";
19
- import handlebars from "handlebars";
18
+ import * as JSONC from "jsonc-parser";
20
19
  import { format } from "oxfmt";
21
- import yaml from "yaml";
22
20
  import os$1 from "node:os";
23
21
 
24
22
  //#region src/utils/get-package-manager.ts
@@ -64,111 +62,6 @@ function getDefaultConfig() {
64
62
  };
65
63
  }
66
64
  const DEFAULT_CONFIG = getDefaultConfig();
67
- const dependencyVersionMap = {
68
- typescript: "^5",
69
- "better-auth": "^1.4.9",
70
- "@better-auth/expo": "^1.4.9",
71
- "@clerk/nextjs": "^6.31.5",
72
- "@clerk/clerk-react": "^5.45.0",
73
- "@clerk/tanstack-react-start": "^0.26.3",
74
- "@clerk/clerk-expo": "^2.14.25",
75
- "drizzle-orm": "^0.45.1",
76
- "drizzle-kit": "^0.31.8",
77
- "@planetscale/database": "^1.19.0",
78
- "@libsql/client": "0.15.15",
79
- libsql: "0.5.22",
80
- "@neondatabase/serverless": "^1.0.2",
81
- pg: "^8.16.3",
82
- "@types/pg": "^8.15.6",
83
- "@types/ws": "^8.18.1",
84
- ws: "^8.18.3",
85
- mysql2: "^3.14.0",
86
- "@prisma/client": "^7.1.0",
87
- prisma: "^7.1.0",
88
- "@prisma/adapter-d1": "^7.1.0",
89
- "@prisma/adapter-neon": "^7.1.0",
90
- "@prisma/adapter-mariadb": "^7.1.0",
91
- "@prisma/adapter-libsql": "^7.1.0",
92
- "@prisma/adapter-better-sqlite3": "^7.1.0",
93
- "@prisma/adapter-pg": "^7.1.0",
94
- "@prisma/adapter-planetscale": "^7.1.0",
95
- mongoose: "^8.14.0",
96
- "vite-plugin-pwa": "^1.0.1",
97
- "@vite-pwa/assets-generator": "^1.0.0",
98
- "@tauri-apps/cli": "^2.4.0",
99
- "@biomejs/biome": "^2.2.0",
100
- oxlint: "^1.34.0",
101
- oxfmt: "^0.19.0",
102
- husky: "^9.1.7",
103
- "lint-staged": "^16.1.2",
104
- tsx: "^4.19.2",
105
- "@types/node": "^22.13.14",
106
- "@types/bun": "^1.3.4",
107
- "@elysiajs/node": "^1.3.1",
108
- "@elysiajs/cors": "^1.3.3",
109
- "@elysiajs/trpc": "^1.1.0",
110
- elysia: "^1.3.21",
111
- "@hono/node-server": "^1.14.4",
112
- "@hono/trpc-server": "^0.4.0",
113
- hono: "^4.8.2",
114
- cors: "^2.8.5",
115
- express: "^5.1.0",
116
- "@types/express": "^5.0.1",
117
- "@types/cors": "^2.8.17",
118
- fastify: "^5.3.3",
119
- "@fastify/cors": "^11.0.1",
120
- turbo: "^2.6.3",
121
- ai: "^6.0.3",
122
- "@ai-sdk/google": "^3.0.1",
123
- "@ai-sdk/vue": "^3.0.3",
124
- "@ai-sdk/svelte": "^4.0.3",
125
- "@ai-sdk/react": "^3.0.3",
126
- "@ai-sdk/devtools": "^0.0.2",
127
- streamdown: "^1.6.10",
128
- shiki: "^3.20.0",
129
- "@orpc/server": "^1.12.2",
130
- "@orpc/client": "^1.12.2",
131
- "@orpc/openapi": "^1.12.2",
132
- "@orpc/zod": "^1.12.2",
133
- "@orpc/tanstack-query": "^1.12.2",
134
- "@trpc/tanstack-react-query": "^11.7.2",
135
- "@trpc/server": "^11.7.2",
136
- "@trpc/client": "^11.7.2",
137
- next: "^16.1.1",
138
- convex: "^1.31.2",
139
- "@convex-dev/react-query": "^0.1.0",
140
- "@convex-dev/agent": "^0.3.2",
141
- "convex-svelte": "^0.0.12",
142
- "convex-nuxt": "0.1.5",
143
- "convex-vue": "^0.1.5",
144
- "@convex-dev/better-auth": "^0.10.9",
145
- "@tanstack/svelte-query": "^5.85.3",
146
- "@tanstack/svelte-query-devtools": "^5.85.3",
147
- "@tanstack/vue-query-devtools": "^5.90.2",
148
- "@tanstack/vue-query": "^5.90.2",
149
- "@tanstack/react-query-devtools": "^5.91.1",
150
- "@tanstack/react-query": "^5.90.12",
151
- "@tanstack/react-router-ssr-query": "^1.142.7",
152
- "@tanstack/solid-query": "^5.87.4",
153
- "@tanstack/solid-query-devtools": "^5.87.4",
154
- "@tanstack/solid-router-devtools": "^1.131.44",
155
- wrangler: "^4.54.0",
156
- "@cloudflare/vite-plugin": "^1.17.1",
157
- "@opennextjs/cloudflare": "^1.14.6",
158
- "nitro-cloudflare-dev": "^0.2.2",
159
- "@sveltejs/adapter-cloudflare": "^7.2.4",
160
- "@cloudflare/workers-types": "^4.20251213.0",
161
- alchemy: "^0.82.1",
162
- dotenv: "^17.2.2",
163
- tsdown: "^0.16.5",
164
- zod: "^4.1.13",
165
- "@t3-oss/env-core": "^0.13.1",
166
- "@t3-oss/env-nextjs": "^0.13.1",
167
- "@t3-oss/env-nuxt": "^0.13.1",
168
- srvx: "0.8.15",
169
- "@polar-sh/better-auth": "^1.1.3",
170
- "@polar-sh/sdk": "^0.34.16"
171
- };
172
65
  const ADDON_COMPATIBILITY = {
173
66
  pwa: [
174
67
  "tanstack-router",
@@ -198,22 +91,57 @@ const ADDON_COMPATIBILITY = {
198
91
  };
199
92
 
200
93
  //#endregion
201
- //#region src/types.ts
202
- var types_exports = {};
203
- import * as import__better_t_stack_types from "@better-t-stack/types";
204
- __reExport(types_exports, import__better_t_stack_types);
205
-
206
- //#endregion
207
- //#region src/utils/compatibility.ts
208
- const WEB_FRAMEWORKS = [
209
- "tanstack-router",
210
- "react-router",
211
- "tanstack-start",
212
- "next",
213
- "nuxt",
214
- "svelte",
215
- "solid"
216
- ];
94
+ //#region src/utils/context.ts
95
+ const cliStorage = new AsyncLocalStorage();
96
+ function defaultContext() {
97
+ return {
98
+ navigation: {
99
+ isFirstPrompt: false,
100
+ lastPromptShownUI: false
101
+ },
102
+ silent: false,
103
+ verbose: false
104
+ };
105
+ }
106
+ function getContext() {
107
+ const ctx = cliStorage.getStore();
108
+ if (!ctx) return defaultContext();
109
+ return ctx;
110
+ }
111
+ function tryGetContext() {
112
+ return cliStorage.getStore();
113
+ }
114
+ function isSilent() {
115
+ return getContext().silent;
116
+ }
117
+ function isFirstPrompt() {
118
+ return getContext().navigation.isFirstPrompt;
119
+ }
120
+ function didLastPromptShowUI() {
121
+ return getContext().navigation.lastPromptShownUI;
122
+ }
123
+ function setIsFirstPrompt$1(value) {
124
+ const ctx = tryGetContext();
125
+ if (ctx) ctx.navigation.isFirstPrompt = value;
126
+ }
127
+ function setLastPromptShownUI(value) {
128
+ const ctx = tryGetContext();
129
+ if (ctx) ctx.navigation.lastPromptShownUI = value;
130
+ }
131
+ async function runWithContextAsync(options, fn) {
132
+ const ctx = {
133
+ navigation: {
134
+ isFirstPrompt: false,
135
+ lastPromptShownUI: false
136
+ },
137
+ silent: options.silent ?? false,
138
+ verbose: options.verbose ?? false,
139
+ projectDir: options.projectDir,
140
+ projectName: options.projectName,
141
+ packageManager: options.packageManager
142
+ };
143
+ return cliStorage.run(ctx, fn);
144
+ }
217
145
 
218
146
  //#endregion
219
147
  //#region src/utils/errors.ts
@@ -230,19 +158,40 @@ var CLIError = class extends Error {
230
158
  }
231
159
  };
232
160
  function exitWithError(message) {
161
+ if (isSilent()) throw new CLIError(message);
233
162
  consola.error(pc.red(message));
234
- throw new CLIError(message);
163
+ process.exit(1);
235
164
  }
236
165
  function exitCancelled(message = "Operation cancelled") {
166
+ if (isSilent()) throw new UserCancelledError(message);
237
167
  cancel(pc.red(message));
238
- throw new UserCancelledError(message);
168
+ process.exit(1);
239
169
  }
240
170
  function handleError(error, fallbackMessage) {
241
171
  const message = error instanceof Error ? error.message : fallbackMessage || String(error);
172
+ if (isSilent()) throw error instanceof Error ? error : new Error(message);
242
173
  consola.error(pc.red(message));
243
- throw error instanceof Error ? error : new Error(message);
174
+ process.exit(1);
244
175
  }
245
176
 
177
+ //#endregion
178
+ //#region src/types.ts
179
+ var types_exports = {};
180
+ import * as import__better_t_stack_types from "@better-t-stack/types";
181
+ __reExport(types_exports, import__better_t_stack_types);
182
+
183
+ //#endregion
184
+ //#region src/utils/compatibility.ts
185
+ const WEB_FRAMEWORKS = [
186
+ "tanstack-router",
187
+ "react-router",
188
+ "tanstack-start",
189
+ "next",
190
+ "nuxt",
191
+ "svelte",
192
+ "solid"
193
+ ];
194
+
246
195
  //#endregion
247
196
  //#region src/utils/compatibility-rules.ts
248
197
  function isWebFrontend(value) {
@@ -337,14 +286,6 @@ function validateAddonCompatibility(addon, frontend, _auth) {
337
286
  }
338
287
  return { isCompatible: true };
339
288
  }
340
- function getCompatibleAddons(allAddons, frontend, existingAddons = [], auth) {
341
- return allAddons.filter((addon) => {
342
- if (existingAddons.includes(addon)) return false;
343
- if (addon === "none") return false;
344
- const { isCompatible } = validateAddonCompatibility(addon, frontend, auth);
345
- return isCompatible;
346
- });
347
- }
348
289
  function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
349
290
  for (const addon of addons) {
350
291
  if (addon === "none") continue;
@@ -376,64 +317,8 @@ function validateExamplesCompatibility(examples, backend, database, frontend, ap
376
317
  }
377
318
  }
378
319
 
379
- //#endregion
380
- //#region src/utils/context.ts
381
- const cliStorage = new AsyncLocalStorage();
382
- function defaultContext() {
383
- return {
384
- navigation: {
385
- isFirstPrompt: false,
386
- lastPromptShownUI: false
387
- },
388
- silent: false,
389
- verbose: false
390
- };
391
- }
392
- function getContext() {
393
- const ctx = cliStorage.getStore();
394
- if (!ctx) return defaultContext();
395
- return ctx;
396
- }
397
- function tryGetContext() {
398
- return cliStorage.getStore();
399
- }
400
- function isSilent() {
401
- return getContext().silent;
402
- }
403
- function isFirstPrompt() {
404
- return getContext().navigation.isFirstPrompt;
405
- }
406
- function didLastPromptShowUI() {
407
- return getContext().navigation.lastPromptShownUI;
408
- }
409
- function setIsFirstPrompt$1(value) {
410
- const ctx = tryGetContext();
411
- if (ctx) ctx.navigation.isFirstPrompt = value;
412
- }
413
- function setLastPromptShownUI(value) {
414
- const ctx = tryGetContext();
415
- if (ctx) ctx.navigation.lastPromptShownUI = value;
416
- }
417
- async function runWithContextAsync(options, fn) {
418
- const ctx = {
419
- navigation: {
420
- isFirstPrompt: false,
421
- lastPromptShownUI: false
422
- },
423
- silent: options.silent ?? false,
424
- verbose: options.verbose ?? false,
425
- projectDir: options.projectDir,
426
- projectName: options.projectName,
427
- packageManager: options.packageManager
428
- };
429
- return cliStorage.run(ctx, fn);
430
- }
431
-
432
320
  //#endregion
433
321
  //#region src/utils/navigation.ts
434
- /**
435
- * Navigation symbols and utilities for prompt navigation
436
- */
437
322
  const GO_BACK_SYMBOL = Symbol("clack:goBack");
438
323
  function isGoBack(value) {
439
324
  return value === GO_BACK_SYMBOL;
@@ -729,29 +614,29 @@ function getAddonDisplay(addon) {
729
614
  };
730
615
  }
731
616
  const ADDON_GROUPS = {
732
- Documentation: ["starlight", "fumadocs"],
733
- Linting: [
617
+ Tooling: [
618
+ "turborepo",
734
619
  "biome",
735
620
  "oxlint",
736
- "ultracite"
621
+ "ultracite",
622
+ "husky"
737
623
  ],
738
- Other: [
739
- "ruler",
624
+ Documentation: ["starlight", "fumadocs"],
625
+ Extensions: [
740
626
  "pwa",
741
627
  "tauri",
742
- "husky",
743
628
  "opentui",
744
629
  "wxt",
745
- "turborepo"
630
+ "ruler"
746
631
  ]
747
632
  };
748
633
  async function getAddonsChoice(addons, frontends, auth) {
749
634
  if (addons !== void 0) return addons;
750
635
  const allAddons = types_exports.AddonsSchema.options.filter((addon) => addon !== "none");
751
636
  const groupedOptions = {
637
+ Tooling: [],
752
638
  Documentation: [],
753
- Linting: [],
754
- Other: []
639
+ Extensions: []
755
640
  };
756
641
  const frontendsArray = frontends || [];
757
642
  for (const addon of allAddons) {
@@ -763,9 +648,9 @@ async function getAddonsChoice(addons, frontends, auth) {
763
648
  label,
764
649
  hint
765
650
  };
766
- if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
767
- else if (ADDON_GROUPS.Linting.includes(addon)) groupedOptions.Linting.push(option);
768
- else if (ADDON_GROUPS.Other.includes(addon)) groupedOptions.Other.push(option);
651
+ if (ADDON_GROUPS.Tooling.includes(addon)) groupedOptions.Tooling.push(option);
652
+ else if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
653
+ else if (ADDON_GROUPS.Extensions.includes(addon)) groupedOptions.Extensions.push(option);
769
654
  }
770
655
  Object.keys(groupedOptions).forEach((group$1) => {
771
656
  if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
@@ -785,43 +670,6 @@ async function getAddonsChoice(addons, frontends, auth) {
785
670
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
786
671
  return response;
787
672
  }
788
- async function getAddonsToAdd(frontend, existingAddons = [], auth) {
789
- const groupedOptions = {
790
- Documentation: [],
791
- Linting: [],
792
- Other: []
793
- };
794
- const frontendArray = frontend || [];
795
- const compatibleAddons = getCompatibleAddons(types_exports.AddonsSchema.options.filter((addon) => addon !== "none"), frontendArray, existingAddons, auth);
796
- for (const addon of compatibleAddons) {
797
- const { label, hint } = getAddonDisplay(addon);
798
- const option = {
799
- value: addon,
800
- label,
801
- hint
802
- };
803
- if (ADDON_GROUPS.Documentation.includes(addon)) groupedOptions.Documentation.push(option);
804
- else if (ADDON_GROUPS.Linting.includes(addon)) groupedOptions.Linting.push(option);
805
- else if (ADDON_GROUPS.Other.includes(addon)) groupedOptions.Other.push(option);
806
- }
807
- Object.keys(groupedOptions).forEach((group$1) => {
808
- if (groupedOptions[group$1].length === 0) delete groupedOptions[group$1];
809
- else {
810
- const groupOrder = ADDON_GROUPS[group$1] || [];
811
- groupedOptions[group$1].sort((a, b) => {
812
- return groupOrder.indexOf(a.value) - groupOrder.indexOf(b.value);
813
- });
814
- }
815
- });
816
- if (Object.keys(groupedOptions).length === 0) return [];
817
- const response = await navigableGroupMultiselect({
818
- message: "Select addons to add",
819
- options: groupedOptions,
820
- required: false
821
- });
822
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
823
- return response;
824
- }
825
673
 
826
674
  //#endregion
827
675
  //#region src/prompts/api.ts
@@ -1441,16 +1289,6 @@ async function getRuntimeChoice(runtime, backend) {
1441
1289
 
1442
1290
  //#endregion
1443
1291
  //#region src/prompts/server-deploy.ts
1444
- function getDeploymentDisplay$1(deployment) {
1445
- if (deployment === "cloudflare") return {
1446
- label: "Cloudflare",
1447
- hint: "Deploy to Cloudflare Workers using Alchemy"
1448
- };
1449
- return {
1450
- label: deployment,
1451
- hint: `Add ${deployment} deployment`
1452
- };
1453
- }
1454
1292
  async function getServerDeploymentChoice(deployment, runtime, backend, _webDeploy) {
1455
1293
  if (deployment !== void 0) return deployment;
1456
1294
  if (backend === "none" || backend === "convex") return "none";
@@ -1458,29 +1296,6 @@ async function getServerDeploymentChoice(deployment, runtime, backend, _webDeplo
1458
1296
  if (runtime === "workers") return "cloudflare";
1459
1297
  return "none";
1460
1298
  }
1461
- async function getServerDeploymentToAdd(runtime, existingDeployment, backend) {
1462
- if (backend !== "hono") return "none";
1463
- const options = [];
1464
- if (runtime === "workers") {
1465
- if (existingDeployment !== "cloudflare") {
1466
- const { label, hint } = getDeploymentDisplay$1("cloudflare");
1467
- options.push({
1468
- value: "cloudflare",
1469
- label,
1470
- hint
1471
- });
1472
- }
1473
- }
1474
- if (existingDeployment && existingDeployment !== "none") return "none";
1475
- if (options.length === 0) return "none";
1476
- const response = await navigableSelect({
1477
- message: "Select server deployment",
1478
- options,
1479
- initialValue: DEFAULT_CONFIG.serverDeploy
1480
- });
1481
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1482
- return response;
1483
- }
1484
1299
 
1485
1300
  //#endregion
1486
1301
  //#region src/prompts/web-deploy.ts
@@ -1515,32 +1330,6 @@ async function getDeploymentChoice(deployment, _runtime, _backend, frontend = []
1515
1330
  if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1516
1331
  return response;
1517
1332
  }
1518
- async function getDeploymentToAdd(frontend, existingDeployment) {
1519
- if (!hasWebFrontend(frontend)) return "none";
1520
- const options = [];
1521
- if (existingDeployment !== "cloudflare") {
1522
- const { label, hint } = getDeploymentDisplay("cloudflare");
1523
- options.push({
1524
- value: "cloudflare",
1525
- label,
1526
- hint
1527
- });
1528
- }
1529
- if (existingDeployment && existingDeployment !== "none") return "none";
1530
- if (options.length > 0) options.push({
1531
- value: "none",
1532
- label: "None",
1533
- hint: "Skip deployment setup"
1534
- });
1535
- if (options.length === 0) return "none";
1536
- const response = await navigableSelect({
1537
- message: "Select web deployment",
1538
- options,
1539
- initialValue: DEFAULT_CONFIG.webDeploy
1540
- });
1541
- if (isCancel$1(response)) return exitCancelled("Operation cancelled");
1542
- return response;
1543
- }
1544
1333
 
1545
1334
  //#endregion
1546
1335
  //#region src/prompts/config-prompts.ts
@@ -2302,40 +2091,36 @@ ${configContent}`;
2302
2091
  const configPath = path.join(projectConfig.projectDir, BTS_CONFIG_FILE);
2303
2092
  await fs.writeFile(configPath, finalContent, "utf-8");
2304
2093
  }
2305
- async function readBtsConfig(projectDir) {
2094
+
2095
+ //#endregion
2096
+ //#region src/utils/file-formatter.ts
2097
+ const formatOptions = {
2098
+ experimentalSortPackageJson: true,
2099
+ experimentalSortImports: { order: "asc" }
2100
+ };
2101
+ async function formatCode(filePath, content) {
2306
2102
  try {
2307
- const configPath = path.join(projectDir, BTS_CONFIG_FILE);
2308
- if (!await fs.pathExists(configPath)) return null;
2309
- const configContent = await fs.readFile(configPath, "utf-8");
2310
- const errors = [];
2311
- const config = JSONC.parse(configContent, errors, {
2312
- allowTrailingComma: true,
2313
- disallowComments: false
2314
- });
2315
- if (errors.length > 0) {
2316
- console.warn("Warning: Found errors parsing bts.jsonc:", errors);
2317
- return null;
2318
- }
2319
- return config;
2103
+ const result = await format(path.basename(filePath), content, formatOptions);
2104
+ if (result.errors && result.errors.length > 0) return null;
2105
+ return result.code;
2320
2106
  } catch {
2321
2107
  return null;
2322
2108
  }
2323
2109
  }
2324
- async function updateBtsConfig(projectDir, updates) {
2325
- try {
2326
- const configPath = path.join(projectDir, BTS_CONFIG_FILE);
2327
- if (!await fs.pathExists(configPath)) return;
2328
- let modifiedContent = await fs.readFile(configPath, "utf-8");
2329
- for (const [key, value] of Object.entries(updates)) {
2330
- const editResult = JSONC.modify(modifiedContent, [key], value, { formattingOptions: {
2331
- tabSize: 2,
2332
- insertSpaces: true,
2333
- eol: "\n"
2334
- } });
2335
- modifiedContent = JSONC.applyEdits(modifiedContent, editResult);
2336
- }
2337
- await fs.writeFile(configPath, modifiedContent, "utf-8");
2338
- } catch {}
2110
+ async function formatProject(projectDir) {
2111
+ async function formatDirectory(dir) {
2112
+ const entries = await fs.readdir(dir, { withFileTypes: true });
2113
+ await Promise.all(entries.map(async (entry) => {
2114
+ const fullPath = path.join(dir, entry.name);
2115
+ if (entry.isDirectory()) await formatDirectory(fullPath);
2116
+ else if (entry.isFile()) try {
2117
+ const content = await fs.readFile(fullPath, "utf-8");
2118
+ const formatted = await formatCode(fullPath, content);
2119
+ if (formatted && formatted !== content) await fs.writeFile(fullPath, formatted, "utf-8");
2120
+ } catch {}
2121
+ }));
2122
+ }
2123
+ await formatDirectory(projectDir);
2339
2124
  }
2340
2125
 
2341
2126
  //#endregion
@@ -2530,32 +2315,35 @@ async function setupRuler(config) {
2530
2315
  const selectedEditors = await autocompleteMultiselect({
2531
2316
  message: "Select AI assistants for Ruler",
2532
2317
  options: Object.entries({
2318
+ agentsmd: { label: "Agents.md" },
2319
+ aider: { label: "Aider" },
2320
+ amazonqcli: { label: "Amazon Q CLI" },
2533
2321
  amp: { label: "AMP" },
2534
- copilot: { label: "GitHub Copilot" },
2322
+ antigravity: { label: "Antigravity" },
2323
+ augmentcode: { label: "AugmentCode" },
2535
2324
  claude: { label: "Claude Code" },
2325
+ cline: { label: "Cline" },
2536
2326
  codex: { label: "OpenAI Codex CLI" },
2327
+ copilot: { label: "GitHub Copilot" },
2328
+ crush: { label: "Crush" },
2537
2329
  cursor: { label: "Cursor" },
2538
- windsurf: { label: "Windsurf" },
2539
- cline: { label: "Cline" },
2540
- aider: { label: "Aider" },
2541
2330
  firebase: { label: "Firebase Studio" },
2542
- "gemini-cli": { label: "Gemini CLI" },
2543
- junie: { label: "Junie" },
2544
- kilocode: { label: "Kilo Code" },
2545
- opencode: { label: "OpenCode" },
2546
- crush: { label: "Crush" },
2547
- zed: { label: "Zed" },
2548
- qwen: { label: "Qwen" },
2549
- amazonqcli: { label: "Amazon Q CLI" },
2550
- augmentcode: { label: "AugmentCode" },
2551
2331
  firebender: { label: "Firebender" },
2332
+ "gemini-cli": { label: "Gemini CLI" },
2552
2333
  goose: { label: "Goose" },
2553
2334
  jules: { label: "Jules" },
2335
+ junie: { label: "Junie" },
2336
+ kilocode: { label: "Kilo Code" },
2554
2337
  kiro: { label: "Kiro" },
2338
+ mistral: { label: "Mistral" },
2339
+ opencode: { label: "OpenCode" },
2555
2340
  openhands: { label: "Open Hands" },
2341
+ qwen: { label: "Qwen" },
2556
2342
  roo: { label: "RooCode" },
2557
2343
  trae: { label: "Trae AI" },
2558
- warp: { label: "Warp" }
2344
+ warp: { label: "Warp" },
2345
+ windsurf: { label: "Windsurf" },
2346
+ zed: { label: "Zed" }
2559
2347
  }).map(([key, v]) => ({
2560
2348
  value: key,
2561
2349
  label: v.label
@@ -2643,26 +2431,9 @@ async function setupTauri(config) {
2643
2431
  if (!await fs.pathExists(clientPackageDir)) return;
2644
2432
  try {
2645
2433
  s.start("Setting up Tauri desktop app support...");
2646
- await addPackageDependency({
2647
- devDependencies: ["@tauri-apps/cli"],
2648
- projectDir: clientPackageDir
2649
- });
2650
- const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
2651
- if (await fs.pathExists(clientPackageJsonPath)) {
2652
- const packageJson = await fs.readJson(clientPackageJsonPath);
2653
- packageJson.scripts = {
2654
- ...packageJson.scripts,
2655
- tauri: "tauri",
2656
- "desktop:dev": "tauri dev",
2657
- "desktop:build": "tauri build"
2658
- };
2659
- await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
2660
- }
2661
- frontend.includes("tanstack-router");
2662
2434
  const hasReactRouter = frontend.includes("react-router");
2663
2435
  const hasNuxt = frontend.includes("nuxt");
2664
2436
  const hasSvelte = frontend.includes("svelte");
2665
- frontend.includes("solid");
2666
2437
  const hasNext = frontend.includes("next");
2667
2438
  const devUrl = hasReactRouter || hasSvelte ? "http://localhost:5173" : hasNext ? "http://localhost:3001" : "http://localhost:3001";
2668
2439
  const frontendDist = hasNuxt ? "../.output/public" : hasSvelte ? "../build" : hasNext ? "../.next" : hasReactRouter ? "../build/client" : "../dist";
@@ -2736,33 +2507,57 @@ async function setupTui(config) {
2736
2507
 
2737
2508
  //#endregion
2738
2509
  //#region src/helpers/addons/ultracite-setup.ts
2510
+ const LINTERS = {
2511
+ biome: {
2512
+ label: "Biome",
2513
+ hint: "Fast formatter and linter"
2514
+ },
2515
+ eslint: {
2516
+ label: "ESLint",
2517
+ hint: "Traditional JavaScript linter"
2518
+ },
2519
+ oxlint: {
2520
+ label: "Oxlint",
2521
+ hint: "Oxidation compiler linter"
2522
+ }
2523
+ };
2739
2524
  const EDITORS = {
2740
- vscode: { label: "VSCode / Cursor / Windsurf" },
2525
+ vscode: { label: "VS Code" },
2526
+ cursor: { label: "Cursor" },
2527
+ windsurf: { label: "Windsurf" },
2528
+ antigravity: { label: "Antigravity" },
2529
+ kiro: { label: "Kiro" },
2530
+ trae: { label: "Trae" },
2531
+ void: { label: "Void" },
2741
2532
  zed: { label: "Zed" }
2742
2533
  };
2743
2534
  const AGENTS = {
2744
- "vscode-copilot": { label: "VS Code Copilot" },
2745
- cursor: { label: "Cursor" },
2746
- windsurf: { label: "Windsurf" },
2747
- zed: { label: "Zed" },
2748
2535
  claude: { label: "Claude" },
2749
2536
  codex: { label: "Codex" },
2750
- kiro: { label: "Kiro" },
2537
+ jules: { label: "Jules" },
2538
+ copilot: { label: "GitHub Copilot" },
2751
2539
  cline: { label: "Cline" },
2752
2540
  amp: { label: "Amp" },
2753
2541
  aider: { label: "Aider" },
2754
2542
  "firebase-studio": { label: "Firebase Studio" },
2755
2543
  "open-hands": { label: "Open Hands" },
2756
- "gemini-cli": { label: "Gemini CLI" },
2544
+ gemini: { label: "Gemini" },
2757
2545
  junie: { label: "Junie" },
2758
2546
  augmentcode: { label: "AugmentCode" },
2759
2547
  "kilo-code": { label: "Kilo Code" },
2760
2548
  goose: { label: "Goose" },
2761
- "roo-code": { label: "Roo Code" }
2549
+ "roo-code": { label: "Roo Code" },
2550
+ warp: { label: "Warp" },
2551
+ droid: { label: "Droid" },
2552
+ opencode: { label: "OpenCode" },
2553
+ crush: { label: "Crush" },
2554
+ qwen: { label: "Qwen" },
2555
+ "amazon-q-cli": { label: "Amazon Q CLI" },
2556
+ firebender: { label: "Firebender" }
2762
2557
  };
2763
2558
  const HOOKS = {
2764
2559
  cursor: { label: "Cursor" },
2765
- claude: { label: "Claude" }
2560
+ windsurf: { label: "Windsurf" }
2766
2561
  };
2767
2562
  function getFrameworksFromFrontend(frontend) {
2768
2563
  const frameworkMap = {
@@ -2785,8 +2580,16 @@ async function setupUltracite(config, hasHusky) {
2785
2580
  const { packageManager, projectDir, frontend } = config;
2786
2581
  try {
2787
2582
  log.info("Setting up Ultracite...");
2788
- await setupBiome(projectDir);
2789
2583
  const result = await group({
2584
+ linter: () => select({
2585
+ message: "Choose linter/formatter",
2586
+ options: Object.entries(LINTERS).map(([key, linter$1]) => ({
2587
+ value: key,
2588
+ label: linter$1.label,
2589
+ hint: linter$1.hint
2590
+ })),
2591
+ initialValue: "biome"
2592
+ }),
2790
2593
  editors: () => multiselect({
2791
2594
  message: "Choose editors",
2792
2595
  options: Object.entries(EDITORS).map(([key, editor]) => ({
@@ -2795,7 +2598,7 @@ async function setupUltracite(config, hasHusky) {
2795
2598
  })),
2796
2599
  required: true
2797
2600
  }),
2798
- agents: () => autocompleteMultiselect({
2601
+ agents: () => multiselect({
2799
2602
  message: "Choose agents",
2800
2603
  options: Object.entries(AGENTS).map(([key, agent]) => ({
2801
2604
  value: key,
@@ -2803,7 +2606,7 @@ async function setupUltracite(config, hasHusky) {
2803
2606
  })),
2804
2607
  required: true
2805
2608
  }),
2806
- hooks: () => autocompleteMultiselect({
2609
+ hooks: () => multiselect({
2807
2610
  message: "Choose hooks",
2808
2611
  options: Object.entries(HOOKS).map(([key, hook]) => ({
2809
2612
  value: key,
@@ -2813,6 +2616,7 @@ async function setupUltracite(config, hasHusky) {
2813
2616
  }, { onCancel: () => {
2814
2617
  exitCancelled("Operation cancelled");
2815
2618
  } });
2619
+ const linter = result.linter;
2816
2620
  const editors = result.editors;
2817
2621
  const agents = result.agents;
2818
2622
  const hooks = result.hooks;
@@ -2820,7 +2624,9 @@ async function setupUltracite(config, hasHusky) {
2820
2624
  const ultraciteArgs = [
2821
2625
  "init",
2822
2626
  "--pm",
2823
- packageManager
2627
+ packageManager,
2628
+ "--linter",
2629
+ linter
2824
2630
  ];
2825
2631
  if (frameworks.length > 0) ultraciteArgs.push("--frameworks", ...frameworks);
2826
2632
  if (editors.length > 0) ultraciteArgs.push("--editors", ...editors);
@@ -2834,2298 +2640,168 @@ async function setupUltracite(config, hasHusky) {
2834
2640
  cwd: projectDir,
2835
2641
  env: { CI: "true" }
2836
2642
  })`${args}`;
2837
- if (hasHusky) await addPackageDependency({
2838
- devDependencies: ["husky", "lint-staged"],
2839
- projectDir
2840
- });
2841
- s.stop("Ultracite setup successfully!");
2842
- } catch (error) {
2843
- log.error(pc.red("Failed to set up Ultracite"));
2844
- if (error instanceof Error) console.error(pc.red(error.message));
2845
- }
2846
- }
2847
-
2848
- //#endregion
2849
- //#region src/utils/ts-morph.ts
2850
- const tsProject = new Project({
2851
- useInMemoryFileSystem: false,
2852
- skipAddingFilesFromTsConfig: true,
2853
- manipulationSettings: {
2854
- quoteKind: QuoteKind.Single,
2855
- indentationText: IndentationText.TwoSpaces
2856
- }
2857
- });
2858
- function ensureArrayProperty(obj, name) {
2859
- return obj.getProperty(name)?.getFirstDescendantByKind(SyntaxKind.ArrayLiteralExpression) ?? obj.addPropertyAssignment({
2860
- name,
2861
- initializer: "[]"
2862
- }).getFirstDescendantByKindOrThrow(SyntaxKind.ArrayLiteralExpression);
2863
- }
2864
-
2865
- //#endregion
2866
- //#region src/helpers/addons/vite-pwa-setup.ts
2867
- async function addPwaToViteConfig(viteConfigPath, projectName) {
2868
- const sourceFile = tsProject.addSourceFileAtPathIfExists(viteConfigPath);
2869
- if (!sourceFile) throw new Error("vite config not found");
2870
- if (!sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === "vite-plugin-pwa")) sourceFile.insertImportDeclaration(0, {
2871
- namedImports: ["VitePWA"],
2872
- moduleSpecifier: "vite-plugin-pwa"
2873
- });
2874
- const defineCall = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((expr) => {
2875
- const expression = expr.getExpression();
2876
- return Node.isIdentifier(expression) && expression.getText() === "defineConfig";
2877
- });
2878
- if (!defineCall) throw new Error("Could not find defineConfig call in vite config");
2879
- const configObject = defineCall.getArguments()[0];
2880
- if (!configObject) throw new Error("defineConfig argument is not an object literal");
2881
- const pluginsArray = ensureArrayProperty(configObject, "plugins");
2882
- if (!pluginsArray.getElements().some((el) => el.getText().startsWith("VitePWA("))) pluginsArray.addElement(`VitePWA({
2883
- registerType: "autoUpdate",
2884
- manifest: {
2885
- name: "${projectName}",
2886
- short_name: "${projectName}",
2887
- description: "${projectName} - PWA Application",
2888
- theme_color: "#0c0c0c",
2889
- },
2890
- pwaAssets: { disabled: false, config: true },
2891
- devOptions: { enabled: true },
2892
- })`);
2893
- await tsProject.save();
2894
- }
2895
-
2896
- //#endregion
2897
- //#region src/helpers/addons/wxt-setup.ts
2898
- const TEMPLATES = {
2899
- vanilla: {
2900
- label: "Vanilla",
2901
- hint: "Vanilla JavaScript template"
2902
- },
2903
- vue: {
2904
- label: "Vue",
2905
- hint: "Vue.js template"
2906
- },
2907
- react: {
2908
- label: "React",
2909
- hint: "React template"
2910
- },
2911
- solid: {
2912
- label: "Solid",
2913
- hint: "SolidJS template"
2914
- },
2915
- svelte: {
2916
- label: "Svelte",
2917
- hint: "Svelte template"
2918
- }
2919
- };
2920
- async function setupWxt(config) {
2921
- const { packageManager, projectDir } = config;
2922
- try {
2923
- log.info("Setting up WXT...");
2924
- const template = await select({
2925
- message: "Choose a template",
2926
- options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
2927
- value: key,
2928
- label: template$1.label,
2929
- hint: template$1.hint
2930
- })),
2931
- initialValue: "react"
2932
- });
2933
- if (isCancel(template)) return exitCancelled("Operation cancelled");
2934
- const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
2935
- const appsDir = path.join(projectDir, "apps");
2936
- await fs.ensureDir(appsDir);
2937
- const s = spinner();
2938
- s.start("Running WXT init command...");
2939
- await $({
2940
- cwd: appsDir,
2941
- env: { CI: "true" }
2942
- })`${args}`;
2943
- const extensionDir = path.join(projectDir, "apps", "extension");
2944
- const packageJsonPath = path.join(extensionDir, "package.json");
2945
- if (await fs.pathExists(packageJsonPath)) {
2946
- const packageJson = await fs.readJson(packageJsonPath);
2947
- packageJson.name = "extension";
2948
- if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port 5555`;
2949
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2950
- }
2951
- s.stop("WXT setup complete!");
2952
- } catch (error) {
2953
- log.error(pc.red("Failed to set up WXT"));
2954
- if (error instanceof Error) console.error(pc.red(error.message));
2955
- }
2956
- }
2957
-
2958
- //#endregion
2959
- //#region src/helpers/addons/addons-setup.ts
2960
- async function setupAddons(config, isAddCommand = false) {
2961
- const { addons, frontend, projectDir, packageManager } = config;
2962
- const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
2963
- const hasNuxtFrontend = frontend.includes("nuxt");
2964
- const hasSvelteFrontend = frontend.includes("svelte");
2965
- const hasSolidFrontend = frontend.includes("solid");
2966
- const hasNextFrontend = frontend.includes("next");
2967
- if (addons.includes("turborepo")) {
2968
- await addPackageDependency({
2969
- devDependencies: ["turbo"],
2970
- projectDir
2971
- });
2972
- if (isAddCommand) log.info(`${pc.yellow("Update your package.json scripts:")}
2973
-
2974
- ${pc.dim("Replace:")} ${pc.yellow("\"pnpm -r dev\"")} ${pc.dim("→")} ${pc.green("\"turbo dev\"")}
2975
- ${pc.dim("Replace:")} ${pc.yellow("\"pnpm --filter web dev\"")} ${pc.dim("→")} ${pc.green("\"turbo -F web dev\"")}
2976
-
2977
- ${pc.cyan("Docs:")} ${pc.underline("https://turborepo.com/docs")}
2978
- `);
2979
- }
2980
- if (addons.includes("pwa") && (hasReactWebFrontend || hasSolidFrontend)) await setupPwa(projectDir, frontend);
2981
- if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
2982
- const hasUltracite = addons.includes("ultracite");
2983
- const hasBiome = addons.includes("biome");
2984
- const hasHusky = addons.includes("husky");
2985
- const hasOxlint = addons.includes("oxlint");
2986
- if (hasUltracite) await setupUltracite(config, hasHusky);
2987
- else {
2988
- if (hasBiome) await setupBiome(projectDir);
2989
- if (hasHusky) {
2990
- let linter;
2991
- if (hasOxlint) linter = "oxlint";
2992
- else if (hasBiome) linter = "biome";
2993
- await setupHusky(projectDir, linter);
2994
- }
2995
- }
2996
- if (hasOxlint) await setupOxlint(projectDir, packageManager);
2997
- if (addons.includes("starlight")) await setupStarlight(config);
2998
- if (addons.includes("ruler")) await setupRuler(config);
2999
- if (addons.includes("fumadocs")) await setupFumadocs(config);
3000
- if (addons.includes("opentui")) await setupTui(config);
3001
- if (addons.includes("wxt")) await setupWxt(config);
3002
- }
3003
- function getWebAppDir(projectDir, frontends) {
3004
- if (frontends.some((f) => [
3005
- "react-router",
3006
- "tanstack-router",
3007
- "nuxt",
3008
- "svelte",
3009
- "solid"
3010
- ].includes(f))) return path.join(projectDir, "apps/web");
3011
- return path.join(projectDir, "apps/web");
3012
- }
3013
- async function setupBiome(projectDir) {
3014
- await addPackageDependency({
3015
- devDependencies: ["@biomejs/biome"],
3016
- projectDir
3017
- });
3018
- const packageJsonPath = path.join(projectDir, "package.json");
3019
- if (await fs.pathExists(packageJsonPath)) {
3020
- const packageJson = await fs.readJson(packageJsonPath);
3021
- packageJson.scripts = {
3022
- ...packageJson.scripts,
3023
- check: "biome check --write ."
3024
- };
3025
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3026
- }
3027
- }
3028
- async function setupHusky(projectDir, linter) {
3029
- await addPackageDependency({
3030
- devDependencies: ["husky", "lint-staged"],
3031
- projectDir
3032
- });
3033
- const packageJsonPath = path.join(projectDir, "package.json");
3034
- if (await fs.pathExists(packageJsonPath)) {
3035
- const packageJson = await fs.readJson(packageJsonPath);
3036
- packageJson.scripts = {
3037
- ...packageJson.scripts,
3038
- prepare: "husky"
3039
- };
3040
- if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
3041
- else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
3042
- else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
3043
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3044
- }
3045
- }
3046
- async function setupPwa(projectDir, frontends) {
3047
- if (!frontends.some((f) => [
3048
- "react-router",
3049
- "tanstack-router",
3050
- "solid"
3051
- ].includes(f))) return;
3052
- const clientPackageDir = getWebAppDir(projectDir, frontends);
3053
- if (!await fs.pathExists(clientPackageDir)) return;
3054
- await addPackageDependency({
3055
- dependencies: ["vite-plugin-pwa"],
3056
- devDependencies: ["@vite-pwa/assets-generator"],
3057
- projectDir: clientPackageDir
3058
- });
3059
- const clientPackageJsonPath = path.join(clientPackageDir, "package.json");
3060
- if (await fs.pathExists(clientPackageJsonPath)) {
3061
- const packageJson = await fs.readJson(clientPackageJsonPath);
3062
- packageJson.scripts = {
3063
- ...packageJson.scripts,
3064
- "generate-pwa-assets": "pwa-assets-generator"
3065
- };
3066
- await fs.writeJson(clientPackageJsonPath, packageJson, { spaces: 2 });
3067
- }
3068
- const viteConfigTs = path.join(clientPackageDir, "vite.config.ts");
3069
- if (await fs.pathExists(viteConfigTs)) await addPwaToViteConfig(viteConfigTs, path.basename(projectDir));
3070
- }
3071
-
3072
- //#endregion
3073
- //#region src/helpers/core/detect-project-config.ts
3074
- async function detectProjectConfig(projectDir) {
3075
- try {
3076
- const btsConfig = await readBtsConfig(projectDir);
3077
- if (btsConfig) return {
3078
- projectDir,
3079
- projectName: path.basename(projectDir),
3080
- database: btsConfig.database,
3081
- orm: btsConfig.orm,
3082
- backend: btsConfig.backend,
3083
- runtime: btsConfig.runtime,
3084
- frontend: btsConfig.frontend,
3085
- addons: btsConfig.addons,
3086
- examples: btsConfig.examples,
3087
- auth: btsConfig.auth,
3088
- payments: btsConfig.payments,
3089
- packageManager: btsConfig.packageManager,
3090
- dbSetup: btsConfig.dbSetup,
3091
- api: btsConfig.api,
3092
- webDeploy: btsConfig.webDeploy,
3093
- serverDeploy: btsConfig.serverDeploy
3094
- };
3095
- return null;
3096
- } catch {
3097
- return null;
3098
- }
3099
- }
3100
- async function isBetterTStackProject(projectDir) {
3101
- try {
3102
- return await fs.pathExists(path.join(projectDir, "bts.jsonc"));
3103
- } catch {
3104
- return false;
3105
- }
3106
- }
3107
-
3108
- //#endregion
3109
- //#region src/helpers/core/install-dependencies.ts
3110
- async function installDependencies({ projectDir, packageManager }) {
3111
- const s = spinner();
3112
- try {
3113
- s.start(`Running ${packageManager} install...`);
3114
- await $({
3115
- cwd: projectDir,
3116
- stderr: "inherit"
3117
- })`${packageManager} install`;
3118
- s.stop("Dependencies installed successfully");
3119
- } catch (error) {
3120
- s.stop(pc.red("Failed to install dependencies"));
3121
- if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
3122
- }
3123
- }
3124
-
3125
- //#endregion
3126
- //#region src/utils/file-formatter.ts
3127
- const formatOptions = {
3128
- experimentalSortPackageJson: true,
3129
- experimentalSortImports: { order: "asc" }
3130
- };
3131
- async function formatFile(filePath, content) {
3132
- try {
3133
- const result = await format(path.basename(filePath), content, formatOptions);
3134
- if (result.errors && result.errors.length > 0) return null;
3135
- return result.code;
3136
- } catch {
3137
- return null;
3138
- }
3139
- }
3140
-
3141
- //#endregion
3142
- //#region src/utils/template-processor.ts
3143
- const BINARY_EXTENSIONS = new Set([
3144
- ".png",
3145
- ".ico",
3146
- ".svg"
3147
- ]);
3148
- function isBinaryFile(filePath) {
3149
- const ext = path.extname(filePath).toLowerCase();
3150
- return BINARY_EXTENSIONS.has(ext);
3151
- }
3152
- async function processTemplate(srcPath, destPath, context) {
3153
- try {
3154
- await fs.ensureDir(path.dirname(destPath));
3155
- if (isBinaryFile(srcPath) && !srcPath.endsWith(".hbs")) {
3156
- await fs.copy(srcPath, destPath);
3157
- return;
3158
- }
3159
- let content;
3160
- if (srcPath.endsWith(".hbs")) {
3161
- const templateContent = await fs.readFile(srcPath, "utf-8");
3162
- content = handlebars.compile(templateContent)(context);
3163
- } else content = await fs.readFile(srcPath, "utf-8");
3164
- try {
3165
- const formattedContent = await formatFile(destPath, content);
3166
- if (formattedContent) content = formattedContent;
3167
- } catch (formatError) {
3168
- consola.debug(`Failed to format ${destPath}:`, formatError);
3169
- }
3170
- await fs.writeFile(destPath, content);
3171
- } catch (error) {
3172
- consola.error(`Error processing template ${srcPath}:`, error);
3173
- throw new Error(`Failed to process template ${srcPath}`);
3174
- }
3175
- }
3176
- handlebars.registerHelper("eq", (a, b) => a === b);
3177
- handlebars.registerHelper("ne", (a, b) => a !== b);
3178
- handlebars.registerHelper("and", (...args) => {
3179
- return args.slice(0, -1).every((value) => value);
3180
- });
3181
- handlebars.registerHelper("or", (...args) => {
3182
- return args.slice(0, -1).some((value) => value);
3183
- });
3184
- handlebars.registerHelper("includes", (array, value) => Array.isArray(array) && array.includes(value));
3185
-
3186
- //#endregion
3187
- //#region src/helpers/core/template-manager.ts
3188
- async function processAndCopyFiles(sourcePattern, baseSourceDir, destDir, context, overwrite = true, ignorePatterns) {
3189
- const sourceFiles = await glob(sourcePattern, {
3190
- cwd: baseSourceDir,
3191
- dot: true,
3192
- onlyFiles: true,
3193
- absolute: false,
3194
- ignore: ignorePatterns
3195
- });
3196
- for (const relativeSrcPath of sourceFiles) {
3197
- const srcPath = path.join(baseSourceDir, relativeSrcPath);
3198
- let relativeDestPath = relativeSrcPath;
3199
- if (relativeSrcPath.endsWith(".hbs")) relativeDestPath = relativeSrcPath.slice(0, -4);
3200
- const basename = path.basename(relativeDestPath);
3201
- if (basename === "_gitignore") relativeDestPath = path.join(path.dirname(relativeDestPath), ".gitignore");
3202
- else if (basename === "_npmrc") relativeDestPath = path.join(path.dirname(relativeDestPath), ".npmrc");
3203
- const destPath = path.join(destDir, relativeDestPath);
3204
- await fs.ensureDir(path.dirname(destPath));
3205
- if (!overwrite && await fs.pathExists(destPath)) continue;
3206
- await processTemplate(srcPath, destPath, context);
3207
- }
3208
- }
3209
- async function copyBaseTemplate(projectDir, context) {
3210
- await processAndCopyFiles(["**/*"], path.join(PKG_ROOT, "templates/base"), projectDir, context);
3211
- }
3212
- async function setupFrontendTemplates(projectDir, context) {
3213
- const hasReactWeb = context.frontend.some((f) => [
3214
- "tanstack-router",
3215
- "react-router",
3216
- "tanstack-start",
3217
- "next"
3218
- ].includes(f));
3219
- const hasNuxtWeb = context.frontend.includes("nuxt");
3220
- const hasSvelteWeb = context.frontend.includes("svelte");
3221
- const hasSolidWeb = context.frontend.includes("solid");
3222
- const hasNativeBare = context.frontend.includes("native-bare");
3223
- const hasNativeUniwind = context.frontend.includes("native-uniwind");
3224
- const hasUnistyles = context.frontend.includes("native-unistyles");
3225
- const isConvex = context.backend === "convex";
3226
- if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) {
3227
- const webAppDir = path.join(projectDir, "apps/web");
3228
- await fs.ensureDir(webAppDir);
3229
- if (hasReactWeb) {
3230
- const webBaseDir = path.join(PKG_ROOT, "templates/frontend/react/web-base");
3231
- if (await fs.pathExists(webBaseDir)) await processAndCopyFiles("**/*", webBaseDir, webAppDir, context);
3232
- const reactFramework = context.frontend.find((f) => [
3233
- "tanstack-router",
3234
- "react-router",
3235
- "tanstack-start",
3236
- "next"
3237
- ].includes(f));
3238
- if (reactFramework) {
3239
- const frameworkSrcDir = path.join(PKG_ROOT, `templates/frontend/react/${reactFramework}`);
3240
- if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, webAppDir, context);
3241
- if (!isConvex && context.api !== "none") {
3242
- const apiWebBaseDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/react/base`);
3243
- if (await fs.pathExists(apiWebBaseDir)) await processAndCopyFiles("**/*", apiWebBaseDir, webAppDir, context);
3244
- }
3245
- if (context.backend === "self" && (reactFramework === "next" || reactFramework === "tanstack-start") && context.api !== "none") {
3246
- const apiFullstackDir = path.join(PKG_ROOT, `templates/api/${context.api}/fullstack/${reactFramework}`);
3247
- if (await fs.pathExists(apiFullstackDir)) await processAndCopyFiles("**/*", apiFullstackDir, webAppDir, context);
3248
- }
3249
- }
3250
- } else if (hasNuxtWeb) {
3251
- const nuxtBaseDir = path.join(PKG_ROOT, "templates/frontend/nuxt");
3252
- if (await fs.pathExists(nuxtBaseDir)) await processAndCopyFiles("**/*", nuxtBaseDir, webAppDir, context);
3253
- if (!isConvex && context.api === "orpc") {
3254
- const apiWebNuxtDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/nuxt`);
3255
- if (await fs.pathExists(apiWebNuxtDir)) await processAndCopyFiles("**/*", apiWebNuxtDir, webAppDir, context);
3256
- }
3257
- } else if (hasSvelteWeb) {
3258
- const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
3259
- if (await fs.pathExists(svelteBaseDir)) await processAndCopyFiles("**/*", svelteBaseDir, webAppDir, context);
3260
- if (!isConvex && context.api === "orpc") {
3261
- const apiWebSvelteDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/svelte`);
3262
- if (await fs.pathExists(apiWebSvelteDir)) await processAndCopyFiles("**/*", apiWebSvelteDir, webAppDir, context);
3263
- }
3264
- } else if (hasSolidWeb) {
3265
- const solidBaseDir = path.join(PKG_ROOT, "templates/frontend/solid");
3266
- if (await fs.pathExists(solidBaseDir)) await processAndCopyFiles("**/*", solidBaseDir, webAppDir, context);
3267
- if (!isConvex && context.api === "orpc") {
3268
- const apiWebSolidDir = path.join(PKG_ROOT, `templates/api/${context.api}/web/solid`);
3269
- if (await fs.pathExists(apiWebSolidDir)) await processAndCopyFiles("**/*", apiWebSolidDir, webAppDir, context);
3270
- }
3271
- }
3272
- }
3273
- if (hasNativeBare || hasNativeUniwind || hasUnistyles) {
3274
- const nativeAppDir = path.join(projectDir, "apps/native");
3275
- await fs.ensureDir(nativeAppDir);
3276
- const nativeBaseCommonDir = path.join(PKG_ROOT, "templates/frontend/native/base");
3277
- if (await fs.pathExists(nativeBaseCommonDir)) await processAndCopyFiles("**/*", nativeBaseCommonDir, nativeAppDir, context);
3278
- let nativeFrameworkPath = "";
3279
- if (hasNativeBare) nativeFrameworkPath = "bare";
3280
- else if (hasNativeUniwind) nativeFrameworkPath = "uniwind";
3281
- else if (hasUnistyles) nativeFrameworkPath = "unistyles";
3282
- const nativeSpecificDir = path.join(PKG_ROOT, `templates/frontend/native/${nativeFrameworkPath}`);
3283
- if (await fs.pathExists(nativeSpecificDir)) await processAndCopyFiles("**/*", nativeSpecificDir, nativeAppDir, context, true);
3284
- if (!isConvex && (context.api === "trpc" || context.api === "orpc")) {
3285
- const apiNativeSrcDir = path.join(PKG_ROOT, `templates/api/${context.api}/native`);
3286
- if (await fs.pathExists(apiNativeSrcDir)) await processAndCopyFiles("**/*", apiNativeSrcDir, nativeAppDir, context);
3287
- }
3288
- }
3289
- }
3290
- async function setupApiPackage$1(projectDir, context) {
3291
- if (context.api === "none") return;
3292
- const apiPackageDir = path.join(projectDir, "packages/api");
3293
- await fs.ensureDir(apiPackageDir);
3294
- const apiServerDir = path.join(PKG_ROOT, `templates/api/${context.api}/server`);
3295
- if (await fs.pathExists(apiServerDir)) await processAndCopyFiles("**/*", apiServerDir, apiPackageDir, context);
3296
- }
3297
- async function setupConfigPackage(projectDir, context) {
3298
- const configPackageDir = path.join(projectDir, "packages/config");
3299
- await fs.ensureDir(configPackageDir);
3300
- const configBaseDir = path.join(PKG_ROOT, "templates/packages/config");
3301
- if (await fs.pathExists(configBaseDir)) await processAndCopyFiles("**/*", configBaseDir, configPackageDir, context);
3302
- }
3303
- async function setupDbPackage$1(projectDir, context) {
3304
- if (context.database === "none" || context.orm === "none") return;
3305
- const dbPackageDir = path.join(projectDir, "packages/db");
3306
- await fs.ensureDir(dbPackageDir);
3307
- const dbBaseDir = path.join(PKG_ROOT, "templates/db/base");
3308
- if (await fs.pathExists(dbBaseDir)) await processAndCopyFiles("**/*", dbBaseDir, dbPackageDir, context);
3309
- const dbOrmBaseDir = path.join(PKG_ROOT, `templates/db/${context.orm}/base`);
3310
- if (await fs.pathExists(dbOrmBaseDir)) await processAndCopyFiles("**/*", dbOrmBaseDir, dbPackageDir, context);
3311
- const dbOrmSrcDir = path.join(PKG_ROOT, `templates/db/${context.orm}/${context.database}`);
3312
- if (await fs.pathExists(dbOrmSrcDir)) await processAndCopyFiles("**/*", dbOrmSrcDir, dbPackageDir, context);
3313
- }
3314
- async function setupConvexBackend(projectDir, context) {
3315
- const serverAppDir = path.join(projectDir, "apps/server");
3316
- if (await fs.pathExists(serverAppDir)) await fs.remove(serverAppDir);
3317
- const convexBackendDestDir = path.join(projectDir, "packages/backend");
3318
- const convexSrcDir = path.join(PKG_ROOT, "templates/backend/convex/packages/backend");
3319
- await fs.ensureDir(convexBackendDestDir);
3320
- if (await fs.pathExists(convexSrcDir)) await processAndCopyFiles("**/*", convexSrcDir, convexBackendDestDir, context);
3321
- }
3322
- async function setupServerApp(projectDir, context) {
3323
- const serverAppDir = path.join(projectDir, "apps/server");
3324
- await fs.ensureDir(serverAppDir);
3325
- const serverBaseDir = path.join(PKG_ROOT, "templates/backend/server/base");
3326
- if (await fs.pathExists(serverBaseDir)) await processAndCopyFiles("**/*", serverBaseDir, serverAppDir, context);
3327
- const frameworkSrcDir = path.join(PKG_ROOT, `templates/backend/server/${context.backend}`);
3328
- if (await fs.pathExists(frameworkSrcDir)) await processAndCopyFiles("**/*", frameworkSrcDir, serverAppDir, context, true);
3329
- }
3330
- async function setupEnvPackage$1(projectDir, context) {
3331
- const hasWebFrontend$1 = context.frontend.some((f) => [
3332
- "tanstack-router",
3333
- "react-router",
3334
- "tanstack-start",
3335
- "next",
3336
- "nuxt",
3337
- "svelte",
3338
- "solid"
3339
- ].includes(f));
3340
- const hasNative = context.frontend.some((f) => [
3341
- "native-bare",
3342
- "native-uniwind",
3343
- "native-unistyles"
3344
- ].includes(f));
3345
- if (!hasWebFrontend$1 && !hasNative && context.backend === "none") return;
3346
- const envPackageDir = path.join(projectDir, "packages/env");
3347
- await fs.ensureDir(envPackageDir);
3348
- const envBaseDir = path.join(PKG_ROOT, "templates/packages/env");
3349
- const packageJsonSrc = path.join(envBaseDir, "package.json.hbs");
3350
- if (await fs.pathExists(packageJsonSrc)) await processAndCopyFiles("package.json.hbs", envBaseDir, envPackageDir, context);
3351
- const tsconfigSrc = path.join(envBaseDir, "tsconfig.json.hbs");
3352
- if (await fs.pathExists(tsconfigSrc)) await processAndCopyFiles("tsconfig.json.hbs", envBaseDir, envPackageDir, context);
3353
- const needsServerEnv = context.backend !== "none" && context.backend !== "convex";
3354
- if (needsServerEnv) {
3355
- const serverSrc = path.join(envBaseDir, "src/server.ts.hbs");
3356
- if (await fs.pathExists(serverSrc)) {
3357
- await fs.ensureDir(path.join(envPackageDir, "src"));
3358
- await processAndCopyFiles("src/server.ts.hbs", envBaseDir, envPackageDir, context);
3359
- }
3360
- }
3361
- if (hasWebFrontend$1) {
3362
- const webSrc = path.join(envBaseDir, "src/web.ts.hbs");
3363
- if (await fs.pathExists(webSrc)) {
3364
- await fs.ensureDir(path.join(envPackageDir, "src"));
3365
- await processAndCopyFiles("src/web.ts.hbs", envBaseDir, envPackageDir, context);
3366
- }
3367
- }
3368
- if (hasNative) {
3369
- const nativeSrc = path.join(envBaseDir, "src/native.ts.hbs");
3370
- if (await fs.pathExists(nativeSrc)) {
3371
- await fs.ensureDir(path.join(envPackageDir, "src"));
3372
- await processAndCopyFiles("src/native.ts.hbs", envBaseDir, envPackageDir, context);
3373
- }
3374
- }
3375
- const packageJsonPath = path.join(envPackageDir, "package.json");
3376
- if (await fs.pathExists(packageJsonPath)) {
3377
- const packageJson = await fs.readJson(packageJsonPath);
3378
- const exports = {};
3379
- if (needsServerEnv) exports["./server"] = "./src/server.ts";
3380
- if (hasWebFrontend$1) exports["./web"] = "./src/web.ts";
3381
- if (hasNative) exports["./native"] = "./src/native.ts";
3382
- packageJson.exports = exports;
3383
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
3384
- }
3385
- }
3386
- async function setupBackendFramework(projectDir, context) {
3387
- await setupConfigPackage(projectDir, context);
3388
- await setupEnvPackage$1(projectDir, context);
3389
- if (context.backend === "none") return;
3390
- if (context.backend === "convex") {
3391
- await setupConvexBackend(projectDir, context);
3392
- return;
3393
- }
3394
- if (context.backend === "self") {
3395
- await setupApiPackage$1(projectDir, context);
3396
- await setupDbPackage$1(projectDir, context);
3397
- return;
3398
- }
3399
- await setupServerApp(projectDir, context);
3400
- await setupApiPackage$1(projectDir, context);
3401
- await setupDbPackage$1(projectDir, context);
3402
- }
3403
- async function setupAuthTemplate(projectDir, context) {
3404
- if (!context.auth || context.auth === "none") return;
3405
- const serverAppDir = path.join(projectDir, "apps/server");
3406
- const webAppDir = path.join(projectDir, "apps/web");
3407
- const nativeAppDir = path.join(projectDir, "apps/native");
3408
- const serverAppDirExists = await fs.pathExists(serverAppDir);
3409
- const webAppDirExists = await fs.pathExists(webAppDir);
3410
- const nativeAppDirExists = await fs.pathExists(nativeAppDir);
3411
- const hasReactWeb = context.frontend.some((f) => [
3412
- "tanstack-router",
3413
- "react-router",
3414
- "tanstack-start",
3415
- "next"
3416
- ].includes(f));
3417
- const hasNuxtWeb = context.frontend.includes("nuxt");
3418
- const hasSvelteWeb = context.frontend.includes("svelte");
3419
- const hasSolidWeb = context.frontend.includes("solid");
3420
- const hasNativeBare = context.frontend.includes("native-bare");
3421
- const hasUniwind = context.frontend.includes("native-uniwind");
3422
- const hasUnistyles = context.frontend.includes("native-unistyles");
3423
- const hasNative = hasNativeBare || hasUniwind || hasUnistyles;
3424
- const authProvider = context.auth;
3425
- if (context.backend === "convex" && authProvider === "clerk") {
3426
- const convexBackendDestDir = path.join(projectDir, "packages/backend");
3427
- const convexClerkBackendSrc = path.join(PKG_ROOT, "templates/auth/clerk/convex/backend");
3428
- if (await fs.pathExists(convexClerkBackendSrc)) {
3429
- await fs.ensureDir(convexBackendDestDir);
3430
- await processAndCopyFiles("**/*", convexClerkBackendSrc, convexBackendDestDir, context);
3431
- }
3432
- if (webAppDirExists) {
3433
- const reactFramework = context.frontend.find((f) => [
3434
- "tanstack-router",
3435
- "react-router",
3436
- "tanstack-start",
3437
- "next"
3438
- ].includes(f));
3439
- if (reactFramework) {
3440
- const convexClerkWebSrc = path.join(PKG_ROOT, `templates/auth/clerk/convex/web/react/${reactFramework}`);
3441
- if (await fs.pathExists(convexClerkWebSrc)) await processAndCopyFiles("**/*", convexClerkWebSrc, webAppDir, context);
3442
- }
3443
- }
3444
- if (nativeAppDirExists) {
3445
- const convexClerkNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/clerk/convex/native/base");
3446
- if (await fs.pathExists(convexClerkNativeBaseSrc)) await processAndCopyFiles("**/*", convexClerkNativeBaseSrc, nativeAppDir, context);
3447
- let nativeFrameworkPath = "";
3448
- if (hasNativeBare) nativeFrameworkPath = "bare";
3449
- else if (hasUniwind) nativeFrameworkPath = "uniwind";
3450
- else if (hasUnistyles) nativeFrameworkPath = "unistyles";
3451
- if (nativeFrameworkPath) {
3452
- const convexClerkNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/clerk/convex/native/${nativeFrameworkPath}`);
3453
- if (await fs.pathExists(convexClerkNativeFrameworkSrc)) await processAndCopyFiles("**/*", convexClerkNativeFrameworkSrc, nativeAppDir, context);
3454
- }
3455
- }
3456
- return;
3457
- }
3458
- if (context.backend === "convex" && authProvider === "better-auth") {
3459
- const convexBackendDestDir = path.join(projectDir, "packages/backend");
3460
- const convexBetterAuthBackendSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/backend");
3461
- if (await fs.pathExists(convexBetterAuthBackendSrc)) {
3462
- await fs.ensureDir(convexBackendDestDir);
3463
- await processAndCopyFiles("**/*", convexBetterAuthBackendSrc, convexBackendDestDir, context);
3464
- }
3465
- if (webAppDirExists && hasReactWeb) {
3466
- const convexBetterAuthWebBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/web/react/base");
3467
- if (await fs.pathExists(convexBetterAuthWebBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebBaseSrc, webAppDir, context);
3468
- const reactFramework = context.frontend.find((f) => [
3469
- "tanstack-router",
3470
- "react-router",
3471
- "tanstack-start",
3472
- "next"
3473
- ].includes(f));
3474
- if (reactFramework) {
3475
- const convexBetterAuthWebSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/web/react/${reactFramework}`);
3476
- if (await fs.pathExists(convexBetterAuthWebSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebSrc, webAppDir, context);
3477
- }
3478
- }
3479
- if (nativeAppDirExists) {
3480
- const convexBetterAuthNativeBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/native/base");
3481
- if (await fs.pathExists(convexBetterAuthNativeBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthNativeBaseSrc, nativeAppDir, context);
3482
- let nativeFrameworkPath = "";
3483
- if (hasNativeBare) nativeFrameworkPath = "bare";
3484
- else if (hasUniwind) nativeFrameworkPath = "uniwind";
3485
- else if (hasUnistyles) nativeFrameworkPath = "unistyles";
3486
- if (nativeFrameworkPath) {
3487
- const convexBetterAuthNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/native/${nativeFrameworkPath}`);
3488
- if (await fs.pathExists(convexBetterAuthNativeFrameworkSrc)) await processAndCopyFiles("**/*", convexBetterAuthNativeFrameworkSrc, nativeAppDir, context);
3489
- }
3490
- }
3491
- return;
3492
- }
3493
- if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
3494
- const authPackageDir = path.join(projectDir, "packages/auth");
3495
- await fs.ensureDir(authPackageDir);
3496
- const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
3497
- if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, authPackageDir, context);
3498
- if (context.orm !== "none" && context.database !== "none") {
3499
- const dbPackageDir = path.join(projectDir, "packages/db");
3500
- await fs.ensureDir(dbPackageDir);
3501
- const orm = context.orm;
3502
- const db = context.database;
3503
- let authDbSrc = "";
3504
- if (orm === "drizzle") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/drizzle/${db}`);
3505
- else if (orm === "prisma") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/prisma/${db}`);
3506
- else if (orm === "mongoose") authDbSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/db/mongoose/${db}`);
3507
- if (authDbSrc && await fs.pathExists(authDbSrc)) await processAndCopyFiles("**/*", authDbSrc, dbPackageDir, context);
3508
- }
3509
- }
3510
- if ((hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) && webAppDirExists) {
3511
- if (hasReactWeb) {
3512
- const authWebBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/base`);
3513
- if (await fs.pathExists(authWebBaseSrc)) await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context);
3514
- const reactFramework = context.frontend.find((f) => [
3515
- "tanstack-router",
3516
- "react-router",
3517
- "tanstack-start",
3518
- "next"
3519
- ].includes(f));
3520
- if (reactFramework) {
3521
- const authWebFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/react/${reactFramework}`);
3522
- if (await fs.pathExists(authWebFrameworkSrc)) await processAndCopyFiles("**/*", authWebFrameworkSrc, webAppDir, context);
3523
- if (context.backend === "self" && (reactFramework === "next" || reactFramework === "tanstack-start")) {
3524
- const authFullstackSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/fullstack/${reactFramework}`);
3525
- if (await fs.pathExists(authFullstackSrc)) await processAndCopyFiles("**/*", authFullstackSrc, webAppDir, context);
3526
- }
3527
- }
3528
- } else if (hasNuxtWeb) {
3529
- const authWebNuxtSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/nuxt`);
3530
- if (await fs.pathExists(authWebNuxtSrc)) await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context);
3531
- } else if (hasSvelteWeb) {
3532
- const authWebSvelteSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/svelte`);
3533
- if (await fs.pathExists(authWebSvelteSrc)) await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context);
3534
- } else if (hasSolidWeb) {
3535
- const authWebSolidSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/web/solid`);
3536
- if (await fs.pathExists(authWebSolidSrc)) await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context);
3537
- }
3538
- }
3539
- if (hasNative && nativeAppDirExists) {
3540
- const authNativeBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/base`);
3541
- if (await fs.pathExists(authNativeBaseSrc)) await processAndCopyFiles("**/*", authNativeBaseSrc, nativeAppDir, context);
3542
- let nativeFrameworkAuthPath = "";
3543
- if (hasNativeBare) nativeFrameworkAuthPath = "bare";
3544
- else if (hasUniwind) nativeFrameworkAuthPath = "uniwind";
3545
- else if (hasUnistyles) nativeFrameworkAuthPath = "unistyles";
3546
- if (nativeFrameworkAuthPath) {
3547
- const authNativeFrameworkSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/native/${nativeFrameworkAuthPath}`);
3548
- if (await fs.pathExists(authNativeFrameworkSrc)) await processAndCopyFiles("**/*", authNativeFrameworkSrc, nativeAppDir, context);
3549
- }
3550
- }
3551
- }
3552
- async function setupPaymentsTemplate(projectDir, context) {
3553
- if (!context.payments || context.payments === "none") return;
3554
- const serverAppDir = path.join(projectDir, "apps/server");
3555
- const webAppDir = path.join(projectDir, "apps/web");
3556
- const serverAppDirExists = await fs.pathExists(serverAppDir);
3557
- const webAppDirExists = await fs.pathExists(webAppDir);
3558
- if ((serverAppDirExists || context.backend === "self") && context.backend !== "convex") {
3559
- const authPackageDir = path.join(projectDir, "packages/auth");
3560
- await fs.ensureDir(authPackageDir);
3561
- const paymentsServerSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/server/base`);
3562
- if (await fs.pathExists(paymentsServerSrc)) await processAndCopyFiles("**/*", paymentsServerSrc, authPackageDir, context);
3563
- }
3564
- const hasReactWeb = context.frontend.some((f) => [
3565
- "tanstack-router",
3566
- "react-router",
3567
- "tanstack-start",
3568
- "next"
3569
- ].includes(f));
3570
- const hasNuxtWeb = context.frontend.includes("nuxt");
3571
- const hasSvelteWeb = context.frontend.includes("svelte");
3572
- const hasSolidWeb = context.frontend.includes("solid");
3573
- if (webAppDirExists && (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb)) {
3574
- if (hasReactWeb) {
3575
- const reactFramework = context.frontend.find((f) => [
3576
- "tanstack-router",
3577
- "react-router",
3578
- "tanstack-start",
3579
- "next"
3580
- ].includes(f));
3581
- if (reactFramework) {
3582
- const paymentsWebSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/react/${reactFramework}`);
3583
- if (await fs.pathExists(paymentsWebSrc)) await processAndCopyFiles("**/*", paymentsWebSrc, webAppDir, context);
3584
- }
3585
- } else if (hasNuxtWeb) {
3586
- const paymentsWebNuxtSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/nuxt`);
3587
- if (await fs.pathExists(paymentsWebNuxtSrc)) await processAndCopyFiles("**/*", paymentsWebNuxtSrc, webAppDir, context);
3588
- } else if (hasSvelteWeb) {
3589
- const paymentsWebSvelteSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/svelte`);
3590
- if (await fs.pathExists(paymentsWebSvelteSrc)) await processAndCopyFiles("**/*", paymentsWebSvelteSrc, webAppDir, context);
3591
- } else if (hasSolidWeb) {
3592
- const paymentsWebSolidSrc = path.join(PKG_ROOT, `templates/payments/${context.payments}/web/solid`);
3593
- if (await fs.pathExists(paymentsWebSolidSrc)) await processAndCopyFiles("**/*", paymentsWebSolidSrc, webAppDir, context);
3594
- }
3595
- }
3596
- }
3597
- async function setupAddonsTemplate(projectDir, context) {
3598
- if (!context.addons || context.addons.length === 0) return;
3599
- for (const addon of context.addons) {
3600
- if (addon === "none") continue;
3601
- let addonSrcDir = path.join(PKG_ROOT, `templates/addons/${addon}`);
3602
- let addonDestDir = projectDir;
3603
- if (addon === "pwa") {
3604
- const webAppDir = path.join(projectDir, "apps/web");
3605
- if (!await fs.pathExists(webAppDir)) continue;
3606
- addonDestDir = webAppDir;
3607
- if (context.frontend.includes("next")) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/next");
3608
- else if (context.frontend.some((f) => [
3609
- "tanstack-router",
3610
- "react-router",
3611
- "solid"
3612
- ].includes(f))) addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
3613
- else continue;
3614
- }
3615
- if (await fs.pathExists(addonSrcDir)) await processAndCopyFiles("**/*", addonSrcDir, addonDestDir, context);
3616
- }
3617
- }
3618
- async function setupExamplesTemplate(projectDir, context) {
3619
- if (!context.examples || context.examples.length === 0 || context.examples[0] === "none") return;
3620
- const serverAppDir = path.join(projectDir, "apps/server");
3621
- const webAppDir = path.join(projectDir, "apps/web");
3622
- const serverAppDirExists = await fs.pathExists(serverAppDir);
3623
- const webAppDirExists = await fs.pathExists(webAppDir);
3624
- const nativeAppDir = path.join(projectDir, "apps/native");
3625
- const nativeAppDirExists = await fs.pathExists(nativeAppDir);
3626
- const hasReactWeb = context.frontend.some((f) => [
3627
- "tanstack-router",
3628
- "react-router",
3629
- "tanstack-start",
3630
- "next"
3631
- ].includes(f));
3632
- const hasNuxtWeb = context.frontend.includes("nuxt");
3633
- const hasSvelteWeb = context.frontend.includes("svelte");
3634
- const hasSolidWeb = context.frontend.includes("solid");
3635
- for (const example of context.examples) {
3636
- if (example === "none") continue;
3637
- const exampleBaseDir = path.join(PKG_ROOT, `templates/examples/${example}`);
3638
- if (context.backend === "convex") {
3639
- const convexBackendDestDir = path.join(projectDir, "packages/backend");
3640
- const convexExampleSrc = path.join(exampleBaseDir, "convex/packages/backend");
3641
- if (await fs.pathExists(convexExampleSrc)) await processAndCopyFiles("**/*", convexExampleSrc, convexBackendDestDir, context, false);
3642
- } else if ((serverAppDirExists || context.backend === "self") && context.backend !== "none") {
3643
- const exampleServerSrc = path.join(exampleBaseDir, "server");
3644
- if (context.api !== "none") {
3645
- const apiPackageDir = path.join(projectDir, "packages/api");
3646
- await fs.ensureDir(apiPackageDir);
3647
- const exampleOrmBaseSrc = path.join(exampleServerSrc, context.orm, "base");
3648
- if (await fs.pathExists(exampleOrmBaseSrc)) await processAndCopyFiles("**/*", exampleOrmBaseSrc, apiPackageDir, context, false);
3649
- }
3650
- if (context.orm !== "none" && context.database !== "none") {
3651
- const dbPackageDir = path.join(projectDir, "packages/db");
3652
- await fs.ensureDir(dbPackageDir);
3653
- const exampleDbSchemaSrc = path.join(exampleServerSrc, context.orm, context.database);
3654
- if (await fs.pathExists(exampleDbSchemaSrc)) await processAndCopyFiles("**/*", exampleDbSchemaSrc, dbPackageDir, context, false);
3655
- }
3656
- }
3657
- if (webAppDirExists) {
3658
- if (hasReactWeb) {
3659
- const exampleWebSrc = path.join(exampleBaseDir, "web/react");
3660
- if (await fs.pathExists(exampleWebSrc)) {
3661
- const reactFramework = context.frontend.find((f) => [
3662
- "next",
3663
- "react-router",
3664
- "tanstack-router",
3665
- "tanstack-start"
3666
- ].includes(f));
3667
- if (reactFramework) {
3668
- const exampleWebFrameworkSrc = path.join(exampleWebSrc, reactFramework);
3669
- if (await fs.pathExists(exampleWebFrameworkSrc)) await processAndCopyFiles("**/*", exampleWebFrameworkSrc, webAppDir, context, false);
3670
- if (context.backend === "self" && (reactFramework === "next" || reactFramework === "tanstack-start")) {
3671
- const exampleFullstackSrc = path.join(exampleBaseDir, `fullstack/${reactFramework}`);
3672
- if (await fs.pathExists(exampleFullstackSrc)) await processAndCopyFiles("**/*", exampleFullstackSrc, webAppDir, context, false);
3673
- }
3674
- }
3675
- }
3676
- } else if (hasNuxtWeb) {
3677
- const exampleWebNuxtSrc = path.join(exampleBaseDir, "web/nuxt");
3678
- if (await fs.pathExists(exampleWebNuxtSrc)) await processAndCopyFiles("**/*", exampleWebNuxtSrc, webAppDir, context, false);
3679
- } else if (hasSvelteWeb) {
3680
- const exampleWebSvelteSrc = path.join(exampleBaseDir, "web/svelte");
3681
- if (await fs.pathExists(exampleWebSvelteSrc)) await processAndCopyFiles("**/*", exampleWebSvelteSrc, webAppDir, context, false);
3682
- } else if (hasSolidWeb) {
3683
- const exampleWebSolidSrc = path.join(exampleBaseDir, "web/solid");
3684
- if (await fs.pathExists(exampleWebSolidSrc)) await processAndCopyFiles("**/*", exampleWebSolidSrc, webAppDir, context, false);
3685
- }
3686
- }
3687
- if (nativeAppDirExists) {
3688
- const hasNativeBare = context.frontend.includes("native-bare");
3689
- const hasUniwind = context.frontend.includes("native-uniwind");
3690
- const hasUnistyles = context.frontend.includes("native-unistyles");
3691
- if (hasNativeBare || hasUniwind || hasUnistyles) {
3692
- let nativeFramework = "";
3693
- if (hasNativeBare) nativeFramework = "bare";
3694
- else if (hasUniwind) nativeFramework = "uniwind";
3695
- else if (hasUnistyles) nativeFramework = "unistyles";
3696
- const exampleNativeSrc = path.join(exampleBaseDir, `native/${nativeFramework}`);
3697
- if (await fs.pathExists(exampleNativeSrc)) await processAndCopyFiles("**/*", exampleNativeSrc, nativeAppDir, context, false);
3698
- }
3699
- }
3700
- }
3701
- }
3702
- async function handleExtras(projectDir, context) {
3703
- const extrasDir = path.join(PKG_ROOT, "templates/extras");
3704
- const hasNativeBare = context.frontend.includes("native-bare");
3705
- const hasUniwind = context.frontend.includes("native-uniwind");
3706
- const hasUnistyles = context.frontend.includes("native-unistyles");
3707
- const hasNative = hasNativeBare || hasUniwind || hasUnistyles;
3708
- if (context.packageManager === "pnpm") {
3709
- const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml");
3710
- const pnpmWorkspaceDest = path.join(projectDir, "pnpm-workspace.yaml");
3711
- if (await fs.pathExists(pnpmWorkspaceSrc)) await processTemplate(pnpmWorkspaceSrc, pnpmWorkspaceDest, context);
3712
- }
3713
- if (context.packageManager === "bun") {
3714
- const bunfigSrc = path.join(extrasDir, "bunfig.toml.hbs");
3715
- if (await fs.pathExists(bunfigSrc)) await processAndCopyFiles("bunfig.toml.hbs", extrasDir, projectDir, context);
3716
- }
3717
- if (context.packageManager === "pnpm" && (hasNative || context.frontend.includes("nuxt"))) {
3718
- const npmrcTemplateSrc = path.join(extrasDir, "_npmrc.hbs");
3719
- if (await fs.pathExists(npmrcTemplateSrc)) await processAndCopyFiles("_npmrc.hbs", extrasDir, projectDir, context);
3720
- }
3721
- }
3722
- async function setupDockerComposeTemplates(projectDir, context) {
3723
- if (context.dbSetup !== "docker" || context.database === "none") return;
3724
- const dbPackageDir = path.join(projectDir, "packages/db");
3725
- const dockerSrcDir = path.join(PKG_ROOT, `templates/db-setup/docker-compose/${context.database}`);
3726
- if (await fs.pathExists(dockerSrcDir)) await processAndCopyFiles("**/*", dockerSrcDir, dbPackageDir, context);
3727
- }
3728
- async function setupDeploymentTemplates(projectDir, context) {
3729
- const isBackendSelf = context.backend === "self";
3730
- if (context.webDeploy === "cloudflare" || context.serverDeploy === "cloudflare") {
3731
- const infraTemplateSrc = path.join(PKG_ROOT, "templates/packages/infra");
3732
- const infraDir = path.join(projectDir, "packages/infra");
3733
- if (await fs.pathExists(infraTemplateSrc)) {
3734
- await fs.ensureDir(infraDir);
3735
- await processAndCopyFiles("package.json.hbs", infraTemplateSrc, infraDir, context);
3736
- await processAndCopyFiles("alchemy.run.ts.hbs", infraTemplateSrc, infraDir, context);
3737
- }
3738
- if (!isBackendSelf) {
3739
- const envTemplateSrc = path.join(PKG_ROOT, "templates/packages/env");
3740
- const envDir = path.join(projectDir, "packages/env");
3741
- const envDtsTemplatePath = path.join(envTemplateSrc, "env.d.ts.hbs");
3742
- if (await fs.pathExists(envDtsTemplatePath)) await processTemplate(envDtsTemplatePath, path.join(envDir, "env.d.ts"), context);
3743
- }
3744
- }
3745
- if (context.webDeploy !== "none" && context.webDeploy !== "cloudflare") {
3746
- const webAppDir = path.join(projectDir, "apps/web");
3747
- if (await fs.pathExists(webAppDir)) {
3748
- const frontends = context.frontend;
3749
- const templateMap = {
3750
- "tanstack-router": "react/tanstack-router",
3751
- "tanstack-start": "react/tanstack-start",
3752
- "react-router": "react/react-router",
3753
- solid: "solid",
3754
- next: "react/next",
3755
- nuxt: "nuxt",
3756
- svelte: "svelte"
3757
- };
3758
- for (const f of frontends) if (templateMap[f]) {
3759
- const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.webDeploy}/web/${templateMap[f]}`);
3760
- if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, webAppDir, context);
3761
- }
3762
- }
3763
- }
3764
- if (context.serverDeploy !== "none" && context.serverDeploy !== "cloudflare" && !isBackendSelf) {
3765
- const serverAppDir = path.join(projectDir, "apps/server");
3766
- if (await fs.pathExists(serverAppDir)) {
3767
- const deployTemplateSrc = path.join(PKG_ROOT, `templates/deploy/${context.serverDeploy}/server`);
3768
- if (await fs.pathExists(deployTemplateSrc)) await processAndCopyFiles("**/*", deployTemplateSrc, serverAppDir, context);
3769
- }
3770
- }
3771
- }
3772
-
3773
- //#endregion
3774
- //#region src/helpers/core/add-addons.ts
3775
- async function addAddonsToProject(input) {
3776
- try {
3777
- const projectDir = input.projectDir || process.cwd();
3778
- if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
3779
- const detectedConfig = await detectProjectConfig(projectDir);
3780
- if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
3781
- const config = {
3782
- projectName: detectedConfig.projectName || path.basename(projectDir),
3783
- projectDir,
3784
- relativePath: ".",
3785
- database: detectedConfig.database || "none",
3786
- orm: detectedConfig.orm || "none",
3787
- backend: detectedConfig.backend || "none",
3788
- runtime: detectedConfig.runtime || "none",
3789
- frontend: detectedConfig.frontend || [],
3790
- addons: input.addons,
3791
- examples: detectedConfig.examples || [],
3792
- auth: detectedConfig.auth || "none",
3793
- payments: detectedConfig.payments || "none",
3794
- git: false,
3795
- packageManager: input.packageManager || detectedConfig.packageManager || "npm",
3796
- install: input.install || false,
3797
- dbSetup: detectedConfig.dbSetup || "none",
3798
- api: detectedConfig.api || "none",
3799
- webDeploy: detectedConfig.webDeploy || "none",
3800
- serverDeploy: detectedConfig.serverDeploy || "none"
3801
- };
3802
- for (const addon of input.addons) {
3803
- const { isCompatible, reason } = validateAddonCompatibility(addon, config.frontend);
3804
- if (!isCompatible) exitWithError(reason || `${addon} addon is not compatible with current frontend configuration`);
3805
- }
3806
- await setupAddonsTemplate(projectDir, config);
3807
- await setupAddons(config, true);
3808
- const currentAddons = detectedConfig.addons || [];
3809
- await updateBtsConfig(projectDir, { addons: [...new Set([...currentAddons, ...input.addons])] });
3810
- if (config.install) await installDependencies({
3811
- projectDir,
3812
- packageManager: config.packageManager
3813
- });
3814
- else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
3815
- } catch (error) {
3816
- exitWithError(`Error adding addons: ${error instanceof Error ? error.message : String(error)}`);
3817
- }
3818
- }
3819
-
3820
- //#endregion
3821
- //#region src/helpers/deployment/alchemy/alchemy-next-setup.ts
3822
- async function setupNextAlchemyDeploy(projectDir, _packageManager, _options) {
3823
- const webAppDir = path.join(projectDir, "apps/web");
3824
- if (!await fs.pathExists(webAppDir)) return;
3825
- await addPackageDependency({
3826
- dependencies: ["@opennextjs/cloudflare"],
3827
- devDependencies: [
3828
- "alchemy",
3829
- "wrangler",
3830
- "@cloudflare/workers-types"
3831
- ],
3832
- projectDir: webAppDir
3833
- });
3834
- const openNextConfigPath = path.join(webAppDir, "open-next.config.ts");
3835
- await fs.writeFile(openNextConfigPath, `import { defineCloudflareConfig } from "@opennextjs/cloudflare";
3836
-
3837
- export default defineCloudflareConfig({});
3838
- `);
3839
- const gitignorePath = path.join(webAppDir, ".gitignore");
3840
- if (await fs.pathExists(gitignorePath)) {
3841
- if (!(await fs.readFile(gitignorePath, "utf-8")).includes("wrangler.jsonc")) await fs.appendFile(gitignorePath, "\nwrangler.jsonc\n");
3842
- } else await fs.writeFile(gitignorePath, "wrangler.jsonc\n");
3843
- }
3844
-
3845
- //#endregion
3846
- //#region src/helpers/deployment/alchemy/alchemy-nuxt-setup.ts
3847
- async function setupNuxtAlchemyDeploy(projectDir, _packageManager, _options) {
3848
- const webAppDir = path.join(projectDir, "apps/web");
3849
- if (!await fs.pathExists(webAppDir)) return;
3850
- await addPackageDependency({
3851
- devDependencies: [
3852
- "alchemy",
3853
- "nitro-cloudflare-dev",
3854
- "wrangler"
3855
- ],
3856
- projectDir: webAppDir
3857
- });
3858
- const nuxtConfigPath = path.join(webAppDir, "nuxt.config.ts");
3859
- if (!await fs.pathExists(nuxtConfigPath)) return;
3860
- try {
3861
- const project = new Project({ manipulationSettings: {
3862
- indentationText: IndentationText.TwoSpaces,
3863
- quoteKind: QuoteKind.Double
3864
- } });
3865
- project.addSourceFileAtPath(nuxtConfigPath);
3866
- const exportAssignment = project.getSourceFileOrThrow(nuxtConfigPath).getExportAssignment((d) => !d.isExportEquals());
3867
- if (!exportAssignment) return;
3868
- const defineConfigCall = exportAssignment.getExpression();
3869
- if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineNuxtConfig") return;
3870
- let configObject = defineConfigCall.getArguments()[0];
3871
- if (!configObject) configObject = defineConfigCall.addArgument("{}");
3872
- if (Node.isObjectLiteralExpression(configObject)) {
3873
- if (!configObject.getProperty("nitro")) configObject.addPropertyAssignment({
3874
- name: "nitro",
3875
- initializer: `{
3876
- preset: "cloudflare_module",
3877
- cloudflare: {
3878
- deployConfig: true,
3879
- nodeCompat: true
3880
- }
3881
- }`
3882
- });
3883
- const modulesProperty = configObject.getProperty("modules");
3884
- if (modulesProperty && Node.isPropertyAssignment(modulesProperty)) {
3885
- const initializer = modulesProperty.getInitializer();
3886
- if (Node.isArrayLiteralExpression(initializer)) {
3887
- if (!initializer.getElements().some((el) => el.getText() === "\"nitro-cloudflare-dev\"" || el.getText() === "'nitro-cloudflare-dev'")) initializer.addElement("\"nitro-cloudflare-dev\"");
3888
- }
3889
- } else if (!modulesProperty) configObject.addPropertyAssignment({
3890
- name: "modules",
3891
- initializer: "[\"nitro-cloudflare-dev\"]"
3892
- });
3893
- }
3894
- await project.save();
3895
- } catch (error) {
3896
- console.warn("Failed to update nuxt.config.ts:", error);
3897
- }
3898
- }
3899
-
3900
- //#endregion
3901
- //#region src/helpers/deployment/alchemy/alchemy-react-router-setup.ts
3902
- async function setupReactRouterAlchemyDeploy(projectDir, _packageManager, _options) {
3903
- const webAppDir = path.join(projectDir, "apps/web");
3904
- if (!await fs.pathExists(webAppDir)) return;
3905
- await addPackageDependency({
3906
- devDependencies: ["alchemy"],
3907
- projectDir: webAppDir
3908
- });
3909
- }
3910
-
3911
- //#endregion
3912
- //#region src/helpers/deployment/alchemy/alchemy-solid-setup.ts
3913
- async function setupSolidAlchemyDeploy(projectDir, _packageManager, _options) {
3914
- const webAppDir = path.join(projectDir, "apps/web");
3915
- if (!await fs.pathExists(webAppDir)) return;
3916
- await addPackageDependency({
3917
- devDependencies: ["alchemy"],
3918
- projectDir: webAppDir
3919
- });
3920
- }
3921
-
3922
- //#endregion
3923
- //#region src/helpers/deployment/alchemy/alchemy-svelte-setup.ts
3924
- async function setupSvelteAlchemyDeploy(projectDir, _packageManager, _options) {
3925
- const webAppDir = path.join(projectDir, "apps/web");
3926
- if (!await fs.pathExists(webAppDir)) return;
3927
- await addPackageDependency({
3928
- devDependencies: ["alchemy", "@sveltejs/adapter-cloudflare"],
3929
- projectDir: webAppDir
3930
- });
3931
- const svelteConfigPath = path.join(webAppDir, "svelte.config.js");
3932
- if (!await fs.pathExists(svelteConfigPath)) return;
3933
- try {
3934
- const project = new Project({ manipulationSettings: {
3935
- indentationText: IndentationText.TwoSpaces,
3936
- quoteKind: QuoteKind.Single
3937
- } });
3938
- project.addSourceFileAtPath(svelteConfigPath);
3939
- const sourceFile = project.getSourceFileOrThrow(svelteConfigPath);
3940
- const adapterImport = sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue().includes("@sveltejs/adapter"));
3941
- if (adapterImport) {
3942
- adapterImport.setModuleSpecifier("alchemy/cloudflare/sveltekit");
3943
- adapterImport.removeDefaultImport();
3944
- adapterImport.setDefaultImport("alchemy");
3945
- } else sourceFile.insertImportDeclaration(0, {
3946
- moduleSpecifier: "alchemy/cloudflare/sveltekit",
3947
- defaultImport: "alchemy"
3948
- });
3949
- const configVariable = sourceFile.getVariableDeclaration("config");
3950
- if (configVariable) {
3951
- const initializer = configVariable.getInitializer();
3952
- if (Node.isObjectLiteralExpression(initializer)) updateAdapterInConfig(initializer);
3953
- }
3954
- await project.save();
3955
- } catch (error) {
3956
- console.warn("Failed to update svelte.config.js:", error);
3957
- }
3958
- }
3959
- function updateAdapterInConfig(configObject) {
3960
- if (!Node.isObjectLiteralExpression(configObject)) return;
3961
- const kitProperty = configObject.getProperty("kit");
3962
- if (kitProperty && Node.isPropertyAssignment(kitProperty)) {
3963
- const kitInitializer = kitProperty.getInitializer();
3964
- if (Node.isObjectLiteralExpression(kitInitializer)) {
3965
- const adapterProperty = kitInitializer.getProperty("adapter");
3966
- if (adapterProperty && Node.isPropertyAssignment(adapterProperty)) {
3967
- const initializer = adapterProperty.getInitializer();
3968
- if (Node.isCallExpression(initializer)) {
3969
- const expression = initializer.getExpression();
3970
- if (Node.isIdentifier(expression) && expression.getText() === "adapter") expression.replaceWithText("alchemy");
3971
- }
3972
- }
3973
- }
3974
- }
3975
- }
3976
-
3977
- //#endregion
3978
- //#region src/helpers/deployment/alchemy/alchemy-tanstack-router-setup.ts
3979
- async function setupTanStackRouterAlchemyDeploy(projectDir, _packageManager, _options) {
3980
- const webAppDir = path.join(projectDir, "apps/web");
3981
- if (!await fs.pathExists(webAppDir)) return;
3982
- await addPackageDependency({
3983
- devDependencies: ["alchemy"],
3984
- projectDir: webAppDir
3985
- });
3986
- }
3987
-
3988
- //#endregion
3989
- //#region src/helpers/deployment/alchemy/alchemy-tanstack-start-setup.ts
3990
- async function setupTanStackStartAlchemyDeploy(projectDir, _packageManager, _options) {
3991
- const webAppDir = path.join(projectDir, "apps/web");
3992
- if (!await fs.pathExists(webAppDir)) return;
3993
- await addPackageDependency({
3994
- devDependencies: ["alchemy", "@cloudflare/vite-plugin"],
3995
- projectDir: webAppDir
3996
- });
3997
- const viteConfigPath = path.join(webAppDir, "vite.config.ts");
3998
- if (await fs.pathExists(viteConfigPath)) try {
3999
- const project = new Project({ manipulationSettings: {
4000
- indentationText: IndentationText.TwoSpaces,
4001
- quoteKind: QuoteKind.Double
4002
- } });
4003
- project.addSourceFileAtPath(viteConfigPath);
4004
- const sourceFile = project.getSourceFileOrThrow(viteConfigPath);
4005
- const alchemyImport = sourceFile.getImportDeclaration("alchemy/cloudflare/tanstack-start");
4006
- if (!alchemyImport) sourceFile.addImportDeclaration({
4007
- moduleSpecifier: "alchemy/cloudflare/tanstack-start",
4008
- defaultImport: "alchemy"
4009
- });
4010
- else alchemyImport.setModuleSpecifier("alchemy/cloudflare/tanstack-start");
4011
- const exportAssignment = sourceFile.getExportAssignment((d) => !d.isExportEquals());
4012
- if (!exportAssignment) return;
4013
- const defineConfigCall = exportAssignment.getExpression();
4014
- if (!Node.isCallExpression(defineConfigCall) || defineConfigCall.getExpression().getText() !== "defineConfig") return;
4015
- let configObject = defineConfigCall.getArguments()[0];
4016
- if (!configObject) configObject = defineConfigCall.addArgument("{}");
4017
- if (Node.isObjectLiteralExpression(configObject)) {
4018
- const pluginsProperty = configObject.getProperty("plugins");
4019
- if (pluginsProperty && Node.isPropertyAssignment(pluginsProperty)) {
4020
- const initializer = pluginsProperty.getInitializer();
4021
- if (Node.isArrayLiteralExpression(initializer)) {
4022
- if (!initializer.getElements().some((el) => el.getText().includes("alchemy("))) initializer.addElement("alchemy()");
4023
- }
4024
- } else configObject.addPropertyAssignment({
4025
- name: "plugins",
4026
- initializer: "[alchemy()]"
4027
- });
4028
- }
4029
- await project.save();
4030
- } catch (error) {
4031
- console.warn("Failed to update vite.config.ts:", error);
4032
- }
4033
- }
4034
-
4035
- //#endregion
4036
- //#region src/helpers/deployment/alchemy/alchemy-combined-setup.ts
4037
- function getInfraFilter(packageManager, hasTurborepo, infraWorkspace) {
4038
- if (hasTurborepo) return (script) => `turbo -F ${infraWorkspace} ${script}`;
4039
- switch (packageManager) {
4040
- case "pnpm": return (script) => `pnpm --filter ${infraWorkspace} ${script}`;
4041
- case "npm": return (script) => `npm run ${script} --workspace ${infraWorkspace}`;
4042
- case "bun": return (script) => `bun run --filter ${infraWorkspace} ${script}`;
4043
- }
4044
- }
4045
- async function setupCombinedAlchemyDeploy(projectDir, packageManager, config) {
4046
- await setupInfraScripts(projectDir, packageManager, config);
4047
- const serverDir = path.join(projectDir, "apps/server");
4048
- if (await fs.pathExists(serverDir)) await setupAlchemyServerDeploy(serverDir, projectDir);
4049
- const frontend = config.frontend;
4050
- const isNext = frontend.includes("next");
4051
- const isNuxt = frontend.includes("nuxt");
4052
- const isSvelte = frontend.includes("svelte");
4053
- const isTanstackRouter = frontend.includes("tanstack-router");
4054
- const isTanstackStart = frontend.includes("tanstack-start");
4055
- const isReactRouter = frontend.includes("react-router");
4056
- const isSolid = frontend.includes("solid");
4057
- if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4058
- else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4059
- else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4060
- else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4061
- else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4062
- else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4063
- else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager, { skipAppScripts: true });
4064
- }
4065
- async function setupInfraScripts(projectDir, packageManager, config) {
4066
- const projectName = config.projectName;
4067
- const hasTurborepo = config.addons.includes("turborepo");
4068
- const infraWorkspace = `@${projectName}/infra`;
4069
- const rootPkgPath = path.join(projectDir, "package.json");
4070
- if (await fs.pathExists(rootPkgPath)) {
4071
- const pkg = await fs.readJson(rootPkgPath);
4072
- const filter = getInfraFilter(packageManager, hasTurborepo, infraWorkspace);
4073
- pkg.scripts = {
4074
- ...pkg.scripts,
4075
- deploy: filter("deploy"),
4076
- destroy: filter("destroy")
4077
- };
4078
- await fs.writeJson(rootPkgPath, pkg, { spaces: 2 });
4079
- }
4080
- if (config.serverDeploy === "cloudflare") {
4081
- const serverPkgPath = path.join(projectDir, "apps/server/package.json");
4082
- if (await fs.pathExists(serverPkgPath)) {
4083
- const serverPkg = await fs.readJson(serverPkgPath);
4084
- if (serverPkg.scripts?.dev) {
4085
- serverPkg.scripts["dev:bare"] = serverPkg.scripts.dev;
4086
- delete serverPkg.scripts.dev;
4087
- await fs.writeJson(serverPkgPath, serverPkg, { spaces: 2 });
4088
- }
4089
- }
4090
- }
4091
- if (config.webDeploy === "cloudflare") {
4092
- const webPkgPath = path.join(projectDir, "apps/web/package.json");
4093
- if (await fs.pathExists(webPkgPath)) {
4094
- const webPkg = await fs.readJson(webPkgPath);
4095
- if (webPkg.scripts?.dev) {
4096
- webPkg.scripts["dev:bare"] = webPkg.scripts.dev;
4097
- delete webPkg.scripts.dev;
4098
- await fs.writeJson(webPkgPath, webPkg, { spaces: 2 });
4099
- }
4100
- }
4101
- }
4102
- }
4103
-
4104
- //#endregion
4105
- //#region src/helpers/deployment/server-deploy-setup.ts
4106
- async function setupServerDeploy(config) {
4107
- const { serverDeploy, webDeploy, projectDir, packageManager } = config;
4108
- if (serverDeploy === "none") return;
4109
- if (serverDeploy === "cloudflare" && webDeploy === "cloudflare") return;
4110
- const serverDir = path.join(projectDir, "apps/server");
4111
- if (!await fs.pathExists(serverDir)) return;
4112
- if (serverDeploy === "cloudflare") {
4113
- await setupInfraScripts(projectDir, packageManager, config);
4114
- await setupAlchemyServerDeploy(serverDir, projectDir);
4115
- }
4116
- }
4117
- async function setupAlchemyServerDeploy(serverDir, projectDir) {
4118
- if (!await fs.pathExists(serverDir)) return;
4119
- await addPackageDependency({
4120
- devDependencies: [
4121
- "alchemy",
4122
- "wrangler",
4123
- "@types/node",
4124
- "@cloudflare/workers-types"
4125
- ],
4126
- projectDir: serverDir
4127
- });
4128
- if (projectDir) await addAlchemyPackagesDependencies$1(projectDir);
4129
- }
4130
- async function addAlchemyPackagesDependencies$1(projectDir) {
4131
- await addPackageDependency({
4132
- devDependencies: ["@cloudflare/workers-types"],
4133
- projectDir
4134
- });
4135
- }
4136
-
4137
- //#endregion
4138
- //#region src/helpers/deployment/web-deploy-setup.ts
4139
- async function setupWebDeploy(config) {
4140
- const { webDeploy, serverDeploy, frontend, projectDir } = config;
4141
- const { packageManager } = config;
4142
- if (webDeploy === "none") return;
4143
- if (webDeploy !== "cloudflare") return;
4144
- if (webDeploy === "cloudflare" && serverDeploy === "cloudflare") {
4145
- await setupCombinedAlchemyDeploy(projectDir, packageManager, config);
4146
- await addAlchemyPackagesDependencies(projectDir);
4147
- return;
4148
- }
4149
- await setupInfraScripts(projectDir, packageManager, config);
4150
- const isNext = frontend.includes("next");
4151
- const isNuxt = frontend.includes("nuxt");
4152
- const isSvelte = frontend.includes("svelte");
4153
- const isTanstackRouter = frontend.includes("tanstack-router");
4154
- const isTanstackStart = frontend.includes("tanstack-start");
4155
- const isReactRouter = frontend.includes("react-router");
4156
- const isSolid = frontend.includes("solid");
4157
- if (isNext) await setupNextAlchemyDeploy(projectDir, packageManager);
4158
- else if (isNuxt) await setupNuxtAlchemyDeploy(projectDir, packageManager);
4159
- else if (isSvelte) await setupSvelteAlchemyDeploy(projectDir, packageManager);
4160
- else if (isTanstackStart) await setupTanStackStartAlchemyDeploy(projectDir, packageManager);
4161
- else if (isTanstackRouter) await setupTanStackRouterAlchemyDeploy(projectDir, packageManager);
4162
- else if (isReactRouter) await setupReactRouterAlchemyDeploy(projectDir, packageManager);
4163
- else if (isSolid) await setupSolidAlchemyDeploy(projectDir, packageManager);
4164
- await addAlchemyPackagesDependencies(projectDir);
4165
- }
4166
- async function addAlchemyPackagesDependencies(projectDir) {
4167
- await addPackageDependency({
4168
- devDependencies: ["@cloudflare/workers-types"],
4169
- projectDir
4170
- });
4171
- }
4172
-
4173
- //#endregion
4174
- //#region src/helpers/core/add-deployment.ts
4175
- async function addDeploymentToProject(input) {
4176
- try {
4177
- const projectDir = input.projectDir || process.cwd();
4178
- if (!await isBetterTStackProject(projectDir)) exitWithError("This doesn't appear to be a Better-T-Stack project. Please run this command from the root of a Better-T-Stack project.");
4179
- const detectedConfig = await detectProjectConfig(projectDir);
4180
- if (!detectedConfig) exitWithError("Could not detect the project configuration. Please ensure this is a valid Better-T-Stack project.");
4181
- if (input.webDeploy && detectedConfig.webDeploy === input.webDeploy) exitWithError(`${input.webDeploy} web deployment is already configured for this project.`);
4182
- if (input.serverDeploy && detectedConfig.serverDeploy === input.serverDeploy) exitWithError(`${input.serverDeploy} server deployment is already configured for this project.`);
4183
- const config = {
4184
- projectName: detectedConfig.projectName || path.basename(projectDir),
4185
- projectDir,
4186
- relativePath: ".",
4187
- database: detectedConfig.database || "none",
4188
- orm: detectedConfig.orm || "none",
4189
- backend: detectedConfig.backend || "none",
4190
- runtime: detectedConfig.runtime || "none",
4191
- frontend: detectedConfig.frontend || [],
4192
- addons: detectedConfig.addons || [],
4193
- examples: detectedConfig.examples || [],
4194
- auth: detectedConfig.auth || "none",
4195
- payments: detectedConfig.payments || "none",
4196
- git: false,
4197
- packageManager: input.packageManager || detectedConfig.packageManager || "npm",
4198
- install: input.install || false,
4199
- dbSetup: detectedConfig.dbSetup || "none",
4200
- api: detectedConfig.api || "none",
4201
- webDeploy: input.webDeploy || detectedConfig.webDeploy || "none",
4202
- serverDeploy: input.serverDeploy || detectedConfig.serverDeploy || "none"
4203
- };
4204
- if (input.webDeploy && input.webDeploy !== "none") log.info(pc.green(`Adding ${input.webDeploy} web deployment to ${config.frontend.join("/")}`));
4205
- if (input.serverDeploy && input.serverDeploy !== "none") log.info(pc.green(`Adding ${input.serverDeploy} server deployment`));
4206
- await setupDeploymentTemplates(projectDir, config);
4207
- await setupWebDeploy(config);
4208
- await setupServerDeploy(config);
4209
- await updateBtsConfig(projectDir, {
4210
- webDeploy: input.webDeploy || config.webDeploy,
4211
- serverDeploy: input.serverDeploy || config.serverDeploy
4212
- });
4213
- if (config.install) await installDependencies({
4214
- projectDir,
4215
- packageManager: config.packageManager
4216
- });
4217
- else if (!input.suppressInstallMessage) log.info(pc.yellow(`Run ${pc.bold(`${config.packageManager} install`)} to install dependencies`));
4218
- } catch (error) {
4219
- exitWithError(`Error adding deployment: ${error instanceof Error ? error.message : String(error)}`);
4220
- }
4221
- }
4222
-
4223
- //#endregion
4224
- //#region src/utils/setup-catalogs.ts
4225
- async function setupCatalogs(projectDir, options) {
4226
- if (options.packageManager === "npm") return;
4227
- const packagePaths = [
4228
- ".",
4229
- "apps/server",
4230
- "apps/web",
4231
- "apps/native",
4232
- "apps/fumadocs",
4233
- "apps/docs",
4234
- "packages/api",
4235
- "packages/db",
4236
- "packages/auth",
4237
- "packages/backend",
4238
- "packages/config",
4239
- "packages/env",
4240
- "packages/infra"
4241
- ];
4242
- const packagesInfo = [];
4243
- for (const pkgPath of packagePaths) {
4244
- const fullPath = path.join(projectDir, pkgPath);
4245
- const pkgJsonPath = path.join(fullPath, "package.json");
4246
- if (await fs.pathExists(pkgJsonPath)) {
4247
- const pkgJson = await fs.readJson(pkgJsonPath);
4248
- packagesInfo.push({
4249
- path: fullPath,
4250
- dependencies: pkgJson.dependencies || {},
4251
- devDependencies: pkgJson.devDependencies || {}
4252
- });
4253
- }
4254
- }
4255
- const catalog = findDuplicateDependencies(packagesInfo, options.projectName);
4256
- if (Object.keys(catalog).length === 0) return;
4257
- if (options.packageManager === "bun") await setupBunCatalogs(projectDir, catalog);
4258
- else if (options.packageManager === "pnpm") await setupPnpmCatalogs(projectDir, catalog);
4259
- await updatePackageJsonsWithCatalogs(packagesInfo, catalog);
4260
- }
4261
- function findDuplicateDependencies(packagesInfo, projectName) {
4262
- const depCount = /* @__PURE__ */ new Map();
4263
- const projectScope = `@${projectName}/`;
4264
- for (const pkg of packagesInfo) {
4265
- const allDeps = {
4266
- ...pkg.dependencies,
4267
- ...pkg.devDependencies
4268
- };
4269
- for (const [depName, version] of Object.entries(allDeps)) {
4270
- if (depName.startsWith(projectScope)) continue;
4271
- if (version.startsWith("workspace:")) continue;
4272
- const existing = depCount.get(depName);
4273
- if (existing) {
4274
- existing.versions.add(version);
4275
- existing.packages.push(pkg.path);
4276
- } else depCount.set(depName, {
4277
- versions: new Set([version]),
4278
- packages: [pkg.path]
4279
- });
4280
- }
4281
- }
4282
- const catalog = {};
4283
- for (const [depName, info] of depCount.entries()) if (info.packages.length > 1 && info.versions.size === 1) catalog[depName] = Array.from(info.versions)[0];
4284
- return catalog;
4285
- }
4286
- async function setupBunCatalogs(projectDir, catalog) {
4287
- const rootPkgJsonPath = path.join(projectDir, "package.json");
4288
- const rootPkgJson = await fs.readJson(rootPkgJsonPath);
4289
- if (!rootPkgJson.workspaces) rootPkgJson.workspaces = {};
4290
- if (Array.isArray(rootPkgJson.workspaces)) rootPkgJson.workspaces = {
4291
- packages: rootPkgJson.workspaces,
4292
- catalog
4293
- };
4294
- else if (typeof rootPkgJson.workspaces === "object") {
4295
- if (!rootPkgJson.workspaces.catalog) rootPkgJson.workspaces.catalog = {};
4296
- rootPkgJson.workspaces.catalog = {
4297
- ...rootPkgJson.workspaces.catalog,
4298
- ...catalog
4299
- };
4300
- }
4301
- await fs.writeJson(rootPkgJsonPath, rootPkgJson, { spaces: 2 });
4302
- }
4303
- async function setupPnpmCatalogs(projectDir, catalog) {
4304
- const workspaceYamlPath = path.join(projectDir, "pnpm-workspace.yaml");
4305
- if (!await fs.pathExists(workspaceYamlPath)) return;
4306
- const workspaceContent = await fs.readFile(workspaceYamlPath, "utf-8");
4307
- const workspaceYaml = yaml.parse(workspaceContent);
4308
- if (!workspaceYaml.catalog) workspaceYaml.catalog = {};
4309
- workspaceYaml.catalog = {
4310
- ...workspaceYaml.catalog,
4311
- ...catalog
4312
- };
4313
- await fs.writeFile(workspaceYamlPath, yaml.stringify(workspaceYaml));
4314
- }
4315
- async function updatePackageJsonsWithCatalogs(packagesInfo, catalog) {
4316
- for (const pkg of packagesInfo) {
4317
- const pkgJsonPath = path.join(pkg.path, "package.json");
4318
- const pkgJson = await fs.readJson(pkgJsonPath);
4319
- let updated = false;
4320
- if (pkgJson.dependencies) {
4321
- for (const depName of Object.keys(pkgJson.dependencies)) if (catalog[depName]) {
4322
- pkgJson.dependencies[depName] = "catalog:";
4323
- updated = true;
4324
- }
4325
- }
4326
- if (pkgJson.devDependencies) {
4327
- for (const depName of Object.keys(pkgJson.devDependencies)) if (catalog[depName]) {
4328
- pkgJson.devDependencies[depName] = "catalog:";
4329
- updated = true;
4330
- }
4331
- }
4332
- if (updated) await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4333
- }
4334
- }
4335
-
4336
- //#endregion
4337
- //#region src/helpers/addons/examples-setup.ts
4338
- async function setupExamples(config) {
4339
- const { examples, backend } = config;
4340
- if (!examples || examples.length === 0 || examples[0] === "none") return;
4341
- if (examples.includes("todo") && backend !== "convex" && backend !== "none") await setupTodoDependencies(config);
4342
- if (examples.includes("ai")) await setupAIDependencies(config);
4343
- }
4344
- async function setupTodoDependencies(config) {
4345
- const { projectDir, orm, database, backend } = config;
4346
- const apiDir = path.join(projectDir, "packages/api");
4347
- if (!await fs.pathExists(apiDir) || backend === "none") return;
4348
- if (orm === "drizzle") {
4349
- const dependencies = ["drizzle-orm"];
4350
- if (database === "postgres") dependencies.push("@types/pg");
4351
- await addPackageDependency({
4352
- dependencies,
4353
- projectDir: apiDir
4354
- });
4355
- } else if (orm === "prisma") await addPackageDependency({
4356
- dependencies: ["@prisma/client"],
4357
- projectDir: apiDir
4358
- });
4359
- else if (orm === "mongoose") await addPackageDependency({
4360
- dependencies: ["mongoose"],
4361
- projectDir: apiDir
4362
- });
4363
- }
4364
- async function setupAIDependencies(config) {
4365
- const { frontend, backend, projectDir } = config;
4366
- const webClientDir = path.join(projectDir, "apps/web");
4367
- const nativeClientDir = path.join(projectDir, "apps/native");
4368
- const serverDir = path.join(projectDir, "apps/server");
4369
- const convexBackendDir = path.join(projectDir, "packages/backend");
4370
- const webClientDirExists = await fs.pathExists(webClientDir);
4371
- const nativeClientDirExists = await fs.pathExists(nativeClientDir);
4372
- const serverDirExists = await fs.pathExists(serverDir);
4373
- const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4374
- const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
4375
- const hasNuxt = frontend.includes("nuxt");
4376
- const hasSvelte = frontend.includes("svelte");
4377
- const hasReactNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
4378
- if (backend === "convex" && convexBackendDirExists) await addPackageDependency({
4379
- dependencies: ["@convex-dev/agent"],
4380
- customDependencies: {
4381
- ai: "^5.0.117",
4382
- "@ai-sdk/google": "^2.0.52"
4383
- },
4384
- projectDir: convexBackendDir
4385
- });
4386
- else if (backend === "self" && webClientDirExists) await addPackageDependency({
4387
- dependencies: [
4388
- "ai",
4389
- "@ai-sdk/google",
4390
- "@ai-sdk/devtools"
4391
- ],
4392
- projectDir: webClientDir
4393
- });
4394
- else if (serverDirExists && backend !== "none") await addPackageDependency({
4395
- dependencies: [
4396
- "ai",
4397
- "@ai-sdk/google",
4398
- "@ai-sdk/devtools"
4399
- ],
4400
- projectDir: serverDir
4401
- });
4402
- if (webClientDirExists) {
4403
- const dependencies = [];
4404
- if (backend === "convex") {
4405
- if (hasReactWeb) dependencies.push("@convex-dev/agent", "streamdown");
4406
- } else {
4407
- dependencies.push("ai");
4408
- if (hasNuxt) dependencies.push("@ai-sdk/vue");
4409
- else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
4410
- else if (hasReactWeb) dependencies.push("@ai-sdk/react", "streamdown");
4411
- }
4412
- if (dependencies.length > 0) await addPackageDependency({
4413
- dependencies,
4414
- projectDir: webClientDir
4415
- });
2643
+ s.stop("Ultracite setup successfully!");
2644
+ } catch (error) {
2645
+ log.error(pc.red("Failed to set up Ultracite"));
2646
+ if (error instanceof Error) console.error(pc.red(error.message));
4416
2647
  }
4417
- if (nativeClientDirExists && hasReactNative) if (backend === "convex") await addPackageDependency({
4418
- dependencies: ["@convex-dev/agent"],
4419
- projectDir: nativeClientDir
4420
- });
4421
- else await addPackageDependency({
4422
- dependencies: ["ai", "@ai-sdk/react"],
4423
- projectDir: nativeClientDir
4424
- });
4425
2648
  }
4426
2649
 
4427
2650
  //#endregion
4428
- //#region src/helpers/core/api-setup.ts
4429
- function getFrontendType(frontend) {
4430
- const reactBasedFrontends = [
4431
- "tanstack-router",
4432
- "react-router",
4433
- "tanstack-start",
4434
- "next"
4435
- ];
4436
- const nativeFrontends = [
4437
- "native-bare",
4438
- "native-uniwind",
4439
- "native-unistyles"
4440
- ];
4441
- return {
4442
- hasReactWeb: frontend.some((f) => reactBasedFrontends.includes(f)),
4443
- hasNuxtWeb: frontend.includes("nuxt"),
4444
- hasSvelteWeb: frontend.includes("svelte"),
4445
- hasSolidWeb: frontend.includes("solid"),
4446
- hasNative: frontend.some((f) => nativeFrontends.includes(f))
4447
- };
4448
- }
4449
- function getApiDependencies(api, frontendType, backend) {
4450
- const deps = {};
4451
- if (api === "orpc") deps.server = { dependencies: [
4452
- "@orpc/server",
4453
- "@orpc/client",
4454
- "@orpc/openapi",
4455
- "@orpc/zod"
4456
- ] };
4457
- else if (api === "trpc") deps.server = { dependencies: ["@trpc/server", "@trpc/client"] };
4458
- if (backend !== "self" && backend !== "convex" && backend !== "none") {
4459
- if (!deps.server) deps.server = { dependencies: [] };
4460
- if (backend === "hono") deps.server.dependencies.push("hono");
4461
- else if (backend === "elysia") deps.server.dependencies.push("elysia");
4462
- }
4463
- if (frontendType.hasReactWeb) {
4464
- if (api === "orpc") deps.web = { dependencies: [
4465
- "@orpc/tanstack-query",
4466
- "@orpc/client",
4467
- "@orpc/server"
4468
- ] };
4469
- else if (api === "trpc") deps.web = { dependencies: [
4470
- "@trpc/tanstack-react-query",
4471
- "@trpc/client",
4472
- "@trpc/server"
4473
- ] };
4474
- } else if (frontendType.hasNuxtWeb && api === "orpc") deps.web = {
4475
- dependencies: [
4476
- "@tanstack/vue-query",
4477
- "@orpc/tanstack-query",
4478
- "@orpc/client",
4479
- "@orpc/server"
4480
- ],
4481
- devDependencies: ["@tanstack/vue-query-devtools"]
4482
- };
4483
- else if (frontendType.hasSvelteWeb && api === "orpc") deps.web = {
4484
- dependencies: [
4485
- "@orpc/tanstack-query",
4486
- "@orpc/client",
4487
- "@orpc/server",
4488
- "@tanstack/svelte-query"
4489
- ],
4490
- devDependencies: ["@tanstack/svelte-query-devtools"]
4491
- };
4492
- else if (frontendType.hasSolidWeb && api === "orpc") deps.web = {
4493
- dependencies: [
4494
- "@orpc/tanstack-query",
4495
- "@orpc/client",
4496
- "@orpc/server",
4497
- "@tanstack/solid-query"
4498
- ],
4499
- devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
4500
- };
4501
- if (api === "trpc") deps.native = { dependencies: [
4502
- "@trpc/tanstack-react-query",
4503
- "@trpc/client",
4504
- "@trpc/server"
4505
- ] };
4506
- else if (api === "orpc") deps.native = { dependencies: ["@orpc/tanstack-query", "@orpc/client"] };
4507
- return deps;
4508
- }
4509
- function getQueryDependencies(frontend) {
4510
- const reactBasedFrontends = [
4511
- "react-router",
4512
- "tanstack-router",
4513
- "tanstack-start",
4514
- "next",
4515
- "native-bare",
4516
- "native-uniwind",
4517
- "native-unistyles"
4518
- ];
4519
- const deps = {};
4520
- if (frontend.some((f) => reactBasedFrontends.includes(f))) {
4521
- const hasReactWeb = frontend.some((f) => f !== "native-bare" && f !== "native-uniwind" && f !== "native-unistyles" && reactBasedFrontends.includes(f));
4522
- const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
4523
- if (hasReactWeb) deps.web = {
4524
- dependencies: ["@tanstack/react-query"],
4525
- devDependencies: ["@tanstack/react-query-devtools"]
4526
- };
4527
- if (hasNative) deps.native = { dependencies: ["@tanstack/react-query"] };
4528
- }
4529
- if (frontend.includes("solid")) deps.web = {
4530
- dependencies: ["@tanstack/solid-query"],
4531
- devDependencies: ["@tanstack/solid-query-devtools", "@tanstack/solid-router-devtools"]
4532
- };
4533
- return deps;
4534
- }
4535
- function getConvexDependencies(frontend) {
4536
- const deps = {
4537
- web: { dependencies: ["convex"] },
4538
- native: { dependencies: ["convex"] }
4539
- };
4540
- if (frontend.includes("tanstack-start")) {
4541
- deps.web.dependencies.push("@convex-dev/react-query");
4542
- deps.web.dependencies.push("@tanstack/react-router-ssr-query");
4543
- }
4544
- if (frontend.includes("svelte")) deps.web.dependencies.push("convex-svelte");
4545
- if (frontend.includes("nuxt")) deps.web.dependencies.push("convex-nuxt", "convex-vue");
4546
- return deps;
4547
- }
4548
- async function setupApi(config) {
4549
- const { api, frontend, backend, projectDir } = config;
4550
- const isConvex = backend === "convex";
4551
- const webDir = path.join(projectDir, "apps/web");
4552
- const nativeDir = path.join(projectDir, "apps/native");
4553
- const serverDir = path.join(projectDir, "apps/server");
4554
- const webDirExists = await fs.pathExists(webDir);
4555
- const nativeDirExists = await fs.pathExists(nativeDir);
4556
- await fs.pathExists(serverDir);
4557
- const frontendType = getFrontendType(frontend);
4558
- if (!isConvex && api !== "none") {
4559
- const apiDeps = getApiDependencies(api, frontendType, backend);
4560
- const apiPackageDir = path.join(projectDir, "packages/api");
4561
- if (apiDeps.server) {
4562
- await addPackageDependency({
4563
- dependencies: apiDeps.server.dependencies,
4564
- projectDir: apiPackageDir
4565
- });
4566
- if (backend === "self" && webDirExists) await addPackageDependency({
4567
- dependencies: apiDeps.server.dependencies,
4568
- projectDir: webDir
4569
- });
4570
- if (backend === "self") {
4571
- const frameworkDeps = [];
4572
- if (frontend.includes("next")) frameworkDeps.push("next");
4573
- if (frameworkDeps.length > 0) await addPackageDependency({
4574
- dependencies: frameworkDeps,
4575
- projectDir: apiPackageDir
4576
- });
4577
- }
4578
- }
4579
- if (config.auth === "better-auth" && (backend === "express" || backend === "fastify")) await addPackageDependency({
4580
- dependencies: ["better-auth"],
4581
- projectDir: apiPackageDir
4582
- });
4583
- if (backend === "express") await addPackageDependency({
4584
- devDependencies: ["@types/express"],
4585
- projectDir: apiPackageDir
4586
- });
4587
- if (webDirExists && apiDeps.web) await addPackageDependency({
4588
- dependencies: apiDeps.web.dependencies,
4589
- devDependencies: apiDeps.web.devDependencies,
4590
- projectDir: webDir
4591
- });
4592
- if (nativeDirExists && apiDeps.native) await addPackageDependency({
4593
- dependencies: apiDeps.native.dependencies,
4594
- projectDir: nativeDir
4595
- });
4596
- }
4597
- if (!isConvex) {
4598
- const queryDeps = getQueryDependencies(frontend);
4599
- if (webDirExists && queryDeps.web) await addPackageDependency({
4600
- dependencies: queryDeps.web.dependencies,
4601
- devDependencies: queryDeps.web.devDependencies,
4602
- projectDir: webDir
4603
- });
4604
- if (nativeDirExists && queryDeps.native) await addPackageDependency({
4605
- dependencies: queryDeps.native.dependencies,
4606
- projectDir: nativeDir
4607
- });
2651
+ //#region src/helpers/addons/wxt-setup.ts
2652
+ const TEMPLATES = {
2653
+ vanilla: {
2654
+ label: "Vanilla",
2655
+ hint: "Vanilla JavaScript template"
2656
+ },
2657
+ vue: {
2658
+ label: "Vue",
2659
+ hint: "Vue.js template"
2660
+ },
2661
+ react: {
2662
+ label: "React",
2663
+ hint: "React template"
2664
+ },
2665
+ solid: {
2666
+ label: "Solid",
2667
+ hint: "SolidJS template"
2668
+ },
2669
+ svelte: {
2670
+ label: "Svelte",
2671
+ hint: "Svelte template"
4608
2672
  }
4609
- if (isConvex) {
4610
- const convexDeps = getConvexDependencies(frontend);
4611
- if (webDirExists) await addPackageDependency({
4612
- dependencies: convexDeps.web.dependencies,
4613
- projectDir: webDir
4614
- });
4615
- if (nativeDirExists) await addPackageDependency({
4616
- dependencies: convexDeps.native.dependencies,
4617
- projectDir: nativeDir
2673
+ };
2674
+ async function setupWxt(config) {
2675
+ const { packageManager, projectDir } = config;
2676
+ try {
2677
+ log.info("Setting up WXT...");
2678
+ const template = await select({
2679
+ message: "Choose a template",
2680
+ options: Object.entries(TEMPLATES).map(([key, template$1]) => ({
2681
+ value: key,
2682
+ label: template$1.label,
2683
+ hint: template$1.hint
2684
+ })),
2685
+ initialValue: "react"
4618
2686
  });
2687
+ if (isCancel(template)) return exitCancelled("Operation cancelled");
2688
+ const args = getPackageExecutionArgs(packageManager, `wxt@latest init extension --template ${template} --pm ${packageManager}`);
2689
+ const appsDir = path.join(projectDir, "apps");
2690
+ await fs.ensureDir(appsDir);
2691
+ const s = spinner();
2692
+ s.start("Running WXT init command...");
2693
+ await $({
2694
+ cwd: appsDir,
2695
+ env: { CI: "true" }
2696
+ })`${args}`;
2697
+ const extensionDir = path.join(projectDir, "apps", "extension");
2698
+ const packageJsonPath = path.join(extensionDir, "package.json");
2699
+ if (await fs.pathExists(packageJsonPath)) {
2700
+ const packageJson = await fs.readJson(packageJsonPath);
2701
+ packageJson.name = "extension";
2702
+ if (packageJson.scripts?.dev) packageJson.scripts.dev = `${packageJson.scripts.dev} --port 5555`;
2703
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
2704
+ }
2705
+ s.stop("WXT setup complete!");
2706
+ } catch (error) {
2707
+ log.error(pc.red("Failed to set up WXT"));
2708
+ if (error instanceof Error) console.error(pc.red(error.message));
4619
2709
  }
4620
2710
  }
4621
2711
 
4622
2712
  //#endregion
4623
- //#region src/helpers/core/backend-setup.ts
4624
- async function setupBackendDependencies(config) {
4625
- const { backend, runtime, api, auth, projectDir } = config;
4626
- if (backend === "convex") {
4627
- await addPackageDependency({
4628
- dependencies: ["convex"],
4629
- projectDir: path.join(projectDir, "packages/backend")
4630
- });
4631
- return;
2713
+ //#region src/helpers/addons/addons-setup.ts
2714
+ async function setupAddons(config) {
2715
+ const { addons, frontend, projectDir } = config;
2716
+ const hasReactWebFrontend = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next");
2717
+ const hasNuxtFrontend = frontend.includes("nuxt");
2718
+ const hasSvelteFrontend = frontend.includes("svelte");
2719
+ const hasSolidFrontend = frontend.includes("solid");
2720
+ const hasNextFrontend = frontend.includes("next");
2721
+ if (addons.includes("tauri") && (hasReactWebFrontend || hasNuxtFrontend || hasSvelteFrontend || hasSolidFrontend || hasNextFrontend)) await setupTauri(config);
2722
+ const hasUltracite = addons.includes("ultracite");
2723
+ const hasBiome = addons.includes("biome");
2724
+ const hasHusky = addons.includes("husky");
2725
+ const hasOxlint = addons.includes("oxlint");
2726
+ if (!hasUltracite) {
2727
+ if (hasBiome) await setupBiome(projectDir);
2728
+ if (hasOxlint) await setupOxlint(projectDir, config.packageManager);
2729
+ if (hasHusky) {
2730
+ let linter;
2731
+ if (hasOxlint) linter = "oxlint";
2732
+ else if (hasBiome) linter = "biome";
2733
+ await setupHusky(projectDir, linter);
2734
+ }
4632
2735
  }
4633
- const framework = backend;
4634
- const serverDir = path.join(projectDir, "apps/server");
4635
- const dependencies = [];
4636
- const devDependencies = [];
4637
- if (framework === "hono") {
4638
- dependencies.push("hono");
4639
- if (runtime === "node") dependencies.push("@hono/node-server");
4640
- } else if (framework === "elysia") {
4641
- dependencies.push("elysia", "@elysiajs/cors");
4642
- if (runtime === "node") dependencies.push("@elysiajs/node");
4643
- } else if (framework === "express") {
4644
- dependencies.push("express", "cors");
4645
- devDependencies.push("@types/express", "@types/cors");
4646
- } else if (framework === "fastify") dependencies.push("fastify", "@fastify/cors");
4647
- if (api === "trpc") {
4648
- dependencies.push("@trpc/server");
4649
- if (framework === "hono") dependencies.push("@hono/trpc-server");
4650
- else if (framework === "elysia") dependencies.push("@elysiajs/trpc");
4651
- } else if (api === "orpc") dependencies.push("@orpc/server", "@orpc/openapi", "@orpc/zod");
4652
- if (auth === "better-auth") dependencies.push("better-auth");
4653
- if (runtime === "node") devDependencies.push("tsx", "@types/node");
4654
- else if (runtime === "bun") devDependencies.push("@types/bun");
4655
- if (dependencies.length > 0 || devDependencies.length > 0) await addPackageDependency({
4656
- dependencies,
4657
- devDependencies,
4658
- projectDir: serverDir
4659
- });
2736
+ if (addons.includes("starlight")) await setupStarlight(config);
2737
+ if (addons.includes("fumadocs")) await setupFumadocs(config);
2738
+ if (addons.includes("opentui")) await setupTui(config);
2739
+ if (addons.includes("wxt")) await setupWxt(config);
2740
+ if (hasUltracite) await setupUltracite(config, hasHusky);
2741
+ if (addons.includes("ruler")) await setupRuler(config);
4660
2742
  }
4661
-
4662
- //#endregion
4663
- //#region src/utils/better-auth-plugin-setup.ts
4664
- async function setupBetterAuthPlugins(projectDir, config) {
4665
- const authIndexPath = `${projectDir}/packages/auth/src/index.ts`;
4666
- const authIndexFile = tsProject.addSourceFileAtPath(authIndexPath);
4667
- if (!authIndexFile) return;
4668
- const pluginsToAdd = [];
4669
- const importsToAdd = [];
4670
- if (config.backend === "self" && config.frontend?.includes("tanstack-start")) {
4671
- pluginsToAdd.push("tanstackStartCookies()");
4672
- importsToAdd.push("import { tanstackStartCookies } from \"better-auth/tanstack-start\";");
4673
- }
4674
- if (config.backend === "self" && config.frontend?.includes("next")) {
4675
- pluginsToAdd.push("nextCookies()");
4676
- importsToAdd.push("import { nextCookies } from \"better-auth/next-js\";");
4677
- }
4678
- if (config.frontend?.includes("native-bare") || config.frontend?.includes("native-uniwind") || config.frontend?.includes("native-unistyles")) {
4679
- pluginsToAdd.push("expo()");
4680
- importsToAdd.push("import { expo } from \"@better-auth/expo\";");
4681
- }
4682
- if (pluginsToAdd.length === 0) return;
4683
- importsToAdd.forEach((importStatement) => {
4684
- if (!authIndexFile.getImportDeclaration((declaration) => declaration.getModuleSpecifierValue().includes(importStatement.split("\"")[1]))) authIndexFile.insertImportDeclaration(0, {
4685
- moduleSpecifier: importStatement.split("\"")[1],
4686
- namedImports: [importStatement.split("{")[1].split("}")[0].trim()]
4687
- });
2743
+ async function setupBiome(projectDir) {
2744
+ await addPackageDependency({
2745
+ devDependencies: ["@biomejs/biome"],
2746
+ projectDir
4688
2747
  });
4689
- const betterAuthCall = authIndexFile.getDescendantsOfKind(SyntaxKind.CallExpression).find((call) => call.getExpression().getText() === "betterAuth");
4690
- if (betterAuthCall) {
4691
- const configObject = betterAuthCall.getArguments()[0];
4692
- if (configObject && configObject.getKind() === SyntaxKind.ObjectLiteralExpression) {
4693
- const pluginsArray = ensureArrayProperty(configObject.asKindOrThrow(SyntaxKind.ObjectLiteralExpression), "plugins");
4694
- pluginsToAdd.forEach((plugin) => {
4695
- pluginsArray.addElement(plugin);
4696
- });
4697
- }
2748
+ const packageJsonPath = path.join(projectDir, "package.json");
2749
+ if (await fs.pathExists(packageJsonPath)) {
2750
+ const packageJson = await fs.readJson(packageJsonPath);
2751
+ packageJson.scripts = {
2752
+ ...packageJson.scripts,
2753
+ check: "biome check --write ."
2754
+ };
2755
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
4698
2756
  }
4699
- authIndexFile.save();
4700
2757
  }
4701
-
4702
- //#endregion
4703
- //#region src/helpers/core/auth-setup.ts
4704
- async function setupAuth(config) {
4705
- const { auth, frontend, backend, projectDir } = config;
4706
- if (!auth || auth === "none") return;
4707
- const serverDir = path.join(projectDir, "apps/server");
4708
- const clientDir = path.join(projectDir, "apps/web");
4709
- const nativeDir = path.join(projectDir, "apps/native");
4710
- const clientDirExists = await fs.pathExists(clientDir);
4711
- const nativeDirExists = await fs.pathExists(nativeDir);
4712
- await fs.pathExists(serverDir);
4713
- try {
4714
- if (backend === "convex") {
4715
- if (auth === "clerk" && clientDirExists) {
4716
- const hasNextJs = frontend.includes("next");
4717
- const hasTanStackStart = frontend.includes("tanstack-start");
4718
- const hasViteReactOther = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
4719
- if (hasNextJs) await addPackageDependency({
4720
- dependencies: ["@clerk/nextjs"],
4721
- projectDir: clientDir
4722
- });
4723
- else if (hasTanStackStart) await addPackageDependency({
4724
- dependencies: ["@clerk/tanstack-react-start", "srvx"],
4725
- projectDir: clientDir
4726
- });
4727
- else if (hasViteReactOther) await addPackageDependency({
4728
- dependencies: ["@clerk/clerk-react"],
4729
- projectDir: clientDir
4730
- });
4731
- }
4732
- if (auth === "better-auth") {
4733
- const convexBackendDir = path.join(projectDir, "packages/backend");
4734
- const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4735
- const hasNativeForBA = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
4736
- if (convexBackendDirExists) {
4737
- await addPackageDependency({
4738
- dependencies: ["better-auth", "@convex-dev/better-auth"],
4739
- customDependencies: { "better-auth": "1.4.9" },
4740
- projectDir: convexBackendDir
4741
- });
4742
- if (hasNativeForBA) await addPackageDependency({
4743
- dependencies: ["@better-auth/expo"],
4744
- customDependencies: { "@better-auth/expo": "1.4.9" },
4745
- projectDir: convexBackendDir
4746
- });
4747
- }
4748
- if (clientDirExists) {
4749
- const hasNextJs = frontend.includes("next");
4750
- const hasTanStackStart = frontend.includes("tanstack-start");
4751
- const hasViteReactOther = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
4752
- if (hasNextJs) await addPackageDependency({
4753
- dependencies: ["better-auth", "@convex-dev/better-auth"],
4754
- customDependencies: { "better-auth": "1.4.9" },
4755
- projectDir: clientDir
4756
- });
4757
- else if (hasTanStackStart) await addPackageDependency({
4758
- dependencies: ["better-auth", "@convex-dev/better-auth"],
4759
- customDependencies: { "better-auth": "1.4.9" },
4760
- projectDir: clientDir
4761
- });
4762
- else if (hasViteReactOther) await addPackageDependency({
4763
- dependencies: ["better-auth", "@convex-dev/better-auth"],
4764
- customDependencies: { "better-auth": "1.4.9" },
4765
- projectDir: clientDir
4766
- });
4767
- }
4768
- const hasNativeBare$1 = frontend.includes("native-bare");
4769
- const hasNativeUniwind$1 = frontend.includes("native-uniwind");
4770
- const hasUnistyles$1 = frontend.includes("native-unistyles");
4771
- if (nativeDirExists && (hasNativeBare$1 || hasNativeUniwind$1 || hasUnistyles$1)) await addPackageDependency({
4772
- dependencies: [
4773
- "better-auth",
4774
- "@better-auth/expo",
4775
- "@convex-dev/better-auth"
4776
- ],
4777
- customDependencies: {
4778
- "better-auth": "1.4.9",
4779
- "@better-auth/expo": "1.4.9"
4780
- },
4781
- projectDir: nativeDir
4782
- });
4783
- }
4784
- const hasNativeBare = frontend.includes("native-bare");
4785
- const hasNativeUniwind = frontend.includes("native-uniwind");
4786
- const hasUnistyles = frontend.includes("native-unistyles");
4787
- if (auth === "clerk" && nativeDirExists && (hasNativeBare || hasNativeUniwind || hasUnistyles)) await addPackageDependency({
4788
- dependencies: ["@clerk/clerk-expo"],
4789
- projectDir: nativeDir
4790
- });
4791
- return;
4792
- }
4793
- const authPackageDir = path.join(projectDir, "packages/auth");
4794
- const authPackageDirExists = await fs.pathExists(authPackageDir);
4795
- if (authPackageDirExists && auth === "better-auth") await addPackageDependency({
4796
- dependencies: ["better-auth"],
4797
- projectDir: authPackageDir
4798
- });
4799
- if (frontend.some((f) => [
4800
- "react-router",
4801
- "tanstack-router",
4802
- "tanstack-start",
4803
- "next",
4804
- "nuxt",
4805
- "svelte",
4806
- "solid"
4807
- ].includes(f)) && clientDirExists) {
4808
- if (auth === "better-auth") await addPackageDependency({
4809
- dependencies: ["better-auth"],
4810
- projectDir: clientDir
4811
- });
4812
- }
4813
- if ((frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) && nativeDirExists) {
4814
- if (auth === "better-auth") {
4815
- await addPackageDependency({
4816
- dependencies: ["better-auth", "@better-auth/expo"],
4817
- projectDir: nativeDir
4818
- });
4819
- if (authPackageDirExists) await addPackageDependency({
4820
- dependencies: ["@better-auth/expo"],
4821
- projectDir: authPackageDir
4822
- });
4823
- }
4824
- }
4825
- if (authPackageDirExists && auth === "better-auth") await setupBetterAuthPlugins(projectDir, config);
4826
- } catch (error) {
4827
- consola.error(pc.red("Failed to configure authentication dependencies"));
4828
- if (error instanceof Error) consola.error(pc.red(error.message));
2758
+ async function setupHusky(projectDir, linter) {
2759
+ await addPackageDependency({
2760
+ devDependencies: ["husky", "lint-staged"],
2761
+ projectDir
2762
+ });
2763
+ const packageJsonPath = path.join(projectDir, "package.json");
2764
+ if (await fs.pathExists(packageJsonPath)) {
2765
+ const packageJson = await fs.readJson(packageJsonPath);
2766
+ packageJson.scripts = {
2767
+ ...packageJson.scripts,
2768
+ prepare: "husky"
2769
+ };
2770
+ if (linter === "oxlint") packageJson["lint-staged"] = { "*": ["oxlint", "oxfmt --write"] };
2771
+ else if (linter === "biome") packageJson["lint-staged"] = { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": ["biome check --write ."] };
2772
+ else packageJson["lint-staged"] = { "**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue,astro,svelte}": "" };
2773
+ await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
4829
2774
  }
4830
2775
  }
4831
- function generateAuthSecret(length = 32) {
4832
- const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4833
- let result = "";
4834
- const charactersLength = 62;
4835
- for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * charactersLength));
4836
- return result;
4837
- }
4838
2776
 
4839
2777
  //#endregion
4840
- //#region src/helpers/core/env-setup.ts
4841
- function getClientServerVar(frontend, backend) {
4842
- const hasNextJs = frontend.includes("next");
4843
- const hasNuxt = frontend.includes("nuxt");
4844
- const hasSvelte = frontend.includes("svelte");
4845
- const hasTanstackStart = frontend.includes("tanstack-start");
4846
- if (backend === "self") return {
4847
- key: "",
4848
- value: "",
4849
- write: false
4850
- };
4851
- let key = "VITE_SERVER_URL";
4852
- if (hasNextJs) key = "NEXT_PUBLIC_SERVER_URL";
4853
- else if (hasNuxt) key = "NUXT_PUBLIC_SERVER_URL";
4854
- else if (hasSvelte) key = "PUBLIC_SERVER_URL";
4855
- else if (hasTanstackStart) key = "VITE_SERVER_URL";
4856
- return {
4857
- key,
4858
- value: "http://localhost:3000",
4859
- write: true
4860
- };
4861
- }
4862
- function getConvexVar(frontend) {
4863
- const hasNextJs = frontend.includes("next");
4864
- const hasNuxt = frontend.includes("nuxt");
4865
- const hasSvelte = frontend.includes("svelte");
4866
- const hasTanstackStart = frontend.includes("tanstack-start");
4867
- if (hasNextJs) return "NEXT_PUBLIC_CONVEX_URL";
4868
- if (hasNuxt) return "NUXT_PUBLIC_CONVEX_URL";
4869
- if (hasSvelte) return "PUBLIC_CONVEX_URL";
4870
- if (hasTanstackStart) return "VITE_CONVEX_URL";
4871
- return "VITE_CONVEX_URL";
4872
- }
4873
- async function addEnvVariablesToFile(filePath, variables) {
4874
- await fs.ensureDir(path.dirname(filePath));
4875
- let envContent = "";
4876
- if (await fs.pathExists(filePath)) envContent = await fs.readFile(filePath, "utf8");
4877
- let modified = false;
4878
- let contentToAdd = "";
4879
- const exampleVariables = [];
4880
- for (const { key, value, condition, comment } of variables) if (condition) {
4881
- const regex = new RegExp(`^${key}=.*$`, "m");
4882
- const valueToWrite = value ?? "";
4883
- exampleVariables.push(`${key}=`);
4884
- if (regex.test(envContent)) {
4885
- const existingMatch = envContent.match(regex);
4886
- if (existingMatch && existingMatch[0] !== `${key}=${valueToWrite}`) {
4887
- envContent = envContent.replace(regex, `${key}=${valueToWrite}`);
4888
- modified = true;
4889
- }
4890
- } else {
4891
- if (comment) contentToAdd += `# ${comment}\n`;
4892
- contentToAdd += `${key}=${valueToWrite}\n`;
4893
- modified = true;
4894
- }
4895
- }
4896
- if (contentToAdd) {
4897
- if (envContent.length > 0 && !envContent.endsWith("\n")) envContent += "\n";
4898
- envContent += contentToAdd;
4899
- }
4900
- if (modified) await fs.writeFile(filePath, envContent.trimEnd());
4901
- const exampleFilePath = filePath.replace(/\.env$/, ".env.example");
4902
- let exampleEnvContent = "";
4903
- if (await fs.pathExists(exampleFilePath)) exampleEnvContent = await fs.readFile(exampleFilePath, "utf8");
4904
- let exampleModified = false;
4905
- let exampleContentToAdd = "";
4906
- for (const exampleVar of exampleVariables) {
4907
- const key = exampleVar.split("=")[0];
4908
- if (!new RegExp(`^${key}=.*$`, "m").test(exampleEnvContent)) {
4909
- exampleContentToAdd += `${exampleVar}\n`;
4910
- exampleModified = true;
4911
- }
4912
- }
4913
- if (exampleContentToAdd) {
4914
- if (exampleEnvContent.length > 0 && !exampleEnvContent.endsWith("\n")) exampleEnvContent += "\n";
4915
- exampleEnvContent += exampleContentToAdd;
4916
- }
4917
- if (exampleModified || !await fs.pathExists(exampleFilePath)) await fs.writeFile(exampleFilePath, exampleEnvContent.trimEnd());
4918
- }
4919
- async function setupEnvironmentVariables(config) {
4920
- const { backend, frontend, database, auth, examples, dbSetup, projectDir, webDeploy, serverDeploy } = config;
4921
- const hasReactRouter = frontend.includes("react-router");
4922
- const hasTanStackRouter = frontend.includes("tanstack-router");
4923
- const hasTanStackStart = frontend.includes("tanstack-start");
4924
- const hasNextJs = frontend.includes("next");
4925
- const hasNuxt = frontend.includes("nuxt");
4926
- const hasSvelte = frontend.includes("svelte");
4927
- const hasSolid = frontend.includes("solid");
4928
- const hasWebFrontend$1 = hasReactRouter || hasTanStackRouter || hasTanStackStart || hasNextJs || hasNuxt || hasSolid || hasSvelte;
4929
- if (hasWebFrontend$1) {
4930
- const clientDir = path.join(projectDir, "apps/web");
4931
- if (await fs.pathExists(clientDir)) {
4932
- const baseVar = getClientServerVar(frontend, backend);
4933
- const clientVars = [{
4934
- key: backend === "convex" ? getConvexVar(frontend) : baseVar.key,
4935
- value: backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value,
4936
- condition: backend === "convex" ? true : baseVar.write
4937
- }];
4938
- if (backend === "convex" && auth === "clerk") {
4939
- if (hasNextJs) clientVars.push({
4940
- key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL",
4941
- value: "",
4942
- condition: true
4943
- }, {
4944
- key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
4945
- value: "",
4946
- condition: true
4947
- }, {
4948
- key: "CLERK_SECRET_KEY",
4949
- value: "",
4950
- condition: true
4951
- });
4952
- else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) {
4953
- clientVars.push({
4954
- key: "VITE_CLERK_PUBLISHABLE_KEY",
4955
- value: "",
4956
- condition: true
4957
- });
4958
- if (hasTanStackStart) clientVars.push({
4959
- key: "CLERK_SECRET_KEY",
4960
- value: "",
4961
- condition: true
4962
- });
4963
- }
4964
- }
4965
- if (backend === "convex" && auth === "better-auth") {
4966
- if (hasNextJs) clientVars.push({
4967
- key: "NEXT_PUBLIC_CONVEX_SITE_URL",
4968
- value: "https://<YOUR_CONVEX_URL>",
4969
- condition: true
4970
- });
4971
- else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) clientVars.push({
4972
- key: "VITE_CONVEX_SITE_URL",
4973
- value: "https://<YOUR_CONVEX_URL>",
4974
- condition: true
4975
- });
4976
- }
4977
- await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
4978
- }
4979
- }
4980
- if (frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) {
4981
- const nativeDir = path.join(projectDir, "apps/native");
4982
- if (await fs.pathExists(nativeDir)) {
4983
- let envVarName = "EXPO_PUBLIC_SERVER_URL";
4984
- let serverUrl = "http://localhost:3000";
4985
- if (backend === "self") serverUrl = "http://localhost:3001";
4986
- if (backend === "convex") {
4987
- envVarName = "EXPO_PUBLIC_CONVEX_URL";
4988
- serverUrl = "https://<YOUR_CONVEX_URL>";
4989
- }
4990
- const nativeVars = [{
4991
- key: envVarName,
4992
- value: serverUrl,
4993
- condition: true
4994
- }];
4995
- if (backend === "convex" && auth === "clerk") nativeVars.push({
4996
- key: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY",
4997
- value: "",
4998
- condition: true
4999
- });
5000
- if (backend === "convex" && auth === "better-auth") nativeVars.push({
5001
- key: "EXPO_PUBLIC_CONVEX_SITE_URL",
5002
- value: "https://<YOUR_CONVEX_URL>",
5003
- condition: true
5004
- });
5005
- await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
5006
- }
5007
- }
5008
- if (backend === "convex") {
5009
- const convexBackendDir = path.join(projectDir, "packages/backend");
5010
- if (await fs.pathExists(convexBackendDir)) {
5011
- const envLocalPath = path.join(convexBackendDir, ".env.local");
5012
- let commentBlocks = "";
5013
- if (examples?.includes("ai")) commentBlocks += `# Set Google AI API key for AI agent
5014
- # npx convex env set GOOGLE_GENERATIVE_AI_API_KEY=your_google_api_key
5015
-
5016
- `;
5017
- if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
5018
- # npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
5019
- ${hasWebFrontend$1 ? "# npx convex env set SITE_URL http://localhost:3001\n" : ""}`;
5020
- if (commentBlocks) {
5021
- let existingContent = "";
5022
- if (await fs.pathExists(envLocalPath)) existingContent = await fs.readFile(envLocalPath, "utf8");
5023
- await fs.writeFile(envLocalPath, commentBlocks + existingContent);
5024
- }
5025
- const convexBackendVars = [];
5026
- if (examples?.includes("ai")) convexBackendVars.push({
5027
- key: "GOOGLE_GENERATIVE_AI_API_KEY",
5028
- value: "",
5029
- condition: true,
5030
- comment: "Google AI API key for AI agent"
5031
- });
5032
- if (auth === "better-auth") {
5033
- const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
5034
- const hasWeb = hasWebFrontend$1;
5035
- if (hasNative) convexBackendVars.push({
5036
- key: "EXPO_PUBLIC_CONVEX_SITE_URL",
5037
- value: "",
5038
- condition: true,
5039
- comment: "Same as CONVEX_URL but ends in .site"
5040
- });
5041
- if (hasWeb) convexBackendVars.push({
5042
- key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
5043
- value: "",
5044
- condition: true,
5045
- comment: "Same as CONVEX_URL but ends in .site"
5046
- }, {
5047
- key: "SITE_URL",
5048
- value: "http://localhost:3001",
5049
- condition: true,
5050
- comment: "Web app URL for authentication"
5051
- });
5052
- }
5053
- if (convexBackendVars.length > 0) await addEnvVariablesToFile(envLocalPath, convexBackendVars);
5054
- }
5055
- return;
5056
- }
5057
- const serverDir = path.join(projectDir, "apps/server");
5058
- let corsOrigin = "http://localhost:3001";
5059
- if (backend === "self") corsOrigin = "http://localhost:3001";
5060
- else if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
5061
- let databaseUrl = null;
5062
- if (database !== "none" && dbSetup === "none") switch (database) {
5063
- case "postgres":
5064
- databaseUrl = "postgresql://postgres:password@localhost:5432/postgres";
5065
- break;
5066
- case "mysql":
5067
- databaseUrl = "mysql://root:password@localhost:3306/mydb";
5068
- break;
5069
- case "mongodb":
5070
- databaseUrl = "mongodb://localhost:27017/mydatabase";
2778
+ //#region src/utils/env-utils.ts
2779
+ async function addEnvVariablesToFile(envPath, variables) {
2780
+ let content = "";
2781
+ if (fs.existsSync(envPath)) content = await fs.readFile(envPath, "utf-8");
2782
+ else await fs.ensureFile(envPath);
2783
+ const existingLines = content.split("\n");
2784
+ const newLines = [];
2785
+ const keysToAdd = /* @__PURE__ */ new Map();
2786
+ for (const variable of variables) {
2787
+ if (variable.condition === false || !variable.key) continue;
2788
+ keysToAdd.set(variable.key, variable.value);
2789
+ }
2790
+ let foundKeys = /* @__PURE__ */ new Set();
2791
+ for (const line of existingLines) {
2792
+ const trimmedLine = line.trim();
2793
+ let lineProcessed = false;
2794
+ for (const [key, value] of keysToAdd) if (trimmedLine.startsWith(`${key}=`)) {
2795
+ newLines.push(`${key}=${value}`);
2796
+ foundKeys.add(key);
2797
+ lineProcessed = true;
5071
2798
  break;
5072
- case "sqlite":
5073
- if (config.runtime === "workers" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") databaseUrl = "http://127.0.0.1:8080";
5074
- else {
5075
- const dbAppDir = backend === "self" ? "apps/web" : "apps/server";
5076
- databaseUrl = `file:${path.join(config.projectDir, dbAppDir, "local.db")}`;
5077
- }
5078
- break;
5079
- }
5080
- const serverVars = [
5081
- {
5082
- key: "BETTER_AUTH_SECRET",
5083
- value: generateAuthSecret(),
5084
- condition: !!auth
5085
- },
5086
- {
5087
- key: "BETTER_AUTH_URL",
5088
- value: backend === "self" ? "http://localhost:3001" : "http://localhost:3000",
5089
- condition: !!auth
5090
- },
5091
- {
5092
- key: "POLAR_ACCESS_TOKEN",
5093
- value: "",
5094
- condition: config.payments === "polar"
5095
- },
5096
- {
5097
- key: "POLAR_SUCCESS_URL",
5098
- value: `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`,
5099
- condition: config.payments === "polar"
5100
- },
5101
- {
5102
- key: "CORS_ORIGIN",
5103
- value: corsOrigin,
5104
- condition: true
5105
- },
5106
- {
5107
- key: "GOOGLE_GENERATIVE_AI_API_KEY",
5108
- value: "",
5109
- condition: examples?.includes("ai") || false
5110
- },
5111
- {
5112
- key: "DATABASE_URL",
5113
- value: databaseUrl,
5114
- condition: database !== "none" && dbSetup === "none"
5115
2799
  }
5116
- ];
5117
- if (backend === "self") {
5118
- const webDir = path.join(projectDir, "apps/web");
5119
- if (await fs.pathExists(webDir)) await addEnvVariablesToFile(path.join(webDir, ".env"), serverVars);
5120
- } else if (await fs.pathExists(serverDir)) await addEnvVariablesToFile(path.join(serverDir, ".env"), serverVars);
5121
- if (webDeploy === "cloudflare" && serverDeploy === "cloudflare" || webDeploy === "cloudflare" || serverDeploy === "cloudflare") {
5122
- const infraDir = path.join(projectDir, "packages/infra");
5123
- if (await fs.pathExists(infraDir)) await addEnvVariablesToFile(path.join(infraDir, ".env"), [{
5124
- key: "ALCHEMY_PASSWORD",
5125
- value: "please-change-this",
5126
- condition: true
5127
- }]);
2800
+ if (!lineProcessed) newLines.push(line);
5128
2801
  }
2802
+ for (const [key, value] of keysToAdd) if (!foundKeys.has(key)) newLines.push(`${key}=${value}`);
2803
+ if (newLines.length > 0 && newLines[newLines.length - 1] === "") newLines.pop();
2804
+ if (foundKeys.size > 0 || keysToAdd.size > foundKeys.size) await fs.writeFile(envPath, newLines.join("\n") + "\n");
5129
2805
  }
5130
2806
 
5131
2807
  //#endregion
@@ -6045,106 +3721,23 @@ async function setupTurso(config, cliInput) {
6045
3721
 
6046
3722
  //#endregion
6047
3723
  //#region src/helpers/core/db-setup.ts
3724
+ /**
3725
+ * Database setup - CLI-only operations
3726
+ * Calls external database provider CLIs (turso, neon, prisma-postgres, etc.)
3727
+ * Dependencies are handled by the generator's db-deps processor
3728
+ */
6048
3729
  async function setupDatabase(config, cliInput) {
6049
- const { database, orm, dbSetup, backend, projectDir } = config;
3730
+ const { database, dbSetup, backend, projectDir } = config;
6050
3731
  if (backend === "convex" || database === "none") {
6051
3732
  if (backend !== "convex") {
6052
- const serverDir = path.join(projectDir, "apps/server");
6053
- const serverDbDir = path.join(serverDir, "src/db");
3733
+ const serverDbDir = path.join(projectDir, "apps/server/src/db");
6054
3734
  if (await fs.pathExists(serverDbDir)) await fs.remove(serverDbDir);
6055
3735
  }
6056
3736
  return;
6057
3737
  }
6058
3738
  const dbPackageDir = path.join(projectDir, "packages/db");
6059
- const webDir = path.join(projectDir, "apps/web");
6060
- const webDirExists = await fs.pathExists(webDir);
6061
3739
  if (!await fs.pathExists(dbPackageDir)) return;
6062
3740
  try {
6063
- if (orm === "prisma") {
6064
- if (database === "mongodb") await addPackageDependency({
6065
- customDependencies: { "@prisma/client": "6.19.0" },
6066
- customDevDependencies: { prisma: "6.19.0" },
6067
- projectDir: dbPackageDir
6068
- });
6069
- else {
6070
- const prismaDependencies = ["@prisma/client"];
6071
- const prismaDevDependencies = ["prisma"];
6072
- if (database === "mysql" && dbSetup === "planetscale") prismaDependencies.push("@prisma/adapter-planetscale", "@planetscale/database");
6073
- else if (database === "mysql") prismaDependencies.push("@prisma/adapter-mariadb");
6074
- else if (database === "sqlite") if (dbSetup === "d1") prismaDependencies.push("@prisma/adapter-d1");
6075
- else prismaDependencies.push("@prisma/adapter-libsql");
6076
- else if (database === "postgres") if (dbSetup === "neon") {
6077
- prismaDependencies.push("@prisma/adapter-neon", "@neondatabase/serverless", "ws");
6078
- prismaDevDependencies.push("@types/ws");
6079
- } else if (dbSetup === "prisma-postgres") prismaDependencies.push("@prisma/adapter-pg");
6080
- else {
6081
- prismaDependencies.push("@prisma/adapter-pg");
6082
- prismaDependencies.push("pg");
6083
- prismaDevDependencies.push("@types/pg");
6084
- }
6085
- await addPackageDependency({
6086
- dependencies: prismaDependencies,
6087
- devDependencies: prismaDevDependencies,
6088
- projectDir: dbPackageDir
6089
- });
6090
- }
6091
- if (await fs.pathExists(webDir)) if (database === "mongodb") await addPackageDependency({
6092
- customDependencies: { "@prisma/client": "6.19.0" },
6093
- projectDir: webDir
6094
- });
6095
- else await addPackageDependency({
6096
- dependencies: ["@prisma/client"],
6097
- projectDir: webDir
6098
- });
6099
- } else if (orm === "drizzle") {
6100
- if (database === "sqlite") {
6101
- await addPackageDependency({
6102
- dependencies: [
6103
- "drizzle-orm",
6104
- "@libsql/client",
6105
- "libsql"
6106
- ],
6107
- devDependencies: ["drizzle-kit"],
6108
- projectDir: dbPackageDir
6109
- });
6110
- if (webDirExists) await addPackageDependency({
6111
- dependencies: ["@libsql/client", "libsql"],
6112
- projectDir: webDir
6113
- });
6114
- } else if (database === "postgres") if (dbSetup === "neon") await addPackageDependency({
6115
- dependencies: [
6116
- "drizzle-orm",
6117
- "@neondatabase/serverless",
6118
- "ws"
6119
- ],
6120
- devDependencies: ["drizzle-kit", "@types/ws"],
6121
- projectDir: dbPackageDir
6122
- });
6123
- else if (dbSetup === "planetscale") await addPackageDependency({
6124
- dependencies: ["drizzle-orm", "pg"],
6125
- devDependencies: ["drizzle-kit", "@types/pg"],
6126
- projectDir: dbPackageDir
6127
- });
6128
- else await addPackageDependency({
6129
- dependencies: ["drizzle-orm", "pg"],
6130
- devDependencies: ["drizzle-kit", "@types/pg"],
6131
- projectDir: dbPackageDir
6132
- });
6133
- else if (database === "mysql") if (dbSetup === "planetscale") await addPackageDependency({
6134
- dependencies: ["drizzle-orm", "@planetscale/database"],
6135
- devDependencies: ["drizzle-kit"],
6136
- projectDir: dbPackageDir
6137
- });
6138
- else await addPackageDependency({
6139
- dependencies: ["drizzle-orm", "mysql2"],
6140
- devDependencies: ["drizzle-kit"],
6141
- projectDir: dbPackageDir
6142
- });
6143
- } else if (orm === "mongoose") await addPackageDependency({
6144
- dependencies: ["mongoose"],
6145
- devDependencies: [],
6146
- projectDir: dbPackageDir
6147
- });
6148
3741
  if (dbSetup === "docker") await setupDockerCompose(config);
6149
3742
  else if (database === "sqlite" && dbSetup === "turso") await setupTurso(config, cliInput);
6150
3743
  else if (database === "sqlite" && dbSetup === "d1") await setupCloudflareD1(config);
@@ -6153,391 +3746,13 @@ async function setupDatabase(config, cliInput) {
6153
3746
  else if (dbSetup === "neon") await setupNeonPostgres(config, cliInput);
6154
3747
  else if (dbSetup === "planetscale") await setupPlanetScale(config);
6155
3748
  else if (dbSetup === "supabase") await setupSupabase(config, cliInput);
6156
- } else if (database === "mysql") {
6157
- if (dbSetup === "planetscale") await setupPlanetScale(config);
6158
- } else if (database === "mongodb" && dbSetup === "mongodb-atlas") await setupMongoDBAtlas(config, cliInput);
3749
+ } else if (database === "mysql" && dbSetup === "planetscale") await setupPlanetScale(config);
3750
+ else if (database === "mongodb" && dbSetup === "mongodb-atlas") await setupMongoDBAtlas(config, cliInput);
6159
3751
  } catch (error) {
6160
3752
  if (error instanceof Error) consola.error(pc.red(error.message));
6161
3753
  }
6162
3754
  }
6163
3755
 
6164
- //#endregion
6165
- //#region src/helpers/core/runtime-setup.ts
6166
- async function setupRuntime(config) {
6167
- const { runtime, backend, projectDir } = config;
6168
- if (backend === "convex" || backend === "self" || runtime === "none") return;
6169
- const serverDir = path.join(projectDir, "apps/server");
6170
- if (!await fs.pathExists(serverDir)) return;
6171
- if (runtime === "bun") await setupBunRuntime(serverDir, backend);
6172
- else if (runtime === "node") await setupNodeRuntime(serverDir, backend);
6173
- }
6174
- async function setupBunRuntime(serverDir, _backend) {
6175
- const packageJsonPath = path.join(serverDir, "package.json");
6176
- if (!await fs.pathExists(packageJsonPath)) return;
6177
- const packageJson = await fs.readJson(packageJsonPath);
6178
- packageJson.scripts = {
6179
- ...packageJson.scripts,
6180
- dev: "bun run --hot src/index.ts",
6181
- start: "bun run dist/index.js"
6182
- };
6183
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
6184
- await addPackageDependency({
6185
- devDependencies: ["@types/bun"],
6186
- projectDir: serverDir
6187
- });
6188
- }
6189
- async function setupNodeRuntime(serverDir, backend) {
6190
- const packageJsonPath = path.join(serverDir, "package.json");
6191
- if (!await fs.pathExists(packageJsonPath)) return;
6192
- const packageJson = await fs.readJson(packageJsonPath);
6193
- packageJson.scripts = {
6194
- ...packageJson.scripts,
6195
- dev: "tsx watch src/index.ts",
6196
- start: "node dist/index.js"
6197
- };
6198
- await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
6199
- await addPackageDependency({
6200
- devDependencies: ["tsx", "@types/node"],
6201
- projectDir: serverDir
6202
- });
6203
- if (backend === "hono") await addPackageDependency({
6204
- dependencies: ["@hono/node-server"],
6205
- projectDir: serverDir
6206
- });
6207
- else if (backend === "elysia") await addPackageDependency({
6208
- dependencies: ["@elysiajs/node"],
6209
- projectDir: serverDir
6210
- });
6211
- }
6212
-
6213
- //#endregion
6214
- //#region src/helpers/core/create-readme.ts
6215
- async function createReadme(projectDir, options) {
6216
- const readmePath = path.join(projectDir, "README.md");
6217
- const content = generateReadmeContent(options);
6218
- try {
6219
- await fs.writeFile(readmePath, content);
6220
- } catch (error) {
6221
- consola.error("Failed to create README.md file:", error);
6222
- }
6223
- }
6224
- function generateReadmeContent(options) {
6225
- const { projectName, packageManager, database, auth, addons = [], orm = "drizzle", runtime = "bun", frontend = ["tanstack-router"], backend = "hono", api = "trpc", webDeploy, serverDeploy } = options;
6226
- const isConvex = backend === "convex";
6227
- const hasReactRouter = frontend.includes("react-router");
6228
- const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
6229
- const hasSvelte = frontend.includes("svelte");
6230
- const packageManagerRunCmd = `${packageManager} run`;
6231
- let webPort = "3001";
6232
- if (hasReactRouter || hasSvelte) webPort = "5173";
6233
- const stackDescription = generateStackDescription(frontend, backend, api, isConvex);
6234
- return `# ${projectName}
6235
-
6236
- This project was created with [Better-T-Stack](https://github.com/AmanVarshney01/create-better-t-stack), a modern TypeScript stack${stackDescription ? ` that combines ${stackDescription}` : ""}.
6237
-
6238
- ## Features
6239
-
6240
- ${generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api)}
6241
-
6242
- ## Getting Started
6243
-
6244
- First, install the dependencies:
6245
-
6246
- \`\`\`bash
6247
- ${packageManager} install
6248
- \`\`\`
6249
- ${isConvex ? `
6250
- ## Convex Setup
6251
-
6252
- This project uses Convex as a backend. You'll need to set up Convex before running the app:
6253
-
6254
- \`\`\`bash
6255
- ${packageManagerRunCmd} dev:setup
6256
- \`\`\`
6257
-
6258
- Follow the prompts to create a new Convex project and connect it to your application.${auth === "clerk" ? " See [Convex + Clerk guide](https://docs.convex.dev/auth/clerk) for auth setup." : ""}` : generateDatabaseSetup(database, auth, packageManagerRunCmd, orm, options.dbSetup, options.serverDeploy, options.backend)}
6259
-
6260
- Then, run the development server:
6261
-
6262
- \`\`\`bash
6263
- ${packageManagerRunCmd} dev
6264
- \`\`\`
6265
-
6266
- ${generateRunningInstructions(frontend, backend, webPort, hasNative, isConvex)}
6267
- ${addons.includes("pwa") && hasReactRouter ? "\n## PWA Support with React Router v7\n\nThere is a known compatibility issue between VitePWA and React Router v7.\nSee: https://github.com/vite-pwa/vite-plugin-pwa/issues/809\n" : ""}
6268
- ${generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy)}
6269
-
6270
- ## Project Structure
6271
-
6272
- \`\`\`
6273
- ${generateProjectStructure(projectName, frontend, backend, addons, isConvex, api, auth)}
6274
- \`\`\`
6275
-
6276
- ## Available Scripts
6277
-
6278
- ${generateScriptsList(packageManagerRunCmd, database, orm, auth, hasNative, addons, backend, options.dbSetup)}
6279
- `;
6280
- }
6281
- function generateStackDescription(frontend, backend, api, isConvex) {
6282
- const parts = [];
6283
- const hasTanstackRouter = frontend.includes("tanstack-router");
6284
- const hasReactRouter = frontend.includes("react-router");
6285
- const hasNext = frontend.includes("next");
6286
- const hasTanstackStart = frontend.includes("tanstack-start");
6287
- const hasSvelte = frontend.includes("svelte");
6288
- const hasNuxt = frontend.includes("nuxt");
6289
- const hasSolid = frontend.includes("solid");
6290
- if (!(frontend.length === 0 || frontend.includes("none"))) {
6291
- if (hasTanstackRouter) parts.push("React, TanStack Router");
6292
- else if (hasReactRouter) parts.push("React, React Router");
6293
- else if (hasNext) parts.push("Next.js");
6294
- else if (hasTanstackStart) parts.push("React, TanStack Start");
6295
- else if (hasSvelte) parts.push("SvelteKit");
6296
- else if (hasNuxt) parts.push("Nuxt");
6297
- else if (hasSolid) parts.push("SolidJS");
6298
- }
6299
- if (backend !== "none") parts.push(backend[0].toUpperCase() + backend.slice(1));
6300
- if (!isConvex && api !== "none") parts.push(api.toUpperCase());
6301
- return parts.length > 0 ? `${parts.join(", ")}, and more` : "";
6302
- }
6303
- function generateRunningInstructions(frontend, backend, webPort, hasNative, isConvex) {
6304
- const instructions = [];
6305
- const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
6306
- const isBackendNone = backend === "none";
6307
- const isBackendSelf = backend === "self";
6308
- if (!hasFrontendNone) {
6309
- const hasTanstackRouter = frontend.includes("tanstack-router");
6310
- const hasReactRouter = frontend.includes("react-router");
6311
- const hasNext = frontend.includes("next");
6312
- const hasTanstackStart = frontend.includes("tanstack-start");
6313
- const hasSvelte = frontend.includes("svelte");
6314
- const hasNuxt = frontend.includes("nuxt");
6315
- const hasSolid = frontend.includes("solid");
6316
- if (hasTanstackRouter || hasReactRouter || hasNext || hasTanstackStart || hasSvelte || hasNuxt || hasSolid) if (isBackendSelf) instructions.push(`Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see your fullstack application.`);
6317
- else instructions.push(`Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see the web application.`);
6318
- }
6319
- if (hasNative) instructions.push("Use the Expo Go app to run the mobile application.");
6320
- if (isConvex) instructions.push("Your app will connect to the Convex cloud backend automatically.");
6321
- else if (!isBackendNone && !isBackendSelf) instructions.push("The API is running at [http://localhost:3000](http://localhost:3000).");
6322
- return instructions.join("\n");
6323
- }
6324
- function generateProjectStructure(projectName, frontend, backend, addons, isConvex, api, auth) {
6325
- const structure = [`${projectName}/`, "├── apps/"];
6326
- const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
6327
- const isBackendNone = backend === "none";
6328
- const isBackendSelf = backend === "self";
6329
- if (!hasFrontendNone) {
6330
- const hasTanstackRouter = frontend.includes("tanstack-router");
6331
- const hasReactRouter = frontend.includes("react-router");
6332
- const hasNext = frontend.includes("next");
6333
- const hasTanstackStart = frontend.includes("tanstack-start");
6334
- const hasSvelte = frontend.includes("svelte");
6335
- const hasNuxt = frontend.includes("nuxt");
6336
- const hasSolid = frontend.includes("solid");
6337
- if (hasTanstackRouter || hasReactRouter || hasNext || hasTanstackStart || hasSvelte || hasNuxt || hasSolid) {
6338
- let frontendType = "";
6339
- if (hasTanstackRouter) frontendType = "React + TanStack Router";
6340
- else if (hasReactRouter) frontendType = "React + React Router";
6341
- else if (hasNext) frontendType = "Next.js";
6342
- else if (hasTanstackStart) frontendType = "React + TanStack Start";
6343
- else if (hasSvelte) frontendType = "SvelteKit";
6344
- else if (hasNuxt) frontendType = "Nuxt";
6345
- else if (hasSolid) frontendType = "SolidJS";
6346
- if (isBackendSelf) structure.push(`│ └── web/ # Fullstack application (${frontendType})`);
6347
- else structure.push(`│ ├── web/ # Frontend application (${frontendType})`);
6348
- }
6349
- }
6350
- if (frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) if (isBackendSelf) structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
6351
- else structure.push("│ ├── native/ # Mobile application (React Native, Expo)");
6352
- if (addons.includes("starlight")) if (isBackendSelf) structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
6353
- else structure.push("│ ├── docs/ # Documentation site (Astro Starlight)");
6354
- if (!isBackendSelf && !isBackendNone && !isConvex) {
6355
- const backendName = backend[0].toUpperCase() + backend.slice(1);
6356
- const apiName = api !== "none" ? api.toUpperCase() : "";
6357
- const backendDesc = apiName ? `${backendName}, ${apiName}` : backendName;
6358
- structure.push(`│ └── server/ # Backend API (${backendDesc})`);
6359
- }
6360
- if (isConvex || !isBackendNone) {
6361
- structure.push("├── packages/");
6362
- if (isConvex) {
6363
- structure.push("│ ├── backend/ # Convex backend functions and schema");
6364
- if (auth === "clerk") structure.push("│ │ ├── convex/ # Convex functions and schema", "│ │ └── .env.local # Convex environment variables");
6365
- }
6366
- if (!isConvex) {
6367
- structure.push("│ ├── api/ # API layer / business logic");
6368
- if (auth !== "none") structure.push("│ ├── auth/ # Authentication configuration & logic");
6369
- if (api !== "none" || auth !== "none") structure.push("│ └── db/ # Database schema & queries");
6370
- }
6371
- }
6372
- return structure.join("\n");
6373
- }
6374
- function generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api) {
6375
- const isConvex = backend === "convex";
6376
- const isBackendNone = backend === "none";
6377
- const hasTanstackRouter = frontend.includes("tanstack-router");
6378
- const hasReactRouter = frontend.includes("react-router");
6379
- const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
6380
- const hasNext = frontend.includes("next");
6381
- const hasTanstackStart = frontend.includes("tanstack-start");
6382
- const hasSvelte = frontend.includes("svelte");
6383
- const hasNuxt = frontend.includes("nuxt");
6384
- const hasSolid = frontend.includes("solid");
6385
- const hasFrontendNone = frontend.length === 0 || frontend.includes("none");
6386
- const addonsList = ["- **TypeScript** - For type safety and improved developer experience"];
6387
- if (!hasFrontendNone) {
6388
- if (hasTanstackRouter) addonsList.push("- **TanStack Router** - File-based routing with full type safety");
6389
- else if (hasReactRouter) addonsList.push("- **React Router** - Declarative routing for React");
6390
- else if (hasNext) addonsList.push("- **Next.js** - Full-stack React framework");
6391
- else if (hasTanstackStart) addonsList.push("- **TanStack Start** - SSR framework with TanStack Router");
6392
- else if (hasSvelte) addonsList.push("- **SvelteKit** - Web framework for building Svelte apps");
6393
- else if (hasNuxt) addonsList.push("- **Nuxt** - The Intuitive Vue Framework");
6394
- else if (hasSolid) addonsList.push("- **SolidJS** - Simple and performant reactivity");
6395
- }
6396
- if (hasNative) {
6397
- addonsList.push("- **React Native** - Build mobile apps using React");
6398
- addonsList.push("- **Expo** - Tools for React Native development");
6399
- }
6400
- if (!hasFrontendNone) addonsList.push("- **TailwindCSS** - Utility-first CSS for rapid UI development", "- **shadcn/ui** - Reusable UI components");
6401
- if (isConvex) addonsList.push("- **Convex** - Reactive backend-as-a-service platform");
6402
- else if (!isBackendNone) {
6403
- if (backend === "hono") addonsList.push("- **Hono** - Lightweight, performant server framework");
6404
- else if (backend === "express") addonsList.push("- **Express** - Fast, unopinionated web framework");
6405
- else if (backend === "fastify") addonsList.push("- **Fastify** - Fast, low-overhead web framework");
6406
- else if (backend === "elysia") addonsList.push("- **Elysia** - Type-safe, high-performance framework");
6407
- if (api === "trpc") addonsList.push("- **tRPC** - End-to-end type-safe APIs");
6408
- else if (api === "orpc") addonsList.push("- **oRPC** - End-to-end type-safe APIs with OpenAPI integration");
6409
- if (runtime !== "none") addonsList.push(`- **${runtime === "bun" ? "Bun" : runtime === "node" ? "Node.js" : runtime}** - Runtime environment`);
6410
- }
6411
- if (database !== "none" && !isConvex) {
6412
- const ormName = orm === "drizzle" ? "Drizzle" : orm === "prisma" ? "Prisma" : orm === "mongoose" ? "Mongoose" : "ORM";
6413
- const dbName = database === "sqlite" ? "SQLite/Turso" : database === "postgres" ? "PostgreSQL" : database === "mysql" ? "MySQL" : database === "mongodb" ? "MongoDB" : "Database";
6414
- addonsList.push(`- **${ormName}** - TypeScript-first ORM`, `- **${dbName}** - Database engine`);
6415
- }
6416
- if (auth !== "none") {
6417
- const authLabel = auth === "clerk" ? "Clerk" : "Better-Auth";
6418
- addonsList.push(`- **Authentication** - ${authLabel}`);
6419
- }
6420
- for (const addon of addons) if (addon === "pwa") addonsList.push("- **PWA** - Progressive Web App support");
6421
- else if (addon === "tauri") addonsList.push("- **Tauri** - Build native desktop applications");
6422
- else if (addon === "biome") addonsList.push("- **Biome** - Linting and formatting");
6423
- else if (addon === "oxlint") addonsList.push("- **Oxlint** - Oxlint + Oxfmt (linting & formatting)");
6424
- else if (addon === "husky") addonsList.push("- **Husky** - Git hooks for code quality");
6425
- else if (addon === "starlight") addonsList.push("- **Starlight** - Documentation site with Astro");
6426
- else if (addon === "turborepo") addonsList.push("- **Turborepo** - Optimized monorepo build system");
6427
- return addonsList.join("\n");
6428
- }
6429
- function generateDatabaseSetup(database, _auth, packageManagerRunCmd, orm, dbSetup, _serverDeploy, backend) {
6430
- if (database === "none") return "";
6431
- const isBackendSelf = backend === "self";
6432
- const envPath = isBackendSelf ? "apps/web/.env" : "apps/server/.env";
6433
- let setup = "## Database Setup\n\n";
6434
- if (database === "sqlite") setup += `This project uses SQLite${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
6435
-
6436
- 1. Start the local SQLite database (optional):
6437
- ${dbSetup === "d1" ? "D1 local development and migrations are handled automatically by Alchemy during dev and deploy." : `\`\`\`bash
6438
- ${packageManagerRunCmd} db:local
6439
- \`\`\``}
6440
-
6441
- 2. Update your \`.env\` file in the \`${isBackendSelf ? "apps/web" : "apps/server"}\` directory with the appropriate connection details if needed.
6442
- `;
6443
- else if (database === "postgres") setup += `This project uses PostgreSQL${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
6444
-
6445
- 1. Make sure you have a PostgreSQL database set up.
6446
- 2. Update your \`${envPath}\` file with your PostgreSQL connection details.
6447
- `;
6448
- else if (database === "mysql") setup += `This project uses MySQL${orm === "drizzle" ? " with Drizzle ORM" : orm === "prisma" ? " with Prisma" : ` with ${orm}`}.
6449
-
6450
- 1. Make sure you have a MySQL database set up.
6451
- 2. Update your \`${envPath}\` file with your MySQL connection details.
6452
- `;
6453
- else if (database === "mongodb") setup += `This project uses MongoDB ${orm === "mongoose" ? "with Mongoose" : orm === "prisma" ? "with Prisma ORM" : `with ${orm}`}.
6454
-
6455
- 1. Make sure you have MongoDB set up.
6456
- 2. Update your \`${envPath}\` file with your MongoDB connection URI.
6457
- `;
6458
- setup += `
6459
- 3. ${orm === "prisma" ? `Generate the Prisma client and push the schema:
6460
- \`\`\`bash
6461
- ${packageManagerRunCmd} db:push
6462
- \`\`\`` : orm === "drizzle" ? `Apply the schema to your database:
6463
- \`\`\`bash
6464
- ${packageManagerRunCmd} db:push
6465
- \`\`\`` : `Apply the schema to your database:
6466
- \`\`\`bash
6467
- ${packageManagerRunCmd} db:push
6468
- \`\`\``}
6469
- `;
6470
- return setup;
6471
- }
6472
- function generateScriptsList(packageManagerRunCmd, database, orm, _auth, hasNative, addons, backend, dbSetup) {
6473
- const isConvex = backend === "convex";
6474
- const isBackendNone = backend === "none";
6475
- const isBackendSelf = backend === "self";
6476
- let scripts = `- \`${packageManagerRunCmd} dev\`: Start all applications in development mode
6477
- - \`${packageManagerRunCmd} build\`: Build all applications`;
6478
- if (!isBackendSelf) scripts += `
6479
- - \`${packageManagerRunCmd} dev:web\`: Start only the web application`;
6480
- if (isConvex) scripts += `
6481
- - \`${packageManagerRunCmd} dev:setup\`: Setup and configure your Convex project`;
6482
- else if (!isBackendNone && !isBackendSelf) scripts += `
6483
- - \`${packageManagerRunCmd} dev:server\`: Start only the server`;
6484
- scripts += `
6485
- - \`${packageManagerRunCmd} check-types\`: Check TypeScript types across all apps`;
6486
- if (hasNative) scripts += `
6487
- - \`${packageManagerRunCmd} dev:native\`: Start the React Native/Expo development server`;
6488
- if (database !== "none" && !isConvex) {
6489
- scripts += `
6490
- - \`${packageManagerRunCmd} db:push\`: Push schema changes to database
6491
- - \`${packageManagerRunCmd} db:studio\`: Open database studio UI`;
6492
- if (database === "sqlite" && dbSetup !== "d1") scripts += `
6493
- - \`${packageManagerRunCmd} db:local\`: Start the local SQLite database`;
6494
- }
6495
- if (addons.includes("biome")) scripts += `
6496
- - \`${packageManagerRunCmd} check\`: Run Biome formatting and linting`;
6497
- if (addons.includes("oxlint")) scripts += `
6498
- - \`${packageManagerRunCmd} check\`: Run Oxlint and Oxfmt`;
6499
- if (addons.includes("pwa")) scripts += `
6500
- - \`cd apps/web && ${packageManagerRunCmd} generate-pwa-assets\`: Generate PWA assets`;
6501
- if (addons.includes("tauri")) scripts += `
6502
- - \`cd apps/web && ${packageManagerRunCmd} desktop:dev\`: Start Tauri desktop app in development
6503
- - \`cd apps/web && ${packageManagerRunCmd} desktop:build\`: Build Tauri desktop app`;
6504
- if (addons.includes("starlight")) scripts += `
6505
- - \`cd apps/docs && ${packageManagerRunCmd} dev\`: Start documentation site
6506
- - \`cd apps/docs && ${packageManagerRunCmd} build\`: Build documentation site`;
6507
- return scripts;
6508
- }
6509
- function generateDeploymentCommands(packageManagerRunCmd, webDeploy, serverDeploy) {
6510
- const lines = [];
6511
- if (webDeploy === "cloudflare" || serverDeploy === "cloudflare") {
6512
- lines.push("## Deployment (Cloudflare via Alchemy)");
6513
- if (webDeploy === "cloudflare" && serverDeploy !== "cloudflare") lines.push(`- Web dev: cd apps/web && ${packageManagerRunCmd} dev`, `- Web deploy: cd apps/web && ${packageManagerRunCmd} deploy`, `- Web destroy: cd apps/web && ${packageManagerRunCmd} destroy`);
6514
- if (serverDeploy === "cloudflare" && webDeploy !== "cloudflare") lines.push(`- Server dev: cd apps/server && ${packageManagerRunCmd} dev`, `- Server deploy: cd apps/server && ${packageManagerRunCmd} deploy`, `- Server destroy: cd apps/server && ${packageManagerRunCmd} destroy`);
6515
- if (webDeploy === "cloudflare" && serverDeploy === "cloudflare") lines.push(`- Dev: ${packageManagerRunCmd} dev`, `- Deploy: ${packageManagerRunCmd} deploy`, `- Destroy: ${packageManagerRunCmd} destroy`);
6516
- lines.push("", "For more details, see the guide on [Deploying to Cloudflare with Alchemy](https://www.better-t-stack.dev/docs/guides/cloudflare-alchemy).");
6517
- }
6518
- return lines.length ? `${lines.join("\n")}\n` : "";
6519
- }
6520
-
6521
- //#endregion
6522
- //#region src/helpers/core/env-package-setup.ts
6523
- async function setupEnvPackageDependencies(projectDir, options) {
6524
- const envDir = path.join(projectDir, "packages/env");
6525
- if (!await fs.pathExists(envDir)) return;
6526
- await addPackageDependency({
6527
- dependencies: getT3EnvDeps(options),
6528
- projectDir: envDir
6529
- });
6530
- }
6531
- function getT3EnvDeps(options) {
6532
- const deps = ["zod"];
6533
- const { frontend, backend, runtime } = options;
6534
- if (frontend.includes("next")) deps.push("@t3-oss/env-nextjs");
6535
- else if (frontend.includes("nuxt")) deps.push("@t3-oss/env-nuxt");
6536
- else deps.push("@t3-oss/env-core");
6537
- if (backend !== "convex" && backend !== "none" && runtime !== "workers" && !deps.includes("@t3-oss/env-core")) deps.push("@t3-oss/env-core");
6538
- return deps;
6539
- }
6540
-
6541
3756
  //#endregion
6542
3757
  //#region src/helpers/core/git.ts
6543
3758
  async function initializeGit(projectDir, useGit) {
@@ -6561,44 +3776,19 @@ async function initializeGit(projectDir, useGit) {
6561
3776
  }
6562
3777
 
6563
3778
  //#endregion
6564
- //#region src/helpers/core/infra-package-setup.ts
6565
- async function setupInfraPackageDependencies(projectDir, _options) {
6566
- const infraDir = path.join(projectDir, "packages/infra");
6567
- if (!await fs.pathExists(infraDir)) return;
6568
- await addPackageDependency({
6569
- devDependencies: ["alchemy"],
6570
- projectDir: infraDir
6571
- });
6572
- }
6573
-
6574
- //#endregion
6575
- //#region src/helpers/core/payments-setup.ts
6576
- async function setupPayments(config) {
6577
- const { payments, projectDir, frontend } = config;
6578
- if (!payments || payments === "none") return;
6579
- const clientDir = path.join(projectDir, "apps/web");
6580
- const authDir = path.join(projectDir, "packages/auth");
6581
- const clientDirExists = await fs.pathExists(clientDir);
6582
- const authDirExists = await fs.pathExists(authDir);
6583
- if (payments === "polar") {
6584
- if (authDirExists) await addPackageDependency({
6585
- dependencies: ["@polar-sh/better-auth", "@polar-sh/sdk"],
6586
- projectDir: authDir
6587
- });
6588
- if (clientDirExists) {
6589
- if (frontend.some((f) => [
6590
- "react-router",
6591
- "tanstack-router",
6592
- "tanstack-start",
6593
- "next",
6594
- "nuxt",
6595
- "svelte",
6596
- "solid"
6597
- ].includes(f))) await addPackageDependency({
6598
- dependencies: ["@polar-sh/better-auth"],
6599
- projectDir: clientDir
6600
- });
6601
- }
3779
+ //#region src/helpers/core/install-dependencies.ts
3780
+ async function installDependencies({ projectDir, packageManager }) {
3781
+ const s = spinner();
3782
+ try {
3783
+ s.start(`Running ${packageManager} install...`);
3784
+ await $({
3785
+ cwd: projectDir,
3786
+ stderr: "inherit"
3787
+ })`${packageManager} install`;
3788
+ s.stop("Dependencies installed successfully");
3789
+ } catch (error) {
3790
+ s.stop(pc.red("Failed to install dependencies"));
3791
+ if (error instanceof Error) consola.error(pc.red(`Installation error: ${error.message}`));
6602
3792
  }
6603
3793
  }
6604
3794
 
@@ -6817,375 +4007,24 @@ function getAlchemyDeployInstructions(runCmd, webDeploy, serverDeploy, backend)
6817
4007
  return instructions.length ? `\n${instructions.join("\n")}` : "";
6818
4008
  }
6819
4009
 
6820
- //#endregion
6821
- //#region src/helpers/core/workspace-setup.ts
6822
- async function setupWorkspaceDependencies(projectDir, options) {
6823
- const { projectName, packageManager, runtime, backend } = options;
6824
- const workspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
6825
- const packages = await detectPackages(projectDir);
6826
- const configDep = packages.config.exists ? { [`@${projectName}/config`]: workspaceVersion } : {};
6827
- const envDep = packages.env.exists ? { [`@${projectName}/env`]: workspaceVersion } : {};
6828
- const ctx = {
6829
- projectName,
6830
- workspaceVersion,
6831
- options,
6832
- commonDeps: ["dotenv", "zod"],
6833
- commonDevDeps: ["typescript"],
6834
- configDep,
6835
- envDep
6836
- };
6837
- await Promise.all([
6838
- setupEnvPackage(packages.env, packages.infra, ctx),
6839
- setupInfraPackage(packages.infra, ctx),
6840
- setupDbPackage(packages.db, ctx),
6841
- setupAuthPackage(packages.auth, packages.db, ctx),
6842
- setupApiPackage(packages.api, packages.auth, packages.db, ctx),
6843
- setupBackendPackage(packages.backend, ctx),
6844
- setupServerPackage(packages.server, packages.api, packages.auth, packages.db, ctx),
6845
- setupWebPackage(packages.web, packages.api, packages.auth, packages.backend, ctx),
6846
- setupNativePackage(packages.native, packages.api, packages.backend, ctx)
6847
- ]);
6848
- const runtimeDevDeps = getRuntimeDevDeps(runtime, backend);
6849
- await addPackageDependency({
6850
- dependencies: ctx.commonDeps,
6851
- devDependencies: [...ctx.commonDevDeps, ...runtimeDevDeps],
6852
- customDependencies: envDep,
6853
- customDevDependencies: configDep,
6854
- projectDir
6855
- });
6856
- }
6857
- async function detectPackages(projectDir) {
6858
- const entries = await Promise.all(Object.entries({
6859
- config: "packages/config",
6860
- env: "packages/env",
6861
- infra: "packages/infra",
6862
- db: "packages/db",
6863
- auth: "packages/auth",
6864
- api: "packages/api",
6865
- backend: "packages/backend",
6866
- server: "apps/server",
6867
- web: "apps/web",
6868
- native: "apps/native"
6869
- }).map(async ([name, relativePath]) => {
6870
- const dir = path.join(projectDir, relativePath);
6871
- return [name, {
6872
- dir,
6873
- exists: await fs.pathExists(dir)
6874
- }];
6875
- }));
6876
- return Object.fromEntries(entries);
6877
- }
6878
- async function setupEnvPackage(pkg, infraPkg, ctx) {
6879
- if (!pkg.exists) return;
6880
- const runtimeDevDeps = getRuntimeDevDeps(ctx.options.runtime, ctx.options.backend);
6881
- const customDevDeps = { ...ctx.configDep };
6882
- if ((ctx.options.serverDeploy === "cloudflare" || ctx.options.webDeploy === "cloudflare") && infraPkg.exists) customDevDeps[`@${ctx.projectName}/infra`] = ctx.workspaceVersion;
6883
- await addPackageDependency({
6884
- dependencies: ctx.commonDeps,
6885
- devDependencies: [...ctx.commonDevDeps, ...runtimeDevDeps],
6886
- customDevDependencies: customDevDeps,
6887
- projectDir: pkg.dir
6888
- });
6889
- }
6890
- async function setupInfraPackage(pkg, ctx) {
6891
- if (!pkg.exists) return;
6892
- await addPackageDependency({
6893
- dependencies: ctx.commonDeps,
6894
- devDependencies: ctx.commonDevDeps,
6895
- customDevDependencies: ctx.configDep,
6896
- projectDir: pkg.dir
6897
- });
6898
- }
6899
- async function setupDbPackage(pkg, ctx) {
6900
- if (!pkg.exists) return;
6901
- await addPackageDependency({
6902
- dependencies: ctx.commonDeps,
6903
- devDependencies: ctx.commonDevDeps,
6904
- customDependencies: ctx.envDep,
6905
- customDevDependencies: ctx.configDep,
6906
- projectDir: pkg.dir
6907
- });
6908
- }
6909
- async function setupAuthPackage(pkg, dbPkg, ctx) {
6910
- if (!pkg.exists) return;
6911
- const deps = { ...ctx.envDep };
6912
- if (ctx.options.database !== "none" && dbPkg.exists) deps[`@${ctx.projectName}/db`] = ctx.workspaceVersion;
6913
- await addPackageDependency({
6914
- dependencies: ctx.commonDeps,
6915
- devDependencies: ctx.commonDevDeps,
6916
- customDependencies: deps,
6917
- customDevDependencies: ctx.configDep,
6918
- projectDir: pkg.dir
6919
- });
6920
- }
6921
- async function setupApiPackage(pkg, authPkg, dbPkg, ctx) {
6922
- if (!pkg.exists) return;
6923
- const deps = { ...ctx.envDep };
6924
- if (ctx.options.auth !== "none" && authPkg.exists) deps[`@${ctx.projectName}/auth`] = ctx.workspaceVersion;
6925
- if (ctx.options.database !== "none" && dbPkg.exists) deps[`@${ctx.projectName}/db`] = ctx.workspaceVersion;
6926
- await addPackageDependency({
6927
- dependencies: ctx.commonDeps,
6928
- devDependencies: ctx.commonDevDeps,
6929
- customDependencies: deps,
6930
- customDevDependencies: ctx.configDep,
6931
- projectDir: pkg.dir
6932
- });
6933
- }
6934
- async function setupBackendPackage(pkg, ctx) {
6935
- if (!pkg.exists) return;
6936
- await addPackageDependency({
6937
- dependencies: ctx.commonDeps,
6938
- devDependencies: ctx.commonDevDeps,
6939
- customDevDependencies: ctx.configDep,
6940
- projectDir: pkg.dir
6941
- });
6942
- }
6943
- async function setupServerPackage(pkg, apiPkg, authPkg, dbPkg, ctx) {
6944
- if (!pkg.exists) return;
6945
- const deps = { ...ctx.envDep };
6946
- if (ctx.options.api !== "none" && apiPkg.exists) deps[`@${ctx.projectName}/api`] = ctx.workspaceVersion;
6947
- if (ctx.options.auth !== "none" && authPkg.exists) deps[`@${ctx.projectName}/auth`] = ctx.workspaceVersion;
6948
- if (ctx.options.database !== "none" && dbPkg.exists) deps[`@${ctx.projectName}/db`] = ctx.workspaceVersion;
6949
- await addPackageDependency({
6950
- dependencies: ctx.commonDeps,
6951
- devDependencies: [...ctx.commonDevDeps, "tsdown"],
6952
- customDependencies: deps,
6953
- customDevDependencies: ctx.configDep,
6954
- projectDir: pkg.dir
6955
- });
6956
- }
6957
- async function setupWebPackage(pkg, apiPkg, authPkg, backendPkg, ctx) {
6958
- if (!pkg.exists) return;
6959
- const deps = { ...ctx.envDep };
6960
- if (ctx.options.api !== "none" && apiPkg.exists) deps[`@${ctx.projectName}/api`] = ctx.workspaceVersion;
6961
- if (ctx.options.auth !== "none" && authPkg.exists) deps[`@${ctx.projectName}/auth`] = ctx.workspaceVersion;
6962
- if (ctx.options.backend === "convex" && backendPkg.exists) deps[`@${ctx.projectName}/backend`] = ctx.workspaceVersion;
6963
- await addPackageDependency({
6964
- dependencies: ctx.commonDeps,
6965
- devDependencies: ctx.commonDevDeps,
6966
- customDependencies: deps,
6967
- customDevDependencies: ctx.configDep,
6968
- projectDir: pkg.dir
6969
- });
6970
- }
6971
- async function setupNativePackage(pkg, apiPkg, backendPkg, ctx) {
6972
- if (!pkg.exists) return;
6973
- const deps = { ...ctx.envDep };
6974
- if (ctx.options.api !== "none" && apiPkg.exists) deps[`@${ctx.projectName}/api`] = ctx.workspaceVersion;
6975
- if (ctx.options.backend === "convex" && backendPkg.exists) deps[`@${ctx.projectName}/backend`] = ctx.workspaceVersion;
6976
- await addPackageDependency({
6977
- dependencies: ctx.commonDeps,
6978
- devDependencies: ctx.commonDevDeps,
6979
- customDependencies: deps,
6980
- customDevDependencies: ctx.configDep,
6981
- projectDir: pkg.dir
6982
- });
6983
- }
6984
- function getRuntimeDevDeps(runtime, backend) {
6985
- if (runtime === "none" && backend === "self") return ["@types/node"];
6986
- if (runtime === "node" || runtime === "workers") return ["@types/node"];
6987
- if (runtime === "bun") return ["@types/bun"];
6988
- return [];
6989
- }
6990
-
6991
- //#endregion
6992
- //#region src/helpers/core/project-config.ts
6993
- async function updatePackageConfigurations(projectDir, options) {
6994
- await updateRootPackageJson(projectDir, options);
6995
- if (options.backend === "convex") await updateConvexPackageJson(projectDir, options);
6996
- else if (options.backend !== "none") {
6997
- await updateDbPackageJson(projectDir, options);
6998
- await updateAuthPackageJson(projectDir, options);
6999
- await updateApiPackageJson(projectDir, options);
7000
- if (options.backend !== "self") await updateServerPackageJson(projectDir, options);
7001
- }
7002
- await setupWorkspaceDependencies(projectDir, options);
7003
- }
7004
- async function updateRootPackageJson(projectDir, options) {
7005
- const rootPackageJsonPath = path.join(projectDir, "package.json");
7006
- if (!await fs.pathExists(rootPackageJsonPath)) return;
7007
- const packageJson = await fs.readJson(rootPackageJsonPath);
7008
- packageJson.name = options.projectName;
7009
- packageJson.scripts = packageJson.scripts || {};
7010
- packageJson.workspaces = packageJson.workspaces || [];
7011
- const scripts = packageJson.scripts;
7012
- const workspaces = packageJson.workspaces;
7013
- const { projectName, packageManager, backend, database, orm, dbSetup, serverDeploy, addons } = options;
7014
- const backendPackageName = backend === "convex" ? `@${projectName}/backend` : "server";
7015
- const dbPackageName = `@${projectName}/db`;
7016
- const hasTurborepo = addons.includes("turborepo");
7017
- const needsDbScripts = backend !== "convex" && database !== "none" && orm !== "none" && orm !== "mongoose";
7018
- const isD1Alchemy = dbSetup === "d1" && serverDeploy === "cloudflare";
7019
- const pmConfig = getPackageManagerConfig(packageManager, hasTurborepo);
7020
- scripts.dev = pmConfig.dev;
7021
- scripts.build = pmConfig.build;
7022
- scripts["check-types"] = pmConfig.checkTypes;
7023
- scripts["dev:native"] = pmConfig.filter("native", "dev");
7024
- scripts["dev:web"] = pmConfig.filter("web", "dev");
7025
- if (backend !== "self" && backend !== "none") scripts["dev:server"] = pmConfig.filter(backendPackageName, "dev");
7026
- if (backend === "convex") scripts["dev:setup"] = pmConfig.filter(backendPackageName, "dev:setup");
7027
- if (needsDbScripts) {
7028
- scripts["db:push"] = pmConfig.filter(dbPackageName, "db:push");
7029
- if (!isD1Alchemy) scripts["db:studio"] = pmConfig.filter(dbPackageName, "db:studio");
7030
- if (orm === "prisma") {
7031
- scripts["db:generate"] = pmConfig.filter(dbPackageName, "db:generate");
7032
- scripts["db:migrate"] = pmConfig.filter(dbPackageName, "db:migrate");
7033
- } else if (orm === "drizzle") {
7034
- scripts["db:generate"] = pmConfig.filter(dbPackageName, "db:generate");
7035
- if (!isD1Alchemy) scripts["db:migrate"] = pmConfig.filter(dbPackageName, "db:migrate");
7036
- }
7037
- }
7038
- if (database === "sqlite" && dbSetup !== "d1") scripts["db:local"] = pmConfig.filter(dbPackageName, "db:local");
7039
- if (dbSetup === "docker") {
7040
- scripts["db:start"] = pmConfig.filter(dbPackageName, "db:start");
7041
- scripts["db:watch"] = pmConfig.filter(dbPackageName, "db:watch");
7042
- scripts["db:stop"] = pmConfig.filter(dbPackageName, "db:stop");
7043
- scripts["db:down"] = pmConfig.filter(dbPackageName, "db:down");
7044
- }
7045
- try {
7046
- const { stdout } = await $`${packageManager} -v`;
7047
- packageJson.packageManager = `${packageManager}@${stdout.trim()}`;
7048
- } catch {
7049
- log.warn(`Could not determine ${packageManager} version.`);
7050
- }
7051
- if (backend === "convex") {
7052
- if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
7053
- if ((options.frontend.length > 0 || addons.includes("starlight")) && !workspaces.includes("apps/*")) workspaces.push("apps/*");
7054
- } else {
7055
- if (!workspaces.includes("apps/*")) workspaces.push("apps/*");
7056
- if (!workspaces.includes("packages/*")) workspaces.push("packages/*");
7057
- }
7058
- await fs.writeJson(rootPackageJsonPath, packageJson, { spaces: 2 });
7059
- }
7060
- function getPackageManagerConfig(packageManager, hasTurborepo) {
7061
- if (hasTurborepo) return {
7062
- dev: "turbo dev",
7063
- build: "turbo build",
7064
- checkTypes: "turbo check-types",
7065
- filter: (workspace, script) => `turbo -F ${workspace} ${script}`
7066
- };
7067
- switch (packageManager) {
7068
- case "pnpm": return {
7069
- dev: "pnpm -r dev",
7070
- build: "pnpm -r build",
7071
- checkTypes: "pnpm -r check-types",
7072
- filter: (workspace, script) => `pnpm --filter ${workspace} ${script}`
7073
- };
7074
- case "npm": return {
7075
- dev: "npm run dev --workspaces",
7076
- build: "npm run build --workspaces",
7077
- checkTypes: "npm run check-types --workspaces",
7078
- filter: (workspace, script) => `npm run ${script} --workspace ${workspace}`
7079
- };
7080
- case "bun": return {
7081
- dev: "bun run --filter '*' dev",
7082
- build: "bun run --filter '*' build",
7083
- checkTypes: "bun run --filter '*' check-types",
7084
- filter: (workspace, script) => `bun run --filter ${workspace} ${script}`
7085
- };
7086
- }
7087
- }
7088
- async function updateServerPackageJson(projectDir, _options) {
7089
- const serverPackageJsonPath = path.join(projectDir, "apps/server/package.json");
7090
- if (!await fs.pathExists(serverPackageJsonPath)) return;
7091
- const serverPackageJson = await fs.readJson(serverPackageJsonPath);
7092
- serverPackageJson.scripts = serverPackageJson.scripts || {};
7093
- await fs.writeJson(serverPackageJsonPath, serverPackageJson, { spaces: 2 });
7094
- }
7095
- async function updateDbPackageJson(projectDir, options) {
7096
- const dbPackageJsonPath = path.join(projectDir, "packages/db/package.json");
7097
- if (!await fs.pathExists(dbPackageJsonPath)) return;
7098
- const dbPackageJson = await fs.readJson(dbPackageJsonPath);
7099
- dbPackageJson.name = `@${options.projectName}/db`;
7100
- dbPackageJson.scripts = dbPackageJson.scripts || {};
7101
- const scripts = dbPackageJson.scripts;
7102
- const { database, orm, dbSetup, serverDeploy } = options;
7103
- const isD1Alchemy = dbSetup === "d1" && serverDeploy === "cloudflare";
7104
- if (database !== "none") {
7105
- if (database === "sqlite" && dbSetup !== "d1") scripts["db:local"] = "turso dev --db-file local.db";
7106
- if (orm === "prisma") {
7107
- scripts["db:push"] = "prisma db push";
7108
- scripts["db:generate"] = "prisma generate";
7109
- scripts["db:migrate"] = "prisma migrate dev";
7110
- if (!isD1Alchemy) scripts["db:studio"] = "prisma studio";
7111
- } else if (orm === "drizzle") {
7112
- scripts["db:push"] = "drizzle-kit push";
7113
- scripts["db:generate"] = "drizzle-kit generate";
7114
- if (!isD1Alchemy) {
7115
- scripts["db:studio"] = "drizzle-kit studio";
7116
- scripts["db:migrate"] = "drizzle-kit migrate";
7117
- }
7118
- }
7119
- }
7120
- if (dbSetup === "docker") {
7121
- scripts["db:start"] = "docker compose up -d";
7122
- scripts["db:watch"] = "docker compose up";
7123
- scripts["db:stop"] = "docker compose stop";
7124
- scripts["db:down"] = "docker compose down";
7125
- }
7126
- await fs.writeJson(dbPackageJsonPath, dbPackageJson, { spaces: 2 });
7127
- }
7128
- async function updateAuthPackageJson(projectDir, options) {
7129
- const authPackageJsonPath = path.join(projectDir, "packages/auth/package.json");
7130
- if (!await fs.pathExists(authPackageJsonPath)) return;
7131
- const authPackageJson = await fs.readJson(authPackageJsonPath);
7132
- authPackageJson.name = `@${options.projectName}/auth`;
7133
- await fs.writeJson(authPackageJsonPath, authPackageJson, { spaces: 2 });
7134
- }
7135
- async function updateApiPackageJson(projectDir, options) {
7136
- const apiPackageJsonPath = path.join(projectDir, "packages/api/package.json");
7137
- if (!await fs.pathExists(apiPackageJsonPath)) return;
7138
- const apiPackageJson = await fs.readJson(apiPackageJsonPath);
7139
- apiPackageJson.name = `@${options.projectName}/api`;
7140
- await fs.writeJson(apiPackageJsonPath, apiPackageJson, { spaces: 2 });
7141
- }
7142
- async function updateConvexPackageJson(projectDir, options) {
7143
- const convexPackageJsonPath = path.join(projectDir, "packages/backend/package.json");
7144
- if (!await fs.pathExists(convexPackageJsonPath)) return;
7145
- const convexPackageJson = await fs.readJson(convexPackageJsonPath);
7146
- convexPackageJson.name = `@${options.projectName}/backend`;
7147
- convexPackageJson.scripts = convexPackageJson.scripts || {};
7148
- await fs.writeJson(convexPackageJsonPath, convexPackageJson, { spaces: 2 });
7149
- }
7150
-
7151
4010
  //#endregion
7152
4011
  //#region src/helpers/core/create-project.ts
7153
4012
  async function createProject(options, cliInput = {}) {
7154
4013
  const projectDir = options.projectDir;
7155
4014
  const isConvex = options.backend === "convex";
7156
- const isSelfBackend = options.backend === "self";
7157
- const needsServerSetup = !isConvex && !isSelfBackend;
7158
4015
  try {
7159
4016
  await fs.ensureDir(projectDir);
7160
- await copyBaseTemplate(projectDir, options);
7161
- await setupFrontendTemplates(projectDir, options);
7162
- await setupBackendFramework(projectDir, options);
7163
- if (needsServerSetup || isSelfBackend && options.dbSetup === "docker") await setupDockerComposeTemplates(projectDir, options);
7164
- await setupAuthTemplate(projectDir, options);
7165
- if (options.payments && options.payments !== "none") await setupPaymentsTemplate(projectDir, options);
7166
- if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamplesTemplate(projectDir, options);
7167
- await setupAddonsTemplate(projectDir, options);
7168
- await setupDeploymentTemplates(projectDir, options);
7169
- await setupEnvPackageDependencies(projectDir, options);
7170
- if (options.serverDeploy === "cloudflare" || options.webDeploy === "cloudflare") await setupInfraPackageDependencies(projectDir, options);
7171
- await setupApi(options);
7172
- if (isConvex || needsServerSetup) await setupBackendDependencies(options);
7173
- if (!isConvex) {
7174
- if (needsServerSetup) await setupRuntime(options);
7175
- await setupDatabase(options, cliInput);
7176
- }
7177
- if (options.examples.length > 0 && options.examples[0] !== "none") await setupExamples(options);
4017
+ const result = await generateVirtualProject({
4018
+ config: options,
4019
+ templates: EMBEDDED_TEMPLATES
4020
+ });
4021
+ if (!result.success || !result.tree) throw new Error(result.error || "Failed to generate project templates");
4022
+ await writeTreeToFilesystem(result.tree, projectDir);
4023
+ await setPackageManagerVersion(projectDir, options.packageManager);
4024
+ if (!isConvex && options.database !== "none") await setupDatabase(options, cliInput);
7178
4025
  if (options.addons.length > 0 && options.addons[0] !== "none") await setupAddons(options);
7179
- if (options.auth && options.auth !== "none") await setupAuth(options);
7180
- if (options.payments && options.payments !== "none") await setupPayments(options);
7181
- await handleExtras(projectDir, options);
7182
- await setupEnvironmentVariables(options);
7183
- await updatePackageConfigurations(projectDir, options);
7184
- await setupWebDeploy(options);
7185
- await setupServerDeploy(options);
7186
- await setupCatalogs(projectDir, options);
7187
- await createReadme(projectDir, options);
7188
4026
  await writeBtsConfig(options);
4027
+ await formatProject(projectDir);
7189
4028
  if (!isSilent()) log.success("Project template successfully scaffolded!");
7190
4029
  if (options.install) await installDependencies({
7191
4030
  projectDir,
@@ -7207,6 +4046,21 @@ async function createProject(options, cliInput = {}) {
7207
4046
  }
7208
4047
  }
7209
4048
  }
4049
+ async function setPackageManagerVersion(projectDir, packageManager) {
4050
+ const pkgJsonPath = path.join(projectDir, "package.json");
4051
+ if (!await fs.pathExists(pkgJsonPath)) return;
4052
+ try {
4053
+ const { stdout } = await $`${packageManager} -v`;
4054
+ const version = stdout.trim();
4055
+ const pkgJson = await fs.readJson(pkgJsonPath);
4056
+ pkgJson.packageManager = `${packageManager}@${version}`;
4057
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4058
+ } catch {
4059
+ const pkgJson = await fs.readJson(pkgJsonPath);
4060
+ delete pkgJson.packageManager;
4061
+ await fs.writeJson(pkgJsonPath, pkgJson, { spaces: 2 });
4062
+ }
4063
+ }
7210
4064
 
7211
4065
  //#endregion
7212
4066
  //#region src/helpers/core/command-handlers.ts
@@ -7412,68 +4266,6 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
7412
4266
  default: throw new Error(`Unknown directory conflict strategy: ${strategy}`);
7413
4267
  }
7414
4268
  }
7415
- async function addAddonsHandler(input) {
7416
- try {
7417
- const projectDir = input.projectDir || process.cwd();
7418
- const detectedConfig = await detectProjectConfig(projectDir);
7419
- if (!detectedConfig) exitWithError("Could not detect project configuration. Please ensure this is a valid Better-T-Stack project.");
7420
- if (!input.addons || input.addons.length === 0) {
7421
- const addonsPrompt = await getAddonsToAdd(detectedConfig.frontend || [], detectedConfig.addons || [], detectedConfig.auth);
7422
- if (addonsPrompt.length > 0) input.addons = addonsPrompt;
7423
- }
7424
- if (!input.webDeploy) {
7425
- const deploymentPrompt = await getDeploymentToAdd(detectedConfig.frontend || [], detectedConfig.webDeploy);
7426
- if (deploymentPrompt !== "none") input.webDeploy = deploymentPrompt;
7427
- }
7428
- if (!input.serverDeploy) {
7429
- const serverDeploymentPrompt = await getServerDeploymentToAdd(detectedConfig.runtime, detectedConfig.serverDeploy, detectedConfig.backend);
7430
- if (serverDeploymentPrompt !== "none") input.serverDeploy = serverDeploymentPrompt;
7431
- }
7432
- const packageManager = input.packageManager || detectedConfig.packageManager || "npm";
7433
- let somethingAdded = false;
7434
- if (input.addons && input.addons.length > 0) {
7435
- await addAddonsToProject({
7436
- ...input,
7437
- install: false,
7438
- suppressInstallMessage: true,
7439
- addons: input.addons
7440
- });
7441
- somethingAdded = true;
7442
- }
7443
- if (input.webDeploy && input.webDeploy !== "none") {
7444
- await addDeploymentToProject({
7445
- ...input,
7446
- install: false,
7447
- suppressInstallMessage: true,
7448
- webDeploy: input.webDeploy
7449
- });
7450
- somethingAdded = true;
7451
- }
7452
- if (input.serverDeploy && input.serverDeploy !== "none") {
7453
- await addDeploymentToProject({
7454
- ...input,
7455
- install: false,
7456
- suppressInstallMessage: true,
7457
- serverDeploy: input.serverDeploy
7458
- });
7459
- somethingAdded = true;
7460
- }
7461
- if (!somethingAdded) {
7462
- outro(pc.yellow("No addons or deployment configurations to add."));
7463
- return;
7464
- }
7465
- if (input.install) await installDependencies({
7466
- projectDir,
7467
- packageManager
7468
- });
7469
- else log.info(`Run ${pc.bold(`${packageManager} install`)} to install dependencies`);
7470
- outro("Add command completed successfully!");
7471
- } catch (error) {
7472
- if (error instanceof UserCancelledError) return;
7473
- if (error instanceof CLIError) throw error;
7474
- handleError(error, "Failed to add addons or deployment");
7475
- }
7476
- }
7477
4269
 
7478
4270
  //#endregion
7479
4271
  //#region src/utils/open-url.ts
@@ -7571,17 +4363,6 @@ const router = os.router({
7571
4363
  });
7572
4364
  if (options.verbose) return result;
7573
4365
  }),
7574
- add: os.meta({ description: "Add addons or deployment configurations to an existing Better-T-Stack project" }).input(z.tuple([z.object({
7575
- addons: z.array(types_exports.AddonsSchema).optional().default([]),
7576
- webDeploy: types_exports.WebDeploySchema.optional(),
7577
- serverDeploy: types_exports.ServerDeploySchema.optional(),
7578
- projectDir: z.string().optional(),
7579
- install: z.boolean().optional().default(false).describe("Install dependencies after adding addons or deployment"),
7580
- packageManager: types_exports.PackageManagerSchema.optional()
7581
- })])).handler(async ({ input }) => {
7582
- const [options] = input;
7583
- await addAddonsHandler(options);
7584
- }),
7585
4366
  sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
7586
4367
  try {
7587
4368
  renderTitle();
@@ -7672,6 +4453,69 @@ async function docs() {
7672
4453
  async function builder() {
7673
4454
  return caller.builder();
7674
4455
  }
4456
+ /**
4457
+ * Programmatic API to generate a project in-memory (virtual filesystem).
4458
+ * Returns a VirtualFileTree without writing to disk.
4459
+ * Useful for web previews and testing.
4460
+ *
4461
+ * @example
4462
+ * ```typescript
4463
+ * import { createVirtual, EMBEDDED_TEMPLATES } from "create-better-t-stack";
4464
+ *
4465
+ * const result = await createVirtual({
4466
+ * frontend: ["tanstack-router"],
4467
+ * backend: "hono",
4468
+ * runtime: "bun",
4469
+ * database: "sqlite",
4470
+ * orm: "drizzle",
4471
+ * });
4472
+ *
4473
+ * if (result.success) {
4474
+ * console.log(`Generated ${result.tree.fileCount} files`);
4475
+ * }
4476
+ * ```
4477
+ */
4478
+ async function createVirtual(options) {
4479
+ try {
4480
+ const result = await generateVirtualProject({
4481
+ config: {
4482
+ projectName: options.projectName || "my-project",
4483
+ projectDir: "/virtual",
4484
+ relativePath: "./virtual",
4485
+ database: options.database || "none",
4486
+ orm: options.orm || "none",
4487
+ backend: options.backend || "hono",
4488
+ runtime: options.runtime || "bun",
4489
+ frontend: options.frontend || ["tanstack-router"],
4490
+ addons: options.addons || [],
4491
+ examples: options.examples || [],
4492
+ auth: options.auth || "none",
4493
+ payments: options.payments || "none",
4494
+ git: options.git ?? false,
4495
+ packageManager: options.packageManager || "bun",
4496
+ install: false,
4497
+ dbSetup: options.dbSetup || "none",
4498
+ api: options.api || "trpc",
4499
+ webDeploy: options.webDeploy || "none",
4500
+ serverDeploy: options.serverDeploy || "none"
4501
+ },
4502
+ templates: EMBEDDED_TEMPLATES
4503
+ });
4504
+ if (result.success && result.tree) return {
4505
+ success: true,
4506
+ tree: result.tree
4507
+ };
4508
+ return {
4509
+ success: false,
4510
+ error: result.error || "Unknown error"
4511
+ };
4512
+ } catch (error) {
4513
+ return {
4514
+ success: false,
4515
+ error: error instanceof Error ? error.message : String(error)
4516
+ };
4517
+ }
4518
+ }
7675
4519
 
7676
4520
  //#endregion
7677
- export { router as a, docs as i, create as n, sponsors as o, createBtsCli as r, builder as t };
4521
+ export { create as a, docs as c, sponsors as d, builder as i, generateVirtualProject$1 as l, TEMPLATE_COUNT as n, createBtsCli as o, VirtualFileSystem as r, createVirtual as s, EMBEDDED_TEMPLATES$1 as t, router as u };