create-ej-app 26.0.0 → 27.0.0

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 (357) hide show
  1. package/README.md +125 -226
  2. package/cli.ts +79 -29
  3. package/package.json +2 -2
  4. package/template/api/.vscode/settings.json +1 -1
  5. package/template/api/{.github/copilot-instructions.md → AGENTS.md} +0 -2
  6. package/template/api/CLAUDE.md +1 -0
  7. package/template/api/package.json +10 -10
  8. package/template/nextjs/.dockerignore +1 -0
  9. package/template/nextjs/AGENTS.md +260 -0
  10. package/template/nextjs/CLAUDE.md +1 -0
  11. package/template/nextjs/README.md +109 -0
  12. package/template/nextjs/bunfig.toml +3 -0
  13. package/template/nextjs/eslint.config.mjs +6 -5
  14. package/template/nextjs/gitignore +6 -1
  15. package/template/nextjs/next.config.ts +8 -1
  16. package/template/nextjs/package.json +46 -39
  17. package/template/nextjs/public/logo-dark.svg +4 -0
  18. package/template/nextjs/public/logo.svg +4 -0
  19. package/template/nextjs/src/app/(admin)/actions/users.ts +183 -0
  20. package/template/nextjs/src/app/(admin)/admin/_components/admin-header.tsx +24 -0
  21. package/template/nextjs/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
  22. package/template/nextjs/src/app/(admin)/admin/_components/admin-sidebar.tsx +42 -0
  23. package/template/nextjs/src/app/(admin)/admin/layout.tsx +53 -0
  24. package/template/nextjs/src/app/(admin)/admin/page.tsx +11 -2
  25. package/template/nextjs/src/app/(admin)/admin/settings/layout.tsx +24 -0
  26. package/template/nextjs/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
  27. package/template/nextjs/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
  28. package/template/nextjs/src/app/(admin)/admin/settings/users/page.tsx +377 -0
  29. package/template/nextjs/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
  30. package/template/nextjs/src/app/(admin)/admin/settings/users/validation.ts +23 -0
  31. package/template/nextjs/src/app/(app)/app/layout.tsx +44 -0
  32. package/template/nextjs/src/app/(app)/app/page.tsx +14 -2
  33. package/template/nextjs/src/app/(auth)/(account)/layout.tsx +64 -0
  34. package/template/nextjs/src/app/(auth)/(account)/profile/page.tsx +16 -0
  35. package/template/nextjs/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
  36. package/template/nextjs/src/app/(auth)/login/email-login-form.tsx +56 -29
  37. package/template/nextjs/src/app/(auth)/login/page.tsx +45 -12
  38. package/template/nextjs/src/app/(auth)/login/social-login-buttons.tsx +85 -17
  39. package/template/nextjs/src/app/(marketing)/layout.tsx +35 -4
  40. package/template/nextjs/src/app/(marketing)/legal.tsx +56 -0
  41. package/template/nextjs/src/app/(marketing)/page.tsx +20 -2
  42. package/template/nextjs/src/app/(marketing)/privacy/page.tsx +43 -2
  43. package/template/nextjs/src/app/(marketing)/terms/page.tsx +33 -2
  44. package/template/nextjs/src/app/error.tsx +37 -0
  45. package/template/nextjs/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
  46. package/template/nextjs/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
  47. package/template/nextjs/src/app/fonts/index.ts +60 -0
  48. package/template/nextjs/src/app/global-error.tsx +38 -0
  49. package/template/nextjs/src/app/globals.css +29 -0
  50. package/template/nextjs/src/app/layout.tsx +45 -18
  51. package/template/nextjs/src/app/not-found.tsx +25 -3
  52. package/template/nextjs/src/app/robots.ts +29 -0
  53. package/template/nextjs/src/app/sitemap.ts +21 -0
  54. package/template/nextjs/src/components/confirm-dialog.tsx +79 -0
  55. package/template/nextjs/src/components/dashboard-header.tsx +70 -0
  56. package/template/nextjs/src/components/data-table/enum.ts +5 -0
  57. package/template/nextjs/src/components/data-table/features.ts +49 -0
  58. package/template/nextjs/src/components/data-table/index.tsx +961 -0
  59. package/template/nextjs/src/components/data-table/types.ts +76 -0
  60. package/template/nextjs/src/components/error.tsx +71 -0
  61. package/template/nextjs/src/components/loader.tsx +18 -2
  62. package/template/nextjs/src/components/logo.tsx +49 -0
  63. package/template/nextjs/src/components/navigation-search.tsx +140 -0
  64. package/template/nextjs/src/components/navigation-sidebar.tsx +241 -0
  65. package/template/nextjs/src/components/page-heading.tsx +38 -0
  66. package/template/nextjs/src/components/react-query-provider.tsx +41 -2
  67. package/template/nextjs/src/components/route-error.tsx +55 -0
  68. package/template/nextjs/src/components/screen-size.tsx +36 -0
  69. package/template/nextjs/src/components/site-footer.tsx +20 -4
  70. package/template/nextjs/src/components/theme-provider.tsx +48 -0
  71. package/template/nextjs/src/components/theme-toggle.tsx +58 -5
  72. package/template/nextjs/src/components/ui/alert-dialog.tsx +199 -0
  73. package/template/nextjs/src/components/ui/avatar.tsx +112 -0
  74. package/template/nextjs/src/components/ui/badge.tsx +54 -0
  75. package/template/nextjs/src/components/ui/button.tsx +3 -1
  76. package/template/nextjs/src/components/ui/card.tsx +4 -4
  77. package/template/nextjs/src/components/ui/collapsible.tsx +33 -0
  78. package/template/nextjs/src/components/ui/command.tsx +192 -0
  79. package/template/nextjs/src/components/ui/dialog.tsx +164 -0
  80. package/template/nextjs/src/components/ui/dropdown-menu.tsx +273 -0
  81. package/template/nextjs/src/components/ui/input-group.tsx +156 -0
  82. package/template/nextjs/src/components/ui/select.tsx +195 -0
  83. package/template/nextjs/src/components/ui/sheet.tsx +143 -0
  84. package/template/nextjs/src/components/ui/sidebar.tsx +702 -0
  85. package/template/nextjs/src/components/ui/skeleton.tsx +13 -0
  86. package/template/nextjs/src/components/ui/table.tsx +116 -0
  87. package/template/nextjs/src/components/ui/textarea.tsx +18 -0
  88. package/template/nextjs/src/components/ui/tooltip.tsx +57 -0
  89. package/template/nextjs/src/components/user-menu.tsx +206 -0
  90. package/template/nextjs/src/db/common.ts +10 -0
  91. package/template/nextjs/src/db/index.ts +16 -6
  92. package/template/nextjs/src/db/relations.ts +21 -0
  93. package/template/nextjs/src/db/schema.ts +85 -46
  94. package/template/nextjs/src/emails/components/email-layout.tsx +27 -11
  95. package/template/nextjs/src/emails/magic-link.tsx +15 -13
  96. package/template/nextjs/src/emails/test.tsx +11 -9
  97. package/template/nextjs/src/emails/welcome.tsx +12 -9
  98. package/template/nextjs/src/env.ts +1 -1
  99. package/template/nextjs/src/hooks/use-mobile.ts +14 -12
  100. package/template/nextjs/src/instrumentation.ts +20 -2
  101. package/template/nextjs/src/lib/actions.ts +55 -0
  102. package/template/nextjs/src/lib/admin-navigation.ts +43 -0
  103. package/template/nextjs/src/lib/auth-client.ts +20 -9
  104. package/template/nextjs/src/lib/auth-permissions.ts +61 -0
  105. package/template/nextjs/src/lib/auth.ts +33 -3
  106. package/template/nextjs/src/lib/callback-url.ts +28 -0
  107. package/template/nextjs/src/lib/constants.ts +21 -0
  108. package/template/nextjs/src/lib/delay.ts +10 -0
  109. package/template/nextjs/src/lib/email-service.ts +21 -0
  110. package/template/nextjs/src/lib/errors.ts +74 -0
  111. package/template/nextjs/src/lib/format-currency.ts +22 -24
  112. package/template/nextjs/src/lib/format-date.ts +69 -6
  113. package/template/nextjs/src/lib/nodemailer.ts +27 -3
  114. package/template/nextjs/src/lib/otel.ts +92 -2
  115. package/template/nextjs/src/lib/safe-action-client.ts +60 -9
  116. package/template/nextjs/src/lib/safe-action.ts +152 -16
  117. package/template/nextjs/src/lib/search.ts +16 -0
  118. package/template/nextjs/src/lib/seo.ts +7 -0
  119. package/template/nextjs/src/lib/siteConfig.ts +17 -1
  120. package/template/nextjs/src/lib/toast-message.ts +87 -1
  121. package/template/nextjs/src/lib/validations.ts +30 -0
  122. package/template/nextjs/src/proxy.ts +48 -0
  123. package/template/nextjs/src/scripts/reset-db.ts +22 -0
  124. package/template/nextjs/src/scripts/seed.ts +59 -8
  125. package/template/nextjs/src/scripts/test-mail.ts +28 -20
  126. package/template/nextjs/tests/admin/users.test.ts +64 -0
  127. package/template/nextjs/tests/db/fk-indexes.test.ts +62 -0
  128. package/template/nextjs/tests/db/seed.test.ts +49 -0
  129. package/template/nextjs/tests/helpers/action.ts +71 -0
  130. package/template/nextjs/tests/helpers/fixtures.ts +78 -0
  131. package/template/nextjs/tests/helpers/index.ts +2 -0
  132. package/template/nextjs/tests/lib/callback-url.test.ts +48 -0
  133. package/template/nextjs/tests/lib/client-server-boundary.test.ts +57 -0
  134. package/template/nextjs/tests/lib/errors.test.ts +10 -0
  135. package/template/nextjs/tests/lib/search.test.ts +23 -0
  136. package/template/nextjs/tests/setup/pglite-db.ts +63 -0
  137. package/template/nextjs/tests/setup/preload.ts +95 -0
  138. package/template/nextjs/tsconfig.json +1 -0
  139. package/template/saas-starter/.dockerignore +1 -0
  140. package/template/saas-starter/.env.example +15 -15
  141. package/template/saas-starter/AGENTS.md +302 -0
  142. package/template/saas-starter/CLAUDE.md +1 -0
  143. package/template/saas-starter/README.md +124 -2
  144. package/template/saas-starter/bunfig.toml +3 -0
  145. package/template/saas-starter/eslint.config.mjs +6 -5
  146. package/template/saas-starter/gitignore +6 -1
  147. package/template/saas-starter/next.config.ts +19 -1
  148. package/template/saas-starter/package.json +49 -44
  149. package/template/saas-starter/public/logo-dark.svg +4 -0
  150. package/template/saas-starter/public/logo.svg +4 -0
  151. package/template/saas-starter/src/app/(admin)/actions/contact-leads.ts +51 -0
  152. package/template/saas-starter/src/app/(admin)/actions/feedbacks.ts +91 -0
  153. package/template/saas-starter/src/app/(admin)/actions/users.ts +145 -59
  154. package/template/saas-starter/src/app/(admin)/admin/_components/admin-header.tsx +4 -1
  155. package/template/saas-starter/src/app/(admin)/admin/_components/admin-sidebar.tsx +12 -10
  156. package/template/saas-starter/src/app/(admin)/admin/contact-leads/page.tsx +123 -0
  157. package/template/saas-starter/src/app/(admin)/admin/feedbacks/page.tsx +161 -0
  158. package/template/saas-starter/src/app/(admin)/admin/layout.tsx +24 -28
  159. package/template/saas-starter/src/app/(admin)/admin/page.tsx +59 -2
  160. package/template/saas-starter/src/app/(admin)/admin/settings/layout.tsx +24 -0
  161. package/template/saas-starter/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
  162. package/template/saas-starter/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
  163. package/template/saas-starter/src/app/(admin)/admin/settings/users/page.tsx +372 -10
  164. package/template/saas-starter/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
  165. package/template/saas-starter/src/app/(admin)/admin/settings/users/validation.ts +23 -0
  166. package/template/saas-starter/src/app/(admin)/admin/support-tickets/[ticketId]/page.tsx +17 -0
  167. package/template/saas-starter/src/app/(admin)/admin/support-tickets/page.tsx +5 -0
  168. package/template/saas-starter/src/app/(app)/actions/billing.ts +171 -107
  169. package/template/saas-starter/src/app/(app)/actions/feedbacks.ts +28 -0
  170. package/template/saas-starter/src/app/(app)/actions/feedbacks.validation.ts +12 -0
  171. package/template/saas-starter/src/app/(app)/actions/members.ts +149 -123
  172. package/template/saas-starter/src/app/(app)/actions/organizations.ts +230 -0
  173. package/template/saas-starter/src/app/(app)/actions/organizations.validation.ts +17 -0
  174. package/template/saas-starter/src/app/(app)/app/[orgId]/dashboard/page.tsx +6 -9
  175. package/template/saas-starter/src/app/(app)/app/[orgId]/layout.tsx +6 -9
  176. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/billing-email-form.tsx +45 -53
  177. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/danger-zone.tsx +40 -47
  178. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/general-settings-form.tsx +80 -105
  179. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/plans-section.tsx +72 -82
  180. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/billing/page.tsx +51 -52
  181. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/layout.tsx +24 -0
  182. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/invite-member-dialog.tsx +15 -16
  183. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/members-tabs.tsx +77 -44
  184. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/page.tsx +11 -18
  185. package/template/saas-starter/src/app/(app)/app/[orgId]/settings/page.tsx +14 -28
  186. package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/[ticketId]/page.tsx +25 -0
  187. package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/page.tsx +25 -0
  188. package/template/saas-starter/src/app/(app)/app/_components/app-header.tsx +15 -13
  189. package/template/saas-starter/src/app/(app)/app/_components/app-search.tsx +15 -6
  190. package/template/saas-starter/src/app/(app)/app/_components/app-sidebar.tsx +6 -3
  191. package/template/saas-starter/src/app/(app)/app/_components/feedback-dialog.tsx +108 -0
  192. package/template/saas-starter/src/app/(app)/app/_components/organization-context.tsx +5 -9
  193. package/template/saas-starter/src/app/(app)/app/_components/organization-switcher.tsx +68 -8
  194. package/template/saas-starter/src/app/(app)/app/layout.tsx +44 -32
  195. package/template/saas-starter/src/app/(app)/app/onboarding/_components/create-organization-form.tsx +75 -87
  196. package/template/saas-starter/src/app/(app)/app/onboarding/page.tsx +10 -12
  197. package/template/saas-starter/src/app/(app)/app/page.tsx +16 -21
  198. package/template/saas-starter/src/app/(auth)/(account)/layout.tsx +64 -0
  199. package/template/saas-starter/src/app/(auth)/(account)/profile/page.tsx +16 -0
  200. package/template/saas-starter/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
  201. package/template/saas-starter/src/app/(auth)/(account)/support/[ticketId]/page.tsx +19 -0
  202. package/template/saas-starter/src/app/(auth)/(account)/support/page.tsx +9 -0
  203. package/template/saas-starter/src/app/(auth)/login/email-login-form.tsx +49 -27
  204. package/template/saas-starter/src/app/(auth)/login/page.tsx +39 -21
  205. package/template/saas-starter/src/app/(auth)/login/social-login-buttons.tsx +80 -18
  206. package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/accept-invitation-form.tsx +16 -10
  207. package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/page.tsx +28 -42
  208. package/template/saas-starter/src/app/(invitation)/actions/invitations.ts +32 -0
  209. package/template/saas-starter/src/app/(marketing)/contact-us/actions.ts +23 -0
  210. package/template/saas-starter/src/app/(marketing)/contact-us/contact-form.tsx +118 -0
  211. package/template/saas-starter/src/app/(marketing)/contact-us/page.tsx +20 -0
  212. package/template/saas-starter/src/app/(marketing)/contact-us/validation.ts +22 -0
  213. package/template/saas-starter/src/app/(marketing)/layout.tsx +38 -4
  214. package/template/saas-starter/src/app/(marketing)/legal.tsx +66 -0
  215. package/template/saas-starter/src/app/(marketing)/page.tsx +23 -2
  216. package/template/saas-starter/src/app/(marketing)/privacy/page.tsx +62 -2
  217. package/template/saas-starter/src/app/(marketing)/terms/page.tsx +40 -2
  218. package/template/saas-starter/src/app/actions/support-tickets.ts +510 -0
  219. package/template/saas-starter/src/app/actions/support-tickets.validation.ts +112 -0
  220. package/template/saas-starter/src/app/actions/uploads.ts +36 -0
  221. package/template/saas-starter/src/app/actions/uploads.validation.ts +24 -0
  222. package/template/saas-starter/src/app/error.tsx +37 -0
  223. package/template/saas-starter/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
  224. package/template/saas-starter/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
  225. package/template/saas-starter/src/app/fonts/index.ts +60 -0
  226. package/template/saas-starter/src/app/global-error.tsx +38 -0
  227. package/template/saas-starter/src/app/globals.css +29 -0
  228. package/template/saas-starter/src/app/layout.tsx +43 -19
  229. package/template/saas-starter/src/app/not-found.tsx +25 -3
  230. package/template/saas-starter/src/app/robots.ts +14 -10
  231. package/template/saas-starter/src/app/sitemap.ts +2 -1
  232. package/template/saas-starter/src/components/confirm-dialog.tsx +79 -0
  233. package/template/saas-starter/src/components/dashboard-header.tsx +7 -1
  234. package/template/saas-starter/src/components/data-table/features.ts +49 -0
  235. package/template/saas-starter/src/components/data-table/index.tsx +65 -64
  236. package/template/saas-starter/src/components/data-table/types.ts +5 -4
  237. package/template/saas-starter/src/components/error.tsx +45 -0
  238. package/template/saas-starter/src/components/file-upload.tsx +65 -26
  239. package/template/saas-starter/src/components/loader.tsx +18 -2
  240. package/template/saas-starter/src/components/logo.tsx +49 -0
  241. package/template/saas-starter/src/components/navigation-search.tsx +2 -2
  242. package/template/saas-starter/src/components/page-heading.tsx +6 -0
  243. package/template/saas-starter/src/components/react-query-provider.tsx +41 -2
  244. package/template/saas-starter/src/components/route-error.tsx +55 -0
  245. package/template/saas-starter/src/components/screen-size.tsx +36 -0
  246. package/template/saas-starter/src/components/site-footer.tsx +20 -4
  247. package/template/saas-starter/src/components/support/create-support-ticket-dialog.tsx +122 -0
  248. package/template/saas-starter/src/components/support/support-ticket-conversation.tsx +236 -0
  249. package/template/saas-starter/src/components/support/support-ticket-shared.tsx +58 -0
  250. package/template/saas-starter/src/components/support/support-tickets-table.tsx +281 -0
  251. package/template/saas-starter/src/components/theme-provider.tsx +48 -0
  252. package/template/saas-starter/src/components/theme-toggle.tsx +58 -5
  253. package/template/saas-starter/src/components/ui/alert-dialog.tsx +199 -0
  254. package/template/saas-starter/src/components/ui/badge.tsx +6 -1
  255. package/template/saas-starter/src/components/ui/button.tsx +2 -0
  256. package/template/saas-starter/src/components/ui/command.tsx +1 -1
  257. package/template/saas-starter/src/components/user-menu.tsx +147 -24
  258. package/template/saas-starter/src/db/common.ts +33 -1
  259. package/template/saas-starter/src/db/enums.ts +24 -0
  260. package/template/saas-starter/src/db/index.ts +14 -5
  261. package/template/saas-starter/src/db/relations.ts +40 -3
  262. package/template/saas-starter/src/db/schema.ts +186 -35
  263. package/template/saas-starter/src/emails/components/email-layout.tsx +27 -11
  264. package/template/saas-starter/src/emails/invitation.tsx +34 -21
  265. package/template/saas-starter/src/emails/magic-link.tsx +15 -13
  266. package/template/saas-starter/src/emails/test.tsx +11 -9
  267. package/template/saas-starter/src/emails/welcome.tsx +12 -9
  268. package/template/saas-starter/src/env.ts +1 -1
  269. package/template/saas-starter/src/hooks/use-mobile.ts +14 -12
  270. package/template/saas-starter/src/instrumentation.ts +20 -2
  271. package/template/saas-starter/src/lib/actions.ts +34 -0
  272. package/template/saas-starter/src/lib/admin-navigation.ts +33 -1
  273. package/template/saas-starter/src/lib/app-navigation.ts +45 -41
  274. package/template/saas-starter/src/lib/auth-client.ts +8 -3
  275. package/template/saas-starter/src/lib/auth-permissions.ts +50 -11
  276. package/template/saas-starter/src/lib/auth.ts +122 -21
  277. package/template/saas-starter/src/lib/billing.ts +59 -24
  278. package/template/saas-starter/src/lib/callback-url.ts +28 -0
  279. package/template/saas-starter/src/lib/constants.ts +78 -9
  280. package/template/saas-starter/src/lib/delay.ts +10 -0
  281. package/template/saas-starter/src/lib/email-service.ts +30 -1
  282. package/template/saas-starter/src/lib/errors.ts +74 -0
  283. package/template/saas-starter/src/lib/format-currency.ts +22 -24
  284. package/template/saas-starter/src/lib/format-date.ts +69 -6
  285. package/template/saas-starter/src/lib/image-url.ts +54 -0
  286. package/template/saas-starter/src/lib/nodemailer.ts +27 -3
  287. package/template/saas-starter/src/lib/organization-access.ts +56 -76
  288. package/template/saas-starter/src/lib/otel.ts +92 -2
  289. package/template/saas-starter/src/lib/plan-limits.ts +37 -0
  290. package/template/saas-starter/src/lib/plans.ts +40 -13
  291. package/template/saas-starter/src/lib/rbac.ts +44 -0
  292. package/template/saas-starter/src/lib/safe-action-client.ts +60 -9
  293. package/template/saas-starter/src/lib/safe-action.ts +137 -17
  294. package/template/saas-starter/src/lib/search.ts +16 -0
  295. package/template/saas-starter/src/lib/seo.ts +7 -0
  296. package/template/saas-starter/src/lib/siteConfig.ts +17 -1
  297. package/template/saas-starter/src/lib/storage.ts +176 -46
  298. package/template/saas-starter/src/lib/stripe.ts +4 -1
  299. package/template/saas-starter/src/lib/toast-message.ts +87 -1
  300. package/template/saas-starter/src/lib/validations.ts +85 -2
  301. package/template/saas-starter/src/proxy.ts +48 -0
  302. package/template/saas-starter/src/scripts/cleanup-temp-uploads.ts +24 -0
  303. package/template/saas-starter/src/scripts/seed.ts +82 -8
  304. package/template/saas-starter/src/scripts/setup-db.ts +72 -0
  305. package/template/saas-starter/src/scripts/setup-stripe-plans.ts +13 -7
  306. package/template/saas-starter/src/scripts/test-mail.ts +28 -20
  307. package/template/saas-starter/tests/admin/users.test.ts +64 -0
  308. package/template/saas-starter/tests/db/fk-indexes.test.ts +62 -0
  309. package/template/saas-starter/tests/db/seed.test.ts +57 -0
  310. package/template/saas-starter/tests/helpers/action.ts +71 -0
  311. package/template/saas-starter/tests/helpers/fixtures.ts +151 -0
  312. package/template/saas-starter/tests/helpers/index.ts +2 -0
  313. package/template/saas-starter/tests/lib/callback-url.test.ts +48 -0
  314. package/template/saas-starter/tests/lib/client-server-boundary.test.ts +57 -0
  315. package/template/saas-starter/tests/lib/errors.test.ts +10 -0
  316. package/template/saas-starter/tests/lib/image-url.test.ts +31 -0
  317. package/template/saas-starter/tests/lib/search.test.ts +23 -0
  318. package/template/saas-starter/tests/marketing/contact-and-feedback.test.ts +86 -0
  319. package/template/saas-starter/tests/organizations/organizations.test.ts +175 -0
  320. package/template/saas-starter/tests/setup/pglite-db.ts +67 -0
  321. package/template/saas-starter/tests/setup/preload.ts +108 -0
  322. package/template/saas-starter/tests/support/support-tickets.test.ts +145 -0
  323. package/template/saas-starter/tsconfig.json +1 -0
  324. package/cli.js +0 -147
  325. package/template/nextjs/src/app/(admin)/layout.tsx +0 -20
  326. package/template/nextjs/src/app/(app)/layout.tsx +0 -20
  327. package/template/nextjs/terraform/artifact-registry.tf +0 -5
  328. package/template/nextjs/terraform/backend-service.tf +0 -13
  329. package/template/nextjs/terraform/cleanup.sh +0 -9
  330. package/template/nextjs/terraform/cloudbuild.tf +0 -70
  331. package/template/nextjs/terraform/cloudrun.tf +0 -68
  332. package/template/nextjs/terraform/database.tf +0 -60
  333. package/template/nextjs/terraform/forwarding-rule.tf +0 -43
  334. package/template/nextjs/terraform/gitignore +0 -4
  335. package/template/nextjs/terraform/ip-address.tf +0 -11
  336. package/template/nextjs/terraform/network-endpoint-group.tf +0 -11
  337. package/template/nextjs/terraform/network.tf +0 -21
  338. package/template/nextjs/terraform/pre.sh +0 -3
  339. package/template/nextjs/terraform/providers.tf +0 -18
  340. package/template/nextjs/terraform/secret.txt +0 -11
  341. package/template/nextjs/terraform/setup.sh +0 -25
  342. package/template/nextjs/terraform/ssl-certs.tf +0 -32
  343. package/template/nextjs/terraform/target-https-proxy copy.tf +0 -14
  344. package/template/nextjs/terraform/target-https-proxy.tf +0 -14
  345. package/template/nextjs/terraform/terraform.tfvars +0 -4
  346. package/template/nextjs/terraform/url-map.tf +0 -23
  347. package/template/nextjs/terraform/variables.tf +0 -21
  348. package/template/saas-starter/next-env.d.ts +0 -6
  349. package/template/saas-starter/src/actions/uploads.ts +0 -28
  350. package/template/saas-starter/src/actions/uploads.validation.ts +0 -14
  351. package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/create-user-dialog.tsx +0 -183
  352. package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/edit-name-dialog.tsx +0 -129
  353. package/template/saas-starter/src/app/(admin)/admin/settings/users/_components/users-table.tsx +0 -415
  354. package/template/saas-starter/src/app/(app)/actions/organization.ts +0 -92
  355. package/template/saas-starter/src/app/proxy.ts +0 -28
  356. package/template/saas-starter/src/components/impersonation-banner.tsx +0 -44
  357. package/template/saas-starter/src/lib/organization-plan-limits.ts +0 -38
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # create-ej-app
2
2
 
3
- A no-bullshit CLI to bootstrap production-ready **fucking beasts** of apps with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, OTEL, and deployment wired up so you don’t screw your shit up in production.
3
+ CLI to bootstrap production-ready apps with Next.js, Better Auth, Drizzle ORM, Elysia, Workflow, shadcn/ui, React Email, OpenTelemetry, and deployment already wired up.
4
4
 
5
5
  ```bash
6
6
  bunx create-ej-app@latest
@@ -8,326 +8,225 @@ bunx create-ej-app@latest
8
8
 
9
9
  ---
10
10
 
11
- ## ✨ Features (aka the good shit)
11
+ ## Features
12
12
 
13
- - **Next.js 16** – Powered by React 19, Server Components, App Router. Fast as hell.
14
-
15
- - **Better Auth** – Auth that doesn’t suck:
16
- - Email/Password
17
- - Magic link (because passwords are annoying as fuck)
18
- - OAuth with GitHub and Google
13
+ - **Next.js 16**: React 19, Server Components, App Router
14
+ - **Better Auth**:
15
+ - Magic link
16
+ - GitHub and Google OAuth
19
17
  - Account linking
20
- - Admin powers
21
-
22
- - **Drizzle ORM** – Type-safe SQL over PostgreSQL without the usual ORM garbage.
23
-
24
- - **Elysia** – Built for Bun. Minimal, fast, no nonsense.
25
-
26
- - **Workflow** – Durable jobs so your background tasks don’t randomly die like weak shit.
27
-
28
- - **React Email** – Build transactional emails using React components instead of ugly HTML nightmares.
29
-
30
- - **OTEL (OpenTelemetry)** – Production-grade observability:
31
- - Distributed tracing
32
- - Metrics
33
- - Structured logs
34
- - Works with GCP, Datadog, Honeycomb, Grafana, etc.
35
-
36
- - **shadcn/ui** – Clean, accessible components built on Radix UI.
37
-
38
- - **TailwindCSS 4** – Utility-first styling with zero CSS drama.
39
-
40
- - **TypeScript** – Strict types so you don’t ship dumb bugs.
41
-
42
- - **TanStack Query** – Server state that doesn’t implode.
43
-
44
- - **Lucide React** – Icons that don’t look like 2009 garbage.
45
-
46
- - **Docker + Terraform** – Ship this shit properly.
47
-
48
- - Terraform setup for Google Cloud Platform so you don’t YOLO infra.
18
+ - `user`, `admin`, and `superadmin` roles
19
+ - Organizations (SaaS starter)
20
+ - **Drizzle ORM**: type-safe SQL over PostgreSQL
21
+ - **Stripe**: subscriptions, trials, and the billing portal (SaaS starter)
22
+ - **Elysia**: HTTP APIs on Bun
23
+ - **Workflow**: durable background jobs
24
+ - **React Email**: transactional emails as React components
25
+ - **OpenTelemetry**: traces with one wide event per server action
26
+ - **shadcn/ui**: accessible components on Radix UI
27
+ - **Tailwind CSS 4**
28
+ - **TypeScript**: strict mode
29
+ - **TanStack Query**
30
+ - **Bun tests on PGlite**: in-memory Postgres, no database server needed
31
+ - **Docker**
49
32
 
50
33
  ---
51
34
 
52
- ## 📋 Templates (Pick your poison)
35
+ ## Templates
36
+
37
+ ### Next.js full-stack app (`nextjs`)
53
38
 
54
- Choose your template based on what you’re building:
39
+ Next.js 16, Better Auth, shadcn/ui, and Drizzle ORM. Use it for web apps that need sign-in, an admin panel, and server actions.
55
40
 
56
- ### Next.js Full-Stack App
41
+ ### SaaS starter (`saas-starter`)
57
42
 
58
- Full-stack setup with Next.js 16, Better Auth, shadcn/ui, and Drizzle ORM. Perfect for web apps that need auth, UI, and backend APIs.
43
+ Everything in `nextjs`, plus organizations with invitations and roles, Stripe subscriptions with trials and the billing portal, R2 uploads, support tickets, in-app feedback, and a contact form.
59
44
 
60
- ### API Server
45
+ ### API server (`api`)
61
46
 
62
- Production-ready REST API built with Elysia on Bun, Drizzle ORM, PostgreSQL, and Zod validation. Perfect for headless APIs, microservices, internal tools, or that backend you actually want to scale without it exploding like fragile shit.
47
+ REST API with Elysia on Bun, Drizzle ORM, PostgreSQL, and Zod validation. Use it for headless APIs, internal tools, and services.
63
48
 
64
49
  ---
65
50
 
66
- ## 🚀 Quick Start (Let’s fucking go)
51
+ ## Quick start
67
52
 
68
53
  ```bash
69
54
  bunx create-ej-app@latest
70
55
  ```
71
56
 
72
- You’ll be prompted for:
57
+ You will be prompted for:
73
58
 
74
59
  - Project name
75
60
  - Description
76
- - Template (nextjs or api)
77
- - Git init (because not using git in 2026 is wild)
61
+ - Template (`nextjs`, `saas-starter`, or `api`)
62
+ - Whether to initialize git
78
63
 
79
64
  Example:
80
65
 
81
- ```bash
82
- ? Enter the project name: badass-app
83
- ? Enter a description: My savage Next.js monster
66
+ ```text
67
+ ? Enter the project name: my-app
68
+ ? Enter a description for the project: Production-ready Next.js app
84
69
  ? Select a template: nextjs
85
70
  ? Initialize a git repository? yes
86
71
  ```
87
72
 
88
- Boom. Done.
89
-
90
73
  ---
91
74
 
92
- ## 📦 What You Actually Get
75
+ ## What you get
93
76
 
94
- ### Next.js Full-Stack Template
77
+ ### Next.js and SaaS templates
95
78
 
96
- **Tech Stack (The real weapons)**
79
+ | Layer | Stack |
80
+ | ------------- | ------------------------------------------------- |
81
+ | Framework | Next.js 16, React 19, React Compiler |
82
+ | Runtime | Bun |
83
+ | Database | PostgreSQL + Drizzle ORM |
84
+ | Auth | Better Auth: magic link, GitHub, Google, roles |
85
+ | Actions | next-safe-action with role-based clients |
86
+ | Errors | `AppError` with customer-safe messages |
87
+ | Observability | OpenTelemetry, one wide event per action |
88
+ | Email | React Email + Nodemailer, sent by Workflow |
89
+ | UI | shadcn/ui, Tailwind CSS 4, dark mode, Geist fonts |
90
+ | Data fetching | TanStack Query + nuqs URL state |
91
+ | Forms | react-hook-form + Zod |
92
+ | Tests | Bun tests on in-memory Postgres (PGlite) |
93
+ | Agent docs | `AGENTS.md` and `CLAUDE.md` with project rules |
97
94
 
98
- - **Framework**: Next.js
99
- - **Runtime**: Bun (fast as shit)
100
- - **Backend**: Elysia APIs
101
- - **Database**: PostgreSQL + Drizzle
102
- - **Auth**: Better Auth
103
- - **Emails**: React Email (transactional + auth templates ready)
104
- - **Observability**: OpenTelemetry
105
- - **Background Jobs**: Workflow
106
- - **UI**: shadcn/ui
107
- - **Styling**: TailwindCSS
108
- - **State**: TanStack Query
109
- - **Forms**: react-hook-form + Zod
110
- - **Theme**: Dark mode out of the box
95
+ Both templates ship with:
111
96
 
112
- ### API Server Template
97
+ - An admin panel with user management, impersonation, and ban controls
98
+ - Profile page, error pages, 404 page, robots.txt, and sitemap
99
+ - A seed script, a database reset script, and focused tests
100
+ - A test that fails when a client component imports server-only code
101
+ - A test that fails when a foreign key has no index or delete rule
113
102
 
114
- **Tech Stack (The real weapons)**
103
+ The SaaS starter adds:
115
104
 
116
- - **Runtime**: Bun (blazing fast as fuck)
117
- - **Framework**: Elysia
118
- - **Database**: PostgreSQL + Drizzle ORM
119
- - **Validation**: Zod v4
120
- - **API Docs**: Auto-generated OpenAPI docs
121
- - **Containerisation**: Docker with distroless images
122
- - **Type Checking**: TypeScript with strict mode
123
- - **Formatting**: Prettier
124
- - **Environment**: Validated env vars with @t3-oss/env-core
105
+ - Organizations with owner, admin, and member roles, plus email invitations
106
+ - Stripe Checkout, trials, the billing portal, and idempotent webhooks
107
+ - Per-plan member limits
108
+ - Organization logos on Cloudflare R2, with ownership checks on every upload
109
+ - Support tickets shared by organization managers and answered from the admin panel
110
+ - In-app feedback and a public contact form, each with an admin inbox
111
+
112
+ ### API server template
113
+
114
+ | Layer | Stack |
115
+ | ----------- | ------------------------------------------ |
116
+ | Runtime | Bun |
117
+ | Framework | Elysia |
118
+ | Database | PostgreSQL + Drizzle ORM |
119
+ | Validation | Zod v4 |
120
+ | API docs | Auto-generated OpenAPI |
121
+ | Containers | Docker, distroless images |
122
+ | Types | TypeScript, strict mode |
123
+ | Formatting | Prettier |
124
+ | Environment | Validated env vars with `@t3-oss/env-core` |
125
125
 
126
126
  ---
127
127
 
128
- ## 🛠 Setup & Development
128
+ ## Setup
129
129
 
130
130
  ### Prerequisites
131
131
 
132
- - Node 24+ or Bun
133
- - PostgreSQL running
134
- - OAuth creds if you want GitHub/Google login (Next.js template)
135
- - Email provider (Resend, SES, Postmark, etc.) (Next.js template)
132
+ - [Bun](https://bun.sh/): the template scripts, tests, and Dockerfiles run on it
133
+ - PostgreSQL
134
+ - OAuth credentials for GitHub and Google (Next.js and SaaS templates)
135
+ - SMTP credentials from a provider such as Resend, SES, or Postmark (Next.js and SaaS templates)
136
+ - A Stripe account and the Stripe CLI (SaaS template)
136
137
 
137
138
  ### Install
138
139
 
139
140
  ```bash
140
141
  cd my-app
141
142
  bun install
142
- # or npm / pnpm / yarn if that’s your thing
143
143
  ```
144
144
 
145
- ---
146
-
147
- ### Environment Variables
145
+ Bun is required: the templates' scripts and Dockerfiles call `bun` directly.
148
146
 
149
- Copy `.env.example` → `.env` and fill your secrets like a responsible adult.
147
+ ### Environment variables
150
148
 
151
- Don’t commit this crap. Seriously.
149
+ The CLI creates `.env` from `.env.example` with a random `BETTER_AUTH_SECRET`. Fill in the rest, and never commit `.env`.
152
150
 
153
151
  ---
154
152
 
155
- ## 💻 Development
153
+ ## Development
156
154
 
157
- ### Next.js Template
155
+ ### Next.js and SaaS templates
158
156
 
159
157
  ```bash
160
158
  bun run dev
161
159
  ```
162
160
 
163
- Open `http://localhost:3000` and admire your badass setup.
161
+ App: `http://localhost:3000`
164
162
 
165
- ### API Template
163
+ ### API template
166
164
 
167
165
  ```bash
168
166
  bun run dev
169
167
  ```
170
168
 
171
- API runs on `http://localhost:3000`. OpenAPI docs at `http://localhost:3000/docs`.
172
-
173
- Fire it up, hit the endpoints, break things on purpose, and watch it handle your shit properly.
169
+ API: `http://localhost:3000`
170
+ OpenAPI docs: `http://localhost:3000/docs`
174
171
 
175
172
  ---
176
173
 
177
- ## 🐳 Docker (Ship it like a pro)
174
+ ## Docker
178
175
 
179
176
  ```bash
180
177
  bun run build:docker
181
- docker run -p 3000:3000 my-awesome-project
182
- ```
183
-
184
- No "works on my machine" bullshit anymore.
185
-
186
- ---
187
-
188
- ## ☁️ Infrastructure (The Cloud Shit) - Next.js Template
189
-
190
- Terraform config included for:
191
-
192
- - Cloud Run
193
- - Cloud SQL
194
- - Artifact Registry
195
- - Cloud Build
196
- - Secret Manager
197
- - Load Balancer
198
- - VPC
199
-
200
- Deploy it:
201
-
202
- ```bash
203
- cd terraform
204
- terraform init
205
- terraform apply
178
+ docker run --env-file .env -p 3000:3000 my-app
206
179
  ```
207
180
 
208
- And now you’re running production infra like a grown-ass engineer.
181
+ The Next.js and SaaS images are tagged `my-app` and the API image `my-api`; change the tag in `package.json`. The build uses `.env.example` values, so pass real ones at run time with `--env-file`. Run `bun install` first so `bun.lock` exists.
209
182
 
210
183
  ---
211
184
 
212
- ## 🔐 Authentication - Next.js Template
213
-
214
- Better Auth gives you:
185
+ ## Next.js and SaaS templates
215
186
 
216
- - Email/Password login
217
- - Magic links (no password drama)
218
- - OAuth (GitHub, Google)
219
- - Admin panel
220
- - User impersonation
187
+ Each generated project documents itself. Read these first:
221
188
 
222
- Secure, extensible, no duct-taped auth spaghetti.
189
+ - `README.md`: setup steps and commands
190
+ - `AGENTS.md`: architecture, authorization rules, and writing rules for you and for coding agents
191
+ - `src/lib/siteConfig.ts`: product name, description, and contact email
192
+ - `src/db/schema.ts`: database schema
223
193
 
224
- ---
225
-
226
- ## 🎨 UI Components - Next.js Template
227
-
228
- Preinstalled:
229
-
230
- - Button
231
- - Card
232
- - Form
233
- - Input
234
- - Toast notifications
194
+ Push the schema with `bun run db:push`, seed it with `bun run db:seed`, and run the tests with `bun test`.
235
195
 
236
- Need more?
196
+ Add shadcn components with:
237
197
 
238
198
  ```bash
239
- bunx shadcn@latest add [component-name]
199
+ bunx shadcn@latest add your_component_name
240
200
  ```
241
201
 
242
- Ship beautiful shit without reinventing buttons for the 900th time.
243
-
244
- ---
245
-
246
- ## 🎯 Route Groups - Next.js Template
247
-
248
- Organized like a sane human:
249
-
250
- - `(admin)` – Admin dashboard
251
- - `(app)` – Main app (auth required)
252
- - `(auth)` – Login/signup
253
- - `(marketing)` – Public pages
254
-
255
- No messy folder hell.
256
-
257
- ---
258
-
259
- ## 🔧 Customization
260
-
261
- ### Next.js Template
262
-
263
- Edit:
264
-
265
- - `siteConfig.ts` – change name/description
266
- - `schema.ts` – change DB schema
267
- - `auth.ts` – tweak providers
268
-
269
- Then push schema and move on with your life.
270
-
271
- ### API Template
272
-
273
- Edit:
274
-
275
- - `src/db/schema.ts` – change DB schema and model your data like a boss
276
- - `src/routes/` – add new API endpoints and wire your logic properly
277
- - `src/env.ts` – add new env vars and validate that shit
278
-
279
- Push schema with `bun run db:push` and type-check with `bun run type-check`.
280
-
281
- ---
282
-
283
- ## 🏗️ API Template Architecture
284
-
285
- Built with a clean layered architecture (no spaghetti garbage):
286
-
287
- - **Routes** (`src/routes/`) – HTTP endpoints, validation, call data-access. Keep it sharp.
288
- - **Data-Access** (`src/data-access/`) – Direct DB interactions per resource. No random queries scattered like WTF.
289
- - **Database** (`src/db/`) – Drizzle client, schemas, reusable fields. Your source of truth.
290
- - **Library** (`src/lib/`) – Shared utilities and Zod schemas so you don’t duplicate crap everywhere.
291
-
292
- Includes a full CRUD API for todos with pagination, sorting, search, and OpenAPI docs so you can see your shit documented without extra effort.
293
-
294
- ### API Commands
295
-
296
- - `bun run dev` – Start dev server with hot-reload
297
- - `bun run build` – Type-check and compile to native binary
298
- - `bun run type-check` – Run TypeScript checker
299
- - `bun run db:push` – Push schema to DB
300
- - `bun run db:studio` – Open Drizzle Studio
301
- - `bun run db:seed` – Seed database
302
-
303
202
  ---
304
203
 
305
- ## 🤝 Contributing
204
+ ## API template architecture
306
205
 
307
- PRs welcome. Don’t submit half-broken shit.
206
+ - **Routes** (`src/routes/`): HTTP, validation, call data-access
207
+ - **Data-access** (`src/data-access/`): database access per resource
208
+ - **Database** (`src/db/`): Drizzle client, schemas, shared fields
209
+ - **Library** (`src/lib/`): shared utilities and Zod schemas
308
210
 
309
- ---
211
+ Includes a sample todos CRUD API with pagination, sorting, search, and OpenAPI docs.
310
212
 
311
- ## 📄 License
213
+ ### API commands
312
214
 
313
- MIT. Do whatever the hell you want, just don’t blame us if you screw it up.
215
+ | Command | Purpose |
216
+ | -------------------- | -------------------------- |
217
+ | `bun run dev` | Dev server with hot reload |
218
+ | `bun run build` | Type-check and compile |
219
+ | `bun run type-check` | TypeScript check |
220
+ | `bun run db:push` | Push schema |
221
+ | `bun run db:studio` | Drizzle Studio |
222
+ | `bun run db:seed` | Seed the database |
314
223
 
315
224
  ---
316
225
 
317
- ## 🔗 Links
226
+ ## Contributing
318
227
 
319
- - Repository: [https://github.com/ejekanshjain/create-ej-app](https://github.com/ejekanshjain/create-ej-app)
320
- - Issues: [https://github.com/ejekanshjain/create-ej-app/issues](https://github.com/ejekanshjain/create-ej-app/issues)
321
- - Docs:
322
- - [Next.js](https://nextjs.org/docs)
323
- - [Better Auth](https://better-auth.com)
324
- - [Drizzle ORM](https://orm.drizzle.team)
325
- - [Elysia](https://elysiajs.com)
326
- - [Workflow](https://useworkflow.dev)
327
- - [shadcn/ui](https://ui.shadcn.com)
328
- - [React Email](https://react.email)
329
- - [Logging](https://loggingsucks.com)
228
+ Pull requests are welcome. Please keep changes focused and working.
330
229
 
331
- ---
230
+ ## License
332
231
 
333
- Now go build some badass production-ready shit instead of another half-baked side project. 🚀
232
+ MIT.
package/cli.ts CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { execSync } from 'child_process'
4
4
  import { program } from 'commander'
5
+ import { randomBytes } from 'crypto'
5
6
  import enquirer from 'enquirer'
6
7
  import fs from 'fs'
7
8
  import path from 'path'
@@ -16,6 +17,25 @@ interface Response {
16
17
  git: string
17
18
  }
18
19
 
20
+ /**
21
+ * Local build and install output that must never reach a new project, even
22
+ * when the CLI runs from a checkout where a template was built or installed.
23
+ */
24
+ const SKIPPED_TEMPLATE_PATHS = new Set([
25
+ 'node_modules',
26
+ '.next',
27
+ '.swc',
28
+ 'out',
29
+ 'build',
30
+ '.env',
31
+ 'next-env.d.ts',
32
+ // Generated by the Workflow SDK on every build
33
+ path.join('src', 'app', '.well-known')
34
+ ])
35
+
36
+ /** Template files whose `{{PROJECT_NAME}}` and `{{PROJECT_DESCRIPTION}}` get filled in. */
37
+ const PLACEHOLDER_FILES = ['README.md', 'AGENTS.md']
38
+
19
39
  const detectPackageManager = () => {
20
40
  const userAgent = process.env.npm_config_user_agent
21
41
 
@@ -41,18 +61,10 @@ const getInstallCommand = (packageManager: string) => {
41
61
  }
42
62
  }
43
63
 
44
- const getDevCommand = (packageManager: string) => {
45
- switch (packageManager) {
46
- case 'pnpm':
47
- return 'pnpm dev'
48
- case 'yarn':
49
- return 'yarn dev'
50
- case 'bun':
51
- return 'bun dev'
52
- default:
53
- return 'npm run dev'
54
- }
55
- }
64
+ const getRunCommand = (packageManager: string, script: string) =>
65
+ packageManager === 'npm'
66
+ ? `npm run ${script}`
67
+ : `${packageManager} run ${script}`
56
68
 
57
69
  program.action(async () => {
58
70
  const response: Response = await enquirer.prompt([
@@ -60,7 +72,12 @@ program.action(async () => {
60
72
  type: 'input',
61
73
  name: 'projectName',
62
74
  message: 'Enter the project name:',
63
- initial: 'my-project'
75
+ initial: 'my-project',
76
+ // The name becomes a directory and the package.json name, so keep it
77
+ // to characters that are safe for both.
78
+ validate: (value: string) =>
79
+ /^[a-z0-9][a-z0-9._-]{0,213}$/.test(value) ||
80
+ 'Use lowercase letters, numbers, dots, hyphens, and underscores.'
64
81
  },
65
82
  {
66
83
  type: 'input',
@@ -76,16 +93,16 @@ program.action(async () => {
76
93
  {
77
94
  name: 'nextjs',
78
95
  message:
79
- 'Full Stack setup with Next 16, Better Auth, shadcn and Drizzle'
96
+ 'Full-stack Next.js 16 app with Better Auth, an admin panel, shadcn/ui, and Drizzle'
80
97
  },
81
98
  {
82
99
  name: 'saas-starter',
83
100
  message:
84
- 'SaaS Starter with Stripe, Next 16, Better Auth, shadcn and Drizzle'
101
+ 'SaaS starter with organizations, Stripe, support tickets, Better Auth, shadcn/ui, and Drizzle'
85
102
  },
86
103
  {
87
104
  name: 'api',
88
- message: 'API Server setup with Elysia, Drizzle, Typescript'
105
+ message: 'API server with Elysia, Drizzle, and TypeScript'
89
106
  }
90
107
  ]
91
108
  },
@@ -102,14 +119,26 @@ program.action(async () => {
102
119
  const projectDir = path.join(process.cwd(), projectName)
103
120
 
104
121
  if (fs.existsSync(projectDir)) {
105
- console.error(`Error: Directory '${projectName}' already exists.`)
122
+ console.error(
123
+ `Directory '${projectName}' already exists. Choose another project name or remove the directory.`
124
+ )
106
125
  process.exit(1)
107
126
  }
108
127
 
109
128
  fs.mkdirSync(projectDir)
110
129
 
111
130
  const templateDir = path.join(__dirname, 'template', template)
112
- fs.cpSync(templateDir, projectDir, { recursive: true })
131
+ fs.cpSync(templateDir, projectDir, {
132
+ recursive: true,
133
+ filter: source => {
134
+ const relative = path.relative(templateDir, source)
135
+ return (
136
+ !SKIPPED_TEMPLATE_PATHS.has(relative) &&
137
+ !relative.split(path.sep).includes('node_modules') &&
138
+ !relative.endsWith('.tsbuildinfo')
139
+ )
140
+ }
141
+ })
113
142
 
114
143
  const renameGitignore = (dir: string) => {
115
144
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
@@ -127,20 +156,36 @@ program.action(async () => {
127
156
  const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'))
128
157
  packageJson.name = projectName
129
158
  packageJson.description = description
130
- fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2))
159
+ fs.writeFileSync(packageJsonFile, `${JSON.stringify(packageJson, null, 2)}\n`)
131
160
 
161
+ // Better Auth warns about weak secrets, so every project gets a random one.
132
162
  const envExampleFile = path.join(projectDir, '.env.example')
133
163
  const envExample = fs.readFileSync(envExampleFile, 'utf-8')
134
- fs.writeFileSync(path.join(projectDir, '.env'), envExample)
135
-
136
164
  fs.writeFileSync(
137
- path.join(projectDir, 'README.md'),
138
- `# ${projectName}
139
-
140
- ${description}
141
- `
165
+ path.join(projectDir, '.env'),
166
+ envExample.replace(
167
+ /^BETTER_AUTH_SECRET=.*$/m,
168
+ `BETTER_AUTH_SECRET="${randomBytes(32).toString('base64')}"`
169
+ )
142
170
  )
143
171
 
172
+ for (const file of PLACEHOLDER_FILES) {
173
+ const filePath = path.join(projectDir, file)
174
+ if (!fs.existsSync(filePath)) continue
175
+ fs.writeFileSync(
176
+ filePath,
177
+ fs
178
+ .readFileSync(filePath, 'utf-8')
179
+ .replaceAll('{{PROJECT_NAME}}', projectName)
180
+ .replaceAll('{{PROJECT_DESCRIPTION}}', description)
181
+ )
182
+ }
183
+
184
+ const readmeFile = path.join(projectDir, 'README.md')
185
+ if (!fs.existsSync(readmeFile)) {
186
+ fs.writeFileSync(readmeFile, `# ${projectName}\n\n${description}\n`)
187
+ }
188
+
144
189
  if (git === 'yes') {
145
190
  try {
146
191
  execSync('git init', { cwd: projectDir, stdio: 'inherit' })
@@ -151,22 +196,27 @@ ${description}
151
196
 
152
197
  const pm = detectPackageManager()
153
198
  const installCommand = getInstallCommand(pm)
154
- const devCommand = getDevCommand(pm)
199
+ const devCommand = getRunCommand(pm, 'dev')
200
+ const dbPushCommand = getRunCommand(pm, 'db:push')
155
201
 
156
202
  console.log(
157
203
  `
158
204
  Created a new app called '${projectName}' 🎉
159
205
 
160
- To open the project in vscode run:
206
+ To open the project in VS Code, run:
161
207
 
162
208
  code ${projectName}
163
209
 
164
210
 
165
- To get started, run the following commands:
211
+ To get started, run these commands:
166
212
  cd ${projectName}
167
213
 
168
214
  ${installCommand}
169
215
 
216
+ Fill in the values in .env, then create the database tables:
217
+
218
+ ${dbPushCommand}
219
+
170
220
  ${devCommand}
171
221
 
172
222
  Happy coding! 🚀
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ej-app",
3
- "version": "26.0.0",
3
+ "version": "27.0.0",
4
4
  "description": "Bootstrap a next.js app with my preferred setup",
5
5
  "main": "cli.js",
6
6
  "type": "module",
@@ -36,7 +36,7 @@
36
36
  "enquirer": "^2.4.1"
37
37
  },
38
38
  "devDependencies": {
39
- "@types/node": "^25.9.3",
39
+ "@types/node": "^26.6.2",
40
40
  "typescript": "^6.0.3"
41
41
  }
42
42
  }
@@ -1,3 +1,3 @@
1
1
  {
2
- "typescript.experimental.useTsgo": true
2
+ "js/ts.experimental.useTsgo": true
3
3
  }
@@ -1,5 +1,3 @@
1
- # Copilot Instructions
2
-
3
1
  ## Project Overview
4
2
 
5
3
  This project is a production-ready REST API built with **Elysia** on the **Bun** runtime. It uses **Drizzle ORM** with **PostgreSQL**, **Zod v4** for validation, and ships with auto-generated OpenAPI docs.