create-ej-app 25.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.
- package/README.md +125 -226
- package/cli.ts +83 -28
- package/package.json +2 -2
- package/template/api/.vscode/settings.json +1 -1
- package/template/api/{.github/copilot-instructions.md → AGENTS.md} +0 -2
- package/template/api/CLAUDE.md +1 -0
- package/template/api/package.json +10 -10
- package/template/nextjs/.dockerignore +1 -0
- package/template/nextjs/AGENTS.md +260 -0
- package/template/nextjs/CLAUDE.md +1 -0
- package/template/nextjs/README.md +109 -0
- package/template/nextjs/bunfig.toml +3 -0
- package/template/nextjs/eslint.config.mjs +6 -5
- package/template/nextjs/gitignore +6 -0
- package/template/nextjs/next.config.ts +8 -1
- package/template/nextjs/package.json +47 -40
- package/template/nextjs/public/logo-dark.svg +4 -0
- package/template/nextjs/public/logo.svg +4 -0
- package/template/nextjs/src/app/(admin)/actions/users.ts +183 -0
- package/template/nextjs/src/app/(admin)/admin/_components/admin-header.tsx +24 -0
- package/template/nextjs/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
- package/template/nextjs/src/app/(admin)/admin/_components/admin-sidebar.tsx +42 -0
- package/template/nextjs/src/app/(admin)/admin/layout.tsx +53 -0
- package/template/nextjs/src/app/(admin)/admin/page.tsx +11 -2
- package/template/nextjs/src/app/(admin)/admin/settings/layout.tsx +24 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/page.tsx +377 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
- package/template/nextjs/src/app/(admin)/admin/settings/users/validation.ts +23 -0
- package/template/nextjs/src/app/(app)/app/layout.tsx +44 -0
- package/template/nextjs/src/app/(app)/app/page.tsx +14 -2
- package/template/nextjs/src/app/(auth)/(account)/layout.tsx +64 -0
- package/template/nextjs/src/app/(auth)/(account)/profile/page.tsx +16 -0
- package/template/nextjs/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
- package/template/nextjs/src/app/(auth)/login/email-login-form.tsx +56 -29
- package/template/nextjs/src/app/(auth)/login/page.tsx +45 -12
- package/template/nextjs/src/app/(auth)/login/social-login-buttons.tsx +85 -17
- package/template/nextjs/src/app/(marketing)/layout.tsx +35 -4
- package/template/nextjs/src/app/(marketing)/legal.tsx +56 -0
- package/template/nextjs/src/app/(marketing)/page.tsx +20 -2
- package/template/nextjs/src/app/(marketing)/privacy/page.tsx +43 -2
- package/template/nextjs/src/app/(marketing)/terms/page.tsx +33 -2
- package/template/nextjs/src/app/error.tsx +37 -0
- package/template/nextjs/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
- package/template/nextjs/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
- package/template/nextjs/src/app/fonts/index.ts +60 -0
- package/template/nextjs/src/app/global-error.tsx +38 -0
- package/template/nextjs/src/app/globals.css +29 -0
- package/template/nextjs/src/app/layout.tsx +45 -18
- package/template/nextjs/src/app/not-found.tsx +25 -3
- package/template/nextjs/src/app/robots.ts +29 -0
- package/template/nextjs/src/app/sitemap.ts +21 -0
- package/template/nextjs/src/components/confirm-dialog.tsx +79 -0
- package/template/nextjs/src/components/dashboard-header.tsx +70 -0
- package/template/nextjs/src/components/data-table/enum.ts +5 -0
- package/template/nextjs/src/components/data-table/features.ts +49 -0
- package/template/nextjs/src/components/data-table/index.tsx +961 -0
- package/template/nextjs/src/components/data-table/types.ts +76 -0
- package/template/nextjs/src/components/error.tsx +71 -0
- package/template/nextjs/src/components/loader.tsx +18 -2
- package/template/nextjs/src/components/logo.tsx +49 -0
- package/template/nextjs/src/components/navigation-search.tsx +140 -0
- package/template/nextjs/src/components/navigation-sidebar.tsx +241 -0
- package/template/nextjs/src/components/page-heading.tsx +38 -0
- package/template/nextjs/src/components/react-query-provider.tsx +41 -2
- package/template/nextjs/src/components/route-error.tsx +55 -0
- package/template/nextjs/src/components/screen-size.tsx +36 -0
- package/template/nextjs/src/components/site-footer.tsx +20 -4
- package/template/nextjs/src/components/theme-provider.tsx +48 -0
- package/template/nextjs/src/components/theme-toggle.tsx +58 -5
- package/template/nextjs/src/components/ui/alert-dialog.tsx +199 -0
- package/template/nextjs/src/components/ui/avatar.tsx +112 -0
- package/template/nextjs/src/components/ui/badge.tsx +54 -0
- package/template/nextjs/src/components/ui/button.tsx +3 -1
- package/template/nextjs/src/components/ui/card.tsx +4 -4
- package/template/nextjs/src/components/ui/collapsible.tsx +33 -0
- package/template/nextjs/src/components/ui/command.tsx +192 -0
- package/template/nextjs/src/components/ui/dialog.tsx +164 -0
- package/template/nextjs/src/components/ui/dropdown-menu.tsx +273 -0
- package/template/nextjs/src/components/ui/input-group.tsx +156 -0
- package/template/nextjs/src/components/ui/select.tsx +195 -0
- package/template/nextjs/src/components/ui/sheet.tsx +143 -0
- package/template/nextjs/src/components/ui/sidebar.tsx +702 -0
- package/template/nextjs/src/components/ui/skeleton.tsx +13 -0
- package/template/nextjs/src/components/ui/table.tsx +116 -0
- package/template/nextjs/src/components/ui/textarea.tsx +18 -0
- package/template/nextjs/src/components/ui/tooltip.tsx +57 -0
- package/template/nextjs/src/components/user-menu.tsx +206 -0
- package/template/nextjs/src/db/common.ts +10 -0
- package/template/nextjs/src/db/index.ts +16 -6
- package/template/nextjs/src/db/relations.ts +21 -0
- package/template/nextjs/src/db/schema.ts +85 -46
- package/template/nextjs/src/emails/components/email-layout.tsx +27 -11
- package/template/nextjs/src/emails/magic-link.tsx +15 -13
- package/template/nextjs/src/emails/test.tsx +11 -9
- package/template/nextjs/src/emails/welcome.tsx +12 -9
- package/template/nextjs/src/env.ts +1 -1
- package/template/nextjs/src/hooks/use-mobile.ts +14 -12
- package/template/nextjs/src/instrumentation.ts +20 -2
- package/template/nextjs/src/lib/actions.ts +55 -0
- package/template/nextjs/src/lib/admin-navigation.ts +43 -0
- package/template/nextjs/src/lib/auth-client.ts +20 -9
- package/template/nextjs/src/lib/auth-permissions.ts +61 -0
- package/template/nextjs/src/lib/auth.ts +33 -3
- package/template/nextjs/src/lib/callback-url.ts +28 -0
- package/template/nextjs/src/lib/constants.ts +21 -0
- package/template/nextjs/src/lib/delay.ts +10 -0
- package/template/nextjs/src/lib/email-service.ts +21 -0
- package/template/nextjs/src/lib/errors.ts +74 -0
- package/template/nextjs/src/lib/format-currency.ts +22 -24
- package/template/nextjs/src/lib/format-date.ts +69 -6
- package/template/nextjs/src/lib/nodemailer.ts +27 -3
- package/template/nextjs/src/lib/otel.ts +92 -2
- package/template/nextjs/src/lib/safe-action-client.ts +60 -9
- package/template/nextjs/src/lib/safe-action.ts +152 -16
- package/template/nextjs/src/lib/search.ts +16 -0
- package/template/nextjs/src/lib/seo.ts +7 -0
- package/template/nextjs/src/lib/siteConfig.ts +17 -1
- package/template/nextjs/src/lib/toast-message.ts +87 -1
- package/template/nextjs/src/lib/validations.ts +30 -0
- package/template/nextjs/src/proxy.ts +48 -0
- package/template/nextjs/src/scripts/reset-db.ts +22 -0
- package/template/nextjs/src/scripts/seed.ts +59 -8
- package/template/nextjs/src/scripts/test-mail.ts +28 -20
- package/template/nextjs/tests/admin/users.test.ts +64 -0
- package/template/nextjs/tests/db/fk-indexes.test.ts +62 -0
- package/template/nextjs/tests/db/seed.test.ts +49 -0
- package/template/nextjs/tests/helpers/action.ts +71 -0
- package/template/nextjs/tests/helpers/fixtures.ts +78 -0
- package/template/nextjs/tests/helpers/index.ts +2 -0
- package/template/nextjs/tests/lib/callback-url.test.ts +48 -0
- package/template/nextjs/tests/lib/client-server-boundary.test.ts +57 -0
- package/template/nextjs/tests/lib/errors.test.ts +10 -0
- package/template/nextjs/tests/lib/search.test.ts +23 -0
- package/template/nextjs/tests/setup/pglite-db.ts +63 -0
- package/template/nextjs/tests/setup/preload.ts +95 -0
- package/template/nextjs/tsconfig.json +1 -0
- package/template/saas-starter/.dockerignore +8 -0
- package/template/saas-starter/.env.example +25 -0
- package/template/saas-starter/.vscode/extensions.json +11 -0
- package/template/saas-starter/.vscode/settings.json +3 -0
- package/template/saas-starter/AGENTS.md +302 -0
- package/template/saas-starter/CLAUDE.md +1 -0
- package/template/saas-starter/Dockerfile +15 -0
- package/template/saas-starter/README.md +125 -0
- package/template/saas-starter/bunfig.toml +3 -0
- package/template/saas-starter/components.json +25 -0
- package/template/saas-starter/drizzle.config.ts +10 -0
- package/template/saas-starter/eslint.config.mjs +24 -0
- package/template/saas-starter/gitignore +14 -0
- package/template/saas-starter/next.config.ts +32 -0
- package/template/saas-starter/package.json +97 -0
- package/template/saas-starter/postcss.config.mjs +7 -0
- package/template/saas-starter/prettier.config.mjs +11 -0
- package/template/saas-starter/public/logo-dark.svg +4 -0
- package/template/saas-starter/public/logo.svg +4 -0
- package/template/saas-starter/public/placeholder.svg +1 -0
- package/template/saas-starter/src/app/(admin)/actions/contact-leads.ts +51 -0
- package/template/saas-starter/src/app/(admin)/actions/feedbacks.ts +91 -0
- package/template/saas-starter/src/app/(admin)/actions/users.ts +183 -0
- package/template/saas-starter/src/app/(admin)/admin/_components/admin-header.tsx +24 -0
- package/template/saas-starter/src/app/(admin)/admin/_components/admin-search.tsx +16 -0
- package/template/saas-starter/src/app/(admin)/admin/_components/admin-sidebar.tsx +42 -0
- package/template/saas-starter/src/app/(admin)/admin/contact-leads/page.tsx +123 -0
- package/template/saas-starter/src/app/(admin)/admin/feedbacks/page.tsx +161 -0
- package/template/saas-starter/src/app/(admin)/admin/layout.tsx +53 -0
- package/template/saas-starter/src/app/(admin)/admin/page.tsx +60 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/layout.tsx +24 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/[userId]/page.tsx +43 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/new/page.tsx +16 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/page.tsx +377 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/user-form.tsx +138 -0
- package/template/saas-starter/src/app/(admin)/admin/settings/users/validation.ts +23 -0
- package/template/saas-starter/src/app/(admin)/admin/support-tickets/[ticketId]/page.tsx +17 -0
- package/template/saas-starter/src/app/(admin)/admin/support-tickets/page.tsx +5 -0
- package/template/saas-starter/src/app/(app)/actions/billing.ts +216 -0
- package/template/saas-starter/src/app/(app)/actions/feedbacks.ts +28 -0
- package/template/saas-starter/src/app/(app)/actions/feedbacks.validation.ts +12 -0
- package/template/saas-starter/src/app/(app)/actions/members.ts +189 -0
- package/template/saas-starter/src/app/(app)/actions/organizations.ts +230 -0
- package/template/saas-starter/src/app/(app)/actions/organizations.validation.ts +17 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/dashboard/page.tsx +17 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/layout.tsx +31 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/billing-email-form.tsx +92 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/danger-zone.tsx +357 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/general-settings-form.tsx +130 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/_components/plans-section.tsx +222 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/billing/page.tsx +115 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/layout.tsx +24 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/invite-member-dialog.tsx +168 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/_components/members-tabs.tsx +604 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/members/page.tsx +33 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/settings/page.tsx +55 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/[ticketId]/page.tsx +25 -0
- package/template/saas-starter/src/app/(app)/app/[orgId]/support-tickets/page.tsx +25 -0
- package/template/saas-starter/src/app/(app)/app/_components/app-header.tsx +31 -0
- package/template/saas-starter/src/app/(app)/app/_components/app-search.tsx +26 -0
- package/template/saas-starter/src/app/(app)/app/_components/app-sidebar.tsx +30 -0
- package/template/saas-starter/src/app/(app)/app/_components/feedback-dialog.tsx +108 -0
- package/template/saas-starter/src/app/(app)/app/_components/organization-context.tsx +37 -0
- package/template/saas-starter/src/app/(app)/app/_components/organization-switcher.tsx +200 -0
- package/template/saas-starter/src/app/(app)/app/layout.tsx +64 -0
- package/template/saas-starter/src/app/(app)/app/onboarding/_components/create-organization-form.tsx +117 -0
- package/template/saas-starter/src/app/(app)/app/onboarding/page.tsx +40 -0
- package/template/saas-starter/src/app/(app)/app/page.tsx +76 -0
- package/template/saas-starter/src/app/(auth)/(account)/layout.tsx +64 -0
- package/template/saas-starter/src/app/(auth)/(account)/profile/page.tsx +16 -0
- package/template/saas-starter/src/app/(auth)/(account)/profile/profile-form.tsx +247 -0
- package/template/saas-starter/src/app/(auth)/(account)/support/[ticketId]/page.tsx +19 -0
- package/template/saas-starter/src/app/(auth)/(account)/support/page.tsx +9 -0
- package/template/saas-starter/src/app/(auth)/login/email-login-form.tsx +131 -0
- package/template/saas-starter/src/app/(auth)/login/page.tsx +105 -0
- package/template/saas-starter/src/app/(auth)/login/social-login-buttons.tsx +137 -0
- package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/accept-invitation-form.tsx +94 -0
- package/template/saas-starter/src/app/(invitation)/accept-invitation/[id]/page.tsx +153 -0
- package/template/saas-starter/src/app/(invitation)/actions/invitations.ts +32 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/actions.ts +23 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/contact-form.tsx +118 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/page.tsx +20 -0
- package/template/saas-starter/src/app/(marketing)/contact-us/validation.ts +22 -0
- package/template/saas-starter/src/app/(marketing)/layout.tsx +48 -0
- package/template/saas-starter/src/app/(marketing)/legal.tsx +66 -0
- package/template/saas-starter/src/app/(marketing)/page.tsx +24 -0
- package/template/saas-starter/src/app/(marketing)/privacy/page.tsx +63 -0
- package/template/saas-starter/src/app/(marketing)/terms/page.tsx +41 -0
- package/template/saas-starter/src/app/actions/support-tickets.ts +510 -0
- package/template/saas-starter/src/app/actions/support-tickets.validation.ts +112 -0
- package/template/saas-starter/src/app/actions/uploads.ts +36 -0
- package/template/saas-starter/src/app/actions/uploads.validation.ts +24 -0
- package/template/saas-starter/src/app/api/auth/[...all]/route.ts +4 -0
- package/template/saas-starter/src/app/api/webhooks/stripe/route.ts +94 -0
- package/template/saas-starter/src/app/error.tsx +37 -0
- package/template/saas-starter/src/app/favicon.ico +0 -0
- package/template/saas-starter/src/app/fonts/geist-mono/GeistMono-Variable.woff2 +0 -0
- package/template/saas-starter/src/app/fonts/geist-sans/Geist-Variable.woff2 +0 -0
- package/template/saas-starter/src/app/fonts/index.ts +60 -0
- package/template/saas-starter/src/app/global-error.tsx +38 -0
- package/template/saas-starter/src/app/globals.css +269 -0
- package/template/saas-starter/src/app/layout.tsx +77 -0
- package/template/saas-starter/src/app/not-found.tsx +49 -0
- package/template/saas-starter/src/app/robots.ts +36 -0
- package/template/saas-starter/src/app/sitemap.ts +21 -0
- package/template/saas-starter/src/components/confirm-dialog.tsx +79 -0
- package/template/saas-starter/src/components/dashboard-header.tsx +70 -0
- package/template/saas-starter/src/components/data-table/enum.ts +5 -0
- package/template/saas-starter/src/components/data-table/features.ts +49 -0
- package/template/saas-starter/src/components/data-table/index.tsx +961 -0
- package/template/saas-starter/src/components/data-table/types.ts +76 -0
- package/template/saas-starter/src/components/error.tsx +71 -0
- package/template/saas-starter/src/components/file-upload.tsx +254 -0
- package/template/saas-starter/src/components/loader.tsx +41 -0
- package/template/saas-starter/src/components/logo.tsx +49 -0
- package/template/saas-starter/src/components/navigation-search.tsx +140 -0
- package/template/saas-starter/src/components/navigation-sidebar.tsx +241 -0
- package/template/saas-starter/src/components/page-heading.tsx +38 -0
- package/template/saas-starter/src/components/react-query-provider.tsx +51 -0
- package/template/saas-starter/src/components/route-error.tsx +55 -0
- package/template/saas-starter/src/components/screen-size.tsx +76 -0
- package/template/saas-starter/src/components/site-footer.tsx +39 -0
- package/template/saas-starter/src/components/support/create-support-ticket-dialog.tsx +122 -0
- package/template/saas-starter/src/components/support/support-ticket-conversation.tsx +236 -0
- package/template/saas-starter/src/components/support/support-ticket-shared.tsx +58 -0
- package/template/saas-starter/src/components/support/support-tickets-table.tsx +281 -0
- package/template/saas-starter/src/components/theme-provider.tsx +115 -0
- package/template/saas-starter/src/components/theme-toggle.tsx +75 -0
- package/template/saas-starter/src/components/ui/alert-dialog.tsx +199 -0
- package/template/saas-starter/src/components/ui/avatar.tsx +112 -0
- package/template/saas-starter/src/components/ui/badge.tsx +54 -0
- package/template/saas-starter/src/components/ui/button.tsx +69 -0
- package/template/saas-starter/src/components/ui/card.tsx +103 -0
- package/template/saas-starter/src/components/ui/checkbox.tsx +32 -0
- package/template/saas-starter/src/components/ui/collapsible.tsx +33 -0
- package/template/saas-starter/src/components/ui/command.tsx +192 -0
- package/template/saas-starter/src/components/ui/dialog.tsx +164 -0
- package/template/saas-starter/src/components/ui/dropdown-menu.tsx +273 -0
- package/template/saas-starter/src/components/ui/field.tsx +238 -0
- package/template/saas-starter/src/components/ui/input-group.tsx +156 -0
- package/template/saas-starter/src/components/ui/input.tsx +19 -0
- package/template/saas-starter/src/components/ui/label.tsx +24 -0
- package/template/saas-starter/src/components/ui/popover.tsx +89 -0
- package/template/saas-starter/src/components/ui/select.tsx +195 -0
- package/template/saas-starter/src/components/ui/separator.tsx +28 -0
- package/template/saas-starter/src/components/ui/sheet.tsx +143 -0
- package/template/saas-starter/src/components/ui/sidebar.tsx +702 -0
- package/template/saas-starter/src/components/ui/skeleton.tsx +13 -0
- package/template/saas-starter/src/components/ui/sonner.tsx +45 -0
- package/template/saas-starter/src/components/ui/switch.tsx +33 -0
- package/template/saas-starter/src/components/ui/table.tsx +116 -0
- package/template/saas-starter/src/components/ui/tabs.tsx +90 -0
- package/template/saas-starter/src/components/ui/textarea.tsx +18 -0
- package/template/saas-starter/src/components/ui/tooltip.tsx +57 -0
- package/template/saas-starter/src/components/user-menu.tsx +213 -0
- package/template/saas-starter/src/db/common.ts +63 -0
- package/template/saas-starter/src/db/enums.ts +50 -0
- package/template/saas-starter/src/db/index.ts +60 -0
- package/template/saas-starter/src/db/relations.ts +112 -0
- package/template/saas-starter/src/db/schema.ts +342 -0
- package/template/saas-starter/src/emails/components/email-layout.tsx +79 -0
- package/template/saas-starter/src/emails/invitation.tsx +69 -0
- package/template/saas-starter/src/emails/magic-link.tsx +51 -0
- package/template/saas-starter/src/emails/test.tsx +34 -0
- package/template/saas-starter/src/emails/welcome.tsx +37 -0
- package/template/saas-starter/src/env.ts +66 -0
- package/template/saas-starter/src/hooks/use-mobile.ts +21 -0
- package/template/saas-starter/src/instrumentation.ts +44 -0
- package/template/saas-starter/src/lib/actions.ts +55 -0
- package/template/saas-starter/src/lib/admin-navigation.ts +63 -0
- package/template/saas-starter/src/lib/app-navigation.ts +63 -0
- package/template/saas-starter/src/lib/auth-client.ts +34 -0
- package/template/saas-starter/src/lib/auth-permissions.ts +61 -0
- package/template/saas-starter/src/lib/auth.ts +312 -0
- package/template/saas-starter/src/lib/billing.ts +152 -0
- package/template/saas-starter/src/lib/callback-url.ts +28 -0
- package/template/saas-starter/src/lib/cn.ts +4 -0
- package/template/saas-starter/src/lib/constants.ts +91 -0
- package/template/saas-starter/src/lib/delay.ts +12 -0
- package/template/saas-starter/src/lib/email-service.ts +118 -0
- package/template/saas-starter/src/lib/errors.ts +74 -0
- package/template/saas-starter/src/lib/format-currency.ts +25 -0
- package/template/saas-starter/src/lib/format-date.ts +75 -0
- package/template/saas-starter/src/lib/image-url.ts +54 -0
- package/template/saas-starter/src/lib/nodemailer.ts +67 -0
- package/template/saas-starter/src/lib/organization-access.ts +73 -0
- package/template/saas-starter/src/lib/otel.ts +117 -0
- package/template/saas-starter/src/lib/plan-limits.ts +37 -0
- package/template/saas-starter/src/lib/plans.ts +90 -0
- package/template/saas-starter/src/lib/rbac.ts +44 -0
- package/template/saas-starter/src/lib/safe-action-client.ts +126 -0
- package/template/saas-starter/src/lib/safe-action.ts +187 -0
- package/template/saas-starter/src/lib/search.ts +16 -0
- package/template/saas-starter/src/lib/seo.ts +7 -0
- package/template/saas-starter/src/lib/siteConfig.ts +20 -0
- package/template/saas-starter/src/lib/storage.ts +311 -0
- package/template/saas-starter/src/lib/stripe.ts +9 -0
- package/template/saas-starter/src/lib/toast-message.ts +101 -0
- package/template/saas-starter/src/lib/validations.ts +95 -0
- package/template/saas-starter/src/proxy.ts +48 -0
- package/template/saas-starter/src/scripts/cleanup-temp-uploads.ts +24 -0
- package/template/saas-starter/src/scripts/reset-db.ts +22 -0
- package/template/saas-starter/src/scripts/seed.ts +85 -0
- package/template/saas-starter/src/scripts/setup-db.ts +72 -0
- package/template/saas-starter/src/scripts/setup-stripe-plans.ts +206 -0
- package/template/saas-starter/src/scripts/test-mail.ts +43 -0
- package/template/saas-starter/tests/admin/users.test.ts +64 -0
- package/template/saas-starter/tests/db/fk-indexes.test.ts +62 -0
- package/template/saas-starter/tests/db/seed.test.ts +57 -0
- package/template/saas-starter/tests/helpers/action.ts +71 -0
- package/template/saas-starter/tests/helpers/fixtures.ts +151 -0
- package/template/saas-starter/tests/helpers/index.ts +2 -0
- package/template/saas-starter/tests/lib/callback-url.test.ts +48 -0
- package/template/saas-starter/tests/lib/client-server-boundary.test.ts +57 -0
- package/template/saas-starter/tests/lib/errors.test.ts +10 -0
- package/template/saas-starter/tests/lib/image-url.test.ts +31 -0
- package/template/saas-starter/tests/lib/search.test.ts +23 -0
- package/template/saas-starter/tests/marketing/contact-and-feedback.test.ts +86 -0
- package/template/saas-starter/tests/organizations/organizations.test.ts +175 -0
- package/template/saas-starter/tests/setup/pglite-db.ts +67 -0
- package/template/saas-starter/tests/setup/preload.ts +108 -0
- package/template/saas-starter/tests/support/support-tickets.test.ts +145 -0
- package/template/saas-starter/tsconfig.json +52 -0
- package/cli.js +0 -143
- package/template/nextjs/src/app/(admin)/layout.tsx +0 -20
- package/template/nextjs/src/app/(app)/layout.tsx +0 -20
- package/template/nextjs/terraform/artifact-registry.tf +0 -5
- package/template/nextjs/terraform/backend-service.tf +0 -13
- package/template/nextjs/terraform/cleanup.sh +0 -9
- package/template/nextjs/terraform/cloudbuild.tf +0 -70
- package/template/nextjs/terraform/cloudrun.tf +0 -68
- package/template/nextjs/terraform/database.tf +0 -60
- package/template/nextjs/terraform/forwarding-rule.tf +0 -43
- package/template/nextjs/terraform/gitignore +0 -4
- package/template/nextjs/terraform/ip-address.tf +0 -11
- package/template/nextjs/terraform/network-endpoint-group.tf +0 -11
- package/template/nextjs/terraform/network.tf +0 -21
- package/template/nextjs/terraform/pre.sh +0 -3
- package/template/nextjs/terraform/providers.tf +0 -18
- package/template/nextjs/terraform/secret.txt +0 -11
- package/template/nextjs/terraform/setup.sh +0 -25
- package/template/nextjs/terraform/ssl-certs.tf +0 -32
- package/template/nextjs/terraform/target-https-proxy copy.tf +0 -14
- package/template/nextjs/terraform/target-https-proxy.tf +0 -14
- package/template/nextjs/terraform/terraform.tfvars +0 -4
- package/template/nextjs/terraform/url-map.tf +0 -23
- package/template/nextjs/terraform/variables.tf +0 -21
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# create-ej-app
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
##
|
|
11
|
+
## Features
|
|
12
12
|
|
|
13
|
-
- **Next.js 16
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
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
|
-
-
|
|
21
|
-
|
|
22
|
-
- **Drizzle ORM
|
|
23
|
-
|
|
24
|
-
- **Elysia
|
|
25
|
-
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
- **
|
|
29
|
-
|
|
30
|
-
- **
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
##
|
|
35
|
+
## Templates
|
|
36
|
+
|
|
37
|
+
### Next.js full-stack app (`nextjs`)
|
|
53
38
|
|
|
54
|
-
|
|
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
|
-
###
|
|
41
|
+
### SaaS starter (`saas-starter`)
|
|
57
42
|
|
|
58
|
-
|
|
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
|
|
45
|
+
### API server (`api`)
|
|
61
46
|
|
|
62
|
-
|
|
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
|
-
##
|
|
51
|
+
## Quick start
|
|
67
52
|
|
|
68
53
|
```bash
|
|
69
54
|
bunx create-ej-app@latest
|
|
70
55
|
```
|
|
71
56
|
|
|
72
|
-
You
|
|
57
|
+
You will be prompted for:
|
|
73
58
|
|
|
74
59
|
- Project name
|
|
75
60
|
- Description
|
|
76
|
-
- Template (nextjs or api)
|
|
77
|
-
-
|
|
61
|
+
- Template (`nextjs`, `saas-starter`, or `api`)
|
|
62
|
+
- Whether to initialize git
|
|
78
63
|
|
|
79
64
|
Example:
|
|
80
65
|
|
|
81
|
-
```
|
|
82
|
-
? Enter the project name:
|
|
83
|
-
? Enter a description:
|
|
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
|
-
##
|
|
75
|
+
## What you get
|
|
93
76
|
|
|
94
|
-
### Next.js
|
|
77
|
+
### Next.js and SaaS templates
|
|
95
78
|
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
103
|
+
The SaaS starter adds:
|
|
115
104
|
|
|
116
|
-
-
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
##
|
|
128
|
+
## Setup
|
|
129
129
|
|
|
130
130
|
### Prerequisites
|
|
131
131
|
|
|
132
|
-
-
|
|
133
|
-
- PostgreSQL
|
|
134
|
-
- OAuth
|
|
135
|
-
-
|
|
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
|
-
|
|
147
|
+
### Environment variables
|
|
150
148
|
|
|
151
|
-
|
|
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
|
-
##
|
|
153
|
+
## Development
|
|
156
154
|
|
|
157
|
-
### Next.js
|
|
155
|
+
### Next.js and SaaS templates
|
|
158
156
|
|
|
159
157
|
```bash
|
|
160
158
|
bun run dev
|
|
161
159
|
```
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
App: `http://localhost:3000`
|
|
164
162
|
|
|
165
|
-
### API
|
|
163
|
+
### API template
|
|
166
164
|
|
|
167
165
|
```bash
|
|
168
166
|
bun run dev
|
|
169
167
|
```
|
|
170
168
|
|
|
171
|
-
API
|
|
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
|
-
##
|
|
174
|
+
## Docker
|
|
178
175
|
|
|
179
176
|
```bash
|
|
180
177
|
bun run build:docker
|
|
181
|
-
docker run -p 3000:3000 my-
|
|
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
|
-
|
|
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
|
-
##
|
|
213
|
-
|
|
214
|
-
Better Auth gives you:
|
|
185
|
+
## Next.js and SaaS templates
|
|
215
186
|
|
|
216
|
-
|
|
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
|
-
|
|
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
|
-
|
|
196
|
+
Add shadcn components with:
|
|
237
197
|
|
|
238
198
|
```bash
|
|
239
|
-
bunx shadcn@latest add
|
|
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
|
-
##
|
|
204
|
+
## API template architecture
|
|
306
205
|
|
|
307
|
-
|
|
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
|
-
|
|
213
|
+
### API commands
|
|
312
214
|
|
|
313
|
-
|
|
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
|
-
##
|
|
226
|
+
## Contributing
|
|
318
227
|
|
|
319
|
-
|
|
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
|
-
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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,11 +93,16 @@ program.action(async () => {
|
|
|
76
93
|
{
|
|
77
94
|
name: 'nextjs',
|
|
78
95
|
message:
|
|
79
|
-
'Full
|
|
96
|
+
'Full-stack Next.js 16 app with Better Auth, an admin panel, shadcn/ui, and Drizzle'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'saas-starter',
|
|
100
|
+
message:
|
|
101
|
+
'SaaS starter with organizations, Stripe, support tickets, Better Auth, shadcn/ui, and Drizzle'
|
|
80
102
|
},
|
|
81
103
|
{
|
|
82
104
|
name: 'api',
|
|
83
|
-
message: 'API
|
|
105
|
+
message: 'API server with Elysia, Drizzle, and TypeScript'
|
|
84
106
|
}
|
|
85
107
|
]
|
|
86
108
|
},
|
|
@@ -97,14 +119,26 @@ program.action(async () => {
|
|
|
97
119
|
const projectDir = path.join(process.cwd(), projectName)
|
|
98
120
|
|
|
99
121
|
if (fs.existsSync(projectDir)) {
|
|
100
|
-
console.error(
|
|
122
|
+
console.error(
|
|
123
|
+
`Directory '${projectName}' already exists. Choose another project name or remove the directory.`
|
|
124
|
+
)
|
|
101
125
|
process.exit(1)
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
fs.mkdirSync(projectDir)
|
|
105
129
|
|
|
106
130
|
const templateDir = path.join(__dirname, 'template', template)
|
|
107
|
-
fs.cpSync(templateDir, projectDir, {
|
|
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
|
+
})
|
|
108
142
|
|
|
109
143
|
const renameGitignore = (dir: string) => {
|
|
110
144
|
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -122,20 +156,36 @@ program.action(async () => {
|
|
|
122
156
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, 'utf-8'))
|
|
123
157
|
packageJson.name = projectName
|
|
124
158
|
packageJson.description = description
|
|
125
|
-
fs.writeFileSync(packageJsonFile, JSON.stringify(packageJson, null, 2))
|
|
159
|
+
fs.writeFileSync(packageJsonFile, `${JSON.stringify(packageJson, null, 2)}\n`)
|
|
126
160
|
|
|
161
|
+
// Better Auth warns about weak secrets, so every project gets a random one.
|
|
127
162
|
const envExampleFile = path.join(projectDir, '.env.example')
|
|
128
163
|
const envExample = fs.readFileSync(envExampleFile, 'utf-8')
|
|
129
|
-
fs.writeFileSync(path.join(projectDir, '.env'), envExample)
|
|
130
|
-
|
|
131
164
|
fs.writeFileSync(
|
|
132
|
-
path.join(projectDir, '
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
${
|
|
136
|
-
|
|
165
|
+
path.join(projectDir, '.env'),
|
|
166
|
+
envExample.replace(
|
|
167
|
+
/^BETTER_AUTH_SECRET=.*$/m,
|
|
168
|
+
`BETTER_AUTH_SECRET="${randomBytes(32).toString('base64')}"`
|
|
169
|
+
)
|
|
137
170
|
)
|
|
138
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
|
+
|
|
139
189
|
if (git === 'yes') {
|
|
140
190
|
try {
|
|
141
191
|
execSync('git init', { cwd: projectDir, stdio: 'inherit' })
|
|
@@ -146,22 +196,27 @@ ${description}
|
|
|
146
196
|
|
|
147
197
|
const pm = detectPackageManager()
|
|
148
198
|
const installCommand = getInstallCommand(pm)
|
|
149
|
-
const devCommand =
|
|
199
|
+
const devCommand = getRunCommand(pm, 'dev')
|
|
200
|
+
const dbPushCommand = getRunCommand(pm, 'db:push')
|
|
150
201
|
|
|
151
202
|
console.log(
|
|
152
203
|
`
|
|
153
204
|
Created a new app called '${projectName}' 🎉
|
|
154
205
|
|
|
155
|
-
To open the project in
|
|
206
|
+
To open the project in VS Code, run:
|
|
156
207
|
|
|
157
208
|
code ${projectName}
|
|
158
209
|
|
|
159
210
|
|
|
160
|
-
To get started, run
|
|
211
|
+
To get started, run these commands:
|
|
161
212
|
cd ${projectName}
|
|
162
213
|
|
|
163
214
|
${installCommand}
|
|
164
215
|
|
|
216
|
+
Fill in the values in .env, then create the database tables:
|
|
217
|
+
|
|
218
|
+
${dbPushCommand}
|
|
219
|
+
|
|
165
220
|
${devCommand}
|
|
166
221
|
|
|
167
222
|
Happy coding! 🚀
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-ej-app",
|
|
3
|
-
"version": "
|
|
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": "^
|
|
39
|
+
"@types/node": "^26.6.2",
|
|
40
40
|
"typescript": "^6.0.3"
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|