From 4403ac128065dc390ff616c978b533ab52120d90 Mon Sep 17 00:00:00 2001 From: Ian Hunter Date: Sun, 22 Feb 2026 16:28:51 -0600 Subject: [PATCH 01/11] cleaning house --- .github/actions/setup/action.yml | 17 - .github/workflows/build-packages.yml | 37 - .github/workflows/build-templates.yml | 117 - .github/workflows/format-lint-typecheck.yml | 62 - .github/workflows/npm-publish.yml | 162 - .github/workflows/test-cli.yml | 102 - .npmrc | 13 - .prettierrc | 5 - .ruler/AGENTS.md | 1000 - .ruler/ruler.toml | 40 - README.md | 205 +- apps/home/.gitignore | 26 - apps/home/.source/browser.ts | 12 - apps/home/.source/dynamic.ts | 8 - apps/home/.source/server.ts | 74 - apps/home/.source/source.config.mjs | 53 - apps/home/biome.jsonc | 7 - .../docs/analytics/analytics-provider.mdx | 145 - .../content/docs/analytics/feature-flags.mdx | 316 - .../docs/analytics/identifying-users.mdx | 255 - apps/home/content/docs/analytics/index.mdx | 177 - apps/home/content/docs/analytics/meta.json | 13 - .../docs/analytics/tracking-events.mdx | 245 - .../content/docs/analytics/usage/identify.mdx | 168 - .../content/docs/analytics/usage/meta.json | 5 - .../content/docs/analytics/usage/page.mdx | 107 - .../content/docs/analytics/usage/reset.mdx | 120 - .../content/docs/analytics/usage/track.mdx | 156 - .../docs/analytics/usage/use-analytics.mdx | 73 - .../content/docs/analytics/usage/use-flag.mdx | 105 - apps/home/content/docs/auth/configuration.mdx | 246 - apps/home/content/docs/auth/index.mdx | 82 - apps/home/content/docs/auth/meta.json | 11 - .../content/docs/auth/session-management.mdx | 320 - apps/home/content/docs/auth/usage/meta.json | 5 - .../content/docs/auth/usage/require-auth.mdx | 139 - .../home/content/docs/auth/usage/use-auth.mdx | 403 - .../content/docs/auth/usage/with-auth.mdx | 180 - apps/home/content/docs/cli/add.mdx | 227 - apps/home/content/docs/cli/agents.mdx | 126 - apps/home/content/docs/cli/index.mdx | 79 - apps/home/content/docs/cli/init.mdx | 164 - apps/home/content/docs/cli/meta.json | 12 - .../home/content/docs/cli/troubleshooting.mdx | 299 - apps/home/content/docs/cli/upgrade.mdx | 176 - apps/home/content/docs/db/index.mdx | 166 - apps/home/content/docs/db/meta.json | 11 - apps/home/content/docs/db/migrations.mdx | 198 - apps/home/content/docs/db/queries.mdx | 381 - apps/home/content/docs/db/schema.mdx | 180 - .../content/docs/deployment/cloudflare.mdx | 112 - apps/home/content/docs/deployment/fly.mdx | 216 - apps/home/content/docs/deployment/index.mdx | 81 - apps/home/content/docs/deployment/railway.mdx | 114 - apps/home/content/docs/deployment/render.mdx | 134 - apps/home/content/docs/deployment/vercel.mdx | 103 - apps/home/content/docs/emails/index.mdx | 170 - apps/home/content/docs/emails/meta.json | 10 - apps/home/content/docs/emails/sending.mdx | 231 - apps/home/content/docs/emails/templates.mdx | 238 - .../docs/getting-started/comparisons.mdx | 92 - .../getting-started/environment-variables.mdx | 149 - .../docs/getting-started/installation.mdx | 73 - .../content/docs/getting-started/meta.json | 12 - .../getting-started/project-structure.mdx | 229 - .../docs/getting-started/quickstart.mdx | 50 - .../content/docs/getting-started/what-is.mdx | 85 - apps/home/content/docs/index.mdx | 139 - apps/home/content/docs/meta.json | 28 - apps/home/content/docs/seo/index.mdx | 250 - apps/home/content/docs/seo/meta.json | 6 - apps/home/content/docs/seo/metadata.mdx | 317 - apps/home/content/docs/seo/robots.mdx | 280 - apps/home/content/docs/seo/sitemap.mdx | 330 - .../home/content/docs/seo/structured-data.mdx | 303 - apps/home/content/docs/ui/components.mdx | 181 - apps/home/content/docs/ui/index.mdx | 277 - apps/home/content/docs/ui/meta.json | 10 - apps/home/content/docs/ui/theming.mdx | 299 - apps/home/next.config.ts | 14 - apps/home/package.json | 47 - apps/home/postcss.config.mjs | 8 - apps/home/public/android-chrome-192x192.png | Bin 18597 -> 0 bytes apps/home/public/android-chrome-512x512.png | Bin 83148 -> 0 bytes apps/home/public/apple-touch-icon.png | Bin 16663 -> 0 bytes apps/home/public/favicon-16x16.png | Bin 529 -> 0 bytes apps/home/public/favicon-32x32.png | Bin 1242 -> 0 bytes apps/home/public/favicon.ico | Bin 15406 -> 0 bytes apps/home/public/images/amp-logo.png | Bin 7477 -> 0 bytes apps/home/public/images/amp.png | Bin 7477 -> 0 bytes apps/home/public/images/claude-logo.png | Bin 52486 -> 0 bytes apps/home/public/images/devin-logo.png | Bin 30926 -> 0 bytes .../images/devin-primarylockup-white.png | Bin 30926 -> 0 bytes apps/home/public/images/expo-logo.png | Bin 13025 -> 0 bytes .../home/public/images/framer-motion-logo.svg | 22 - .../public/images/google-analytics-logo.svg | 14 - apps/home/public/images/lucide-logo.svg | 11 - apps/home/public/images/opencode-logo.png | Bin 2837 -> 0 bytes apps/home/public/images/posthog-logo.svg | 15 - apps/home/public/images/shadcn-ui-logo.svg | 5 - apps/home/public/images/tailwind-logo.svg | 11 - apps/home/public/images/vite-logo.png | Bin 239196 -> 0 bytes apps/home/public/robots.txt | 11 - apps/home/public/site.webmanifest | 21 - apps/home/public/sitemap.xml | 15 - apps/home/public/startupkit-logo.png | Bin 3444 -> 0 bytes apps/home/scripts/sync-package-docs.ts | 164 - apps/home/source.config.ts | 53 - apps/home/src/app/(marketing)/layout.tsx | 13 - apps/home/src/app/(marketing)/page.tsx | 21 - .../home/src/app/(marketing)/privacy/page.tsx | 78 - apps/home/src/app/(marketing)/terms/page.tsx | 89 - apps/home/src/app/docs/[[...slug]]/page.tsx | 67 - apps/home/src/app/docs/docs.css | 167 - apps/home/src/app/docs/layout.tsx | 57 - apps/home/src/app/globals.css | 33 - apps/home/src/app/layout.tsx | 113 - apps/home/src/app/providers.tsx | 28 - apps/home/src/app/source.ts | 16 - apps/home/src/components/ai-ready-section.tsx | 59 - .../src/components/bento-features-section.tsx | 244 - apps/home/src/components/copy-button.tsx | 42 - apps/home/src/components/cta-section.tsx | 28 - .../src/components/drizzle-code-snippet.tsx | 119 - apps/home/src/components/faq-section.tsx | 116 - apps/home/src/components/footer.tsx | 112 - .../src/components/github-star-button.tsx | 42 - apps/home/src/components/hero-section.tsx | 31 - apps/home/src/components/logo.tsx | 55 - apps/home/src/components/navigation.tsx | 40 - apps/home/src/components/problem-section.tsx | 121 - apps/home/src/components/services-section.tsx | 118 - apps/home/src/components/terminal-demo.tsx | 49 - apps/home/src/components/ui/accordion.tsx | 63 - apps/home/src/components/ui/button.tsx | 51 - apps/home/src/components/ui/utils.ts | 6 - apps/home/src/lib/constants.ts | 1 - apps/home/tsconfig.json | 46 - apps/home/twoslash.d.ts | 244 - biome.jsonc | 3 - config/biome/biome.jsonc | 134 - config/biome/package.json | 10 - config/nextjs/index.mjs | 10 - config/nextjs/package.json | 10 - config/typescript/base.json | 53 - config/typescript/library.json | 22 - config/typescript/nextjs.json | 33 - config/typescript/package.json | 5 - package.json | 74 +- packages/analytics/README.md | 693 - packages/analytics/biome.jsonc | 4 - packages/analytics/package.json | 56 - packages/analytics/rollup.config.mjs | 50 - .../analytics/src/ahrefs/ahrefs-provider.tsx | 97 - packages/analytics/src/ahrefs/index.ts | 1 - packages/analytics/src/context.ts | 14 - .../src/datafast/datafast-provider.tsx | 144 - packages/analytics/src/datafast/index.ts | 1 - .../src/google/google-analytics-provider.tsx | 109 - packages/analytics/src/google/index.ts | 1 - packages/analytics/src/gtm/gtm-provider.tsx | 143 - packages/analytics/src/gtm/index.ts | 1 - packages/analytics/src/index.ts | 17 - packages/analytics/src/openpanel/index.ts | 1 - .../src/openpanel/openpanel-provider.tsx | 185 - packages/analytics/src/posthog/index.ts | 1 - .../src/posthog/posthog-provider.tsx | 124 - packages/analytics/src/provider.tsx | 480 - packages/analytics/src/types.ts | 55 - packages/analytics/src/use-analytics.ts | 8 - packages/analytics/src/use-flag.ts | 17 - packages/analytics/tsconfig.json | 10 - packages/auth/README.md | 433 - packages/auth/biome.jsonc | 4 - packages/auth/package.json | 50 - packages/auth/rollup.config.mjs | 45 - packages/auth/src/components/context.ts | 27 - packages/auth/src/components/index.ts | 4 - packages/auth/src/components/provider.tsx | 109 - packages/auth/src/components/use-auth.ts | 6 - packages/auth/src/index.ts | 1 - packages/auth/src/server.ts | 42 - packages/auth/tsconfig.json | 10 - packages/cli/README.md | 93 - packages/cli/biome.jsonc | 4 - packages/cli/package.json | 36 +- packages/cli/rollup.config.mjs | 92 +- packages/cli/src/cli.ts | 117 +- packages/cli/src/cmd/README.test.md | 116 - packages/cli/src/cmd/add.integration.test.ts | 266 - packages/cli/src/cmd/add.test.ts | 439 - packages/cli/src/cmd/add.ts | 466 - packages/cli/src/cmd/init.integration.test.ts | 172 - packages/cli/src/cmd/init.test.ts | 254 - packages/cli/src/cmd/init.ts | 295 - packages/cli/src/cmd/make.integration.test.ts | 217 - packages/cli/src/cmd/make.test.ts | 551 - packages/cli/src/cmd/make.ts | 223 - packages/cli/src/cmd/skills.ts | 328 + packages/cli/src/cmd/update.ts | 41 - packages/cli/src/cmd/upgrade.test.ts | 463 - packages/cli/src/cmd/upgrade.ts | 485 - packages/cli/src/config.md | 143 - packages/cli/src/config.ts | 62 - packages/cli/src/lib/spinner.ts | 18 - packages/cli/src/lib/system.ts | 67 - packages/cli/src/post-install-check.ts | 147 - packages/cli/tsconfig.json | 28 +- packages/cli/vitest.config.ts | 27 - packages/seo/README.md | 379 - packages/seo/biome.jsonc | 4 - packages/seo/package.json | 37 - packages/seo/rollup.config.mjs | 24 - packages/seo/src/index.ts | 12 - packages/seo/src/metadata.ts | 190 - packages/seo/src/robots.ts | 22 - packages/seo/src/sitemap.ts | 30 - packages/seo/src/structured-data.ts | 94 - packages/seo/tsconfig.json | 10 - pnpm-lock.yaml | 25467 +--------------- pnpm-workspace.yaml | 52 - scripts/bump | 43 - scripts/link-local.js | 159 - scripts/release-notes | 53 - templates/apps/next/.gitignore | 42 - templates/apps/next/README.md | 227 - templates/apps/next/biome.jsonc | 4 - templates/apps/next/components.json | 20 - templates/apps/next/next.config.ts | 13 - templates/apps/next/package.json | 38 - templates/apps/next/postcss.config.mjs | 2 - .../apps/next/public/apple-touch-icon.png | Bin 14598 -> 0 bytes templates/apps/next/public/favicon-96x96.png | Bin 5321 -> 0 bytes templates/apps/next/public/favicon.ico | Bin 15086 -> 0 bytes templates/apps/next/public/favicon.svg | 1 - templates/apps/next/public/site.webmanifest | 31 - .../next/public/web-app-manifest-192x192.png | Bin 16361 -> 0 bytes .../next/public/web-app-manifest-512x512.png | Bin 88458 -> 0 bytes .../src/app/(auth)/auth/[...all]/route.ts | 3 - templates/apps/next/src/app/(auth)/layout.tsx | 11 - .../apps/next/src/app/(auth)/sign-in/page.tsx | 175 - .../app/(main)/dashboard/logout-button.tsx | 23 - .../next/src/app/(main)/dashboard/page.tsx | 91 - templates/apps/next/src/app/(main)/layout.tsx | 11 - .../next/src/app/(static)/privacy/page.tsx | 15 - .../apps/next/src/app/(static)/terms/page.tsx | 15 - templates/apps/next/src/app/globals.css | 1 - templates/apps/next/src/app/layout.tsx | 51 - templates/apps/next/src/app/page.tsx | 36 - templates/apps/next/src/app/providers.tsx | 55 - templates/apps/next/src/app/robots.ts | 10 - templates/apps/next/src/app/sitemap.ts | 26 - .../apps/next/src/components/container.tsx | 27 - templates/apps/next/src/lib/metadata.ts | 22 - templates/apps/next/startupkit.config.ts | 11 - templates/apps/next/tailwind.config.ts | 1 - templates/apps/next/tsconfig.json | 17 - templates/apps/next/turbo.json | 13 - templates/apps/storybook/.gitignore | 2 - templates/apps/storybook/.storybook/main.ts | 133 - .../storybook/.storybook/preview-head.html | 18 - .../apps/storybook/.storybook/preview.ts | 58 - .../apps/storybook/.storybook/stubs/db.ts | 97 - .../apps/storybook/.storybook/stubs/empty.ts | 28 - .../.storybook/stubs/prisma-client.ts | 26 - .../apps/storybook/.storybook/styles.css | 5 - templates/apps/storybook/package.json | 35 - templates/apps/storybook/postcss.config.mjs | 8 - templates/apps/storybook/startupkit.config.ts | 10 - templates/apps/storybook/tailwind.config.ts | 94 - templates/apps/storybook/vercel.json | 5 - templates/apps/vite/index.html | 13 - templates/apps/vite/package.json | 33 - templates/apps/vite/src/App.css | 14 - templates/apps/vite/src/App.tsx | 27 - templates/apps/vite/src/index.css | 69 - templates/apps/vite/src/main.tsx | 10 - templates/apps/vite/startupkit.config.ts | 11 - templates/apps/vite/tsconfig.json | 23 - templates/apps/vite/tsconfig.node.json | 11 - templates/apps/vite/vite.config.ts | 9 - templates/packages/analytics/README.md | 257 - templates/packages/analytics/biome.jsonc | 4 - templates/packages/analytics/package.json | 44 - .../src/components/analytics-provider.tsx | 169 - .../packages/analytics/src/hooks/use-flag.ts | 6 - templates/packages/analytics/src/index.ts | 7 - templates/packages/analytics/src/server.ts | 45 - templates/packages/analytics/src/types.ts | 66 - .../packages/analytics/src/vendor/posthog.ts | 51 - .../packages/analytics/startupkit.config.ts | 10 - templates/packages/analytics/tsconfig.json | 5 - templates/packages/auth/README.md | 314 - templates/packages/auth/biome.jsonc | 7 - templates/packages/auth/package.json | 39 - .../packages/auth/src/components/index.ts | 2 - .../packages/auth/src/components/provider.tsx | 48 - templates/packages/auth/src/hooks/use-auth.ts | 18 - templates/packages/auth/src/index.ts | 13 - templates/packages/auth/src/lib/auth.ts | 155 - templates/packages/auth/src/server.ts | 30 - templates/packages/auth/src/types.ts | 4 - templates/packages/auth/startupkit.config.ts | 11 - templates/packages/auth/tsconfig.json | 5 - templates/packages/db/biome.jsonc | 4 - templates/packages/db/drizzle.config.ts | 11 - .../packages/db/drizzle/0000_initial.sql | 86 - .../db/drizzle/meta/0000_snapshot.json | 655 - .../packages/db/drizzle/meta/_journal.json | 13 - templates/packages/db/package.json | 33 - templates/packages/db/src/index.ts | 36 - templates/packages/db/src/schema.ts | 181 - templates/packages/db/startupkit.config.ts | 10 - templates/packages/db/tsconfig.json | 5 - templates/packages/emails/.gitignore | 1 - templates/packages/emails/package.json | 28 - templates/packages/emails/src/index.tsx | 53 - .../packages/emails/src/lib/preview-email.ts | 61 - .../emails/src/templates/team-invite.tsx | 96 - .../emails/src/templates/verify-code.tsx | 85 - .../packages/emails/startupkit.config.ts | 10 - templates/packages/emails/tsconfig.json | 9 - templates/packages/ui/.gitignore | 0 templates/packages/ui/.storybook/main.ts | 20 - .../packages/ui/.storybook/preview-head.html | 9 - templates/packages/ui/.storybook/preview.ts | 16 - templates/packages/ui/.storybook/styles.css | 5 - templates/packages/ui/biome.jsonc | 7 - templates/packages/ui/components.json | 21 - templates/packages/ui/package.json | 95 - templates/packages/ui/postcss.config.mjs | 7 - templates/packages/ui/shadcn.sh | 2 - .../packages/ui/src/components/accordion.tsx | 65 - .../src/components/alert-dialog.stories.tsx | 45 - .../ui/src/components/alert-dialog.tsx | 137 - .../ui/src/components/alert.stories.tsx | 61 - .../packages/ui/src/components/alert.tsx | 61 - .../ui/src/components/avatar.stories.tsx | 52 - .../packages/ui/src/components/avatar.tsx | 86 - .../ui/src/components/badge.stories.tsx | 39 - .../packages/ui/src/components/badge.tsx | 45 - .../ui/src/components/breadcrumb.stories.tsx | 65 - .../packages/ui/src/components/breadcrumb.tsx | 113 - .../ui/src/components/button.stories.tsx | 53 - .../packages/ui/src/components/button.tsx | 145 - .../ui/src/components/card.stories.tsx | 64 - templates/packages/ui/src/components/card.tsx | 86 - .../ui/src/components/checkbox.stories.tsx | 53 - .../packages/ui/src/components/checkbox.tsx | 30 - .../ui/src/components/collapsible.stories.tsx | 57 - .../ui/src/components/collapsible.tsx | 11 - .../ui/src/components/dialog.stories.tsx | 85 - .../packages/ui/src/components/dialog.tsx | 126 - .../ui/src/components/drawer.stories.tsx | 104 - .../packages/ui/src/components/drawer.tsx | 118 - .../src/components/dropdown-menu.stories.tsx | 152 - .../ui/src/components/dropdown-menu.tsx | 202 - .../ui/src/components/form.stories.tsx | 166 - templates/packages/ui/src/components/form.tsx | 179 - .../ui/src/components/input.stories.tsx | 63 - .../packages/ui/src/components/input.tsx | 71 - .../ui/src/components/label.stories.tsx | 60 - .../packages/ui/src/components/label.tsx | 26 - templates/packages/ui/src/components/logo.tsx | 93 - .../ui/src/components/markdown.stories.tsx | 97 - .../packages/ui/src/components/markdown.tsx | 135 - .../ui/src/components/scroll-area.stories.tsx | 88 - .../ui/src/components/scroll-area.tsx | 48 - .../ui/src/components/select.stories.tsx | 95 - .../packages/ui/src/components/select.tsx | 160 - .../ui/src/components/separator.stories.tsx | 41 - .../packages/ui/src/components/separator.tsx | 31 - .../ui/src/components/sheet.stories.tsx | 115 - .../packages/ui/src/components/sheet.tsx | 137 - .../ui/src/components/sidebar.stories.tsx | 207 - .../packages/ui/src/components/sidebar.tsx | 831 - .../ui/src/components/skeleton.stories.tsx | 44 - .../packages/ui/src/components/skeleton.tsx | 15 - .../ui/src/components/spinner.stories.tsx | 32 - .../packages/ui/src/components/spinner.tsx | 31 - .../ui/src/components/switch.stories.tsx | 52 - .../packages/ui/src/components/switch.tsx | 29 - .../ui/src/components/table.stories.tsx | 128 - .../packages/ui/src/components/table.tsx | 120 - .../ui/src/components/tabs.stories.tsx | 84 - templates/packages/ui/src/components/tabs.tsx | 55 - .../ui/src/components/textarea.stories.tsx | 54 - .../packages/ui/src/components/textarea.tsx | 24 - .../ui/src/components/theme-toggle.tsx | 39 - .../packages/ui/src/components/toast.tsx | 31 - .../src/components/toggle-group.stories.tsx | 120 - .../ui/src/components/toggle-group.tsx | 73 - .../packages/ui/src/components/toggle.tsx | 47 - .../ui/src/components/tooltip.stories.tsx | 107 - .../packages/ui/src/components/tooltip.tsx | 49 - templates/packages/ui/src/hooks/index.ts | 4 - .../packages/ui/src/hooks/use-is-mobile.ts | 21 - .../ui/src/hooks/use-scroll-to-bottom.ts | 33 - templates/packages/ui/src/lib/utils.ts | 6 - .../ui/src/providers/alert-provider.tsx | 73 - templates/packages/ui/src/providers/index.tsx | 27 - .../ui/src/providers/theme-provider.tsx | 8 - templates/packages/ui/src/styles/index.css | 89 - templates/packages/ui/startupkit.config.ts | 10 - templates/packages/ui/tailwind.config.ts | 105 - templates/packages/ui/tsconfig.json | 9 - templates/packages/utils/package.json | 40 - templates/packages/utils/src/index.ts | 7 - templates/packages/utils/src/lib/array.ts | 35 - templates/packages/utils/src/lib/date.ts | 40 - templates/packages/utils/src/lib/markdown.ts | 12 - templates/packages/utils/src/lib/object.ts | 54 - templates/packages/utils/src/lib/string.ts | 12 - templates/packages/utils/src/lib/urls.ts | 57 - templates/packages/utils/src/lib/uuid.ts | 7 - templates/packages/utils/startupkit.config.ts | 10 - templates/packages/utils/tsconfig.json | 5 - templates/repo/.claude/settings.local.json | 44 - .../repo/.github/workflows/lint-typecheck.yml | 31 - templates/repo/.gitignore | 96 - templates/repo/.npmrc | 11 - templates/repo/.nvmrc | 1 - templates/repo/.ruler/AGENTS.md | 821 - templates/repo/.ruler/ruler.toml | 40 - templates/repo/.startupkit/ralph.json | 16 - templates/repo/apps/.gitkeep | 0 templates/repo/biome.jsonc | 3 - templates/repo/components.json | 20 - templates/repo/config/biome/biome.jsonc | 134 - templates/repo/config/biome/package.json | 11 - templates/repo/config/nextjs/index.mjs | 10 - templates/repo/config/nextjs/package.json | 9 - templates/repo/config/typescript/base.json | 24 - .../config/typescript/internal-package.json | 11 - templates/repo/config/typescript/nextjs.json | 32 - templates/repo/config/typescript/package.json | 8 - .../repo/config/typescript/react-library.json | 11 - templates/repo/package.json | 43 - templates/repo/pnpm-lock.yaml | 5061 --- templates/repo/pnpm-workspace.yaml | 30 - templates/repo/turbo.json | 57 - turbo.json | 52 - 442 files changed, 2170 insertions(+), 64954 deletions(-) delete mode 100644 .github/actions/setup/action.yml delete mode 100644 .github/workflows/build-packages.yml delete mode 100644 .github/workflows/build-templates.yml delete mode 100644 .github/workflows/format-lint-typecheck.yml delete mode 100644 .github/workflows/npm-publish.yml delete mode 100644 .github/workflows/test-cli.yml delete mode 100644 .npmrc delete mode 100644 .prettierrc delete mode 100644 .ruler/AGENTS.md delete mode 100644 .ruler/ruler.toml delete mode 100644 apps/home/.gitignore delete mode 100644 apps/home/.source/browser.ts delete mode 100644 apps/home/.source/dynamic.ts delete mode 100644 apps/home/.source/server.ts delete mode 100644 apps/home/.source/source.config.mjs delete mode 100644 apps/home/biome.jsonc delete mode 100644 apps/home/content/docs/analytics/analytics-provider.mdx delete mode 100644 apps/home/content/docs/analytics/feature-flags.mdx delete mode 100644 apps/home/content/docs/analytics/identifying-users.mdx delete mode 100644 apps/home/content/docs/analytics/index.mdx delete mode 100644 apps/home/content/docs/analytics/meta.json delete mode 100644 apps/home/content/docs/analytics/tracking-events.mdx delete mode 100644 apps/home/content/docs/analytics/usage/identify.mdx delete mode 100644 apps/home/content/docs/analytics/usage/meta.json delete mode 100644 apps/home/content/docs/analytics/usage/page.mdx delete mode 100644 apps/home/content/docs/analytics/usage/reset.mdx delete mode 100644 apps/home/content/docs/analytics/usage/track.mdx delete mode 100644 apps/home/content/docs/analytics/usage/use-analytics.mdx delete mode 100644 apps/home/content/docs/analytics/usage/use-flag.mdx delete mode 100644 apps/home/content/docs/auth/configuration.mdx delete mode 100644 apps/home/content/docs/auth/index.mdx delete mode 100644 apps/home/content/docs/auth/meta.json delete mode 100644 apps/home/content/docs/auth/session-management.mdx delete mode 100644 apps/home/content/docs/auth/usage/meta.json delete mode 100644 apps/home/content/docs/auth/usage/require-auth.mdx delete mode 100644 apps/home/content/docs/auth/usage/use-auth.mdx delete mode 100644 apps/home/content/docs/auth/usage/with-auth.mdx delete mode 100644 apps/home/content/docs/cli/add.mdx delete mode 100644 apps/home/content/docs/cli/agents.mdx delete mode 100644 apps/home/content/docs/cli/index.mdx delete mode 100644 apps/home/content/docs/cli/init.mdx delete mode 100644 apps/home/content/docs/cli/meta.json delete mode 100644 apps/home/content/docs/cli/troubleshooting.mdx delete mode 100644 apps/home/content/docs/cli/upgrade.mdx delete mode 100644 apps/home/content/docs/db/index.mdx delete mode 100644 apps/home/content/docs/db/meta.json delete mode 100644 apps/home/content/docs/db/migrations.mdx delete mode 100644 apps/home/content/docs/db/queries.mdx delete mode 100644 apps/home/content/docs/db/schema.mdx delete mode 100644 apps/home/content/docs/deployment/cloudflare.mdx delete mode 100644 apps/home/content/docs/deployment/fly.mdx delete mode 100644 apps/home/content/docs/deployment/index.mdx delete mode 100644 apps/home/content/docs/deployment/railway.mdx delete mode 100644 apps/home/content/docs/deployment/render.mdx delete mode 100644 apps/home/content/docs/deployment/vercel.mdx delete mode 100644 apps/home/content/docs/emails/index.mdx delete mode 100644 apps/home/content/docs/emails/meta.json delete mode 100644 apps/home/content/docs/emails/sending.mdx delete mode 100644 apps/home/content/docs/emails/templates.mdx delete mode 100644 apps/home/content/docs/getting-started/comparisons.mdx delete mode 100644 apps/home/content/docs/getting-started/environment-variables.mdx delete mode 100644 apps/home/content/docs/getting-started/installation.mdx delete mode 100644 apps/home/content/docs/getting-started/meta.json delete mode 100644 apps/home/content/docs/getting-started/project-structure.mdx delete mode 100644 apps/home/content/docs/getting-started/quickstart.mdx delete mode 100644 apps/home/content/docs/getting-started/what-is.mdx delete mode 100644 apps/home/content/docs/index.mdx delete mode 100644 apps/home/content/docs/meta.json delete mode 100644 apps/home/content/docs/seo/index.mdx delete mode 100644 apps/home/content/docs/seo/meta.json delete mode 100644 apps/home/content/docs/seo/metadata.mdx delete mode 100644 apps/home/content/docs/seo/robots.mdx delete mode 100644 apps/home/content/docs/seo/sitemap.mdx delete mode 100644 apps/home/content/docs/seo/structured-data.mdx delete mode 100644 apps/home/content/docs/ui/components.mdx delete mode 100644 apps/home/content/docs/ui/index.mdx delete mode 100644 apps/home/content/docs/ui/meta.json delete mode 100644 apps/home/content/docs/ui/theming.mdx delete mode 100644 apps/home/next.config.ts delete mode 100644 apps/home/package.json delete mode 100644 apps/home/postcss.config.mjs delete mode 100644 apps/home/public/android-chrome-192x192.png delete mode 100644 apps/home/public/android-chrome-512x512.png delete mode 100644 apps/home/public/apple-touch-icon.png delete mode 100644 apps/home/public/favicon-16x16.png delete mode 100644 apps/home/public/favicon-32x32.png delete mode 100644 apps/home/public/favicon.ico delete mode 100644 apps/home/public/images/amp-logo.png delete mode 100644 apps/home/public/images/amp.png delete mode 100644 apps/home/public/images/claude-logo.png delete mode 100644 apps/home/public/images/devin-logo.png delete mode 100644 apps/home/public/images/devin-primarylockup-white.png delete mode 100644 apps/home/public/images/expo-logo.png delete mode 100644 apps/home/public/images/framer-motion-logo.svg delete mode 100644 apps/home/public/images/google-analytics-logo.svg delete mode 100644 apps/home/public/images/lucide-logo.svg delete mode 100644 apps/home/public/images/opencode-logo.png delete mode 100644 apps/home/public/images/posthog-logo.svg delete mode 100644 apps/home/public/images/shadcn-ui-logo.svg delete mode 100644 apps/home/public/images/tailwind-logo.svg delete mode 100644 apps/home/public/images/vite-logo.png delete mode 100644 apps/home/public/robots.txt delete mode 100644 apps/home/public/site.webmanifest delete mode 100644 apps/home/public/sitemap.xml delete mode 100644 apps/home/public/startupkit-logo.png delete mode 100644 apps/home/scripts/sync-package-docs.ts delete mode 100644 apps/home/source.config.ts delete mode 100644 apps/home/src/app/(marketing)/layout.tsx delete mode 100644 apps/home/src/app/(marketing)/page.tsx delete mode 100644 apps/home/src/app/(marketing)/privacy/page.tsx delete mode 100644 apps/home/src/app/(marketing)/terms/page.tsx delete mode 100644 apps/home/src/app/docs/[[...slug]]/page.tsx delete mode 100644 apps/home/src/app/docs/docs.css delete mode 100644 apps/home/src/app/docs/layout.tsx delete mode 100644 apps/home/src/app/globals.css delete mode 100644 apps/home/src/app/layout.tsx delete mode 100644 apps/home/src/app/providers.tsx delete mode 100644 apps/home/src/app/source.ts delete mode 100644 apps/home/src/components/ai-ready-section.tsx delete mode 100644 apps/home/src/components/bento-features-section.tsx delete mode 100644 apps/home/src/components/copy-button.tsx delete mode 100644 apps/home/src/components/cta-section.tsx delete mode 100644 apps/home/src/components/drizzle-code-snippet.tsx delete mode 100644 apps/home/src/components/faq-section.tsx delete mode 100644 apps/home/src/components/footer.tsx delete mode 100644 apps/home/src/components/github-star-button.tsx delete mode 100644 apps/home/src/components/hero-section.tsx delete mode 100644 apps/home/src/components/logo.tsx delete mode 100644 apps/home/src/components/navigation.tsx delete mode 100644 apps/home/src/components/problem-section.tsx delete mode 100644 apps/home/src/components/services-section.tsx delete mode 100644 apps/home/src/components/terminal-demo.tsx delete mode 100644 apps/home/src/components/ui/accordion.tsx delete mode 100644 apps/home/src/components/ui/button.tsx delete mode 100644 apps/home/src/components/ui/utils.ts delete mode 100644 apps/home/src/lib/constants.ts delete mode 100644 apps/home/tsconfig.json delete mode 100644 apps/home/twoslash.d.ts delete mode 100644 biome.jsonc delete mode 100644 config/biome/biome.jsonc delete mode 100644 config/biome/package.json delete mode 100644 config/nextjs/index.mjs delete mode 100644 config/nextjs/package.json delete mode 100644 config/typescript/base.json delete mode 100644 config/typescript/library.json delete mode 100644 config/typescript/nextjs.json delete mode 100644 config/typescript/package.json delete mode 100644 packages/analytics/README.md delete mode 100644 packages/analytics/biome.jsonc delete mode 100644 packages/analytics/package.json delete mode 100644 packages/analytics/rollup.config.mjs delete mode 100644 packages/analytics/src/ahrefs/ahrefs-provider.tsx delete mode 100644 packages/analytics/src/ahrefs/index.ts delete mode 100644 packages/analytics/src/context.ts delete mode 100644 packages/analytics/src/datafast/datafast-provider.tsx delete mode 100644 packages/analytics/src/datafast/index.ts delete mode 100644 packages/analytics/src/google/google-analytics-provider.tsx delete mode 100644 packages/analytics/src/google/index.ts delete mode 100644 packages/analytics/src/gtm/gtm-provider.tsx delete mode 100644 packages/analytics/src/gtm/index.ts delete mode 100644 packages/analytics/src/index.ts delete mode 100644 packages/analytics/src/openpanel/index.ts delete mode 100644 packages/analytics/src/openpanel/openpanel-provider.tsx delete mode 100644 packages/analytics/src/posthog/index.ts delete mode 100644 packages/analytics/src/posthog/posthog-provider.tsx delete mode 100644 packages/analytics/src/provider.tsx delete mode 100644 packages/analytics/src/types.ts delete mode 100644 packages/analytics/src/use-analytics.ts delete mode 100644 packages/analytics/src/use-flag.ts delete mode 100644 packages/analytics/tsconfig.json delete mode 100644 packages/auth/README.md delete mode 100644 packages/auth/biome.jsonc delete mode 100644 packages/auth/package.json delete mode 100644 packages/auth/rollup.config.mjs delete mode 100644 packages/auth/src/components/context.ts delete mode 100644 packages/auth/src/components/index.ts delete mode 100644 packages/auth/src/components/provider.tsx delete mode 100644 packages/auth/src/components/use-auth.ts delete mode 100644 packages/auth/src/index.ts delete mode 100644 packages/auth/src/server.ts delete mode 100644 packages/auth/tsconfig.json delete mode 100644 packages/cli/README.md delete mode 100644 packages/cli/biome.jsonc delete mode 100644 packages/cli/src/cmd/README.test.md delete mode 100644 packages/cli/src/cmd/add.integration.test.ts delete mode 100644 packages/cli/src/cmd/add.test.ts delete mode 100644 packages/cli/src/cmd/add.ts delete mode 100644 packages/cli/src/cmd/init.integration.test.ts delete mode 100644 packages/cli/src/cmd/init.test.ts delete mode 100644 packages/cli/src/cmd/init.ts delete mode 100644 packages/cli/src/cmd/make.integration.test.ts delete mode 100644 packages/cli/src/cmd/make.test.ts delete mode 100644 packages/cli/src/cmd/make.ts create mode 100644 packages/cli/src/cmd/skills.ts delete mode 100644 packages/cli/src/cmd/update.ts delete mode 100644 packages/cli/src/cmd/upgrade.test.ts delete mode 100644 packages/cli/src/cmd/upgrade.ts delete mode 100644 packages/cli/src/config.md delete mode 100644 packages/cli/src/config.ts delete mode 100644 packages/cli/src/lib/spinner.ts delete mode 100644 packages/cli/src/lib/system.ts delete mode 100644 packages/cli/src/post-install-check.ts delete mode 100644 packages/cli/vitest.config.ts delete mode 100644 packages/seo/README.md delete mode 100644 packages/seo/biome.jsonc delete mode 100644 packages/seo/package.json delete mode 100644 packages/seo/rollup.config.mjs delete mode 100644 packages/seo/src/index.ts delete mode 100644 packages/seo/src/metadata.ts delete mode 100644 packages/seo/src/robots.ts delete mode 100644 packages/seo/src/sitemap.ts delete mode 100644 packages/seo/src/structured-data.ts delete mode 100644 packages/seo/tsconfig.json delete mode 100755 scripts/bump delete mode 100755 scripts/link-local.js delete mode 100755 scripts/release-notes delete mode 100644 templates/apps/next/.gitignore delete mode 100644 templates/apps/next/README.md delete mode 100644 templates/apps/next/biome.jsonc delete mode 100644 templates/apps/next/components.json delete mode 100644 templates/apps/next/next.config.ts delete mode 100644 templates/apps/next/package.json delete mode 100644 templates/apps/next/postcss.config.mjs delete mode 100644 templates/apps/next/public/apple-touch-icon.png delete mode 100644 templates/apps/next/public/favicon-96x96.png delete mode 100644 templates/apps/next/public/favicon.ico delete mode 100644 templates/apps/next/public/favicon.svg delete mode 100644 templates/apps/next/public/site.webmanifest delete mode 100644 templates/apps/next/public/web-app-manifest-192x192.png delete mode 100644 templates/apps/next/public/web-app-manifest-512x512.png delete mode 100644 templates/apps/next/src/app/(auth)/auth/[...all]/route.ts delete mode 100644 templates/apps/next/src/app/(auth)/layout.tsx delete mode 100644 templates/apps/next/src/app/(auth)/sign-in/page.tsx delete mode 100644 templates/apps/next/src/app/(main)/dashboard/logout-button.tsx delete mode 100644 templates/apps/next/src/app/(main)/dashboard/page.tsx delete mode 100644 templates/apps/next/src/app/(main)/layout.tsx delete mode 100644 templates/apps/next/src/app/(static)/privacy/page.tsx delete mode 100644 templates/apps/next/src/app/(static)/terms/page.tsx delete mode 100644 templates/apps/next/src/app/globals.css delete mode 100644 templates/apps/next/src/app/layout.tsx delete mode 100644 templates/apps/next/src/app/page.tsx delete mode 100644 templates/apps/next/src/app/providers.tsx delete mode 100644 templates/apps/next/src/app/robots.ts delete mode 100644 templates/apps/next/src/app/sitemap.ts delete mode 100644 templates/apps/next/src/components/container.tsx delete mode 100644 templates/apps/next/src/lib/metadata.ts delete mode 100644 templates/apps/next/startupkit.config.ts delete mode 100644 templates/apps/next/tailwind.config.ts delete mode 100644 templates/apps/next/tsconfig.json delete mode 100644 templates/apps/next/turbo.json delete mode 100644 templates/apps/storybook/.gitignore delete mode 100644 templates/apps/storybook/.storybook/main.ts delete mode 100644 templates/apps/storybook/.storybook/preview-head.html delete mode 100644 templates/apps/storybook/.storybook/preview.ts delete mode 100644 templates/apps/storybook/.storybook/stubs/db.ts delete mode 100644 templates/apps/storybook/.storybook/stubs/empty.ts delete mode 100644 templates/apps/storybook/.storybook/stubs/prisma-client.ts delete mode 100644 templates/apps/storybook/.storybook/styles.css delete mode 100644 templates/apps/storybook/package.json delete mode 100644 templates/apps/storybook/postcss.config.mjs delete mode 100644 templates/apps/storybook/startupkit.config.ts delete mode 100644 templates/apps/storybook/tailwind.config.ts delete mode 100644 templates/apps/storybook/vercel.json delete mode 100644 templates/apps/vite/index.html delete mode 100644 templates/apps/vite/package.json delete mode 100644 templates/apps/vite/src/App.css delete mode 100644 templates/apps/vite/src/App.tsx delete mode 100644 templates/apps/vite/src/index.css delete mode 100644 templates/apps/vite/src/main.tsx delete mode 100644 templates/apps/vite/startupkit.config.ts delete mode 100644 templates/apps/vite/tsconfig.json delete mode 100644 templates/apps/vite/tsconfig.node.json delete mode 100644 templates/apps/vite/vite.config.ts delete mode 100644 templates/packages/analytics/README.md delete mode 100644 templates/packages/analytics/biome.jsonc delete mode 100644 templates/packages/analytics/package.json delete mode 100644 templates/packages/analytics/src/components/analytics-provider.tsx delete mode 100644 templates/packages/analytics/src/hooks/use-flag.ts delete mode 100644 templates/packages/analytics/src/index.ts delete mode 100644 templates/packages/analytics/src/server.ts delete mode 100644 templates/packages/analytics/src/types.ts delete mode 100644 templates/packages/analytics/src/vendor/posthog.ts delete mode 100644 templates/packages/analytics/startupkit.config.ts delete mode 100644 templates/packages/analytics/tsconfig.json delete mode 100644 templates/packages/auth/README.md delete mode 100644 templates/packages/auth/biome.jsonc delete mode 100644 templates/packages/auth/package.json delete mode 100644 templates/packages/auth/src/components/index.ts delete mode 100644 templates/packages/auth/src/components/provider.tsx delete mode 100644 templates/packages/auth/src/hooks/use-auth.ts delete mode 100644 templates/packages/auth/src/index.ts delete mode 100644 templates/packages/auth/src/lib/auth.ts delete mode 100644 templates/packages/auth/src/server.ts delete mode 100644 templates/packages/auth/src/types.ts delete mode 100644 templates/packages/auth/startupkit.config.ts delete mode 100644 templates/packages/auth/tsconfig.json delete mode 100644 templates/packages/db/biome.jsonc delete mode 100644 templates/packages/db/drizzle.config.ts delete mode 100644 templates/packages/db/drizzle/0000_initial.sql delete mode 100644 templates/packages/db/drizzle/meta/0000_snapshot.json delete mode 100644 templates/packages/db/drizzle/meta/_journal.json delete mode 100644 templates/packages/db/package.json delete mode 100644 templates/packages/db/src/index.ts delete mode 100644 templates/packages/db/src/schema.ts delete mode 100644 templates/packages/db/startupkit.config.ts delete mode 100644 templates/packages/db/tsconfig.json delete mode 100644 templates/packages/emails/.gitignore delete mode 100644 templates/packages/emails/package.json delete mode 100644 templates/packages/emails/src/index.tsx delete mode 100644 templates/packages/emails/src/lib/preview-email.ts delete mode 100644 templates/packages/emails/src/templates/team-invite.tsx delete mode 100644 templates/packages/emails/src/templates/verify-code.tsx delete mode 100644 templates/packages/emails/startupkit.config.ts delete mode 100644 templates/packages/emails/tsconfig.json delete mode 100644 templates/packages/ui/.gitignore delete mode 100644 templates/packages/ui/.storybook/main.ts delete mode 100644 templates/packages/ui/.storybook/preview-head.html delete mode 100644 templates/packages/ui/.storybook/preview.ts delete mode 100644 templates/packages/ui/.storybook/styles.css delete mode 100644 templates/packages/ui/biome.jsonc delete mode 100644 templates/packages/ui/components.json delete mode 100644 templates/packages/ui/package.json delete mode 100644 templates/packages/ui/postcss.config.mjs delete mode 100755 templates/packages/ui/shadcn.sh delete mode 100644 templates/packages/ui/src/components/accordion.tsx delete mode 100644 templates/packages/ui/src/components/alert-dialog.stories.tsx delete mode 100644 templates/packages/ui/src/components/alert-dialog.tsx delete mode 100644 templates/packages/ui/src/components/alert.stories.tsx delete mode 100644 templates/packages/ui/src/components/alert.tsx delete mode 100644 templates/packages/ui/src/components/avatar.stories.tsx delete mode 100644 templates/packages/ui/src/components/avatar.tsx delete mode 100644 templates/packages/ui/src/components/badge.stories.tsx delete mode 100644 templates/packages/ui/src/components/badge.tsx delete mode 100644 templates/packages/ui/src/components/breadcrumb.stories.tsx delete mode 100644 templates/packages/ui/src/components/breadcrumb.tsx delete mode 100644 templates/packages/ui/src/components/button.stories.tsx delete mode 100644 templates/packages/ui/src/components/button.tsx delete mode 100644 templates/packages/ui/src/components/card.stories.tsx delete mode 100644 templates/packages/ui/src/components/card.tsx delete mode 100644 templates/packages/ui/src/components/checkbox.stories.tsx delete mode 100644 templates/packages/ui/src/components/checkbox.tsx delete mode 100644 templates/packages/ui/src/components/collapsible.stories.tsx delete mode 100644 templates/packages/ui/src/components/collapsible.tsx delete mode 100644 templates/packages/ui/src/components/dialog.stories.tsx delete mode 100644 templates/packages/ui/src/components/dialog.tsx delete mode 100644 templates/packages/ui/src/components/drawer.stories.tsx delete mode 100644 templates/packages/ui/src/components/drawer.tsx delete mode 100644 templates/packages/ui/src/components/dropdown-menu.stories.tsx delete mode 100644 templates/packages/ui/src/components/dropdown-menu.tsx delete mode 100644 templates/packages/ui/src/components/form.stories.tsx delete mode 100644 templates/packages/ui/src/components/form.tsx delete mode 100644 templates/packages/ui/src/components/input.stories.tsx delete mode 100644 templates/packages/ui/src/components/input.tsx delete mode 100644 templates/packages/ui/src/components/label.stories.tsx delete mode 100644 templates/packages/ui/src/components/label.tsx delete mode 100644 templates/packages/ui/src/components/logo.tsx delete mode 100644 templates/packages/ui/src/components/markdown.stories.tsx delete mode 100644 templates/packages/ui/src/components/markdown.tsx delete mode 100644 templates/packages/ui/src/components/scroll-area.stories.tsx delete mode 100644 templates/packages/ui/src/components/scroll-area.tsx delete mode 100644 templates/packages/ui/src/components/select.stories.tsx delete mode 100644 templates/packages/ui/src/components/select.tsx delete mode 100644 templates/packages/ui/src/components/separator.stories.tsx delete mode 100644 templates/packages/ui/src/components/separator.tsx delete mode 100644 templates/packages/ui/src/components/sheet.stories.tsx delete mode 100644 templates/packages/ui/src/components/sheet.tsx delete mode 100644 templates/packages/ui/src/components/sidebar.stories.tsx delete mode 100644 templates/packages/ui/src/components/sidebar.tsx delete mode 100644 templates/packages/ui/src/components/skeleton.stories.tsx delete mode 100644 templates/packages/ui/src/components/skeleton.tsx delete mode 100644 templates/packages/ui/src/components/spinner.stories.tsx delete mode 100644 templates/packages/ui/src/components/spinner.tsx delete mode 100644 templates/packages/ui/src/components/switch.stories.tsx delete mode 100644 templates/packages/ui/src/components/switch.tsx delete mode 100644 templates/packages/ui/src/components/table.stories.tsx delete mode 100644 templates/packages/ui/src/components/table.tsx delete mode 100644 templates/packages/ui/src/components/tabs.stories.tsx delete mode 100644 templates/packages/ui/src/components/tabs.tsx delete mode 100644 templates/packages/ui/src/components/textarea.stories.tsx delete mode 100644 templates/packages/ui/src/components/textarea.tsx delete mode 100644 templates/packages/ui/src/components/theme-toggle.tsx delete mode 100644 templates/packages/ui/src/components/toast.tsx delete mode 100644 templates/packages/ui/src/components/toggle-group.stories.tsx delete mode 100644 templates/packages/ui/src/components/toggle-group.tsx delete mode 100644 templates/packages/ui/src/components/toggle.tsx delete mode 100644 templates/packages/ui/src/components/tooltip.stories.tsx delete mode 100644 templates/packages/ui/src/components/tooltip.tsx delete mode 100644 templates/packages/ui/src/hooks/index.ts delete mode 100644 templates/packages/ui/src/hooks/use-is-mobile.ts delete mode 100644 templates/packages/ui/src/hooks/use-scroll-to-bottom.ts delete mode 100644 templates/packages/ui/src/lib/utils.ts delete mode 100644 templates/packages/ui/src/providers/alert-provider.tsx delete mode 100644 templates/packages/ui/src/providers/index.tsx delete mode 100644 templates/packages/ui/src/providers/theme-provider.tsx delete mode 100644 templates/packages/ui/src/styles/index.css delete mode 100644 templates/packages/ui/startupkit.config.ts delete mode 100644 templates/packages/ui/tailwind.config.ts delete mode 100644 templates/packages/ui/tsconfig.json delete mode 100644 templates/packages/utils/package.json delete mode 100644 templates/packages/utils/src/index.ts delete mode 100644 templates/packages/utils/src/lib/array.ts delete mode 100644 templates/packages/utils/src/lib/date.ts delete mode 100644 templates/packages/utils/src/lib/markdown.ts delete mode 100644 templates/packages/utils/src/lib/object.ts delete mode 100644 templates/packages/utils/src/lib/string.ts delete mode 100644 templates/packages/utils/src/lib/urls.ts delete mode 100644 templates/packages/utils/src/lib/uuid.ts delete mode 100644 templates/packages/utils/startupkit.config.ts delete mode 100644 templates/packages/utils/tsconfig.json delete mode 100644 templates/repo/.claude/settings.local.json delete mode 100644 templates/repo/.github/workflows/lint-typecheck.yml delete mode 100644 templates/repo/.gitignore delete mode 100644 templates/repo/.npmrc delete mode 100644 templates/repo/.nvmrc delete mode 100644 templates/repo/.ruler/AGENTS.md delete mode 100644 templates/repo/.ruler/ruler.toml delete mode 100644 templates/repo/.startupkit/ralph.json delete mode 100644 templates/repo/apps/.gitkeep delete mode 100644 templates/repo/biome.jsonc delete mode 100644 templates/repo/components.json delete mode 100644 templates/repo/config/biome/biome.jsonc delete mode 100644 templates/repo/config/biome/package.json delete mode 100644 templates/repo/config/nextjs/index.mjs delete mode 100644 templates/repo/config/nextjs/package.json delete mode 100644 templates/repo/config/typescript/base.json delete mode 100644 templates/repo/config/typescript/internal-package.json delete mode 100644 templates/repo/config/typescript/nextjs.json delete mode 100644 templates/repo/config/typescript/package.json delete mode 100644 templates/repo/config/typescript/react-library.json delete mode 100644 templates/repo/package.json delete mode 100644 templates/repo/pnpm-lock.yaml delete mode 100644 templates/repo/pnpm-workspace.yaml delete mode 100644 templates/repo/turbo.json delete mode 100644 turbo.json diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml deleted file mode 100644 index b19eb009..00000000 --- a/.github/actions/setup/action.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: 'Setup' -description: 'Shared setup steps for all jobs' -runs: - using: 'composite' - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: 22 - - - name: Setup pnpm - uses: pnpm/action-setup@v2 - with: - version: 9.6.0 \ No newline at end of file diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml deleted file mode 100644 index fde821e6..00000000 --- a/.github/workflows/build-packages.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Build Packages - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened] - branches: - - main - -jobs: - build-packages: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20, 22] - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: ${{ matrix.node-version }} - - - name: Setup pnpm - uses: pnpm/action-setup@v2 - with: - version: 9.6.0 - - - name: Install dependencies - run: pnpm install - - - name: Run build - run: pnpm run build diff --git a/.github/workflows/build-templates.yml b/.github/workflows/build-templates.yml deleted file mode 100644 index a7346339..00000000 --- a/.github/workflows/build-templates.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: Build Template - -on: - push: - branches: - - main - pull_request: - types: [opened, synchronize, reopened] - branches: - - main - -jobs: - build-repo-template: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: ./.github/actions/setup - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Install template dependencies - run: pnpm install --frozen-lockfile - working-directory: ./templates/repo - - - name: Build packages - run: pnpm --filter "./packages/**" build - - - name: Add working directory - run: mkdir -p ./tmp - - - name: Install repo (with branch) - run: | - node $GITHUB_WORKSPACE/packages/cli/dist/cli.js init --name repo --repo "ian/startupkit/templates/repo#${GITHUB_REF_NAME}" - working-directory: ./tmp - - - name: Link local packages for testing - run: | - echo "🔗 Linking local @startupkit/* packages..." - node "$GITHUB_WORKSPACE/scripts/link-local.js" link "tmp/repo" "$GITHUB_WORKSPACE/packages" - - - name: Reinstall with local packages - run: | - rm -rf node_modules pnpm-lock.yaml - pnpm install --no-frozen-lockfile - working-directory: ./tmp/repo - - - name: Verify Install - run: | - node $GITHUB_WORKSPACE/packages/cli/dist/post-install-check.js --mode repo - working-directory: ./tmp/repo - - - name: Lint - run: pnpm run lint - working-directory: ./tmp/repo - - - name: Typecheck - run: pnpm run typecheck - working-directory: ./tmp/repo - - build-next-template: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: ./.github/actions/setup - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build packages - run: pnpm --filter "./packages/**" build - - - name: Add working directory - run: mkdir -p ./tmp - - - name: Install repo (with branch) - run: | - node $GITHUB_WORKSPACE/packages/cli/dist/cli.js init --name repo --repo "ian/startupkit/templates/repo#${GITHUB_REF_NAME}" - working-directory: ./tmp - - - name: Link local packages for testing - run: | - echo "🔗 Linking local @startupkit/* packages..." - node "$GITHUB_WORKSPACE/scripts/link-local.js" link "tmp/repo" "$GITHUB_WORKSPACE/packages" - - - name: Reinstall with local packages - run: | - rm -rf node_modules pnpm-lock.yaml - pnpm install --no-frozen-lockfile - working-directory: ./tmp/repo - - - name: Install app (with branch) - run: | - node $GITHUB_WORKSPACE/packages/cli/dist/cli.js add next --name next --repo "ian/startupkit/templates/apps/next#${GITHUB_REF_NAME}" - working-directory: ./tmp/repo - - - name: Link local packages in app - run: | - echo "🔗 Linking local @startupkit/* packages in apps/next..." - node "$GITHUB_WORKSPACE/scripts/link-local.js" link "tmp/repo" "$GITHUB_WORKSPACE/packages" - - - name: Reinstall after adding app - run: pnpm install --no-frozen-lockfile - working-directory: ./tmp/repo - - - name: Verify Install - run: | - node $GITHUB_WORKSPACE/packages/cli/dist/post-install-check.js --mode app - working-directory: ./tmp/repo/apps/next - - - name: Lint - run: pnpm --filter "./apps/next" run lint - working-directory: ./tmp/repo - - - name: Typecheck - run: pnpm --filter "./apps/next" run typecheck - working-directory: ./tmp/repo diff --git a/.github/workflows/format-lint-typecheck.yml b/.github/workflows/format-lint-typecheck.yml deleted file mode 100644 index 70f0d90c..00000000 --- a/.github/workflows/format-lint-typecheck.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: Lint & Typecheck - -on: - pull_request - -permissions: - contents: read - -jobs: - format: - runs-on: ubuntu-latest - env: - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ribera - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - - run_install: false - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm format - - lint: - runs-on: ubuntu-latest - env: - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ribera - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - - run_install: false - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm lint - - typecheck: - runs-on: ubuntu-latest - env: - TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} - TURBO_TEAM: ribera - steps: - - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - with: - - run_install: false - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "pnpm" - - run: pnpm install --frozen-lockfile - - run: pnpm typecheck diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml deleted file mode 100644 index 766ec02b..00000000 --- a/.github/workflows/npm-publish.yml +++ /dev/null @@ -1,162 +0,0 @@ -name: Publish to NPM - -on: - release: - types: [published] - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Extract version from release tag - run: | - TAG_NAME="${{ github.event.release.tag_name }}" - # Remove 'v' prefix if present (e.g., v1.2.3 -> 1.2.3) - VERSION="${TAG_NAME#v}" - echo "VERSION=$VERSION" >> $GITHUB_ENV - echo "📦 Publishing version: $VERSION" - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - registry-url: 'https://registry.npmjs.org' - - - name: Setup pnpm - uses: pnpm/action-setup@v2 - with: - version: 9.6.0 - - - name: Install dependencies - run: pnpm install - - - name: Set package versions - run: | - echo "📝 Setting all packages to version $VERSION..." - pnpm -r exec -- npm version $VERSION --no-git-tag-version --allow-same-version - - - name: Verify package versions - run: | - echo "🔍 Verifying package versions..." - find packages -name "package.json" -exec node -e " - const pkg = require('{}'); - const expectedVersion = '$VERSION'; - if (pkg.version === expectedVersion) { - console.log('✅ ' + pkg.name + '@' + pkg.version); - } else { - console.log('❌ ' + pkg.name + ' version mismatch: expected ' + expectedVersion + ', got ' + pkg.version); - process.exit(1); - } - " \; - - - name: Build packages - run: pnpm run build - - - name: List packages to be published - run: | - echo "📦 Packages to publish (version $VERSION):" - find packages -name "package.json" -exec node -e " - const pkg = require('{}'); - if (!pkg.private) { - console.log(' • ' + pkg.name + '@' + pkg.version); - } - " \; - - - name: Generate OTP code - run: | - echo "🔐 Generating OTP code for 2FA..." - OTP_CODE=$(node -e " - const crypto = require('crypto'); - - // Base32 decoder - function base32Decode(encoded) { - const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; - let bits = ''; - let result = []; - - for (let i = 0; i < encoded.length; i++) { - const val = alphabet.indexOf(encoded.charAt(i).toUpperCase()); - if (val >= 0) { - bits += val.toString(2).padStart(5, '0'); - } - } - - for (let i = 0; i + 8 <= bits.length; i += 8) { - result.push(parseInt(bits.substr(i, 8), 2)); - } - - return Buffer.from(result); - } - - const secret = '${{ secrets.NPM_OTP_SECRET }}'; - const time = Math.floor(Date.now() / 1000 / 30); - const buffer = Buffer.alloc(8); - buffer.writeUInt32BE(time, 4); - - const secretBuffer = base32Decode(secret); - const hmac = crypto.createHmac('sha1', secretBuffer); - hmac.update(buffer); - const hash = hmac.digest(); - - const offset = hash[hash.length - 1] & 0xf; - const code = ((hash[offset] & 0x7f) << 24) | - ((hash[offset + 1] & 0xff) << 16) | - ((hash[offset + 2] & 0xff) << 8) | - (hash[offset + 3] & 0xff); - - console.log(String(code % 1000000).padStart(6, '0')); - ") - echo "OTP_CODE=$OTP_CODE" >> $GITHUB_ENV - echo "✅ OTP code generated: $OTP_CODE" - - - name: Publish to NPM - run: | - echo "🔐 Using 2FA authentication with OTP" - echo "🚀 Publishing all packages..." - - find packages -name "package.json" -exec dirname {} \; | while read package_dir; do - package_name=$(node -p "require('./$package_dir/package.json').name") - is_private=$(node -p "require('./$package_dir/package.json').private || false") - - if [ "$is_private" != "true" ]; then - echo "📦 Publishing $package_name@$VERSION..." - cd "$package_dir" - npm publish --access public --otp $OTP_CODE - cd - > /dev/null - fi - done - - echo "✅ All packages published!" - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Update template references - run: | - echo "📝 Updating @startupkit/* and startupkit references in templates..." - - # Find all package.json files in templates and update startupkit package references - find templates -name "package.json" -type f | while read file; do - # Update @startupkit/* package versions (handles both "x.x.x" and "^x.x.x" formats) - sed -i 's/"@startupkit\/\([^"]*\)": "\^*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"/"@startupkit\/\1": "^'"$VERSION"'"/g' "$file" - # Update startupkit CLI version - sed -i 's/"startupkit": "\^*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*"/"startupkit": "^'"$VERSION"'"/g' "$file" - done - - # Show what was updated - echo "🔍 Updated template references:" - grep -r '"@startupkit/\|"startupkit":' templates --include="package.json" || echo " (none found)" - - - name: Commit version bumps - run: | - git config --local user.email "github-actions[bot]@users.noreply.github.com" - git config --local user.name "github-actions[bot]" - git add packages/*/package.json - git add templates/*/package.json templates/*/*/package.json templates/*/*/*/package.json 2>/dev/null || true - git commit -m "chore: bump package versions to $VERSION [skip ci]" || echo "No changes to commit" - git push origin HEAD:main \ No newline at end of file diff --git a/.github/workflows/test-cli.yml b/.github/workflows/test-cli.yml deleted file mode 100644 index d64c9345..00000000 --- a/.github/workflows/test-cli.yml +++ /dev/null @@ -1,102 +0,0 @@ -name: Test CLI - -on: - push: - branches: - - main - pull_request: - -jobs: - test-cli: - name: CLI Tests - runs-on: ubuntu-latest - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - with: - version: 9.6.0 - run_install: false - - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "pnpm" - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Build CLI package - run: pnpm --filter startupkit build - - - name: Run Vitest tests - run: pnpm --filter startupkit test - - - name: Run Vitest tests with coverage - run: pnpm --filter startupkit test:coverage - - test-cli-integration: - name: CLI Integration - Full Build & Deploy - runs-on: ubuntu-latest - needs: test-cli - - steps: - - uses: actions/checkout@v4 - - - uses: pnpm/action-setup@v4 - with: - version: 9.6.0 - run_install: false - - - uses: actions/setup-node@v4 - with: - node-version: "22" - cache: "pnpm" - - - name: Install dependencies and build - run: | - pnpm install --frozen-lockfile - pnpm build - - - name: Create and test full project - run: | - mkdir -p test-projects - cd test-projects - - echo "🔍 Creating full project with init command..." - node "$GITHUB_WORKSPACE/packages/cli/dist/cli.js" init --name "test-startup" --repo "ian/startupkit#${{ github.ref_name }}" - - echo "📂 Verifying packages directory exists..." - test -d test-startup/packages || exit 1 - - echo "📦 Installing dependencies..." - cd test-startup - pnpm install --no-frozen-lockfile - - echo "🔍 Running typecheck..." - pnpm typecheck - - echo "🏗️ Running build..." - pnpm build - - echo "✅ Full integration test passed" - - - name: Test add command - run: | - cd test-projects/test-startup - - echo "🔍 Testing add command..." - node "$GITHUB_WORKSPACE/packages/cli/dist/cli.js" add next --name "web" --repo "ian/startupkit/templates/apps/next#${{ github.ref_name }}" - - test -d apps/web || exit 1 - test -f apps/web/package.json || exit 1 - - echo "✅ Add command test passed" - - - name: Clean up - if: always() - run: rm -rf test-projects - diff --git a/.npmrc b/.npmrc deleted file mode 100644 index b7868428..00000000 --- a/.npmrc +++ /dev/null @@ -1,13 +0,0 @@ -# Workspace configuration -link-workspace-packages=true -prefer-workspace-packages=true - -# Reduce log spam - only show warnings and errors -loglevel=warn - -# Auto-install peer dependencies to reduce warnings -auto-install-peers=true - -# Suppress update check notifications -update-notifier=false - diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 26434cbb..00000000 --- a/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "singleQuote": false, - "tabWidth": 2, - "useTabs": false -} diff --git a/.ruler/AGENTS.md b/.ruler/AGENTS.md deleted file mode 100644 index a7cffef8..00000000 --- a/.ruler/AGENTS.md +++ /dev/null @@ -1,1000 +0,0 @@ -# StartupKit Framework - AI Development Agent Guidelines - -**StartupKit** is a meta-framework for building SaaS applications. This repository contains the framework CLI, npm packages, and templates that developers scaffold with. - -This is a **pnpm monorepo** built with **TypeScript**, **Next.js 16**, **React 19**, and **Turbo** for task orchestration. - ---- - -## Project Structure - -### Workspace Layout - -``` -/ -├── apps/ # Example Next.js applications -│ └── home/ # Marketing site for startupkit.com -├── packages/ # Published npm packages (@startupkit/*) -│ ├── analytics/ # @startupkit/analytics npm package -│ ├── auth/ # @startupkit/auth npm package -│ └── cli/ # startupkit CLI tool -├── templates/ # Scaffolding templates (used by CLI) -│ ├── next/ # Next.js app template -│ ├── package/ # Generic package template -│ ├── repo/ # Full monorepo template (@repo/* packages) -│ └── vite/ # Vite app template -├── config/ # Shared configurations -│ ├── biome/ # Biome linter/formatter config -│ └── typescript/ # TypeScript base configurations -└── pnpm-workspace.yaml # Workspace & catalog definitions -``` - -### Package Architecture - -**Published packages** (in root `packages/` - published to npm as `@startupkit/*`): -- **`packages/cli`** - The `startupkit` CLI tool for scaffolding projects -- **`packages/analytics`** - Published to npm as `@startupkit/analytics` -- **`packages/auth`** - Published to npm as `@startupkit/auth` - -**Templates** (in `templates/` - used by CLI for scaffolding): -- **`templates/repo`** - Full monorepo template with `@repo/*` packages (most comprehensive) -- **`templates/next`** - Next.js app template -- **`templates/package`** - Generic package template -- **`templates/vite`** - Vite app template - -**Important distinction**: -- `packages/*` → Published npm packages (`@startupkit/*`) -- `templates/repo/packages/*` → Workspace packages for scaffolded projects (`@repo/*`) - ---- - -## Templates Architecture - -**CRITICAL: Understanding the Templates Structure** - -This repository contains **two types of templates** that work together differently than you might expect. - -### Template Types - -#### 1. `templates/repo` - Complete Monorepo Template - -The **source of truth** for all workspace packages. Contains: - -``` -templates/repo/ -├── packages/ -│ ├── analytics/ # @repo/analytics - Analytics with PostHog -│ ├── auth/ # @repo/auth - Authentication (Better Auth wrapper) -│ ├── db/ # @repo/db - Database with Drizzle ORM -│ ├── emails/ # @repo/emails - Email templates -│ ├── ui/ # @repo/ui - Shadcn UI components -│ └── utils/ # @repo/utils - Utility functions -└── apps/ # (empty, apps go here when scaffolded) -``` - -**Key characteristics**: -- Contains **complete implementations** of all workspace packages -- Has its own Better Auth configuration in `packages/auth/src/lib/auth.ts` -- Has `authClient` exported from `packages/auth/src/index.ts` -- Uses `basePath: "/auth"` for auth routes -- This is what gets copied when users run `startupkit init` - -#### 2. `templates/next` - Standalone Next.js App Template - -A **Next.js app that expects to live in a workspace with `packages/`**. This is NOT standalone. - -``` -templates/next/ -└── src/ - ├── app/ - │ ├── auth/[...all]/ # Auth API route (imports from @repo/auth/server) - │ ├── dashboard/ # Example protected page - │ ├── sign-in/ # Example sign-in page - │ ├── layout.tsx # Uses withAuth from @repo/auth/server - │ └── providers.tsx # Uses AuthProvider from @repo/auth - └── components/ - ├── header.tsx # Uses useAuth from @repo/auth - └── ... -``` - -**Key characteristics**: -- **DOES NOT contain lib/auth.ts or lib/auth-client.ts** - these live in `@repo/auth` -- Imports from workspace packages: `@repo/auth`, `@repo/ui`, `@repo/db`, etc. -- Assumes these packages exist in `packages/*` directory at workspace root -- This template demonstrates **how to use** the workspace packages - -### How Templates Relate to Each Other - -``` -When developers scaffold with StartupKit CLI: - -1. Run `startupkit init` - → Copies `templates/repo/*` to project root - → Creates packages/analytics, packages/auth, packages/db, etc. - -2. Later: Add a new Next.js app - → Copies `templates/next/*` to `apps/my-app/` - → This app can now import from `@repo/auth`, `@repo/ui`, etc. -``` - -### Critical Rules for Working with Templates - -#### ❌ DO NOT: Add Configuration Files to `templates/next` - -**WRONG**: -```typescript -// ❌ DON'T create templates/next/src/lib/auth.ts -import { betterAuth } from "better-auth" -export const auth = betterAuth({ ... }) -``` - -**WRONG**: -```typescript -// ❌ DON'T create templates/next/src/lib/auth-client.ts -import { createAuthClient } from "better-auth/react" -export const authClient = createAuthClient({ ... }) -``` - -**WHY**: These already exist in `templates/repo/packages/auth/` and are imported via workspace packages. - -#### ✅ DO: Import from Workspace Packages - -**CORRECT**: -```typescript -// templates/next/src/app/layout.tsx -import { withAuth } from "@repo/auth/server" - -export default async function RootLayout({ children }) { - const { user } = await withAuth() - // ... -} -``` - -**CORRECT**: -```typescript -// templates/next/src/app/providers.tsx -import { AuthProvider } from "@repo/auth" - -export function Providers({ children, user }) { - return {children} -} -``` - -**CORRECT**: -```typescript -// templates/next/src/app/auth/[...all]/route.ts -import { handler } from "@repo/auth/server" - -export const { GET, POST } = handler() -``` - -#### ❌ DO NOT: Add package dependencies already in workspace packages - -**WRONG**: -```json -// templates/next/package.json -{ - "dependencies": { - "better-auth": "^1.3.6", // ❌ Already in @repo/auth - "drizzle-orm": "^0.38.0" // ❌ Already in @repo/db - } -} -``` - -**CORRECT**: -```json -// templates/next/package.json -{ - "dependencies": { - "@repo/auth": "workspace:*", // ✅ Use workspace package - "@repo/db": "workspace:*", // ✅ Use workspace package - "@repo/ui": "workspace:*" // ✅ Use workspace package - } -} -``` - -### Authentication Configuration Example - -This is a perfect example of the templates relationship: - -**In `templates/repo/packages/auth/src/lib/auth.ts`** (source of truth): -```typescript -export const auth = betterAuth({ - basePath: "/auth", - database: drizzleAdapter(db, { provider: "pg" }), - plugins: [emailOTP({ sendVerificationOTP }), nextCookies()], - socialProviders: { google: { ... } } -}) -``` - -**In `templates/repo/packages/auth/src/index.ts`** (client export): -```typescript -export const authClient = createAuthClient({ - basePath: "/auth", - plugins: [emailOTPClient()] -}) -``` - -**In `templates/repo/packages/auth/src/server.ts`** (server export): -```typescript -export { auth } from "./lib/auth" -export type Session = typeof auth.$Infer.Session.session -export type User = typeof auth.$Infer.Session.user -export function handler() { - return toNextJsHandler(auth.handler) -} -export async function withAuth(opts?: { flags?: boolean }) { ... } -``` - -**In `templates/next/src/app/auth/[...all]/route.ts`** (usage): -```typescript -import { handler } from "@repo/auth/server" // ✅ Imports from workspace -export const { GET, POST } = handler() -``` - -### Auth Routes Configuration - -**Critical**: The auth routes path must match the `basePath` configuration: - -- `templates/repo/packages/auth/src/lib/auth.ts` sets `basePath: "/auth"` -- Therefore, Next.js apps need routes at `app/auth/[...all]/route.ts` -- **NOT** at `app/api/auth/[...all]/route.ts` -- Auth endpoints are served at `/auth/*` (e.g., `/auth/sign-in/google`) - -### When to Modify Each Template - -**Modify `templates/repo/packages/*`** when: -- Changing authentication configuration (providers, session duration) -- Adding new database tables or schema changes -- Adding new UI components to the shared library -- Changing email templates -- Modifying analytics tracking - -**Modify `templates/next/`** when: -- Adding example pages that demonstrate package usage -- Creating new app-specific components -- Adding new routes or layouts -- Showing authentication patterns - -### Testing Template Changes - -When you modify templates, test in this order: - -1. **Test `templates/repo` packages directly**: - ```bash - cd templates/repo - pnpm install - pnpm build - pnpm typecheck - ``` - -2. **Test `templates/next` in a workspace context**: - ```bash - # Create a test workspace - mkdir test-workspace - cp -r templates/repo/* test-workspace/ - cp -r templates/next test-workspace/apps/web - cd test-workspace - pnpm install - pnpm --filter web build - ``` - -### Common Mistakes to Avoid - -1. ❌ **Creating duplicate configuration files in `templates/next`** - - Don't create `lib/auth.ts` or `lib/auth-client.ts` - - These belong in `templates/repo/packages/auth/` - -2. ❌ **Adding dependencies that are already in workspace packages** - - Check `templates/repo/packages/*/package.json` first - - Use `workspace:*` references instead - -3. ❌ **Using wrong import paths** - - Use `@repo/auth` not `../../../packages/auth` - - Use `@repo/auth/server` for server-side auth - -4. ❌ **Mismatching auth route paths** - - If `basePath: "/auth"` → use `app/auth/[...all]/route.ts` - - If `basePath: "/api/auth"` → use `app/api/auth/[...all]/route.ts` - -5. ❌ **Importing from wrong entry points** - - Client: `import { useAuth } from "@repo/auth"` - - Server: `import { withAuth } from "@repo/auth/server"` - - Don't: `import { auth } from "@repo/auth"` (auth is server-only) - -### Package Exports Pattern - -All `templates/repo/packages/*` follow this pattern: - -```typescript -// package.json -{ - "exports": { - ".": "./src/index.ts", // Client exports - "./server": "./src/server.ts" // Server exports (if needed) - } -} -``` - -This allows dual-client/server packages like `@repo/auth`: -- `@repo/auth` → client exports (AuthProvider, useAuth, authClient) -- `@repo/auth/server` → server exports (auth, withAuth, handler) - -### Adding New Features to Templates - -**Example: Adding GitHub OAuth** - -1. **Update the workspace package** (`templates/repo/packages/auth/src/lib/auth.ts`): - ```typescript - socialProviders: { - google: { ... }, - github: { // ← Add here - clientId: process.env.GITHUB_CLIENT_ID as string, - clientSecret: process.env.GITHUB_CLIENT_SECRET as string - } - } - ``` - -2. **Update example usage** (`templates/next/src/app/sign-in/page.tsx`): - ```typescript - import { useAuth } from "@repo/auth" - - export function SignIn() { - const { googleAuth, githubAuth } = useAuth() // ← Use from @repo/auth - return ( - <> - - {/* ← Add example */} - - ) - } - ``` - -3. **Update documentation** (`templates/next/README.md`): - - Add GitHub to the list of auth methods - - Show environment variables needed - - Provide usage examples - -**Key principle**: Configuration goes in `templates/repo/packages/*`, usage examples go in `templates/next/`. - ---- - -## TypeScript & Code Quality Standards - -### Strict TypeScript Configuration - -The base TypeScript config (`config/typescript/base.json`) enforces: -- ✅ **`strict: true`** - All strict checks enabled -- ✅ **`strictNullChecks: true`** - Explicit null/undefined handling -- ✅ **`noUncheckedIndexedAccess: true`** - Array/object access returns `T | undefined` -- ✅ **`checkJs: true`** - Type-check JavaScript files -- ✅ **`moduleResolution: "bundler"`** - Modern module resolution - -### Code Style Rules - -**NEVER use `any`** - Biome's `noExplicitAny` is intentionally turned **OFF** in the config, but you should still avoid it. Use proper types, `unknown`, or generics instead. - -**NEVER use `never`** unless modeling impossible states (e.g., exhaustive type guards or functions that throw). - -**Prefer `interface` over `type`** for object shapes: -```typescript -interface UserProps { - name: string; - email: string; -} -``` - -**Avoid enums** - Use const objects with `as const` or string literal unions: -```typescript -const Role = { - OWNER: "owner", - MEMBER: "member" -} as const; - -type RoleType = typeof Role[keyof typeof Role]; -``` - -**Use functional components with explicit interfaces**: -```typescript -interface ButtonProps { - label: string; - onClick: () => void; -} - -export function Button({ label, onClick }: ButtonProps) { - return ; -} -``` - -**Use the `function` keyword for pure functions**: -```typescript -function calculateTotal(items: Item[]): number { - return items.reduce((sum, item) => sum + item.price, 0); -} -``` - ---- - -## Package Management (pnpm) - -### Core Commands - -**Install dependencies**: -```bash -pnpm install -``` - -**Run tasks across packages** (via Turbo): -```bash -pnpm dev # Start all dev servers -pnpm build # Build all packages -pnpm lint # Lint all packages -pnpm lint:fix # Auto-fix linting issues -pnpm typecheck # Type-check all packages -pnpm clean # Clean build artifacts -``` - -**Run commands in specific packages**: -```bash -pnpm --filter @startupkit/analytics build -pnpm --filter startupkit build -``` - -### Publishing to npm - -🚨 **CRITICAL: AI agents must NEVER run publish commands.** - -Publishing packages to npm (`pnpm publish`, `npm publish`, or any variant) must **ONLY** be performed by humans. This includes: -- ❌ `pnpm publish` -- ❌ `npm publish` -- ❌ `pnpm --filter @startupkit/* publish` -- ❌ Any automated publishing scripts or CI commands - -If a user requests package publishing, inform them that this must be done manually and cannot be automated by AI agents. - -### Dependency Catalogs - -The workspace uses **pnpm catalogs** to manage shared dependency versions in `pnpm-workspace.yaml`: - -**Available catalogs**: -- `catalog:stack` - Core stack (`next`, `@types/node`, `zod`) -- `catalog:react19` - React 19 and type definitions -- `catalog:react18` - React 18 (fallback) -- `catalog:ui` - UI dependencies (`lucide-react`, `framer-motion`, `tailwindcss`, etc.) -- `catalog:analytics` - Analytics packages -- `catalog:otel` - OpenTelemetry, Sentry - -**Using catalogs in `package.json`**: -```json -{ - "dependencies": { - "next": "catalog:stack", - "react": "catalog:react19", - "lucide-react": "catalog:ui" - } -} -``` - ---- - -## Database (Drizzle ORM) - -### Schema Location - -Database schema is defined in `@repo/db` package: -``` -templates/repo/packages/db/src/schema.ts -``` - -In a scaffolded project, this will be at: -``` -packages/db/src/schema.ts -``` - -### Migration Commands - -**Generate migrations** (after modifying `schema.ts`): -```bash -pnpm db:generate -``` -This creates SQL migration files in `packages/db/drizzle/`. - -**Apply migrations** to database: -```bash -pnpm db:migrate -``` - -**Push schema** without migrations (dev only): -```bash -pnpm db:push -``` - -**Open Drizzle Studio** (database GUI): -```bash -pnpm db:studio -``` - -### Schema Patterns - -Always export inferred types from tables: -```typescript -export const users = pgTable("User", { - id: text("id").primaryKey(), - email: text("email").unique(), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), -}); - -export type User = typeof users.$inferSelect; -export type NewUser = typeof users.$inferInsert; -``` - -Use `relations` for joins: -```typescript -export const usersRelations = relations(users, ({ many }) => ({ - sessions: many(sessions) -})); -``` - -### Environment Variables - -Drizzle reads `DATABASE_URL` from environment: -```bash -DATABASE_URL=postgresql://user:pass@localhost:5432/dbname -``` - -If it's a ENV var meant to be used for frontend, don't prefix with EXPO_PUBLIC or NEXT_PUBLIC. Instead, add it to turbo.json and the appropriate env initializer (eg next.config.ts under env: { ... }) - ---- - -## UI Components (Shadcn) - -### Component Location - -All UI components live in `@repo/ui` package: -``` -templates/repo/packages/ui/src/components/ -``` - -In a scaffolded project, this will be at: -``` -packages/ui/src/components/ -``` - -### Adding Components - -Use the custom Shadcn wrapper script: -```bash -pnpm shadcn add button -pnpm shadcn add dialog -``` - -This installs components into `packages/ui/` with the correct configuration. - -### Importing Components - -```typescript -import { Button } from "@repo/ui/components/button"; -import { Dialog } from "@repo/ui/components/dialog"; -``` - -### Styling - -- **Tailwind config**: `packages/ui/tailwind.config.ts` -- **Global styles**: `packages/ui/src/styles/index.css` -- **CSS output**: `packages/ui/dist/styles.css` (built via `pnpm build`) - -Import styles in your Next.js app: -```typescript -import "@repo/ui/styles.css"; -``` - ---- - -## Authentication (@repo/auth) - -Built on [better-auth](https://better-auth.com) with dual exports. - -### Client-Side Usage - -```typescript -import { AuthProvider, useAuth } from "@repo/auth"; - -// In root layout: -{children} - -// In components: -const { user, isAuthenticated, logout, googleAuth, sendAuthCode, verifyAuthCode } = useAuth(); -``` - -### Server-Side Usage - -```typescript -import { withAuth, handler, auth } from "@repo/auth/server"; - -// In Server Components: -const { user, session } = await withAuth(); - -// API route handler (app/auth/[...all]/route.ts): -export const { GET, POST } = handler(); -``` - -### Authentication Methods - -1. **Google OAuth** - Configure via `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` -2. **Email OTP** - One-time password via email (10-minute expiration) - -### Session Configuration - -- **Expiration**: 7 days -- **Auto-refresh**: Every 24 hours when active - ---- - -## Analytics (@repo/analytics) - -PostHog integration with feature flags. - -### Client-Side - -```typescript -import { AnalyticsProvider, useAnalytics } from "@repo/analytics"; - -{children} - -const { identify, track, flags } = useAnalytics(); -``` - -### Server-Side - -```typescript -import { getFeatureFlags, identifyUser, trackEvent } from "@repo/analytics/server"; - -const flags = await getFeatureFlags(userId); -``` - ---- - -## File Placement Guidelines - -### Where to Put New Files - -**Note**: When referencing `packages/*` below, this refers to `templates/repo/packages/*` (for template development) or the user's scaffolded project `packages/*` (for app development). This is NOT the root `packages/*` which contains published npm packages. - -| File Type | Location | Example | -|-----------|----------|---------| -| Next.js pages | `apps/{app-name}/src/app/` | `apps/web/src/app/dashboard/page.tsx` | -| React components (app-specific) | `apps/{app-name}/src/components/` | `apps/web/src/components/header.tsx` | -| UI components (shared) | `templates/repo/packages/ui/src/components/` | `button.tsx`, `dialog.tsx` | -| Database schema | `templates/repo/packages/db/src/schema.ts` | Add tables to existing schema | -| Database migrations | `templates/repo/packages/db/drizzle/` | Auto-generated via `pnpm db:generate` | -| Auth logic | `templates/repo/packages/auth/src/` | Extend `lib/auth.ts` or add hooks | -| Email templates | `templates/repo/packages/emails/src/templates/` | `welcome.tsx`, `verify-code.tsx` | -| Utility functions | `templates/repo/packages/utils/src/lib/` | `string.ts`, `date.ts`, `array.ts` | -| Hooks (shared) | `templates/repo/packages/ui/src/hooks/` | `use-is-mobile.ts` | -| Hooks (app-specific) | `apps/{app-name}/src/hooks/` | App-specific hooks | -| API routes | `apps/{app-name}/src/app/api/` | `apps/web/src/app/api/users/route.ts` | -| Auth API routes | `apps/{app-name}/src/app/auth/[...all]/` | Auth handler (NOT in `/api/auth/`) | - -### Creating New Apps - -To add a new Next.js app: -1. Copy the template: `cp -r templates/next apps/my-app` -2. Update `apps/my-app/package.json` name and description -3. Update imports and metadata in `apps/my-app/src/app/layout.tsx` - -### Creating New Packages - -To add a new package: -1. Copy the template: `cp -r templates/package packages/my-package` -2. Update `packages/my-package/package.json` name -3. Add exports in `src/index.ts` -4. Run `pnpm install` to register in workspace - ---- - -## Linting & Formatting - -### Biome Configuration - -The project uses [Biome](https://biomejs.dev) for linting and formatting. - -**Config location**: `config/biome/biome.jsonc` - -**Key settings**: -- **Indentation**: Tabs (width: 2) -- **Line width**: 80 characters -- **Quotes**: Double quotes -- **Semicolons**: As needed (ASI) -- **Trailing commas**: None -- **Arrow parens**: Always - -### Running Biome - -```bash -pnpm lint # Check all files -pnpm lint:fix # Auto-fix issues -pnpm format # Format code -``` - -### Important Lint Rules - -- ✅ `noUnusedImports` - Remove unused imports -- ✅ `noUselessStringConcat` - Simplify string concatenation -- ❌ `noExplicitAny` - OFF (but you should still avoid `any`) -- ❌ `noForEach` - OFF (`.forEach()` is allowed) -- ❌ `useExhaustiveDependencies` - OFF (use ESLint if needed) - ---- - -## React & Next.js Best Practices - -### Server Components First - -**Minimize `"use client"`** - Default to Server Components unless you need: -- Browser APIs (localStorage, window, etc.) -- Event handlers (onClick, onChange) -- React hooks (useState, useEffect) - -**Bad**: -```typescript -"use client"; - -export function UserProfile({ userId }: { userId: string }) { - const [user, setUser] = useState(null); - - useEffect(() => { - fetch(`/api/users/${userId}`).then(/* ... */); - }, [userId]); - - return
{user?.name}
; -} -``` - -**Good**: -```typescript -async function UserProfile({ userId }: { userId: string }) { - const user = await db.query.users.findFirst({ - where: eq(users.id, userId) - }); - - return
{user?.name}
; -} -``` - -### Client Component Patterns - -When you need client-side state, **wrap in Suspense**: -```typescript -import { Suspense } from "react"; - -export default function Page() { - return ( - }> - - - ); -} -``` - -**Use `nuqs` for URL state management** (search params): -```typescript -import { useQueryState } from "nuqs"; - -const [search, setSearch] = useQueryState("q"); -``` - -### Performance Optimization - -- **Images**: Use Next.js ``, WebP format, include dimensions, lazy load -- **Dynamic imports**: For non-critical components -- **Avoid unnecessary re-renders**: Use React.memo, useMemo, useCallback sparingly - ---- - -## Environment Variables - -### Required Variables - -**Database**: -```bash -DATABASE_URL=postgresql://user:pass@localhost:5432/dbname -``` - -**Authentication**: -```bash -GOOGLE_CLIENT_ID=your_google_client_id -GOOGLE_CLIENT_SECRET=your_google_client_secret -``` - -**Analytics (optional)**: -```bash -NEXT_PUBLIC_POSTHOG_KEY=phc_... -NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com -``` - -**Monitoring (optional)**: -```bash -SENTRY_DSN=https://... -SENTRY_AUTH_TOKEN=... -``` - -### Loading Variables - -**Always use `with-env` and `with-test-env` wrappers** when running commands that need environment variables. - -The project uses **dotenv-cli** to automatically load environment variables: -- `pnpm with-env` → Loads `.env.local` (for development) -- `pnpm with-test-env` → Loads `.env.test` (for testing) - -**Development commands**: -```bash -pnpm with-env pnpm dev # Start dev servers -pnpm with-env pnpm --filter @repo/db db:migrate # Run migrations -pnpm with-env pnpm --filter @repo/web dev # Start specific app -``` - -**Test commands**: -```bash -pnpm with-test-env pnpm test # Run all tests -pnpm with-test-env pnpm --filter ./packages/analytics test # Test specific package -pnpm with-test-env turbo run test --ui stream # Run tests via Turbo -``` - -**Why use these wrappers?** -- ✅ Automatically loads correct environment file -- ✅ Works across all packages in monorepo -- ✅ Prevents accidentally using wrong environment -- ✅ No need to manually export variables - -**Don't** run commands without the wrapper: -```bash -pnpm dev # ❌ Missing environment variables -pnpm --filter @repo/web dev # ❌ Won't load .env.local -``` - ---- - -## Testing - -### Running Tests - -**Always use `with-test-env` when running tests** to ensure proper environment variables are loaded. - -```bash -pnpm with-test-env pnpm test # Run all tests -pnpm with-test-env pnpm --filter ./packages/analytics test # Test specific package -pnpm with-test-env turbo run test --ui stream # Run tests via Turbo -``` - -Tests use the `.env.test` file for environment variables (automatically loaded by `with-test-env`). - ---- - -## AI Agent Instructions (Ruler) - -This project uses [Ruler](https://github.com/intellectronica/ruler) to generate concatenated AI instructions. - -### Updating Agent Instructions - -Modify files in: -``` -.ruler/AGENTS.md -.ruler/*.md -``` - -Then regenerate: -```bash -pnpm agents.md -``` - -This updates `AGENTS.md`, `CLAUDE.md`, `WARP.md`, etc. at the repository root. - ---- - -## Common Tasks Cheatsheet - -### Add a new database table - -1. Edit `templates/repo/packages/db/src/schema.ts` (or `packages/db/src/schema.ts` in scaffolded project) -2. Add table definition and relations -3. Export types (`User`, `NewUser`) -4. Generate migration: `pnpm db:generate` -5. Apply migration: `pnpm db:migrate` - -### Add a new UI component - -1. Run: `pnpm shadcn add component-name` -2. Component added to `templates/repo/packages/ui/src/components/` (or `packages/ui/src/components/` in scaffolded project) -3. Import: `import { Component } from "@repo/ui/components/component-name"` - -### Add a new page to Next.js app - -1. Create: `apps/{app}/src/app/my-page/page.tsx` -2. Use Server Component by default -3. Add client interactivity only where needed - -### Add an email template - -1. Create: `templates/repo/packages/emails/src/templates/my-email.tsx` (or `packages/emails/src/templates/` in scaffolded project) -2. Use React Email components -3. Export from `packages/emails/src/index.tsx` - ---- - -## Troubleshooting - -### Build Failures - -```bash -pnpm clean # Clear build caches -pnpm install # Reinstall dependencies -pnpm build # Rebuild all packages -``` - -### Type Errors - -```bash -pnpm typecheck # Check all packages -``` - -If workspace types are stale, restart your TypeScript server. - -### Database Issues - -```bash -pnpm db:push # Sync schema without migrations (dev only) -pnpm db:studio # Inspect database with GUI -``` - -### Module Resolution - -If imports fail, ensure: -- Package is listed in `pnpm-workspace.yaml` -- Package has correct `exports` in `package.json` -- You've run `pnpm install` after adding new packages - ---- - -## Code Comments Policy - -**Do NOT include useless comments.** Only add comments if **ABSOLUTELY NECESSARY** to explain: -- Complex algorithms or business logic -- Non-obvious TypeScript type assertions -- Workarounds for library bugs -- Performance-critical code - -**Bad**: -```typescript -const total = items.reduce((sum, item) => sum + item.price, 0); -``` - -**Good** (only if calculation is non-obvious): -```typescript -const total = items.reduce((sum, item) => { - return sum + (item.price * item.quantity) - item.discount; -}, 0); -``` - ---- - -## Summary for AI Agents - -When working on this codebase: - -1. ✅ **Understand the dual package structure**: - - `packages/*` → Published npm packages (`@startupkit/*`) - - `templates/repo/packages/*` → Template workspace packages (`@repo/*`) -2. ✅ **Never duplicate auth config in `templates/next`** - It imports from `@repo/auth` -3. ✅ **Auth routes go in `app/auth/[...all]/`** - NOT `app/api/auth/[...all]/` -4. ✅ **Use strict TypeScript** - No `any`, prefer `interface`, avoid `enum` -5. ✅ **Follow Biome formatting** - Tabs, double quotes, 80-char lines -6. ✅ **Prefer Server Components** - Minimize `"use client"` -7. ✅ **Import from workspace packages** - Use `@repo/*` in templates/next -8. ✅ **Use pnpm catalogs** - Reference `catalog:stack`, `catalog:react19`, etc. -9. ✅ **Database changes** → `pnpm db:generate` → `pnpm db:migrate` -10. ✅ **UI components** → `pnpm shadcn add component-name` -11. ✅ **Run tasks via Turbo** - `pnpm dev`, `pnpm build`, `pnpm lint:fix` -12. ✅ **Use env wrappers** - `pnpm with-env` for dev, `pnpm with-test-env` for tests -13. ✅ **Place files correctly** - See "File Placement Guidelines" and "Templates Architecture" -14. ✅ **No unnecessary comments** - Code should be self-documenting -15. 🚨 **NEVER publish to npm** - Publishing packages (`pnpm publish`, `npm publish`) must ONLY be done by humans, never by AI agents diff --git a/.ruler/ruler.toml b/.ruler/ruler.toml deleted file mode 100644 index 67436d47..00000000 --- a/.ruler/ruler.toml +++ /dev/null @@ -1,40 +0,0 @@ -# Ruler Configuration File -# See https://ai.intellectronica.net/ruler for documentation. - -# To specify which agents are active by default when --agents is not used, -# uncomment and populate the following line. If omitted, all agents are active. -default_agents = ["Cursor", "OpenCode"] - -# Enable nested rule loading from nested .ruler directories -# When enabled, ruler will search for and process .ruler directories throughout the project hierarchy -# nested = false - -# --- Agent Specific Configurations --- -# You can enable/disable agents and override their default output paths here. -# Use lowercase agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, kilocode - -# [agents.copilot] -# enabled = true -# output_path = ".github/copilot-instructions.md" - -# [agents.aider] -# enabled = true -# output_path_instructions = "AGENTS.md" -# output_path_config = ".aider.conf.yml" - -# [agents.gemini-cli] -# enabled = true - -# --- MCP Servers --- -# Define Model Context Protocol servers here. Two examples: -# 1. A stdio server (local executable) -# 2. A remote server (HTTP-based) - -# [mcp_servers.example_stdio] -# command = "node" -# args = ["scripts/your-mcp-server.js"] -# env = { API_KEY = "replace_me" } - -# [mcp_servers.example_remote] -# url = "https://api.example.com/mcp" -# headers = { Authorization = "Bearer REPLACE_ME" } diff --git a/README.md b/README.md index 8249253e..4db37596 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,109 @@ # StartupKit -The startup stack for the AI era. +Startup skills for AI agents. -[**startupkit.com**](https://startupkit.com) | [GitHub](https://github.com/ian/startupkit) | [Documentation](https://startupkit.com) +**startupkit.com** | [GitHub](https://github.com/ian/startupkit) ## What is StartupKit? -StartupKit is a meta-framework for building SaaS applications. Built for founders who move fast, loved by the AI tools that help them. Pre-configured auth, analytics, database, and UI components with clear patterns your copilot can follow. +StartupKit equips your project with a comprehensive set of AI agent skills covering entrepreneur, dev, marketing, and product expertise. Works with OpenCode and Claude Code. -One command to start: +One command to install: ```bash -npx startupkit init +npx startupkit add ``` ## Why StartupKit? -### AI Needs Constraints to Be Useful +AI agents are only as good as their instructions. StartupKit provides curated skills that give your agents the expertise of a full startup team: -Without structure, every project becomes a different architecture. That's **AI slop**. - -| Without StartupKit | With StartupKit | -|-------------------|-----------------| -| Where should auth logic live? | `@repo/auth` → Better Auth, ready | -| Prisma or Drizzle? Which pattern? | `@repo/db` → Drizzle + Postgres, configured | -| App router or pages? RSC or client? | Next.js 16 App Router, RSC by default | -| How do I structure shared code? | Monorepo → share everything | -| Which analytics provider? | `@repo/analytics` → Provider-agnostic hooks | - -**Start at 70%.** AI handles the details, not the foundation. - -### Built for the New Era of Development - -StartupKit is designed to work seamlessly with AI development tools: - -- ✅ **Devin** ready -- ✅ **Claude** ready -- ✅ **Amp** ready -- ✅ **OpenCode** ready - -Every project includes `AGENTS.md` with clear conventions, file placement guidelines, and architecture patterns that AI tools understand. +| Category | Skills Include | +| ---------------- | --------------------------------------------------- | +| **Entrepreneur** | Brainstorming, planning, execution, verification | +| **Dev** | React/Next.js patterns, debugging, TDD, code review | +| **Marketing** | Copywriting, SEO, content strategy, pricing | +| **Product** | CRO, onboarding, A/B testing, growth loops | ## Quick Start ```bash -npx startupkit init -cd my-project -cp .env.example .env.local -pnpm dev -``` - -Visit [http://localhost:3000](http://localhost:3000) - -## What's Included - -### 📦 Pre-Built Packages - -- **`@repo/auth`** - Authentication with Better Auth (Google OAuth, Email OTP) -- **`@repo/analytics`** - Provider-agnostic analytics hooks and context -- **`@repo/db`** - Database with Drizzle ORM + PostgreSQL -- **`@repo/ui`** - 60+ Shadcn components, pre-configured -- **`@repo/emails`** - Email templates with React Email -- **`@repo/utils`** - Common utilities for SaaS applications - -### 🏗️ Monorepo Architecture - -- **pnpm workspaces** - Efficient dependency management -- **Turbo** - Fast task orchestration (build, dev, lint) -- **TypeScript** - Strict type checking across all packages -- **Biome** - Fast linting and formatting - -### 🎨 UI & Styling - -- **Shadcn UI** - Beautiful, accessible components -- **Tailwind CSS** - Utility-first styling -- **Lucide Icons** - Clean, consistent icons - -### 🗄️ Database +# Interactive selection +npx startupkit add -- **Drizzle ORM** - Type-safe database access -- **PostgreSQL** - Production-ready database setup -- **Migrations** - Version-controlled schema changes +# Install all skills +npx startupkit add --all -## Project Structure +# Install specific category +npx startupkit add --category dev +# Install globally +npx startupkit add --global ``` -my-project/ -├── apps/ -│ └── web/ # Main Next.js application -├── packages/ -│ ├── analytics/ # Analytics implementation -│ ├── auth/ # Authentication setup -│ ├── db/ # Database schema & migrations -│ ├── emails/ # Email templates -│ ├── ui/ # Shared UI components -│ └── utils/ # Utility functions -├── config/ -│ ├── biome/ # Linter configuration -│ └── typescript/ # TypeScript configs -├── AGENTS.md # AI development guidelines -└── pnpm-workspace.yaml # Workspace definition -``` - -## Common Tasks -### Development - -```bash -pnpm dev # Start all apps -pnpm --filter web dev # Start specific app -pnpm build # Build all packages -``` +## Supported Agents -### Database +- ✅ **OpenCode** +- ✅ **Claude Code** -```bash -pnpm db:generate # Generate migration files -pnpm db:migrate # Apply migrations -pnpm db:studio # Open database GUI -``` +Skills are installed to both `.opencode/skills/` and `.claude/skills/` by default. -### UI Components +## Commands ```bash -pnpm shadcn add button -pnpm shadcn add dialog +startupkit skills # List available skills +startupkit skills add # Add skills interactively +startupkit add # Shortcut for skills add +startupkit add --all # Install everything +startupkit add --category dev # Specific category +startupkit add --global # Install to user directory ``` -### Code Quality - -```bash -pnpm lint # Check all files -pnpm lint:fix # Fix issues -pnpm typecheck # Type check all packages -``` +## Skill Categories -## Add New Services +### Entrepreneur -Expand your monorepo with new apps instantly: +- `brainstorming` - Ideation techniques +- `writing-plans` - Comprehensive planning +- `executing-plans` - Systematic execution +- `verification-before-completion` - Quality assurance -- **Next.js** - Full-stack React framework ✅ -- **Vite** - Lightning fast frontend tooling ✅ -- **Expo** - React Native for mobile (coming soon) +### Dev -## Environment Setup +- `vercel-react-best-practices` - React/Next.js performance +- `vercel-composition-patterns` - Scalable component patterns +- `web-design-guidelines` - Web interface compliance +- `systematic-debugging` - Debug methodology +- `test-driven-development` - TDD practices +- `requesting-code-review` - Effective code reviews +- `receiving-code-review` - Handling feedback +- `subagent-driven-development` - Subagent patterns +- `dispatching-parallel-agents` - Parallel orchestration -Configure your environment variables in `.env.local`: +### Marketing -```bash -# Database -DATABASE_URL=postgresql://user:pass@localhost:5432/mydb +- `copywriting` - Persuasive writing +- `marketing-psychology` - Consumer psychology +- `seo-audit` - SEO optimization +- `programmatic-seo` - Scale SEO strategies +- `content-strategy` - Content planning +- `pricing-strategy` - Pricing models +- `launch-strategy` - Product launches +- `email-sequence` - Email campaigns +- `paid-ads` - Advertising strategies -# Authentication -GOOGLE_CLIENT_ID=your_google_client_id -GOOGLE_CLIENT_SECRET=your_google_client_secret +### Product -# Analytics (optional) -NEXT_PUBLIC_POSTHOG_KEY=phc_... -NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com -``` +- `page-cro` - Landing page optimization +- `onboarding-cro` - Onboarding flows +- `signup-flow-cro` - Signup optimization +- `ab-test-setup` - A/B testing +- `referral-program` - Referral systems ## Tech Stack -- **Framework:** Next.js 16 (App Router) -- **UI:** React 19 + Shadcn UI + Tailwind CSS -- **Language:** TypeScript (strict mode) -- **Database:** PostgreSQL + Drizzle ORM -- **Auth:** Better Auth -- **Email:** React Email + Resend -- **Monorepo:** pnpm + Turbo -- **Linting:** Biome - -## Support & Resources - -- **Website:** [startupkit.com](https://startupkit.com) -- **GitHub:** [github.com/ian/startupkit](https://github.com/ian/startupkit) -- **Issues:** [github.com/ian/startupkit/issues](https://github.com/ian/startupkit/issues) +This CLI wraps the [skills.sh](https://skills.sh) ecosystem. Skills are SKILL.md files that work across AI agent platforms. ## License ISC © 2025 01 Studio - ---- - -**Stop burning tokens. Start shipping faster.** 🚀 diff --git a/apps/home/.gitignore b/apps/home/.gitignore deleted file mode 100644 index 3beb7662..00000000 --- a/apps/home/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# Dependencies -node_modules/ - -# Build -dist/ -build/ -out/ - -# Cache -.turbo/ -.cache/ - -# Environment -.env -.env.local -.env.*.local - -# IDE -.idea/ -.vscode/ -*.swp -*.swo - -# OS -.DS_Store -Thumbs.db diff --git a/apps/home/.source/browser.ts b/apps/home/.source/browser.ts deleted file mode 100644 index 97ce2e05..00000000 --- a/apps/home/.source/browser.ts +++ /dev/null @@ -1,12 +0,0 @@ -// @ts-nocheck -import { browser } from 'fumadocs-mdx/runtime/browser'; -import type * as Config from '../source.config'; - -const create = browser(); -const browserCollections = { - docs: create.doc("docs", {"index.mdx": () => import("../content/docs/index.mdx?collection=docs"), "analytics/analytics-provider.mdx": () => import("../content/docs/analytics/analytics-provider.mdx?collection=docs"), "analytics/feature-flags.mdx": () => import("../content/docs/analytics/feature-flags.mdx?collection=docs"), "analytics/identifying-users.mdx": () => import("../content/docs/analytics/identifying-users.mdx?collection=docs"), "analytics/index.mdx": () => import("../content/docs/analytics/index.mdx?collection=docs"), "analytics/tracking-events.mdx": () => import("../content/docs/analytics/tracking-events.mdx?collection=docs"), "auth/configuration.mdx": () => import("../content/docs/auth/configuration.mdx?collection=docs"), "auth/index.mdx": () => import("../content/docs/auth/index.mdx?collection=docs"), "auth/session-management.mdx": () => import("../content/docs/auth/session-management.mdx?collection=docs"), "cli/add.mdx": () => import("../content/docs/cli/add.mdx?collection=docs"), "cli/agents.mdx": () => import("../content/docs/cli/agents.mdx?collection=docs"), "cli/index.mdx": () => import("../content/docs/cli/index.mdx?collection=docs"), "cli/init.mdx": () => import("../content/docs/cli/init.mdx?collection=docs"), "cli/troubleshooting.mdx": () => import("../content/docs/cli/troubleshooting.mdx?collection=docs"), "cli/upgrade.mdx": () => import("../content/docs/cli/upgrade.mdx?collection=docs"), "db/index.mdx": () => import("../content/docs/db/index.mdx?collection=docs"), "db/migrations.mdx": () => import("../content/docs/db/migrations.mdx?collection=docs"), "db/queries.mdx": () => import("../content/docs/db/queries.mdx?collection=docs"), "db/schema.mdx": () => import("../content/docs/db/schema.mdx?collection=docs"), "deployment/cloudflare.mdx": () => import("../content/docs/deployment/cloudflare.mdx?collection=docs"), "deployment/fly.mdx": () => import("../content/docs/deployment/fly.mdx?collection=docs"), "deployment/index.mdx": () => import("../content/docs/deployment/index.mdx?collection=docs"), "deployment/railway.mdx": () => import("../content/docs/deployment/railway.mdx?collection=docs"), "deployment/render.mdx": () => import("../content/docs/deployment/render.mdx?collection=docs"), "deployment/vercel.mdx": () => import("../content/docs/deployment/vercel.mdx?collection=docs"), "emails/index.mdx": () => import("../content/docs/emails/index.mdx?collection=docs"), "emails/sending.mdx": () => import("../content/docs/emails/sending.mdx?collection=docs"), "emails/templates.mdx": () => import("../content/docs/emails/templates.mdx?collection=docs"), "getting-started/comparisons.mdx": () => import("../content/docs/getting-started/comparisons.mdx?collection=docs"), "getting-started/environment-variables.mdx": () => import("../content/docs/getting-started/environment-variables.mdx?collection=docs"), "getting-started/installation.mdx": () => import("../content/docs/getting-started/installation.mdx?collection=docs"), "getting-started/project-structure.mdx": () => import("../content/docs/getting-started/project-structure.mdx?collection=docs"), "getting-started/quickstart.mdx": () => import("../content/docs/getting-started/quickstart.mdx?collection=docs"), "getting-started/what-is.mdx": () => import("../content/docs/getting-started/what-is.mdx?collection=docs"), "ui/components.mdx": () => import("../content/docs/ui/components.mdx?collection=docs"), "ui/index.mdx": () => import("../content/docs/ui/index.mdx?collection=docs"), "ui/theming.mdx": () => import("../content/docs/ui/theming.mdx?collection=docs"), "seo/index.mdx": () => import("../content/docs/seo/index.mdx?collection=docs"), "seo/metadata.mdx": () => import("../content/docs/seo/metadata.mdx?collection=docs"), "seo/robots.mdx": () => import("../content/docs/seo/robots.mdx?collection=docs"), "seo/sitemap.mdx": () => import("../content/docs/seo/sitemap.mdx?collection=docs"), "seo/structured-data.mdx": () => import("../content/docs/seo/structured-data.mdx?collection=docs"), "analytics/usage/identify.mdx": () => import("../content/docs/analytics/usage/identify.mdx?collection=docs"), "analytics/usage/page.mdx": () => import("../content/docs/analytics/usage/page.mdx?collection=docs"), "analytics/usage/reset.mdx": () => import("../content/docs/analytics/usage/reset.mdx?collection=docs"), "analytics/usage/track.mdx": () => import("../content/docs/analytics/usage/track.mdx?collection=docs"), "analytics/usage/use-analytics.mdx": () => import("../content/docs/analytics/usage/use-analytics.mdx?collection=docs"), "analytics/usage/use-flag.mdx": () => import("../content/docs/analytics/usage/use-flag.mdx?collection=docs"), "auth/usage/require-auth.mdx": () => import("../content/docs/auth/usage/require-auth.mdx?collection=docs"), "auth/usage/use-auth.mdx": () => import("../content/docs/auth/usage/use-auth.mdx?collection=docs"), "auth/usage/with-auth.mdx": () => import("../content/docs/auth/usage/with-auth.mdx?collection=docs"), }), -}; -export default browserCollections; \ No newline at end of file diff --git a/apps/home/.source/dynamic.ts b/apps/home/.source/dynamic.ts deleted file mode 100644 index 7dd9c10a..00000000 --- a/apps/home/.source/dynamic.ts +++ /dev/null @@ -1,8 +0,0 @@ -// @ts-nocheck -import { dynamic } from 'fumadocs-mdx/runtime/dynamic'; -import * as Config from '../source.config'; - -const create = await dynamic(Config, {"configPath":"source.config.ts","environment":"next","outDir":".source"}, {"doc":{"passthroughs":["extractedReferences"]}}); \ No newline at end of file diff --git a/apps/home/.source/server.ts b/apps/home/.source/server.ts deleted file mode 100644 index ccd9ae65..00000000 --- a/apps/home/.source/server.ts +++ /dev/null @@ -1,74 +0,0 @@ -// @ts-nocheck -import { default as __fd_glob_61 } from "../content/docs/auth/usage/meta.json?collection=meta" -import { default as __fd_glob_60 } from "../content/docs/analytics/usage/meta.json?collection=meta" -import { default as __fd_glob_59 } from "../content/docs/ui/meta.json?collection=meta" -import { default as __fd_glob_58 } from "../content/docs/seo/meta.json?collection=meta" -import { default as __fd_glob_57 } from "../content/docs/getting-started/meta.json?collection=meta" -import { default as __fd_glob_56 } from "../content/docs/emails/meta.json?collection=meta" -import { default as __fd_glob_55 } from "../content/docs/db/meta.json?collection=meta" -import { default as __fd_glob_54 } from "../content/docs/cli/meta.json?collection=meta" -import { default as __fd_glob_53 } from "../content/docs/auth/meta.json?collection=meta" -import { default as __fd_glob_52 } from "../content/docs/analytics/meta.json?collection=meta" -import { default as __fd_glob_51 } from "../content/docs/meta.json?collection=meta" -import * as __fd_glob_50 from "../content/docs/auth/usage/with-auth.mdx?collection=docs" -import * as __fd_glob_49 from "../content/docs/auth/usage/use-auth.mdx?collection=docs" -import * as __fd_glob_48 from "../content/docs/auth/usage/require-auth.mdx?collection=docs" -import * as __fd_glob_47 from "../content/docs/analytics/usage/use-flag.mdx?collection=docs" -import * as __fd_glob_46 from "../content/docs/analytics/usage/use-analytics.mdx?collection=docs" -import * as __fd_glob_45 from "../content/docs/analytics/usage/track.mdx?collection=docs" -import * as __fd_glob_44 from "../content/docs/analytics/usage/reset.mdx?collection=docs" -import * as __fd_glob_43 from "../content/docs/analytics/usage/page.mdx?collection=docs" -import * as __fd_glob_42 from "../content/docs/analytics/usage/identify.mdx?collection=docs" -import * as __fd_glob_41 from "../content/docs/seo/structured-data.mdx?collection=docs" -import * as __fd_glob_40 from "../content/docs/seo/sitemap.mdx?collection=docs" -import * as __fd_glob_39 from "../content/docs/seo/robots.mdx?collection=docs" -import * as __fd_glob_38 from "../content/docs/seo/metadata.mdx?collection=docs" -import * as __fd_glob_37 from "../content/docs/seo/index.mdx?collection=docs" -import * as __fd_glob_36 from "../content/docs/ui/theming.mdx?collection=docs" -import * as __fd_glob_35 from "../content/docs/ui/index.mdx?collection=docs" -import * as __fd_glob_34 from "../content/docs/ui/components.mdx?collection=docs" -import * as __fd_glob_33 from "../content/docs/getting-started/what-is.mdx?collection=docs" -import * as __fd_glob_32 from "../content/docs/getting-started/quickstart.mdx?collection=docs" -import * as __fd_glob_31 from "../content/docs/getting-started/project-structure.mdx?collection=docs" -import * as __fd_glob_30 from "../content/docs/getting-started/installation.mdx?collection=docs" -import * as __fd_glob_29 from "../content/docs/getting-started/environment-variables.mdx?collection=docs" -import * as __fd_glob_28 from "../content/docs/getting-started/comparisons.mdx?collection=docs" -import * as __fd_glob_27 from "../content/docs/emails/templates.mdx?collection=docs" -import * as __fd_glob_26 from "../content/docs/emails/sending.mdx?collection=docs" -import * as __fd_glob_25 from "../content/docs/emails/index.mdx?collection=docs" -import * as __fd_glob_24 from "../content/docs/deployment/vercel.mdx?collection=docs" -import * as __fd_glob_23 from "../content/docs/deployment/render.mdx?collection=docs" -import * as __fd_glob_22 from "../content/docs/deployment/railway.mdx?collection=docs" -import * as __fd_glob_21 from "../content/docs/deployment/index.mdx?collection=docs" -import * as __fd_glob_20 from "../content/docs/deployment/fly.mdx?collection=docs" -import * as __fd_glob_19 from "../content/docs/deployment/cloudflare.mdx?collection=docs" -import * as __fd_glob_18 from "../content/docs/db/schema.mdx?collection=docs" -import * as __fd_glob_17 from "../content/docs/db/queries.mdx?collection=docs" -import * as __fd_glob_16 from "../content/docs/db/migrations.mdx?collection=docs" -import * as __fd_glob_15 from "../content/docs/db/index.mdx?collection=docs" -import * as __fd_glob_14 from "../content/docs/cli/upgrade.mdx?collection=docs" -import * as __fd_glob_13 from "../content/docs/cli/troubleshooting.mdx?collection=docs" -import * as __fd_glob_12 from "../content/docs/cli/init.mdx?collection=docs" -import * as __fd_glob_11 from "../content/docs/cli/index.mdx?collection=docs" -import * as __fd_glob_10 from "../content/docs/cli/agents.mdx?collection=docs" -import * as __fd_glob_9 from "../content/docs/cli/add.mdx?collection=docs" -import * as __fd_glob_8 from "../content/docs/auth/session-management.mdx?collection=docs" -import * as __fd_glob_7 from "../content/docs/auth/index.mdx?collection=docs" -import * as __fd_glob_6 from "../content/docs/auth/configuration.mdx?collection=docs" -import * as __fd_glob_5 from "../content/docs/analytics/tracking-events.mdx?collection=docs" -import * as __fd_glob_4 from "../content/docs/analytics/index.mdx?collection=docs" -import * as __fd_glob_3 from "../content/docs/analytics/identifying-users.mdx?collection=docs" -import * as __fd_glob_2 from "../content/docs/analytics/feature-flags.mdx?collection=docs" -import * as __fd_glob_1 from "../content/docs/analytics/analytics-provider.mdx?collection=docs" -import * as __fd_glob_0 from "../content/docs/index.mdx?collection=docs" -import { server } from 'fumadocs-mdx/runtime/server'; -import type * as Config from '../source.config'; - -const create = server({"doc":{"passthroughs":["extractedReferences"]}}); - -export const docs = await create.doc("docs", "content/docs", {"index.mdx": __fd_glob_0, "analytics/analytics-provider.mdx": __fd_glob_1, "analytics/feature-flags.mdx": __fd_glob_2, "analytics/identifying-users.mdx": __fd_glob_3, "analytics/index.mdx": __fd_glob_4, "analytics/tracking-events.mdx": __fd_glob_5, "auth/configuration.mdx": __fd_glob_6, "auth/index.mdx": __fd_glob_7, "auth/session-management.mdx": __fd_glob_8, "cli/add.mdx": __fd_glob_9, "cli/agents.mdx": __fd_glob_10, "cli/index.mdx": __fd_glob_11, "cli/init.mdx": __fd_glob_12, "cli/troubleshooting.mdx": __fd_glob_13, "cli/upgrade.mdx": __fd_glob_14, "db/index.mdx": __fd_glob_15, "db/migrations.mdx": __fd_glob_16, "db/queries.mdx": __fd_glob_17, "db/schema.mdx": __fd_glob_18, "deployment/cloudflare.mdx": __fd_glob_19, "deployment/fly.mdx": __fd_glob_20, "deployment/index.mdx": __fd_glob_21, "deployment/railway.mdx": __fd_glob_22, "deployment/render.mdx": __fd_glob_23, "deployment/vercel.mdx": __fd_glob_24, "emails/index.mdx": __fd_glob_25, "emails/sending.mdx": __fd_glob_26, "emails/templates.mdx": __fd_glob_27, "getting-started/comparisons.mdx": __fd_glob_28, "getting-started/environment-variables.mdx": __fd_glob_29, "getting-started/installation.mdx": __fd_glob_30, "getting-started/project-structure.mdx": __fd_glob_31, "getting-started/quickstart.mdx": __fd_glob_32, "getting-started/what-is.mdx": __fd_glob_33, "ui/components.mdx": __fd_glob_34, "ui/index.mdx": __fd_glob_35, "ui/theming.mdx": __fd_glob_36, "seo/index.mdx": __fd_glob_37, "seo/metadata.mdx": __fd_glob_38, "seo/robots.mdx": __fd_glob_39, "seo/sitemap.mdx": __fd_glob_40, "seo/structured-data.mdx": __fd_glob_41, "analytics/usage/identify.mdx": __fd_glob_42, "analytics/usage/page.mdx": __fd_glob_43, "analytics/usage/reset.mdx": __fd_glob_44, "analytics/usage/track.mdx": __fd_glob_45, "analytics/usage/use-analytics.mdx": __fd_glob_46, "analytics/usage/use-flag.mdx": __fd_glob_47, "auth/usage/require-auth.mdx": __fd_glob_48, "auth/usage/use-auth.mdx": __fd_glob_49, "auth/usage/with-auth.mdx": __fd_glob_50, }); - -export const meta = await create.meta("meta", "content/docs", {"meta.json": __fd_glob_51, "analytics/meta.json": __fd_glob_52, "auth/meta.json": __fd_glob_53, "cli/meta.json": __fd_glob_54, "db/meta.json": __fd_glob_55, "emails/meta.json": __fd_glob_56, "getting-started/meta.json": __fd_glob_57, "seo/meta.json": __fd_glob_58, "ui/meta.json": __fd_glob_59, "analytics/usage/meta.json": __fd_glob_60, "auth/usage/meta.json": __fd_glob_61, }); \ No newline at end of file diff --git a/apps/home/.source/source.config.mjs b/apps/home/.source/source.config.mjs deleted file mode 100644 index 7cf11d4c..00000000 --- a/apps/home/.source/source.config.mjs +++ /dev/null @@ -1,53 +0,0 @@ -// source.config.ts -import { defineDocs, defineConfig } from "fumadocs-mdx/config"; -import { transformerTwoslash } from "fumadocs-twoslash"; -import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs"; -import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins"; -import fs from "node:fs"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; -var { docs, meta } = defineDocs({ - dir: "content/docs" -}); -var __dirname = path.dirname(fileURLToPath(import.meta.url)); -var homeDir = __dirname.includes(".source") ? path.resolve(__dirname, "..") : __dirname; -var twoslashTypes = fs.readFileSync( - path.resolve(homeDir, "twoslash.d.ts"), - "utf-8" -); -var source_config_default = defineConfig({ - mdxOptions: { - rehypeCodeOptions: { - themes: { - light: "github-light", - dark: "github-dark" - }, - transformers: [ - ...rehypeCodeDefaultOptions.transformers ?? [], - transformerTwoslash({ - cache: true, - typesCache: createFileSystemTypesCache(), - twoslashOptions: { - compilerOptions: { - moduleResolution: 100, - module: 99, - target: 9, - strict: true, - esModuleInterop: true, - skipLibCheck: true, - jsx: 4 - }, - extraFiles: { - "startupkit.d.ts": twoslashTypes - } - } - }) - ] - } - } -}); -export { - source_config_default as default, - docs, - meta -}; diff --git a/apps/home/biome.jsonc b/apps/home/biome.jsonc deleted file mode 100644 index 00a2f5ef..00000000 --- a/apps/home/biome.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "extends": ["../../config/biome/biome.jsonc"], - "files": { - "ignore": [".source/**"] - } -} diff --git a/apps/home/content/docs/analytics/analytics-provider.mdx b/apps/home/content/docs/analytics/analytics-provider.mdx deleted file mode 100644 index c3939f4f..00000000 --- a/apps/home/content/docs/analytics/analytics-provider.mdx +++ /dev/null @@ -1,145 +0,0 @@ ---- -title: AnalyticsProvider -description: Context provider for analytics ---- - -Wrap your app to enable analytics tracking. - -## Import - -```tsx -import { AnalyticsProvider } from "@repo/analytics" -``` - -## Usage - -```tsx title="app/providers.tsx" -"use client" - -import { AnalyticsProvider } from "@repo/analytics" -import { analyticsPlugins } from "@/lib/analytics" - -export function Providers({ - children, - flags -}: { - children: React.ReactNode - flags: Record -}) { - return ( - - {children} - - ) -} -``` - -## Props - -| Prop | Type | Description | -|------|------|-------------| -| `plugins` | `AnalyticsPlugin[]` | Array of configured analytics plugins | -| `flags` | `Record` | Feature flags from server | -| `children` | `ReactNode` | App content | - -## Plugins - -Configure which analytics providers to use: - -```tsx title="lib/analytics.ts" -import { - PostHogPlugin, - GoogleAnalyticsPlugin, - GoogleTagManagerPlugin, - OpenPanelPlugin, - DatafastPlugin, - AhrefsPlugin -} from "@repo/analytics" - -export const analyticsPlugins = [ - PostHogPlugin({ - apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY! - }), - GoogleAnalyticsPlugin({ - measurementId: process.env.NEXT_PUBLIC_GA_ID! - }) -] -``` - -### AhrefsPlugin - -```tsx -AhrefsPlugin({ - key: process.env.NEXT_PUBLIC_AHREFS_API_KEY! -}) -``` - -### DatafastPlugin - -```tsx -DatafastPlugin({ - websiteId: process.env.NEXT_PUBLIC_DATAFAST_WEBSITE_ID!, // dfid_... - domain: "yourdomain.com" // Optional -}) -``` - -### GoogleAnalyticsPlugin - -```tsx -GoogleAnalyticsPlugin({ - measurementId: process.env.NEXT_PUBLIC_GA_ID! // G-XXXXXXXXXX -}) -``` - -### GoogleTagManagerPlugin - -```tsx -GoogleTagManagerPlugin({ - containerId: process.env.NEXT_PUBLIC_GTM_CONTAINER_ID! // GTM-XXXXXXX -}) -``` - -### OpenPanelPlugin - -```tsx -OpenPanelPlugin({ - clientId: process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID!, - trackScreenViews: true // Optional -}) -``` - -### PostHogPlugin - -```tsx -PostHogPlugin({ - apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY!, - apiHost: "https://app.posthog.com" // Optional, defaults to PostHog cloud -}) -``` - -## Passing flags from server - -Fetch feature flags on the server and pass to the provider: - -```tsx title="app/layout.tsx" -import { Providers } from "./providers" -import { getFeatureFlags } from "@/lib/feature-flags" - -export default async function RootLayout({ children }) { - const flags = await getFeatureFlags() - - return ( - - - - {children} - - - - ) -} -``` - -## Automatic page tracking - -`AnalyticsProvider` automatically tracks page views on route changes in Next.js. No additional setup required. diff --git a/apps/home/content/docs/analytics/feature-flags.mdx b/apps/home/content/docs/analytics/feature-flags.mdx deleted file mode 100644 index c7dfb0a1..00000000 --- a/apps/home/content/docs/analytics/feature-flags.mdx +++ /dev/null @@ -1,316 +0,0 @@ ---- -title: Feature Flags -description: How to use feature flags with @repo/analytics ---- - -Control feature rollouts and run experiments with feature flags. Flags are passed through the `AnalyticsProvider` and accessible via hooks. - -## Setup - -### 1. Fetch Flags Server-Side - -```tsx twoslash title="app/layout.tsx" -// @noErrors -import type { ReactNode } from "react" -import { getFeatureFlags } from "@repo/analytics/server" -import { auth } from "@/lib/auth" -import { headers } from "next/headers" -import { Providers } from "./providers" - -export default async function RootLayout({ children }: { children: ReactNode }) { - const session = await auth.api.getSession({ headers: await headers() }) - const userId = session?.user?.id - const flags = await getFeatureFlags(userId) - - return ( - - - - {children} - - - - ) -} -``` - -### 2. Pass to Provider - -```tsx twoslash title="app/providers.tsx" -// @noErrors -"use client" - -import { AnalyticsProvider } from "@repo/analytics" -import { analyticsPlugins } from "@/lib/analytics" - -export function Providers({ children, flags }) { - return ( - - {children} - - ) -} -``` - -## Using Flags - -### useFlag Hook - -Check a single flag: - -```tsx twoslash -// @noErrors -"use client" - -import { useFlag } from "@repo/analytics" - -export function NewFeature() { - const isEnabled = useFlag("new-dashboard") - - if (!isEnabled) return null - - return
Welcome to the new dashboard!
-} -``` - -### useAnalytics Hook - -Access all flags at once: - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function FeatureGate() { - const { flags } = useAnalytics() - - return ( -
- {flags["feature-a"] && } - {flags["feature-b"] && } -
- ) -} -``` - -## Flag Types - -Flags can be boolean or string values: - -```typescript twoslash -interface Flags { - "new-dashboard": boolean // On/off - "pricing-variant": "A" | "B" // A/B test - "api-version": "v1" | "v2" // Gradual rollout -} -``` - -### Boolean Flags - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const showBanner = useFlag("show-banner") - -if (showBanner) { - return -} -``` - -### String/Variant Flags - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const variant = useFlag("checkout-variant") - -switch (variant) { - case "A": - return - case "B": - return - default: - return -} -``` - -## Server-Side Flag Checks - -Check flags in Server Components or API routes: - -```tsx twoslash title="app/dashboard/page.tsx" -// @noErrors -import { getFeatureFlags } from "@repo/analytics/server" -import { auth } from "@/lib/auth" -import { headers } from "next/headers" - -export default async function DashboardPage() { - const session = await auth.api.getSession({ headers: await headers() }) - const flags = await getFeatureFlags(session?.user?.id) - - if (flags["new-dashboard"]) { - return - } - - return -} -``` - -## Type Safety - -Define your flags for better TypeScript support: - -```typescript twoslash title="types/flags.ts" -export interface Flags { - "new-dashboard": boolean - "dark-mode": boolean - "checkout-variant": "control" | "variant-a" | "variant-b" -} -``` - -Then use with the hook: - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -import type { Flags } from "@/types/flags" - -export function MyComponent() { - const { flags } = useAnalytics() as { flags: Flags } - - // TypeScript knows the flag types - if (flags["new-dashboard"]) { - // ... - } -} -``` - -## Common Patterns - -### Gradual Rollout - -Roll out a feature to a percentage of users: - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -// In PostHog, set "new-feature" to 10% rollout -const hasNewFeature = useFlag("new-feature") - -return hasNewFeature ? : -``` - -### Beta Access - -Gate features for beta users: - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const isBetaUser = useFlag("beta-access") - -if (isBetaUser) { - return ( -
- - -
- ) -} -``` - -### A/B Testing - -Run experiments: - -```tsx twoslash -// @noErrors -"use client" - -import { useEffect } from "react" -import { useAnalytics, useFlag } from "@repo/analytics" - -function PricingExperiment() { - const variant = useFlag("pricing-experiment") - const { track } = useAnalytics() - - useEffect(() => { - track("EXPERIMENT_VIEWED", { - experiment: "pricing-experiment", - variant - }) - }, [variant, track]) - - return variant === "A" ? : -} -``` - -### Kill Switch - -Disable a feature instantly: - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const isFeatureEnabled = useFlag("feature-x-enabled") - -if (!isFeatureEnabled) { - return -} - -return -``` - -## Fallback Values - -When flags are loading or unavailable: - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const isEnabled = useFlag("new-feature") - -// isEnabled is null if flags haven't loaded -if (isEnabled === null) { - return -} - -if (isEnabled) { - return -} - -return -``` - -## PostHog Integration - -If using PostHog, flags are fetched automatically: - -```tsx twoslash title="lib/analytics-server.ts" -// @noErrors -import { PostHog } from "posthog-node" - -const posthog = new PostHog(process.env.POSTHOG_API_KEY!) - -export async function getFeatureFlags(userId?: string) { - if (!userId) return {} - - const flags = await posthog.getAllFlags(userId) - return flags -} -``` - -See the [PostHog documentation](https://posthog.com/docs/feature-flags) for full configuration. - -## Next Steps - -- [Tracking events](/docs/analytics/tracking-events) - Learn the tracking API -- [Identifying users](/docs/analytics/identifying-users) - Associate events with users diff --git a/apps/home/content/docs/analytics/identifying-users.mdx b/apps/home/content/docs/analytics/identifying-users.mdx deleted file mode 100644 index e6c6ea1b..00000000 --- a/apps/home/content/docs/analytics/identifying-users.mdx +++ /dev/null @@ -1,255 +0,0 @@ ---- -title: Identifying Users -description: How to associate analytics events with specific users ---- - -Connect analytics events to specific users with the `identify` function. This enables user-level analytics, cohort analysis, and personalized experiences. - -## Basic Usage - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" -import { useEffect } from "react" - -export function UserIdentifier({ user }) { - const { identify } = useAnalytics() - - useEffect(() => { - if (user) { - identify(user.id, { - email: user.email, - name: user.name, - plan: user.plan - }) - } - }, [user, identify]) - - return null -} -``` - -## The identify() Function - -```typescript -identify(userId: string, traits?: Record) -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `userId` | `string` | Unique identifier for the user | -| `traits` | `object` | Optional user properties | - -## When to Identify - -Call `identify()` when: - -1. **User signs in** - After successful authentication -2. **User signs up** - When a new account is created -3. **Profile updates** - When user properties change - -### After Sign In - -```tsx twoslash -// @noErrors -"use client" - -import { useAuth } from "@startupkit/auth" -import { useAnalytics } from "@repo/analytics" -import { useEffect } from "react" - -export function AuthIdentifier() { - const { user, isAuthenticated } = useAuth() - const { identify } = useAnalytics() - - useEffect(() => { - if (isAuthenticated && user) { - identify(user.id, { - email: user.email, - name: user.name, - createdAt: user.createdAt - }) - } - }, [isAuthenticated, user, identify]) - - return null -} -``` - -### In Your Root Layout - -A common pattern is to identify users in your providers: - -```tsx twoslash title="app/providers.tsx" -// @noErrors -"use client" - -import type { ReactNode } from "react" -import { AuthProvider } from "@startupkit/auth" -import { AnalyticsProvider } from "@repo/analytics" -import { analyticsPlugins } from "@/lib/analytics" -import { authClient } from "@/lib/auth" - -interface ProvidersProps { - children: ReactNode - user?: { id: string; email: string; name?: string } - flags?: Record -} - -export function Providers({ children, user, flags }: ProvidersProps) { - return ( - - { - // This is called when user signs in - // The AnalyticsProvider handles the identify call - }} - > - {children} - - - ) -} -``` - -## User Traits - -Include properties that help segment your users: - -```typescript twoslash -// @noErrors -declare function identify(userId: string, traits: Record): void -declare const user: { id: string; email: string; name: string; createdAt: string } -declare const team: { id: string } -// ---cut--- -identify(user.id, { - // Core info - email: user.email, - name: user.name, - - // Account info - plan: "pro", - role: "admin", - teamId: team.id, - - // Lifecycle - createdAt: user.createdAt, - signupSource: "google", - - // Usage - projectCount: 5, - lastActiveAt: new Date().toISOString() -}) -``` - -### Recommended Traits - -| Trait | Description | -|-------|-------------| -| `email` | User's email address | -| `name` | Display name | -| `plan` | Subscription tier | -| `createdAt` | Account creation date | -| `role` | User role (admin, member) | -| `company` | Organization name | - -## Updating Traits - -Call `identify()` again when user properties change: - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -import { useAuth } from "@repo/auth" -declare function upgradePlan(plan: string): Promise -// ---cut--- -function PlanUpgrade({ newPlan }) { - const { identify } = useAnalytics() - const { user } = useAuth() - - const handleUpgrade = async () => { - await upgradePlan(newPlan) - - // Update analytics with new plan - identify(user.id, { - plan: newPlan, - upgradedAt: new Date().toISOString() - }) - } - - return -} -``` - -## Reset on Logout - -Clear the user identity when they sign out: - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function LogoutButton() { - const { reset } = useAnalytics() - - const handleLogout = async () => { - await signOut() - reset() // Clear analytics identity - } - - return -} -``` - -The `reset()` function: -- Clears the current user identity -- Generates a new anonymous ID -- Ensures subsequent events aren't associated with the previous user - -## Anonymous Users - -Before `identify()` is called, users are tracked anonymously. Most analytics providers: - -- Generate a random anonymous ID -- Store it in a cookie or local storage -- Merge anonymous history when `identify()` is called - -This means pre-signup activity is preserved and attributed to the user. - -## Privacy Considerations - -Be mindful of what you include in traits: - -```typescript twoslash -// @noErrors -declare function identify(userId: string, traits: Record): void -declare const user: { id: string; plan: string; role: string; createdAt: string; ssn: string; card: string; password: string; address: string } -// ---cut--- -// Good - necessary for analytics -identify(user.id, { - plan: user.plan, - role: user.role, - createdAt: user.createdAt -}) - -// Avoid - sensitive data -identify(user.id, { - ssn: user.ssn, // Never - creditCard: user.card, // Never - password: user.password, // Never - fullAddress: user.address // Usually unnecessary -}) -``` - -Check your privacy policy and ensure compliance with GDPR, CCPA, etc. - -## Next Steps - -- [Feature flags](/docs/analytics/feature-flags) - Target features to specific users -- [Tracking events](/docs/analytics/tracking-events) - Learn the tracking API diff --git a/apps/home/content/docs/analytics/index.mdx b/apps/home/content/docs/analytics/index.mdx deleted file mode 100644 index 34d3eed8..00000000 --- a/apps/home/content/docs/analytics/index.mdx +++ /dev/null @@ -1,177 +0,0 @@ ---- -title: Analytics Overview -description: Provider-agnostic analytics with @repo/analytics ---- - -`@repo/analytics` provides a unified interface for tracking events across multiple analytics providers. Write once, send everywhere. - -## Features - -- **Multi-provider support** - PostHog, Google Analytics, GTM, OpenPanel, Datafast, Ahrefs -- **Automatic page tracking** - Tracks route changes in Next.js -- **Feature flags** - Built-in support for PostHog feature flags -- **Type-safe** - Full TypeScript support for events and properties -- **Plugin architecture** - Easy to add or remove providers - -## Quick Setup - -### 1. Configure Plugins - -```tsx title="lib/analytics.ts" -import { - PostHogPlugin, - GoogleAnalyticsPlugin -} from "@repo/analytics" - -export const analyticsPlugins = [ - PostHogPlugin({ - apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY! - }), - GoogleAnalyticsPlugin({ - measurementId: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID! - }) -] -``` - -### 2. Add Provider - -```tsx title="app/providers.tsx" -"use client" - -import { AnalyticsProvider } from "@repo/analytics" -import { analyticsPlugins } from "@/lib/analytics" - -export function Providers({ children, flags }) { - return ( - - {children} - - ) -} -``` - -### 3. Track Events - -```tsx -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function SignUpButton() { - const { track, identify, page, reset } = useAnalytics() - - return ( - - ) -} -``` - -## How It Works - -The `AnalyticsProvider` wraps your app and manages connections to all configured providers. When you call `track()`, the event is sent to every provider simultaneously. - -```mermaid -flowchart LR - A[Your App] --> B[AnalyticsProvider] - B --> C[PostHog] - B --> D[Google Analytics] - B --> E[OpenPanel] - B --> F[Datafast] - B --> G[Ahrefs] -``` - -Events are fanned out automatically—no need to call each provider individually. - -## Usage - -### useAnalytics - -The main hook for tracking events and identifying users: - -```tsx -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function MyComponent() { - const { track, identify, page, reset } = useAnalytics() - - // ... -} -``` - -| Method | Description | -|--------|-------------| -| `track(event, properties?)` | Track a custom event with optional properties | -| `identify(userId, traits?)` | Associate events with a user identity | -| `page(name?, properties?)` | Track a page view (automatic in Next.js) | -| `reset()` | Clear user identity (on logout) | - -#### track - -```tsx -track("BUTTON_CLICKED", { - buttonId: "signup-hero", - variant: "primary" -}) -``` - -#### identify - -```tsx -identify(user.id, { - email: user.email, - plan: user.plan, - createdAt: user.createdAt -}) -``` - -#### reset - -```tsx -async function handleLogout() { - await signOut() - reset() -} -``` - -### useFlag - -Access feature flags from PostHog: - -```tsx -"use client" - -import { useFlag } from "@repo/analytics" - -export function NewFeature() { - const isEnabled = useFlag("new-checkout-flow") - - if (!isEnabled) return null - - return -} -``` - -See [Feature Flags](/docs/analytics/feature-flags) for setup details. - -## Provider Options - -| Provider | Best For | -|----------|----------| -| PostHog | Product analytics, feature flags, session recordings | -| Google Analytics | Traffic analysis, marketing attribution | -| Google Tag Manager | Tag management, loading multiple tracking scripts | -| OpenPanel | Privacy-focused, self-hostable | -| Datafast | Privacy-focused analytics, revenue attribution | -| Ahrefs | SEO and traffic analytics | - -You can use one provider or all of them together. - -## Next Steps - -- [Track events](/docs/analytics/tracking-events) - Learn the tracking API -- [Identify users](/docs/analytics/identifying-users) - Associate events with users -- [Feature flags](/docs/analytics/feature-flags) - Control feature rollouts diff --git a/apps/home/content/docs/analytics/meta.json b/apps/home/content/docs/analytics/meta.json deleted file mode 100644 index 5a201a91..00000000 --- a/apps/home/content/docs/analytics/meta.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "title": "Analytics", - "icon": "ChartBar", - "pages": [ - "index", - "analytics-provider", - "tracking-events", - "identifying-users", - "feature-flags", - "usage" - ], - "defaultOpen": true -} diff --git a/apps/home/content/docs/analytics/tracking-events.mdx b/apps/home/content/docs/analytics/tracking-events.mdx deleted file mode 100644 index f95eb62e..00000000 --- a/apps/home/content/docs/analytics/tracking-events.mdx +++ /dev/null @@ -1,245 +0,0 @@ ---- -title: Tracking Events -description: How to track analytics events with @repo/analytics ---- - -Track user actions and product usage with the `useAnalytics` hook. - -## Basic Usage - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function PricingCard({ plan }: { plan: string }) { - const { track } = useAnalytics() - - const handleSelect = () => { - track("PLAN_SELECTED", { - plan, - source: "pricing_page" - }) - } - - return ( - - ) -} -``` - -## The track() Function - -```typescript -track(event: string, properties?: Record) -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `event` | `string` | Event name (e.g., `"BUTTON_CLICKED"`) | -| `properties` | `object` | Optional metadata about the event | - -## Event Naming Conventions - -Use `ALL_CAPS_SNAKE_CASE` for event names: - -```typescript twoslash -// @noErrors -declare function track(event: string, properties?: Record): void -// ---cut--- -// Good -track("USER_SIGNED_UP") -track("PLAN_UPGRADED") -track("FEATURE_USED") - -// Avoid -track("user signed up") -track("planUpgraded") -track("feature-used") -``` - -This convention: -- Makes events easy to find in analytics dashboards -- Clearly distinguishes events from other data -- Is widely adopted across analytics tools - -## Common Event Patterns - -### User Actions - -```typescript twoslash -// @noErrors -declare function track(event: string, properties?: Record): void -// ---cut--- -track("BUTTON_CLICKED", { buttonId: "cta", page: "home" }) -track("FORM_SUBMITTED", { formId: "contact", success: true }) -track("LINK_CLICKED", { url: "/pricing", external: false }) -``` - -### Feature Usage - -```typescript twoslash -// @noErrors -declare function track(event: string, properties?: Record): void -// ---cut--- -track("FEATURE_USED", { feature: "dark_mode", enabled: true }) -track("SEARCH_PERFORMED", { query: "billing", results: 5 }) -track("FILTER_APPLIED", { filter: "date", value: "last_7_days" }) -``` - -### Business Events - -```typescript twoslash -// @noErrors -declare function track(event: string, properties?: Record): void -// ---cut--- -track("PLAN_SELECTED", { plan: "pro", interval: "monthly" }) -track("CHECKOUT_STARTED", { plan: "pro", amount: 29 }) -track("SUBSCRIPTION_CREATED", { plan: "pro", trial: false }) -``` - -### Errors - -```typescript twoslash -// @noErrors -declare function track(event: string, properties?: Record): void -// ---cut--- -track("ERROR_OCCURRED", { - code: "PAYMENT_FAILED", - message: "Card declined" -}) -``` - -## Adding Properties - -Include relevant context with every event: - -```typescript twoslash -// @noErrors -declare function track(event: string, properties?: Record): void -declare const doc: { id: string } -declare const team: { id: string } -// ---cut--- -track("DOCUMENT_CREATED", { - documentId: doc.id, - documentType: "invoice", - teamId: team.id, - templateUsed: true -}) -``` - -Properties help you: -- Filter and segment in analytics dashboards -- Debug issues with more context -- Build funnels and cohorts - -## Tracking on Page Load - -For events that should fire when a page loads: - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" -import { useEffect } from "react" - -export function PricingPage() { - const { track } = useAnalytics() - - useEffect(() => { - track("PRICING_PAGE_VIEWED", { - referrer: document.referrer - }) - }, [track]) - - return
Pricing content...
-} -``` - -## Automatic Page Tracking - -The `AnalyticsProvider` automatically tracks page views when routes change. You don't need to manually call `track()` for page views. - -The automatic tracking: -- Fires on every route change -- Filters out route groups (e.g., `(dashboard)`) -- Replaces long IDs with `:id` for cleaner data - -Example transformations: -- `/dashboard/(settings)/profile/abc123` → `/dashboard/profile/:id` -- `/(marketing)/pricing` → `/pricing` - -### Disable Auto Page Tracking - -```tsx twoslash -// @noErrors -declare const flags: Record -declare const plugins: unknown[] -declare const children: React.ReactNode -declare function AnalyticsProvider(props: { flags: Record; plugins: unknown[]; autoPageTracking?: boolean; children: React.ReactNode }): JSX.Element -// ---cut--- - - {children} - -``` - -## Type-Safe Events - -For better type safety, define your events: - -```typescript twoslash title="types/analytics.ts" -interface AnalyticsEvents { - SIGNUP_CLICKED: { source: string } - PLAN_SELECTED: { plan: string; interval: "monthly" | "yearly" } - FEATURE_USED: { feature: string; enabled: boolean } -} - -type EventName = keyof AnalyticsEvents -``` - -Then create a typed wrapper: - -```typescript twoslash title="lib/analytics.ts" -// @noErrors -interface AnalyticsEvents { - SIGNUP_CLICKED: { source: string } - PLAN_SELECTED: { plan: string; interval: "monthly" | "yearly" } - FEATURE_USED: { feature: string; enabled: boolean } -} -type EventName = keyof AnalyticsEvents -// ---cut--- -import { useAnalytics as useBaseAnalytics } from "@repo/analytics" - -export function useAnalytics() { - const analytics = useBaseAnalytics() - - return { - ...analytics, - track: ( - event: T, - properties: AnalyticsEvents[T] - ) => analytics.track(event, properties) - } -} -``` - -## Best Practices - -1. **Be consistent** - Use the same event name for the same action across your app -2. **Include context** - Add properties that help you understand the event -3. **Don't over-track** - Focus on events that drive business decisions -4. **Avoid PII** - Don't include emails, names, or other personal data in properties -5. **Use present tense verbs** - `BUTTON_CLICKED` not `BUTTON_WAS_CLICKED` - -## Next Steps - -- [Identify users](/docs/analytics/identifying-users) - Associate events with users -- [Feature flags](/docs/analytics/feature-flags) - Control feature rollouts diff --git a/apps/home/content/docs/analytics/usage/identify.mdx b/apps/home/content/docs/analytics/usage/identify.mdx deleted file mode 100644 index a07b7363..00000000 --- a/apps/home/content/docs/analytics/usage/identify.mdx +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: identify -description: Associate events with a user ---- - -Associate analytics events with a user identity. - -## Client - -Use the `useAnalytics` hook in client components: - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" - -const { identify } = useAnalytics() -``` - -### Usage - -```tsx -identify(userId: string | null, traits?: Record) -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `userId` | `string \| null` | Unique user identifier | -| `traits` | `Record` | Optional user properties | - -### Examples - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function useSignIn() { - const { identify } = useAnalytics() - - async function signIn(email: string, code: string) { - const { user } = await verifyAuthCode(email, code) - - identify(user.id, { - email: user.email, - name: user.name, - plan: user.plan - }) - - return user - } - - return { signIn } -} -``` - -### With AuthProvider - -The `AuthProvider` can automatically identify users: - -```tsx twoslash title="app/providers.tsx" -// @noErrors -import type React from "react" -declare const children: React.ReactNode -declare function identify(userId: string, traits: Record): void -declare function AuthProvider(props: { onIdentify: (user: { id: string; email: string; name: string }) => void; children: React.ReactNode }): JSX.Element -// ---cut--- - { - identify(user.id, { - email: user.email, - name: user.name - }) - }} -> - {children} - -``` - -## Server - -Server-side identification happens automatically when you include user traits in `track()` calls: - -```tsx twoslash -// @noErrors -import { track } from "@repo/analytics/server" -``` - -### Usage - -PostHog automatically associates traits when you track events with a `userId`: - -```tsx twoslash -// @noErrors -import { track } from "@repo/analytics/server" -declare const user: { id: string; email: string; firstName: string; lastName: string } -// ---cut--- -await track({ - event: "USER_SIGNED_UP", - userId: user.id, - user: { - email: user.email, - firstName: user.firstName, - lastName: user.lastName - } -}) -``` - -### Examples - -#### After sign-up - -```tsx twoslash -// @noErrors -"use server" - -import { track } from "@repo/analytics/server" - -export async function signUp(email: string) { - const user = await createUser({ email }) - - await track({ - event: "USER_SIGNED_UP", - userId: user.id, - user: { - id: user.id, - email: user.email, - firstName: user.firstName, - lastName: user.lastName - } - }) - - return user -} -``` - -#### Plan upgrade - -```tsx twoslash -// @noErrors -"use server" - -import { track } from "@repo/analytics/server" - -export async function upgradePlan(userId: string, plan: string) { - await db.update(users).set({ plan }).where(eq(users.id, userId)) - - await track({ - event: "PLAN_UPGRADED", - userId, - plan, - previousPlan: "free" - }) -} -``` - -## Common traits - -| Trait | Description | -|-------|-------------| -| `email` | User's email address | -| `name` | Display name | -| `firstName` | First name | -| `lastName` | Last name | -| `plan` | Subscription plan | -| `createdAt` | Account creation date | -| `company` | Company/organization name | -| `role` | User role (admin, member, etc.) | diff --git a/apps/home/content/docs/analytics/usage/meta.json b/apps/home/content/docs/analytics/usage/meta.json deleted file mode 100644 index 52645d8b..00000000 --- a/apps/home/content/docs/analytics/usage/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Usage", - "pages": ["use-analytics", "use-flag", "track", "identify", "page", "reset"], - "defaultOpen": true -} diff --git a/apps/home/content/docs/analytics/usage/page.mdx b/apps/home/content/docs/analytics/usage/page.mdx deleted file mode 100644 index 7fadd12a..00000000 --- a/apps/home/content/docs/analytics/usage/page.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: page -description: Track page views ---- - -Track page views with optional properties. - -## Client - -Use the `useAnalytics` hook in client components: - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" - -const { page } = useAnalytics() -``` - -### Usage - -```tsx -page(name?: string, properties?: Record) -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `name` | `string` | Optional page name | -| `properties` | `Record` | Optional page properties | - -### Automatic tracking - -`AnalyticsProvider` automatically tracks page views on Next.js route changes. Manual calls are typically not needed. - -### Examples - -#### Manual page tracking - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -const { page } = useAnalytics() -// ---cut--- -page("Dashboard") -``` - -#### With properties - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -const { page } = useAnalytics() -declare const team: { id: string } -// ---cut--- -page("Dashboard", { - section: "analytics", - teamId: team.id -}) -``` - -#### Virtual page views - -For single-page navigation that doesn't change the URL: - -```tsx twoslash -// @noErrors -"use client" - -import { useEffect } from "react" -import { useAnalytics } from "@repo/analytics" - -function TabPanel({ tab }: { tab: string }) { - const { page } = useAnalytics() - - useEffect(() => { - page(`Settings - ${tab}`, { tab }) - }, [tab, page]) - - return
...
-} -``` - -## Server - -Page tracking is **client-only**. Server components and actions don't have access to navigation context. - -For server-side page analytics, track as a custom event: - -```tsx twoslash -// @noErrors -import { track } from "@repo/analytics/server" -declare const user: { id: string } -declare const request: Request -// ---cut--- -await track({ - event: "PAGE_VIEWED", - userId: user.id, - page: "/dashboard", - referrer: request.headers.get("referer") -}) -``` - -## When to call page manually - -- Tab changes within a page -- Modal or drawer navigation -- Multi-step wizards -- Any navigation that doesn't change the URL diff --git a/apps/home/content/docs/analytics/usage/reset.mdx b/apps/home/content/docs/analytics/usage/reset.mdx deleted file mode 100644 index 9493e038..00000000 --- a/apps/home/content/docs/analytics/usage/reset.mdx +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: reset -description: Clear user identity ---- - -Clear the current user identity on logout. - -## Client - -Use the `useAnalytics` hook in client components: - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" - -const { reset } = useAnalytics() -``` - -### Usage - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -const { reset } = useAnalytics() -// ---cut--- -reset() -``` - -No parameters. Clears the user identity across all analytics providers. - -### Examples - -#### On logout - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" -import { useAuth } from "@repo/auth" - -export function LogoutButton() { - const { logout } = useAuth() - const { reset } = useAnalytics() - - async function handleLogout() { - await logout() - reset() - } - - return -} -``` - -#### With AuthProvider - -The `AuthProvider` can automatically reset on logout: - -```tsx twoslash title="app/providers.tsx" -// @noErrors -declare const children: React.ReactNode -declare function reset(): void -declare function AuthProvider(props: { onReset: () => void; children: React.ReactNode }): JSX.Element -// ---cut--- - reset()} -> - {children} - -``` - -#### Account switching - -```tsx twoslash -// @noErrors -declare function reset(): void -declare function identify(userId: string, traits: Record): void -declare function loadAccount(id: string): Promise<{ id: string; email: string; name: string }> -// ---cut--- -async function switchAccount(newAccountId: string) { - reset() - - const user = await loadAccount(newAccountId) - - identify(user.id, { - email: user.email, - name: user.name - }) -} -``` - -## Server - -Reset is **client-only**. It clears browser-side state (cookies, local storage) used by analytics providers. - -For server-side logout tracking: - -```tsx twoslash -// @noErrors -import { track } from "@repo/analytics/server" -declare const user: { id: string; email: string } -// ---cut--- -await track({ - event: "USER_SIGNED_OUT", - userId: user.id, - user: { - id: user.id, - email: user.email - } -}) -``` - -## Why reset matters - -Always call `reset()` when a user signs out to: - -- Prevent events from being attributed to the wrong user -- Clear stored user properties in the browser -- Start a fresh anonymous session -- Comply with privacy requirements diff --git a/apps/home/content/docs/analytics/usage/track.mdx b/apps/home/content/docs/analytics/usage/track.mdx deleted file mode 100644 index f2243298..00000000 --- a/apps/home/content/docs/analytics/usage/track.mdx +++ /dev/null @@ -1,156 +0,0 @@ ---- -title: track -description: Track custom events ---- - -Track custom events with optional properties. - -## Client - -Use the `useAnalytics` hook in client components: - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" - -const { track } = useAnalytics() -``` - -### Usage - -```tsx -track(event: string, properties?: Record) -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `event` | `string` | Event name (e.g., `"SIGNUP_CLICKED"`) | -| `properties` | `Record` | Optional event properties | - -### Examples - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function SignupButton() { - const { track } = useAnalytics() - - return ( - - ) -} -``` - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -const { track } = useAnalytics() -// ---cut--- -track("PURCHASE_COMPLETED", { - amount: 99.99, - currency: "USD", - productId: "pro-plan" -}) -``` - -## Server - -Use the `track` function from `@repo/analytics/server` in Server Actions, API routes, or server components: - -```tsx twoslash -// @noErrors -import { track } from "@repo/analytics/server" -``` - -### Usage - -```tsx -await track(event: TrackableEvent | TrackableEvent[]) -``` - -Server-side tracking requires a `userId` or `anonymousId`: - -```tsx twoslash -interface TrackableEvent { - event: string - userId: string - anonymousId?: string - [key: string]: unknown -} -``` - -### Examples - -#### Server Action - -```tsx twoslash -// @noErrors -"use server" - -import { track } from "@repo/analytics/server" -import { auth } from "@repo/auth/server" - -export async function createTeam(name: string) { - const { user } = await auth() - - const team = await db.insert(teams).values({ name }).returning() - - await track({ - event: "TEAM_CREATED", - userId: user.id, - teamId: team.id, - teamName: name - }) - - return team -} -``` - -#### API Route - -```tsx twoslash title="app/api/purchase/route.ts" -// @noErrors -import { track } from "@repo/analytics/server" - -export async function POST(request: Request) { - const { userId, productId, amount } = await request.json() - - await track({ - event: "PURCHASE_COMPLETED", - userId, - productId, - amount - }) - - return Response.json({ success: true }) -} -``` - -#### Batch tracking - -```tsx twoslash -// @noErrors -import { track } from "@repo/analytics/server" -declare const user: { id: string } -declare const team: { id: string } -// ---cut--- -await track([ - { event: "TEAM_CREATED", userId: user.id, teamId: team.id }, - { event: "TEAM_JOINED", userId: user.id, teamId: team.id } -]) -``` - -## Event naming conventions - -Use `SCREAMING_SNAKE_CASE` for consistency: - -- `SIGNUP_CLICKED` -- `PURCHASE_COMPLETED` -- `FEATURE_USED` -- `TEAM_CREATED` -- `ERROR_OCCURRED` diff --git a/apps/home/content/docs/analytics/usage/use-analytics.mdx b/apps/home/content/docs/analytics/usage/use-analytics.mdx deleted file mode 100644 index 45c9f3c4..00000000 --- a/apps/home/content/docs/analytics/usage/use-analytics.mdx +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: useAnalytics -description: Hook for tracking events and identifying users ---- - -The main hook for analytics in client components. - -## Import - -```tsx twoslash -// @noErrors -import { useAnalytics } from "@repo/analytics" -``` - -## Usage - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" - -export function MyComponent() { - const { track, identify, page, reset } = useAnalytics() - - return ( - - ) -} -``` - -## Return Value - -| Method | Description | -|--------|-------------| -| [`track`](/docs/analytics/usage/track) | Track a custom event with optional properties | -| [`identify`](/docs/analytics/usage/identify) | Associate events with a user identity | -| [`page`](/docs/analytics/usage/page) | Track a page view (automatic in Next.js) | -| [`reset`](/docs/analytics/usage/reset) | Clear user identity (on logout) | - -## Example - -```tsx twoslash -// @noErrors -"use client" - -import { useAnalytics } from "@repo/analytics" -import { useAuth } from "@repo/auth" - -export function Dashboard() { - const { track, reset } = useAnalytics() - const { logout } = useAuth() - - async function handleExport() { - track("EXPORT_CLICKED", { format: "csv" }) - await exportData() - } - - async function handleLogout() { - await logout() - reset() - } - - return ( -
- - -
- ) -} -``` diff --git a/apps/home/content/docs/analytics/usage/use-flag.mdx b/apps/home/content/docs/analytics/usage/use-flag.mdx deleted file mode 100644 index b46bc5a7..00000000 --- a/apps/home/content/docs/analytics/usage/use-flag.mdx +++ /dev/null @@ -1,105 +0,0 @@ ---- -title: useFlag -description: Hook for accessing feature flags ---- - -Access feature flags from PostHog in client components. - -## Import - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -``` - -## Usage - -```tsx twoslash -// @noErrors -"use client" - -import { useFlag } from "@repo/analytics" - -export function NewFeature() { - const isEnabled = useFlag("new-checkout-flow") - - if (!isEnabled) return null - - return -} -``` - -## Parameters - -| Parameter | Type | Description | -|-----------|------|-------------| -| `name` | `string` | The feature flag key from PostHog | - -## Return Value - -Returns the flag value (`boolean`, `string`, or `undefined` if not found). - -## Examples - -### Boolean flag - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const showBetaFeatures = useFlag("beta-features") - -if (showBetaFeatures) { - return -} -``` - -### String/variant flag - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const pricingVariant = useFlag("pricing-experiment") - -switch (pricingVariant) { - case "variant-a": - return - case "variant-b": - return - default: - return -} -``` - -### With fallback - -Handle undefined flags gracefully: - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -const isEnabled = useFlag("new-feature") ?? false -``` - -### Typed flags - -For type safety across your app: - -```tsx twoslash -// @noErrors -import { useFlag } from "@repo/analytics" -// ---cut--- -interface MyFlags { - "new-checkout-flow": boolean - "pricing-experiment": "control" | "variant-a" | "variant-b" -} - -const variant = useFlag("pricing-experiment") -// Type: "control" | "variant-a" | "variant-b" | undefined -``` - -## Setup - -Feature flags require PostHog. See [Feature Flags](/docs/analytics/feature-flags) for configuration. diff --git a/apps/home/content/docs/auth/configuration.mdx b/apps/home/content/docs/auth/configuration.mdx deleted file mode 100644 index aae93ec9..00000000 --- a/apps/home/content/docs/auth/configuration.mdx +++ /dev/null @@ -1,246 +0,0 @@ ---- -title: Configuration -description: Setting up authentication in your StartupKit monorepo ---- - -StartupKit uses [Better Auth](https://better-auth.com) for authentication. This guide covers how auth is structured in your monorepo. - -## Architecture - -Auth in StartupKit has two layers: - -| Package | Purpose | -|---------|---------| -| `@startupkit/auth` | NPM package with React hooks and utilities | -| `@repo/auth` | Your customizable auth configuration | - -The `@repo/auth` package in your monorepo imports from both `better-auth` directly and `@startupkit/auth`. This gives you full control over your auth configuration while using StartupKit's convenience utilities. - -## Project Structure - -``` -packages/auth/ -├── src/ -│ ├── lib/ -│ │ └── auth.ts # Better Auth configuration -│ ├── components/ -│ │ ├── index.ts -│ │ └── provider.tsx # AuthProvider wrapper -│ ├── hooks/ -│ │ └── use-auth.ts # Typed useAuth hook -│ ├── index.ts # Client exports + authClient -│ ├── server.ts # Server exports (withAuth, handler) -│ └── types.ts # User/Session types -└── package.json -``` - -## Server Configuration - -### 1. Configure Better Auth - -Create your Better Auth instance in `packages/auth/src/lib/auth.ts`: - -```ts twoslash title="packages/auth/src/lib/auth.ts" -// @noErrors -import { betterAuth } from "better-auth" -import { drizzleAdapter } from "better-auth/adapters/drizzle" -import { nextCookies } from "better-auth/next-js" -import { emailOTP } from "better-auth/plugins" -import * as dbSchema from "@repo/db" - -export const auth = betterAuth({ - basePath: "/auth", - database: drizzleAdapter(dbSchema.db, { - provider: "pg", - schema: { - user: dbSchema.users, - account: dbSchema.accounts, - session: dbSchema.sessions, - verification: dbSchema.verifications - } - }), - plugins: [ - emailOTP({ - sendVerificationOTP: async ({ email, otp }) => { - // Send OTP via your email service - } - }), - nextCookies() - ], - socialProviders: { - google: { - clientId: process.env.GOOGLE_CLIENT_ID || "", - clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", - } - } -}) -``` - -### 2. Create Server Exports - -Export server utilities in `packages/auth/src/server.ts`: - -```ts twoslash title="packages/auth/src/server.ts" -// @noErrors -import { toNextJsHandler } from "better-auth/next-js" -import { headers } from "next/headers" -import { auth } from "./lib/auth" - -export { auth } - -export type Session = typeof auth.$Infer.Session.session -export type User = typeof auth.$Infer.Session.user - -export function handler() { - return toNextJsHandler(auth.handler) -} - -export async function withAuth() { - const session = await auth.api.getSession({ - headers: await headers() - }) - - return { - user: session?.user ?? null, - session: session?.session ?? null - } -} -``` - -### 3. Create API Route - -Add the auth handler route in your Next.js app: - -```ts twoslash title="apps/web/app/auth/[...all]/route.ts" -// @noErrors -import { handler } from "@repo/auth/server" - -export const { GET, POST } = handler() -``` - -## Client Configuration - -### 1. Create Auth Client - -Set up the Better Auth client in `packages/auth/src/index.ts`: - -```ts twoslash title="packages/auth/src/index.ts" -// @noErrors -import { emailOTPClient } from "better-auth/client/plugins" -import { createAuthClient } from "better-auth/react" - -export * from "./components" -export * from "./types" - -export const authClient = createAuthClient({ - basePath: "/auth", - plugins: [emailOTPClient()] -}) -``` - -### 2. Create AuthProvider - -Wrap the StartupKit AuthProvider in `packages/auth/src/components/provider.tsx`: - -```tsx twoslash title="packages/auth/src/components/provider.tsx" -// @noErrors -"use client" - -import { AuthProvider as StartupKitAuthProvider } from "@startupkit/auth" -import type React from "react" -import { authClient } from "../index" -import type { User } from "../types" - -interface AuthProviderProps { - children: React.ReactNode - user?: User -} - -export function AuthProvider({ children, user }: AuthProviderProps) { - return ( - - {children} - - ) -} -``` - -### 3. Add to App Layout - -Wrap your app with the AuthProvider: - -```tsx twoslash title="apps/web/app/providers.tsx" -// @noErrors -"use client" - -import { AuthProvider } from "@repo/auth" -import type { User } from "@repo/auth" - -interface ProvidersProps { - children: React.ReactNode - user?: User -} - -export function Providers({ children, user }: ProvidersProps) { - return ( - - {children} - - ) -} -``` - -```tsx twoslash title="apps/web/app/layout.tsx" -// @noErrors -import { withAuth } from "@repo/auth/server" -import { Providers } from "./providers" - -export default async function RootLayout({ - children -}: { - children: React.ReactNode -}) { - const { user } = await withAuth() - - return ( - - - - {children} - - - - ) -} -``` - -## Environment Variables - -Add these to your `.env.local`: - -```bash -# Database -DATABASE_URL="postgresql://..." - -# Google OAuth (optional) -GOOGLE_CLIENT_ID="your-client-id" -GOOGLE_CLIENT_SECRET="your-client-secret" -``` - -## Authentication Providers - -The example above configures **Email OTP** and **Google OAuth**. Better Auth supports many more providers including GitHub, Apple, Discord, and others. - -See the [Better Auth Providers documentation](https://www.better-auth.com/docs/concepts/providers) for the full list and configuration options. - -To add a provider: - -1. Add the provider config to `auth.ts` -2. Add the client plugin to `authClient` if needed -3. Add environment variables - -## Next Steps - -- [requireAuth](/docs/auth/usage/require-auth) - Protect pages with redirect -- [withAuth](/docs/auth/usage/with-auth) - Get session for conditional rendering -- [Session Management](/docs/auth/session-management) - Configure session duration diff --git a/apps/home/content/docs/auth/index.mdx b/apps/home/content/docs/auth/index.mdx deleted file mode 100644 index ac6f360c..00000000 --- a/apps/home/content/docs/auth/index.mdx +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Auth Overview -description: Authentication with @startupkit/auth built on Better Auth ---- - -StartupKit authentication is built on [Better Auth](https://better-auth.com), a flexible authentication framework for TypeScript. - -## What StartupKit Provides - -`@startupkit/auth` adds convenience utilities on top of Better Auth: - -- **Server utilities** - `withAuth()`, `requireAuth()` for Server Components -- **React hooks** - `useAuth()` for client components -- **Context provider** - `AuthProvider` with analytics integration - -## Quick Example - -**Server Component:** - -```tsx -import { withAuth } from "@repo/auth/server" - -export default async function Dashboard() { - const { user } = await withAuth() - - if (!user) return - - return
Welcome, {user.name}
-} -``` - -**Client Component:** - -```tsx -"use client" - -import { useAuth } from "@repo/auth" - -export function Header() { - const { isAuthenticated, user, logout } = useAuth() - - if (!isAuthenticated) return Sign In - - return ( -
- {user?.name} - -
- ) -} -``` - -## API Reference - -### Server (`@repo/auth/server`) - -| Export | Description | -|--------|-------------| -| [`withAuth()`](/docs/auth/usage/with-auth) | Get current session (returns `{ user, session }` or nulls) | -| [`requireAuth()`](/docs/auth/usage/require-auth) | Get session or redirect to sign-in | -| `handler()` | Create Next.js API route handler | - -### Client (`@repo/auth`) - -| Export | Description | -|--------|-------------| -| [`useAuth()`](/docs/auth/usage/use-auth) | Hook for auth state and methods | -| `AuthProvider` | Context provider for auth | -| `authClient` | Better Auth client instance | - -## Authentication Providers - -Better Auth supports many authentication methods including Email OTP, Google, GitHub, Apple, and more. See the [Better Auth Providers documentation](https://www.better-auth.com/docs/concepts/providers) for the full list. - -The [Configuration](/docs/auth/configuration) page shows how to set up Email OTP and Google as examples. - -## Next Steps - -- [Configuration](/docs/auth/configuration) - Set up auth in your monorepo -- [requireAuth](/docs/auth/usage/require-auth) - Protect pages with redirect -- [withAuth](/docs/auth/usage/with-auth) - Get session for conditional rendering -- [useAuth](/docs/auth/usage/use-auth) - Client-side auth hook diff --git a/apps/home/content/docs/auth/meta.json b/apps/home/content/docs/auth/meta.json deleted file mode 100644 index 57e34b0c..00000000 --- a/apps/home/content/docs/auth/meta.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "Auth", - "icon": "Lock", - "pages": [ - "index", - "configuration", - "session-management", - "usage" - ], - "defaultOpen": true -} diff --git a/apps/home/content/docs/auth/session-management.mdx b/apps/home/content/docs/auth/session-management.mdx deleted file mode 100644 index 9fb41cad..00000000 --- a/apps/home/content/docs/auth/session-management.mdx +++ /dev/null @@ -1,320 +0,0 @@ ---- -title: Session Management -description: How sessions work and how to configure them ---- - -Sessions track authenticated users across requests. Better Auth handles session storage, cookies, and expiration automatically. - -## Default Configuration - -Out of the box, sessions are configured with: - -| Setting | Default | Description | -|---------|---------|-------------| -| `expiresIn` | 7 days | Session duration | -| `updateAge` | 24 hours | How often to refresh | - -Sessions refresh automatically when users are active. - -## How Sessions Work - -1. **Sign in**: User authenticates via email OTP or OAuth -2. **Create session**: Better Auth creates a session record in your database -3. **Set cookie**: A secure HTTP-only cookie is sent to the browser -4. **Validate requests**: On each request, the cookie is validated against the database -5. **Refresh**: Active sessions are automatically extended - -## Customizing Session Duration - -```tsx twoslash title="lib/auth.ts" -// @noErrors -import { betterAuth } from "better-auth" - -export const auth = betterAuth({ - // ... - session: { - expiresIn: 60 * 60 * 24 * 30, // 30 days - updateAge: 60 * 60 * 24 // Refresh every 24 hours - } -}) -``` - -### Common Configurations - -| Use Case | expiresIn | updateAge | -|----------|-----------|-----------| -| Standard app | 7 days | 24 hours | -| High security | 1 hour | 15 minutes | -| "Remember me" | 30 days | 24 hours | -| Mobile app | 90 days | 7 days | - -## Session Storage - -Sessions are stored in your database via the Drizzle adapter: - -```tsx twoslash -// @noErrors -import { drizzleAdapter } from "better-auth/adapters/drizzle" -declare const db: unknown -declare const sessionsTable: unknown -// ---cut--- -database: drizzleAdapter(db, { - provider: "pg", - schema: { - session: sessionsTable // Your sessions table - } -}) -``` - -The sessions table stores: -- `id` - Unique session ID -- `userId` - Associated user -- `token` - Session token (in cookie) -- `expiresAt` - Expiration timestamp -- `ipAddress` - Client IP (optional) -- `userAgent` - Browser info (optional) - -## Accessing Session Data - -### In Server Components - -```tsx twoslash -// @noErrors -import { auth } from "@/lib/auth" -import { headers } from "next/headers" - -export default async function Page() { - const session = await auth.api.getSession({ - headers: await headers() - }) - - // session.user - User object - // session.session - Session metadata -} -``` - -### In Client Components - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -export function Component() { - const { user, isAuthenticated, isLoading } = useAuth() -} -``` - -### Session Metadata - -```tsx twoslash -// @noErrors -import { auth } from "@/lib/auth" -import { headers } from "next/headers" -// ---cut--- -const session = await auth.api.getSession({ - headers: await headers() -}) - -console.log(session.session) -// { -// id: "session_123", -// userId: "user_456", -// expiresAt: Date, -// ipAddress: "192.168.1.1", -// userAgent: "Mozilla/5.0..." -// } -``` - -## Invalidating Sessions - -### Sign Out Current Session - -```tsx twoslash -// @noErrors -"use client" - -import { useAuth } from "@startupkit/auth" - -export function LogoutButton() { - const { logout } = useAuth() - - return -} -``` - -### Sign Out All Sessions - -For "sign out everywhere" functionality: - -```tsx twoslash -// @noErrors -import { auth } from "@/lib/auth" -import { headers } from "next/headers" - -export async function signOutAll() { - const session = await auth.api.getSession({ - headers: await headers() - }) - - if (session) { - // Delete all sessions for this user - await db.delete(sessions) - .where(eq(sessions.userId, session.user.id)) - } -} -``` - -## Session Refresh - -Sessions automatically refresh when `updateAge` has passed: - -1. User makes a request -2. Better Auth checks session age -3. If older than `updateAge`, extends `expiresAt` -4. User continues without interruption - -This ensures active users stay logged in. - -## Cookie Configuration - -Better Auth uses secure HTTP-only cookies: - -```tsx twoslash -// @noErrors -import { betterAuth } from "better-auth" -// ---cut--- -export const auth = betterAuth({ - // Cookies are configured automatically, but you can customize: - advanced: { - cookiePrefix: "myapp", // Custom cookie prefix - crossSubDomainCookies: { - enabled: true, - domain: ".myapp.com" // Share across subdomains - } - } -}) -``` - -### Cookie Settings - -| Setting | Value | -|---------|-------| -| `httpOnly` | `true` (can't be accessed by JS) | -| `secure` | `true` in production | -| `sameSite` | `lax` | -| `path` | `/` | - -## Multiple Sessions - -Users can have multiple active sessions (different devices): - -```tsx twoslash -// @noErrors -import { sessions } from "@repo/db/schema" -declare const db: { select: () => { from: (t: unknown) => { where: (c: unknown) => Promise<{ id: string; userAgent: string; ipAddress: string }[]> } } } -declare const eq: (a: unknown, b: unknown) => unknown -declare const user: { id: string } -declare function revokeSession(id: string): void -// ---cut--- -// Get all sessions for current user -const userSessions = await db.select() - .from(sessions) - .where(eq(sessions.userId, user.id)) - -// Display in UI -
    - {userSessions.map(session => ( -
  • - {session.userAgent} - {session.ipAddress} - -
  • - ))} -
-``` - -## Security Considerations - -### IP Address Validation - -Track IP for security monitoring: - -```tsx twoslash -// @noErrors -import { auth } from "@/lib/auth" -import { headers } from "next/headers" -import { track } from "@repo/analytics/server" -declare const currentIp: string -// ---cut--- -const session = await auth.api.getSession({ - headers: await headers() -}) - -// Check for suspicious IP changes -if (session.session.ipAddress !== currentIp) { - // Log security event - await track({ - event: "SUSPICIOUS_IP_CHANGE", - userId: session.user.id - }) -} -``` - -### Force Re-authentication - -For sensitive actions, require fresh authentication: - -```tsx twoslash -// @noErrors -import { auth } from "@/lib/auth" -import { headers } from "next/headers" -// ---cut--- -export async function sensitiveAction() { - const session = await auth.api.getSession({ - headers: await headers() - }) - - // Check when session was created - const sessionAge = Date.now() - session.session.createdAt.getTime() - const fifteenMinutes = 15 * 60 * 1000 - - if (sessionAge > fifteenMinutes) { - throw new Error("Please re-authenticate for this action") - } - - // Proceed with sensitive action -} -``` - -## Debugging Sessions - -### Check Session in Database - -```sql -SELECT * FROM "Session" WHERE "userId" = 'user_123'; -``` - -### Log Session Events - -```tsx twoslash -// @noErrors -import { betterAuth } from "better-auth" -// ---cut--- -export const auth = betterAuth({ - hooks: { - after: async (ctx) => { - if (ctx.newSession) { - console.log("New session created:", ctx.newSession.id) - } - } - } -}) -``` - -## Next Steps - -- [requireAuth](/docs/auth/usage/require-auth) - Protect pages with redirect -- [withAuth](/docs/auth/usage/with-auth) - Get session for conditional rendering -- [useAuth](/docs/auth/usage/use-auth) - Client-side auth hook diff --git a/apps/home/content/docs/auth/usage/meta.json b/apps/home/content/docs/auth/usage/meta.json deleted file mode 100644 index 5328dc90..00000000 --- a/apps/home/content/docs/auth/usage/meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "title": "Usage", - "pages": ["require-auth", "with-auth", "use-auth"], - "defaultOpen": true -} diff --git a/apps/home/content/docs/auth/usage/require-auth.mdx b/apps/home/content/docs/auth/usage/require-auth.mdx deleted file mode 100644 index e8b71e65..00000000 --- a/apps/home/content/docs/auth/usage/require-auth.mdx +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: requireAuth -description: Protect pages by redirecting unauthenticated users ---- - -`requireAuth()` gets the current session and redirects to sign-in if no user is authenticated. Use it when a page should only be accessible to logged-in users. - -## Basic Usage - -```tsx twoslash title="app/dashboard/page.tsx" -// @noErrors -import { requireAuth } from "@repo/auth/server" - -export default async function DashboardPage() { - const { user } = await requireAuth() - - return

Welcome, {user.name}

-} -``` - -If no session exists, the user is redirected to `/sign-in`. - -## Custom Redirect Path - -```tsx twoslash -// @noErrors -import { requireAuth } from "@repo/auth/server" -// ---cut--- -const { user } = await requireAuth("/login") -``` - -## Return Value - -Returns the full session object: - -```tsx twoslash -// @noErrors -import { requireAuth } from "@repo/auth/server" -// ---cut--- -const { user, session } = await requireAuth() - -// user - The authenticated user -// session - Session metadata (id, expiresAt, etc.) -``` - -## Layout Protection - -Protect all pages under a route group: - -```tsx twoslash title="app/(dashboard)/layout.tsx" -// @noErrors -import { requireAuth } from "@repo/auth/server" - -export default async function DashboardLayout({ - children -}: { - children: React.ReactNode -}) { - await requireAuth() - - return ( -
- - {children} -
- ) -} -``` - -All pages in `app/(dashboard)/` are now protected. - -## Server Actions - -Protect server actions: - -```tsx twoslash title="app/actions/profile.ts" -// @noErrors -"use server" - -import { requireAuth } from "@repo/auth/server" - -export async function updateProfile(data: { name: string }) { - const { user } = await requireAuth() - - await db.update(users) - .set({ name: data.name }) - .where(eq(users.id, user.id)) - - return { success: true } -} -``` - -## Role-Based Access - -Build on `requireAuth` for role checks: - -```tsx twoslash title="lib/auth-utils.ts" -// @noErrors -import { requireAuth } from "@repo/auth/server" -import { redirect } from "next/navigation" - -export async function requireAdmin() { - const session = await requireAuth() - - if (session.user.role !== "admin") { - redirect("/dashboard") - } - - return session -} -``` - -Use in pages: - -```tsx twoslash title="app/admin/page.tsx" -// @noErrors -import { requireAdmin } from "@/lib/auth-utils" - -export default async function AdminPage() { - const { user } = await requireAdmin() - - return
Admin Panel for {user.name}
-} -``` - -## When to Use - -| Use Case | Function | -|----------|----------| -| Protected pages | `requireAuth()` | -| Protected layouts | `requireAuth()` | -| Server actions | `requireAuth()` | -| Conditional UI | [`withAuth()`](/docs/auth/usage/with-auth) | -| API routes | [`withAuth()`](/docs/auth/usage/with-auth) | - -## Next Steps - -- [withAuth](/docs/auth/usage/with-auth) - Get session without redirecting -- [useAuth](/docs/auth/usage/use-auth) - Client-side auth hook diff --git a/apps/home/content/docs/auth/usage/use-auth.mdx b/apps/home/content/docs/auth/usage/use-auth.mdx deleted file mode 100644 index 24f9f593..00000000 --- a/apps/home/content/docs/auth/usage/use-auth.mdx +++ /dev/null @@ -1,403 +0,0 @@ ---- -title: useAuth -description: Complete API reference for the useAuth hook ---- - -The `useAuth` hook provides authentication state and methods in client components. - -## Import - -```tsx twoslash -import { useAuth } from "@startupkit/auth" -``` - -## Basic Usage - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -export function MyComponent() { - const { - isAuthenticated, - isLoading, - user, - logout, - sendAuthCode, - verifyAuthCode, - googleAuth - } = useAuth() -} -``` - -## Return Values - -### isAuthenticated - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { isAuthenticated } = useAuth() -} -``` - -Whether the user is currently signed in. - -```tsx -if (isAuthenticated) { - return -} -return -``` - -### isLoading - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { isLoading } = useAuth() -} -``` - -Whether the auth state is being determined. Always check this before `isAuthenticated` to avoid flashing content. - -```tsx -if (isLoading) { - return -} -if (!isAuthenticated) { - return -} -``` - -### user - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { user } = useAuth() -} -``` - -The current user object. `null` if not authenticated, `undefined` while loading. - -```tsx -interface User { - id: string - email: string - name: string - firstName?: string - lastName?: string - image?: string - emailVerified: boolean - createdAt: Date - updatedAt: Date -} -``` - -Usage: - -```tsx -if (user) { - return Welcome, {user.name} -} -``` - -## Methods - -### logout() - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { logout } = useAuth() -} -``` - -Signs out the current user and redirects to home. - -```tsx - -``` - -Or with custom handling: - -```tsx -const handleLogout = async () => { - await logout() - // Additional cleanup if needed -} -``` - -### sendAuthCode() - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { sendAuthCode } = useAuth() -} -``` - -Sends a one-time password to the specified email. - -```tsx -const [email, setEmail] = useState("") - -const handleSend = async () => { - try { - await sendAuthCode(email) - // Show code input - } catch (error) { - // Handle error (invalid email, rate limited, etc.) - } -} -``` - -### verifyAuthCode() - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { verifyAuthCode } = useAuth() -} -``` - -Verifies the OTP code and creates a session. - -```tsx -const handleVerify = async () => { - try { - await verifyAuthCode(email, code) - // User is now signed in, redirected automatically - } catch (error) { - // Invalid or expired code - } -} -``` - -### googleAuth() - -```tsx twoslash -"use client" - -import { useAuth } from "@startupkit/auth" - -function Example() { - const { googleAuth } = useAuth() -} -``` - -Initiates Google OAuth sign-in flow. - -```tsx - -``` - -## AuthProvider - -The `useAuth` hook requires the `AuthProvider` wrapper: - -```tsx title="app/providers.tsx" -"use client" - -import { AuthProvider } from "@startupkit/auth" -import { authClient } from "@/lib/auth-client" - -interface ProvidersProps { - children: React.ReactNode - user?: User -} - -export function Providers({ children, user }: ProvidersProps) { - return ( - - {children} - - ) -} -``` - -### AuthProvider Props - -| Prop | Type | Description | -|------|------|-------------| -| `children` | `ReactNode` | Child components | -| `authClient` | `AuthClient` | Better Auth client instance | -| `user` | `User` | Initial user (from server) | -| `onIdentify` | `(user: User) => void` | Called when user signs in | -| `onReset` | `() => void` | Called on logout | - -### Integration with Analytics - -```tsx - { - // Identify user in analytics - analytics.identify(user.id, { - email: user.email, - name: user.name - }) - }} - onReset={() => { - // Reset analytics identity - analytics.reset() - }} -> - {children} - -``` - -## TypeScript - -### Custom User Type - -Extend the user type with additional fields: - -```tsx -interface CustomUser { - id: string - email: string - name: string - plan: "free" | "pro" | "enterprise" - teamId: string -} - -// In your component -const { user } = useAuth() as { user: CustomUser | null } -``` - -### Type Definitions - -```tsx -interface AuthContextType { - isAuthenticated: boolean - isLoading: boolean - user: User | null | undefined - logout: () => Promise - sendAuthCode: (email: string) => Promise - verifyAuthCode: (email: string, code: string) => Promise - googleAuth: () => Promise -} -``` - -## Common Patterns - -### Protected Component - -```tsx -"use client" - -import { useAuth } from "@startupkit/auth" - -export function ProtectedFeature() { - const { isAuthenticated, isLoading } = useAuth() - - if (isLoading) return - if (!isAuthenticated) return null - - return -} -``` - -### Auth-Dependent UI - -```tsx -"use client" - -import { useAuth } from "@startupkit/auth" - -export function Header() { - const { isAuthenticated, user, logout } = useAuth() - - return ( -
- - -
- ) -} -``` - -### Redirect After Auth - -```tsx -"use client" - -import { useAuth } from "@startupkit/auth" -import { useRouter } from "next/navigation" -import { useEffect } from "react" - -export function AuthRedirect({ redirectTo = "/dashboard" }) { - const { isAuthenticated, isLoading } = useAuth() - const router = useRouter() - - useEffect(() => { - if (!isLoading && isAuthenticated) { - router.push(redirectTo) - } - }, [isAuthenticated, isLoading, redirectTo, router]) - - return null -} -``` - -## Error Handling - -All auth methods can throw errors. Always check the error type before accessing properties: - -```tsx -try { - await sendAuthCode(email) -} catch (error) { - if (error instanceof Error) { - const message = error.message - if (message.includes("rate limit")) { - setError("Too many attempts. Please wait.") - } else if (message.includes("invalid email")) { - setError("Please enter a valid email.") - } else { - setError("Something went wrong. Please try again.") - } - } else { - setError("An unexpected error occurred.") - } -} -``` - -## Next Steps - -- [requireAuth](/docs/auth/usage/require-auth) - Server-side page protection -- [withAuth](/docs/auth/usage/with-auth) - Get session for conditional rendering -- [Configuration](/docs/auth/configuration) - Set up auth providers diff --git a/apps/home/content/docs/auth/usage/with-auth.mdx b/apps/home/content/docs/auth/usage/with-auth.mdx deleted file mode 100644 index 15425fa6..00000000 --- a/apps/home/content/docs/auth/usage/with-auth.mdx +++ /dev/null @@ -1,180 +0,0 @@ ---- -title: withAuth -description: Get the current session without redirecting ---- - -`withAuth()` returns the current session if one exists, or `null` values if not. Use it for conditional rendering or API routes where you need to handle unauthenticated states yourself. - -## Basic Usage - -```tsx twoslash title="app/page.tsx" -// @noErrors -import { withAuth } from "@repo/auth/server" - -export default async function HomePage() { - const { user } = await withAuth() - - return ( -
- {user ? ( - Welcome back, {user.name} - ) : ( - Sign In - )} -
- ) -} -``` - -## Return Value - -Always returns an object with `user` and `session`: - -```tsx twoslash -// @noErrors -import { withAuth } from "@repo/auth/server" -// ---cut--- -const { user, session } = await withAuth() - -// Authenticated: { user: User, session: Session } -// Not authenticated: { user: null, session: null } -``` - -## API Routes - -Protect API routes and return proper HTTP status: - -```tsx twoslash title="app/api/user/route.ts" -// @noErrors -import { withAuth } from "@repo/auth/server" -import { NextResponse } from "next/server" - -export async function GET() { - const { user } = await withAuth() - - if (!user) { - return NextResponse.json( - { error: "Unauthorized" }, - { status: 401 } - ) - } - - return NextResponse.json({ user }) -} -``` - -## Conditional Content - -Show different content based on auth state: - -```tsx twoslash title="app/pricing/page.tsx" -// @noErrors -import { withAuth } from "@repo/auth/server" - -export default async function PricingPage() { - const { user } = await withAuth() - - return ( -
-

Pricing

- {user ? ( - - ) : ( - - )} -
- ) -} -``` - -## Root Layout - -Pass user to client providers: - -```tsx twoslash title="app/layout.tsx" -// @noErrors -import { withAuth } from "@repo/auth/server" -import { Providers } from "./providers" - -export default async function RootLayout({ - children -}: { - children: React.ReactNode -}) { - const { user } = await withAuth() - - return ( - - - - {children} - - - - ) -} -``` - -## Client Components - -For client-side auth checks, use the `useAuth` hook instead: - -```tsx twoslash -// @noErrors -"use client" - -import { useAuth } from "@repo/auth" - -export function Header() { - const { isAuthenticated, user, isLoading } = useAuth() - - if (isLoading) return - - return isAuthenticated ? ( - {user?.name} - ) : ( - Sign In - ) -} -``` - -## Middleware - -For edge-based checks, use Next.js middleware: - -```tsx twoslash title="middleware.ts" -// @noErrors -import { NextResponse } from "next/server" -import type { NextRequest } from "next/server" - -export function middleware(request: NextRequest) { - const sessionToken = request.cookies.get("better-auth.session_token") - - if (request.nextUrl.pathname.startsWith("/dashboard")) { - if (!sessionToken) { - return NextResponse.redirect(new URL("/sign-in", request.url)) - } - } - - return NextResponse.next() -} - -export const config = { - matcher: ["/dashboard/:path*"] -} -``` - -## When to Use - -| Use Case | Function | -|----------|----------| -| Conditional UI | `withAuth()` | -| API routes | `withAuth()` | -| Root layout | `withAuth()` | -| Protected pages | [`requireAuth()`](/docs/auth/usage/require-auth) | -| Server actions | [`requireAuth()`](/docs/auth/usage/require-auth) | - -## Next Steps - -- [requireAuth](/docs/auth/usage/require-auth) - Redirect unauthenticated users -- [useAuth](/docs/auth/usage/use-auth) - Client-side auth hook diff --git a/apps/home/content/docs/cli/add.mdx b/apps/home/content/docs/cli/add.mdx deleted file mode 100644 index 16be6b80..00000000 --- a/apps/home/content/docs/cli/add.mdx +++ /dev/null @@ -1,227 +0,0 @@ ---- -title: Adding Apps -description: How to add new applications to your StartupKit workspace ---- - -The `add` command scaffolds new applications into your monorepo's `apps/` directory. - -## Basic Usage - -```bash -npx startupkit add -``` - -This launches an interactive menu to select your app type: - -``` -? Which app would you like to add? -❯ Next.js - Vite - ---- coming soon ---- - Expo (disabled) - Capacitor Mobile (disabled) - Electron (disabled) -``` - -## Supported App Types - -| Type | Command | Description | -|------|---------|-------------| -| Next.js | `startupkit add next` | Full-stack React with App Router | -| Vite | `startupkit add vite` | Fast React SPA | - -### Coming Soon - -Vote for the features you want to see next! - -| Type | Description | Vote | -|------|-------------|------| -| Expo | React Native mobile apps | [👍 Vote](https://startupkit.featurebase.app/p/expo) | -| Capacitor | Cross-platform mobile apps | [👍 Vote](https://startupkit.featurebase.app/p/capacitor) | -| Electron | Desktop applications | [👍 Vote](https://startupkit.featurebase.app/p/electron) | -| Tauri | Lightweight desktop apps | [👍 Vote](https://startupkit.featurebase.app/p/tauri) | -| Astro | Content-focused websites | [👍 Vote](https://startupkit.featurebase.app/p/astro) | -| Hono | Edge-first API server | [👍 Vote](https://startupkit.featurebase.app/p/hono) | -| Plasmo | Browser extensions | [👍 Vote](https://startupkit.featurebase.app/p/chrome-extension) | - -Have another idea? [Request a template →](https://startupkit.featurebase.app/) - -## Command Options - -```bash -npx startupkit add [type] [options] -``` - -| Option | Description | -|--------|-------------| -| `type` | App type: `next` or `vite` | -| `--name ` | App name (skips prompt) | -| `--repo ` | Custom template repository | - -## Examples - -### Add a Next.js App - -```bash -npx startupkit add next --name web -``` - -Creates `apps/web/` with a Next.js application. - -### Add a Vite App - -```bash -npx startupkit add vite --name marketing -``` - -Creates `apps/marketing/` with a Vite SPA. - -### Non-Interactive Mode - -```bash -npx startupkit add next --name dashboard -``` - -## Automatic Dependency Resolution - -When you add an app, the CLI automatically: - -1. **Checks template dependencies** - Reads `startupkit.config.ts` from the template -2. **Detects missing packages** - Scans your workspace for required `@repo/*` packages -3. **Prompts to install** - Offers to install any missing dependencies - -Example output: - -``` -🔍 Checking template dependencies... - -⚠️ This template requires 3 workspace dependencies that are not installed: - - Packages: - - @repo/auth - - @repo/db - - @repo/ui - -? Would you like to install these dependencies now? (Y/n) -``` - -If you decline, the command exits—apps require their dependencies to function. - -## What Gets Created - -A Next.js app includes: - -``` -apps/web/ -├── src/ -│ ├── app/ -│ │ ├── layout.tsx -│ │ ├── page.tsx -│ │ └── providers.tsx -│ ├── components/ -│ └── lib/ -├── public/ -├── next.config.ts -├── tailwind.config.ts -├── tsconfig.json -└── package.json -``` - -The app is pre-configured with: - -- **@repo/ui** - Shadcn components -- **@repo/auth** - Authentication hooks -- **@repo/analytics** - Analytics tracking -- **@repo/tsconfig** - Shared TypeScript config -- **@repo/biome** - Linting and formatting - -## Template Configuration - -Each app template includes a `startupkit.config.ts` that declares dependencies: - -```typescript -import type { StartupKitConfig } from "startupkit" - -export default { - type: "app", - dependencies: { - packages: ["auth", "db", "ui", "analytics"], - config: ["tsconfig", "nextjs", "biome"] - } -} satisfies StartupKitConfig -``` - -## Running Multiple Apps - -After adding apps, run them all: - -```bash -pnpm dev -``` - -Or run specific apps: - -```bash -pnpm dev --filter web -pnpm dev --filter dashboard -``` - -Turbo handles caching and parallel execution automatically. - -## Custom Templates - -Use your own templates with `--repo`: - -```bash -npx startupkit add next --repo myorg/my-next-template -``` - -Your template should: -- Include a `startupkit.config.ts` declaring dependencies -- Use `PROJECT_NEXT` as the placeholder name (for Next.js templates) -- Use `PROJECT_VITE` as the placeholder name (for Vite templates) - -## App Naming - -The app name you provide is converted to a URL-safe slug: - -| Input | Slug | -|-------|------| -| `My Dashboard` | `my-dashboard` | -| `Admin_Panel` | `admin-panel` | -| `Web App 2.0` | `web-app-20` | - -The slug becomes: -- Directory name: `apps/my-dashboard/` -- Package name in `package.json` - -## Troubleshooting - -### "Directory already exists" - -Choose a different name or remove the existing directory: - -```bash -rm -rf apps/web -npx startupkit add next --name web -``` - -### Missing Dependencies Error - -If the CLI reports missing `@repo/*` packages, accept the prompt to install them, or manually install: - -```bash -# Clone missing package manually -npx degit ian/startupkit/templates/packages/auth packages/auth -pnpm install -``` - -### pnpm Catalog Errors - -If you see `ERR_PNPM_CATALOG` errors: - -```bash -pnpm install --no-frozen-lockfile -``` - -See [Troubleshooting](/docs/cli/troubleshooting) for more solutions. diff --git a/apps/home/content/docs/cli/agents.mdx b/apps/home/content/docs/cli/agents.mdx deleted file mode 100644 index 7636fc08..00000000 --- a/apps/home/content/docs/cli/agents.mdx +++ /dev/null @@ -1,126 +0,0 @@ ---- -title: AGENTS.md -description: AI agent instructions file for guiding AI coding assistants in your project ---- - -StartupKit projects include an `AGENTS.md` file that provides context and guidelines for AI coding assistants like Claude, Cursor, and GitHub Copilot. - -## What is AGENTS.md? - -The `AGENTS.md` file is a comprehensive guide that helps AI assistants understand your project's architecture, conventions, and best practices. When you work with AI tools, they can reference this file to provide more accurate, context-aware suggestions. - -## File Location - -The agent instructions are managed in the `.ruler/` directory: - -``` -my-project/ -├── .ruler/ -│ └── AGENTS.md # Source file for AI instructions -├── AGENTS.md # Generated root file -├── CLAUDE.md # Claude-specific instructions -└── WARP.md # Warp terminal instructions -``` - -## What's Included - -The generated `AGENTS.md` covers: - -- **Project Structure** - Workspace layout, package architecture, file locations -- **TypeScript Standards** - Strict configuration, code style rules, naming conventions -- **Package Management** - pnpm commands, dependency catalogs, workspace protocol -- **Database** - Drizzle ORM schema patterns, migration commands -- **UI Components** - Shadcn usage, styling conventions, component imports -- **Authentication** - @repo/auth configuration, client/server usage, route locations -- **Analytics** - PostHog integration, feature flags, tracking -- **Best Practices** - Server Components, environment variables, testing - -## Updating Agent Instructions - -To customize the AI agent instructions for your project: - -1. **Edit the source file**: - ```bash - # Modify the main instructions - code .ruler/AGENTS.md - ``` - -2. **Regenerate the output files**: - ```bash - pnpm agents.md - ``` - -This regenerates `AGENTS.md`, `CLAUDE.md`, `WARP.md`, and other AI-specific instruction files at the repository root. - -## Using with AI Assistants - -Most AI coding assistants automatically detect and use `AGENTS.md`: - -| Assistant | Detection | -|-----------|-----------| -| Cursor | Automatically reads AGENTS.md | -| Claude (Anthropic) | Reads CLAUDE.md when available | -| GitHub Copilot | Uses AGENTS.md for context | -| Warp Terminal | Uses WARP.md for commands | - -## Customization Tips - -### Add Project-Specific Context - -Include information specific to your domain: - -```markdown -## Business Logic - -- Users can have multiple organizations -- Billing is handled through Stripe -- Feature flags control premium features -``` - -### Document Custom Conventions - -If your team has specific patterns: - -```markdown -## Our Conventions - -- API routes return `{ data, error }` format -- Use `trpc` for type-safe API calls -- All dates are stored in UTC -``` - -### Include External Service Info - -Document integrations: - -```markdown -## External Services - -- **Stripe**: Handles payments, webhooks at `/api/webhooks/stripe` -- **Resend**: Email delivery via @repo/emails -- **PostHog**: Analytics and feature flags -``` - -## Ruler Integration - -StartupKit uses [Ruler](https://github.com/intellectronica/ruler) to manage AI instructions. Ruler concatenates multiple markdown files into unified instruction sets. - -### Adding More Context Files - -Create additional files in `.ruler/`: - -``` -.ruler/ -├── AGENTS.md # Main instructions -├── api-patterns.md # API conventions -├── testing.md # Testing guidelines -└── deployment.md # Deployment docs -``` - -Then regenerate: - -```bash -pnpm agents.md -``` - -All `.ruler/*.md` files are concatenated into the final output. diff --git a/apps/home/content/docs/cli/index.mdx b/apps/home/content/docs/cli/index.mdx deleted file mode 100644 index 85e4d64e..00000000 --- a/apps/home/content/docs/cli/index.mdx +++ /dev/null @@ -1,79 +0,0 @@ ---- -title: Overview -description: The StartupKit command-line interface for scaffolding and managing projects ---- - -The StartupKit CLI helps you create new projects, add apps to your workspace, and keep your setup up to date. - -## Installation - -You can use the CLI directly with `npx` (no installation required): - -```bash -npx startupkit init -``` - -Or install it globally: - -```bash -npm install -g startupkit -startupkit init -``` - -## Commands - -| Command | Description | -|---------|-------------| -| [`init`](/docs/cli/init) | Create a new StartupKit project | -| [`add`](/docs/cli/add) | Add a new app to your workspace | -| [`upgrade`](/docs/cli/upgrade) | Upgrade packages and config to latest | -| `help` | Show help information | - -## Quick Start - -Create a new project: - -```bash -npx startupkit init -``` - -The CLI will prompt you for: -1. **Project name** - The display name for your project -2. **Project key** - URL-safe slug (auto-generated from name) -3. **Directory** - Where to create the project - -After initialization, your project includes: -- Pre-configured monorepo with pnpm workspaces -- Workspace packages (`@repo/analytics`, `@repo/auth`, `@repo/db`, etc.) -- Shared config (`@repo/tsconfig`, `@repo/biome`) -- Turbo for build orchestration -- AI agent instructions (AGENTS.md) - -## What Gets Scaffolded - -``` -my-project/ -├── apps/ # Your applications -├── packages/ -│ ├── analytics/ # @repo/analytics -│ ├── auth/ # @repo/auth -│ ├── db/ # @repo/db (Drizzle ORM) -│ ├── emails/ # @repo/emails -│ ├── ui/ # @repo/ui (Shadcn) -│ └── utils/ # @repo/utils -├── config/ -│ ├── biome/ # @repo/biome -│ ├── nextjs/ # @repo/nextjs -│ └── typescript/ # @repo/tsconfig -├── .ruler/ -│ └── AGENTS.md # AI agent instructions -├── turbo.json -├── pnpm-workspace.yaml -└── package.json -``` - -## Next Steps - -- [Create a new project](/docs/cli/init) - Detailed init command guide -- [Add apps to your workspace](/docs/cli/add) - Add Next.js, Vite, and more -- [Keep your project updated](/docs/cli/upgrade) - Upgrade packages and config diff --git a/apps/home/content/docs/cli/init.mdx b/apps/home/content/docs/cli/init.mdx deleted file mode 100644 index bc861cbe..00000000 --- a/apps/home/content/docs/cli/init.mdx +++ /dev/null @@ -1,164 +0,0 @@ ---- -title: Creating Projects -description: How to create a new StartupKit project with the init command ---- - -The `init` command scaffolds a complete StartupKit monorepo with all packages and configuration. - -## Basic Usage - -```bash -npx startupkit init -``` - -This launches an interactive wizard that prompts for: - -1. **Project name** - Display name (e.g., "My App") -2. **Project key** - URL-safe slug, auto-generated from name (e.g., `my-app`) -3. **Directory** - Where to create the project (defaults to `./`) - -## Command Options - -```bash -npx startupkit init [options] -``` - -| Option | Description | -|--------|-------------| -| `--name ` | Project name (skips prompt) | -| `--dir ` | Directory to create project in (use `.` for current) | -| `--repo ` | Custom template repository | - -## Examples - -### Non-Interactive Mode - -Skip all prompts by providing options: - -```bash -npx startupkit init --name "My App" --dir ./my-app -``` - -### Initialize in Current Directory - -```bash -npx startupkit init --dir . -``` - -### Use Custom Template - -```bash -npx startupkit init --repo myorg/my-template -``` - -## What Happens During Init - -1. **Clone template** - Downloads the StartupKit template from GitHub -2. **Install packages** - Clones all workspace packages to `/packages` -3. **Replace placeholders** - Updates `PROJECT` placeholders with your project key -4. **Install dependencies** - Runs `pnpm install` -5. **Generate AGENTS.md** - Creates AI agent instructions for your codebase - -## Post-Init Steps - -After initialization, you'll want to: - -### 1. Set Up Environment Variables - -```bash -cd my-project -cp .env.example .env.local -``` - -Edit `.env.local` with your configuration: - -```bash -# Database -DATABASE_URL=postgresql://... - -# Auth -GOOGLE_CLIENT_ID=... -GOOGLE_CLIENT_SECRET=... -BETTER_AUTH_SECRET=... - -# Analytics -NEXT_PUBLIC_POSTHOG_KEY=... -``` - -### 2. Set Up Database - -```bash -pnpm db:push # Push schema to database -# or -pnpm db:migrate # Run migrations -``` - -### 3. Start Development - -```bash -pnpm dev -``` - -### 4. Add Your First App - -```bash -npx startupkit add next --name web -``` - -## Project Key Conventions - -The project key is used throughout your codebase: - -- Package names: `@my-project/ui` -- Import paths: `import { Button } from "@my-project/ui"` -- Database schema prefixes -- URL slugs - -Choose a short, memorable key. It's difficult to change later. - -## Template Structure - -The init command clones from `ian/startupkit/templates/repo` by default: - -``` -templates/ -├── repo/ # Base monorepo structure -│ ├── .github/ -│ ├── .ruler/ -│ ├── config/ -│ ├── turbo.json -│ └── package.json -└── packages/ # Workspace packages - ├── analytics/ - ├── auth/ - ├── db/ - ├── emails/ - ├── ui/ - └── utils/ -``` - -## Troubleshooting - -### "Directory already exists" - -The CLI won't overwrite existing directories. Either: -- Choose a different directory -- Remove the existing directory first - -### Network/Clone Errors - -The CLI uses [degit](https://github.com/Rich-Harris/degit) to clone templates. If you see network errors: -- Check your internet connection -- Try again (GitHub rate limits can cause temporary failures) -- Use `--repo` to specify an alternative source - -### pnpm Install Failures - -If dependency installation fails: - -```bash -cd my-project -pnpm install --no-frozen-lockfile -``` - -See [Troubleshooting](/docs/cli/troubleshooting) for more solutions. diff --git a/apps/home/content/docs/cli/meta.json b/apps/home/content/docs/cli/meta.json deleted file mode 100644 index 64c53c4e..00000000 --- a/apps/home/content/docs/cli/meta.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "CLI", - "pages": [ - "index", - "init", - "add", - "upgrade", - "agents", - "troubleshooting" - ], - "defaultOpen": true -} diff --git a/apps/home/content/docs/cli/troubleshooting.mdx b/apps/home/content/docs/cli/troubleshooting.mdx deleted file mode 100644 index 27f7cabc..00000000 --- a/apps/home/content/docs/cli/troubleshooting.mdx +++ /dev/null @@ -1,299 +0,0 @@ ---- -title: Troubleshooting -description: Common CLI issues and how to resolve them ---- - -Solutions to common issues when using the StartupKit CLI. - -## Installation Issues - -### "command not found: startupkit" - -If you installed globally but the command isn't found: - -```bash -# Check if it's installed -npm list -g startupkit - -# Reinstall globally -npm install -g startupkit - -# Or use npx instead -npx startupkit init -``` - -### Permission Denied - -On macOS/Linux, you may need to fix npm permissions: - -```bash -# Option 1: Use a node version manager (recommended) -# Install nvm: https://github.com/nvm-sh/nvm - -# Option 2: Change npm's default directory -mkdir ~/.npm-global -npm config set prefix '~/.npm-global' -export PATH=~/.npm-global/bin:$PATH -``` - -## Init Command Issues - -### "Directory already exists" - -The CLI won't overwrite existing directories: - -```bash -# Remove the directory first -rm -rf my-project - -# Or use a different name -npx startupkit init --name "My Project 2" -``` - -### Clone/Network Errors - -If template cloning fails: - -```bash -# Check internet connection -ping github.com - -# Try again (GitHub rate limits can cause temporary failures) -npx startupkit init - -# Clear degit cache -rm -rf ~/.degit -npx startupkit init -``` - -### "Failed to load template config" - -The CLI couldn't read the template's configuration: - -```bash -# Try with a specific repo -npx startupkit init --repo ian/startupkit - -# Or clone manually -git clone https://github.com/ian/startupkit.git -cp -r startupkit/templates/repo my-project -``` - -## Dependency Installation Issues - -### pnpm Not Found - -```bash -# Install pnpm -npm install -g pnpm - -# Or use corepack (Node.js 16+) -corepack enable -corepack prepare pnpm@latest --activate -``` - -### ERR_PNPM_CATALOG - -Catalog dependency errors usually mean the workspace isn't properly configured: - -```bash -# Try installing without lockfile -pnpm install --no-frozen-lockfile - -# If that fails, clear the cache -rm -rf node_modules -rm pnpm-lock.yaml -pnpm install -``` - -### Peer Dependency Warnings - -Warnings about peer dependencies are usually safe to ignore. To suppress them: - -```bash -# Add to .npmrc in your project root -echo "strict-peer-dependencies=false" >> .npmrc - -# Then reinstall -pnpm install -``` - -Or configure in `package.json`: - -```json -{ - "pnpm": { - "peerDependencyRules": { - "ignoreMissing": ["@types/*"] - } - } -} -``` - -### "Cannot find module '@repo/...'" - -Missing workspace packages: - -```bash -# Reinstall dependencies -pnpm install - -# If still missing, the package might not exist -ls packages/ - -# Add missing package manually -npx degit ian/startupkit/templates/packages/auth packages/auth -pnpm install -``` - -## Add Command Issues - -### "Template type not found" - -Only `next` and `vite` are currently supported: - -```bash -# Supported types -npx startupkit add next -npx startupkit add vite -``` - -### Missing @repo/* Dependencies - -When the CLI detects missing dependencies: - -1. **Accept the prompt** - Let the CLI install them -2. **Or install manually**: - -```bash -# Clone specific package -npx degit ian/startupkit/templates/packages/ui packages/ui -pnpm install -``` - -### App Name Conflicts - -If an app with that name exists: - -```bash -# Check existing apps -ls apps/ - -# Remove if needed -rm -rf apps/web - -# Add again -npx startupkit add next --name web -``` - -## Upgrade Command Issues - -### "No packages to upgrade" - -You may not have any `@startupkit/*` packages installed: - -```bash -# Check what's installed -pnpm list @startupkit/* - -# Install packages first -pnpm add @startupkit/analytics @startupkit/auth @startupkit/seo -``` - -### Config Sync Overwrote My Changes - -Restore from git: - -```bash -git checkout -- turbo.json -git checkout -- biome.jsonc -``` - -Or use `--dry-run` first: - -```bash -npx startupkit upgrade --config --dry-run -``` - -## Build/Runtime Issues - -### TypeScript Errors After Init - -Run type checking to see all errors: - -```bash -pnpm typecheck -``` - -Common fixes: - -```bash -# Regenerate types -pnpm db:generate # For Drizzle types - -# Clear TypeScript cache -rm -rf .next -rm -rf node_modules/.cache -``` - -### "Module not found" in IDE - -Your IDE may need to restart after init: - -1. Close your editor -2. Reopen the project -3. Wait for TypeScript to initialize - -Or reload the window: -- **VS Code**: `Cmd/Ctrl + Shift + P` → "Reload Window" -- **Cursor**: Same as VS Code - -### Turbo Cache Issues - -If builds seem stale: - -```bash -# Clear Turbo cache -rm -rf .turbo -rm -rf node_modules/.cache/turbo - -# Rebuild everything -pnpm build --force -``` - -## Environment Issues - -### Missing Environment Variables - -```bash -# Copy example env file -cp .env.example .env.local - -# Check what's needed -cat .env.example -``` - -### Database Connection Failed - -```bash -# Test your connection string -psql $DATABASE_URL - -# Common fixes: -# - Check DATABASE_URL format: postgresql://user:pass@host:5432/db -# - Ensure database exists -# - Check firewall/network access -``` - -## Getting Help - -If none of these solutions work: - -1. **Check GitHub Issues**: [github.com/ian/startupkit/issues](https://github.com/ian/startupkit/issues) -2. **Search existing issues** before creating a new one -3. **Include details** when reporting: - - Node.js version (`node --version`) - - pnpm version (`pnpm --version`) - - Operating system - - Full error message - - Steps to reproduce diff --git a/apps/home/content/docs/cli/upgrade.mdx b/apps/home/content/docs/cli/upgrade.mdx deleted file mode 100644 index f6b69f72..00000000 --- a/apps/home/content/docs/cli/upgrade.mdx +++ /dev/null @@ -1,176 +0,0 @@ ---- -title: Upgrading -description: How to upgrade StartupKit packages and configuration ---- - -The `upgrade` command keeps your StartupKit project up to date with the latest packages and configuration. - -## Basic Usage - -```bash -npx startupkit upgrade -``` - -This upgrades both: -- **Packages** - All `@startupkit/*` npm packages -- **Config** - Configuration files from the latest template - -## Command Options - -```bash -npx startupkit upgrade [options] -``` - -| Option | Description | -|--------|-------------| -| `--packages` | Only upgrade npm packages | -| `--config` | Only sync config files | -| `--dry-run` | Preview changes without applying | - -## Examples - -### Upgrade Everything - -```bash -npx startupkit upgrade -``` - -### Upgrade Packages Only - -```bash -npx startupkit upgrade --packages -``` - -Upgrades `@startupkit/analytics`, `@startupkit/auth`, `@startupkit/seo`, etc. - -### Sync Config Only - -```bash -npx startupkit upgrade --config -``` - -Syncs configuration files from the latest template: -- `biome.jsonc` -- `turbo.json` -- TypeScript configs - -### Preview Changes - -```bash -npx startupkit upgrade --dry-run -``` - -Shows what would change without modifying files. - -## What Gets Upgraded - -### Package Upgrades (`--packages`) - -Updates all `@startupkit/*` dependencies in your workspace: - -```json -{ - "dependencies": { - "@startupkit/analytics": "^1.2.0", // Updated - "@startupkit/auth": "^1.2.0", // Updated - "@startupkit/seo": "^1.2.0" // Updated - } -} -``` - -### Config Sync (`--config`) - -Syncs configuration files that may have changed upstream: - -| File | Description | -|------|-------------| -| `turbo.json` | Turbo pipeline configuration | -| `biome.jsonc` | Linting and formatting rules | -| `config/typescript/*.json` | TypeScript configurations | -| `.github/workflows/*` | CI/CD workflows | - -## Upgrade Strategy - -The upgrade command follows these principles: - -1. **Non-destructive** - Won't overwrite your custom code -2. **Additive for config** - Adds new options, preserves existing -3. **Semantic versioning** - Follows semver for packages - -## When to Upgrade - -- **After major releases** - New features and improvements -- **Security updates** - Patch vulnerabilities in dependencies -- **Bug fixes** - Resolve issues reported by the community - -Check the [changelog](https://github.com/ian/startupkit/releases) for release notes. - -## Manual Upgrades - -If you prefer manual control: - -### Upgrade npm Packages - -```bash -pnpm update @startupkit/analytics @startupkit/auth @startupkit/seo -``` - -### Sync Specific Config - -```bash -# Get latest turbo.json -npx degit ian/startupkit/templates/repo/turbo.json turbo.json - -# Get latest biome config -npx degit ian/startupkit/templates/repo/biome.jsonc biome.jsonc -``` - -## Handling Conflicts - -If config sync encounters conflicts: - -1. **Backup your changes** - The CLI won't overwrite without asking -2. **Review diffs** - Use `--dry-run` to see what would change -3. **Merge manually** - For complex customizations - -## Post-Upgrade Steps - -After upgrading: - -```bash -# Install any new dependencies -pnpm install - -# Run type checking -pnpm typecheck - -# Run linting -pnpm lint - -# Test your app -pnpm dev -``` - -## Troubleshooting - -### Version Conflicts - -If you see peer dependency warnings: - -```bash -pnpm install --force -``` - -### Breaking Changes - -Check migration guides in release notes for major version bumps. - -### Config Overwritten - -If you accidentally overwrote custom config: - -```bash -git checkout -- turbo.json # Restore from git -``` - -See [Troubleshooting](/docs/cli/troubleshooting) for more solutions. diff --git a/apps/home/content/docs/db/index.mdx b/apps/home/content/docs/db/index.mdx deleted file mode 100644 index 6202d920..00000000 --- a/apps/home/content/docs/db/index.mdx +++ /dev/null @@ -1,166 +0,0 @@ ---- -title: Database Overview -description: Type-safe database access with Drizzle ORM ---- - -The `@repo/db` package provides type-safe database access using [Drizzle ORM](https://orm.drizzle.team/) with PostgreSQL. - -## Architecture - -StartupKit uses a local package architecture. Your project imports from `@repo/db` (a local workspace package), not directly from external packages: - -``` -your-project/ -├── packages/ -│ └── db/ # @repo/db - local package -│ ├── src/ -│ │ ├── index.ts # Database client -│ │ └── schema.ts # Your schema -│ └── drizzle/ # Migrations -└── apps/ - └── web/ # Imports from @repo/db -``` - -This architecture means: -- **Full control** - Modify the database layer to fit your needs -- **Type safety** - Schema types are available throughout your app -- **Single source of truth** - One schema definition for all apps - -## Features - -- **Drizzle ORM** - Type-safe queries with full TypeScript support -- **PostgreSQL** - Production-ready relational database -- **Connection pooling** - Optimized for serverless environments -- **Migrations** - Version-controlled schema changes -- **Drizzle Studio** - Visual database browser - -## Quick Start - -### Import the client - -```tsx title="app/api/users/route.ts" -import { db, users } from "@repo/db" -import { eq } from "drizzle-orm" - -export async function GET() { - const allUsers = await db.select().from(users) - return Response.json(allUsers) -} -``` - -### Query with relations - -```tsx -import { db, users, teams, teamMembers } from "@repo/db" -import { eq } from "drizzle-orm" - -const userWithTeams = await db.query.users.findFirst({ - where: eq(users.id, userId), - with: { - memberships: { - with: { - team: true - } - } - } -}) -``` - -## Environment Setup - -Add your database connection string to `.env.local`: - -```bash title=".env.local" -DATABASE_URL="postgresql://user:password@host:5432/database" -``` - -For local development, you can use: -- [Neon](https://neon.tech) - Serverless Postgres (recommended) -- [Supabase](https://supabase.com) - Postgres with extras -- Local PostgreSQL via Docker - -## Available Scripts - -Run these from the `packages/db` directory: - -| Command | Description | -|---------|-------------| -| `pnpm db:generate` | Generate migrations from schema changes | -| `pnpm db:migrate` | Apply pending migrations | -| `pnpm db:push` | Push schema directly (dev only) | -| `pnpm db:studio` | Open Drizzle Studio | - -## Included Schema - -The starter includes tables for authentication and teams: - -| Table | Purpose | -|-------|---------| -| `users` | User accounts | -| `accounts` | OAuth provider accounts | -| `sessions` | User sessions | -| `teams` | Team/organization records | -| `teamMembers` | Team membership with roles | -| `verifications` | Email verification tokens | - -## Type Exports - -Each table exports inferred types for selects and inserts: - -```ts -import type { User, NewUser, Team, NewTeam } from "@repo/db" -``` - -| Type | Description | -|------|-------------| -| `User` | Select type for users table | -| `NewUser` | Insert type for users table | -| `Team` | Select type for teams table | -| `NewTeam` | Insert type for teams table | -| `TeamMember` | Select type for team members | -| `NewTeamMember` | Insert type for team members | -| `Account` | Select type for OAuth accounts | -| `Session` | Select type for sessions | -| `Verification` | Select type for verifications | - -### Using types in your code - -```ts -import { db, users, type User, type NewUser } from "@repo/db" - -async function createUser(data: NewUser): Promise { - const [user] = await db.insert(users).values(data).returning() - return user -} - -async function getUserById(id: string): Promise { - return db.query.users.findFirst({ - where: (users, { eq }) => eq(users.id, id) - }) -} -``` - -### Extending the schema - -When you add new tables, export types using Drizzle's inference helpers: - -```ts title="packages/db/src/schema.ts" -import { pgTable, text, timestamp } from "drizzle-orm/pg-core" - -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - title: text("title").notNull(), - content: text("content"), - authorId: text("authorId").notNull(), - createdAt: timestamp("createdAt").defaultNow().notNull() -}) - -export type Post = typeof posts.$inferSelect -export type NewPost = typeof posts.$inferInsert -``` - -## Next Steps - -- [Schema definition](/docs/db/schema) - Define your data models -- [Migrations](/docs/db/migrations) - Manage schema changes -- [Queries](/docs/db/queries) - Query patterns and examples diff --git a/apps/home/content/docs/db/meta.json b/apps/home/content/docs/db/meta.json deleted file mode 100644 index a56c64a4..00000000 --- a/apps/home/content/docs/db/meta.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "title": "Database", - "icon": "Database", - "pages": [ - "index", - "schema", - "migrations", - "queries" - ], - "defaultOpen": false -} diff --git a/apps/home/content/docs/db/migrations.mdx b/apps/home/content/docs/db/migrations.mdx deleted file mode 100644 index 6cd1e4f5..00000000 --- a/apps/home/content/docs/db/migrations.mdx +++ /dev/null @@ -1,198 +0,0 @@ ---- -title: Migrations -description: Manage database schema changes with Drizzle Kit ---- - -Drizzle Kit handles database migrations—generating SQL from your schema changes and applying them to your database. - -## Migration Workflow - -1. Modify your schema in `packages/db/src/schema.ts` -2. Generate a migration with `pnpm db:generate` -3. Review the generated SQL -4. Apply with `pnpm db:migrate` - -## Generate Migrations - -After changing your schema, generate a migration: - -```bash title="packages/db" -pnpm db:generate -``` - -This creates a new SQL file in `packages/db/drizzle/`: - -``` -packages/db/ -└── drizzle/ - ├── 0000_initial.sql - ├── 0001_add_posts_table.sql # New migration - └── meta/ - └── _journal.json -``` - -## Apply Migrations - -Run pending migrations: - -```bash title="packages/db" -pnpm db:migrate -``` - -This executes all unapplied migrations in order. - -## Push (Development Only) - -For rapid iteration during development, push schema changes directly: - -```bash title="packages/db" -pnpm db:push -``` - - -`db:push` modifies the database without creating migration files. Use it only for local development—never in production. - - -## Migration Files - -Generated migrations are plain SQL: - -```sql title="drizzle/0001_add_posts_table.sql" -CREATE TABLE IF NOT EXISTS "Post" ( - "id" text PRIMARY KEY NOT NULL, - "title" varchar(255) NOT NULL, - "content" text, - "published" boolean DEFAULT false NOT NULL, - "authorId" text NOT NULL, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp DEFAULT now() NOT NULL -); - -CREATE INDEX IF NOT EXISTS "Post_authorId_idx" ON "Post" ("authorId"); - -ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_User_id_fk" - FOREIGN KEY ("authorId") REFERENCES "User"("id") - ON DELETE cascade ON UPDATE no action; -``` - -You can edit these files before applying if needed. - -## Drizzle Studio - -Explore your database visually: - -```bash title="packages/db" -pnpm db:studio -``` - -Opens a browser-based interface to: -- Browse tables and data -- Execute queries -- Edit records directly -- View schema structure - -## Configuration - -Migration settings are in `packages/db/drizzle.config.ts`: - -```tsx title="packages/db/drizzle.config.ts" -import { defineConfig } from "drizzle-kit" - -export default defineConfig({ - dialect: "postgresql", - schema: "./src/schema.ts", - out: "./drizzle", - dbCredentials: { - url: process.env.DATABASE_URL || "" - } -}) -``` - -## Production Migrations - -For production deployments: - -1. **Generate locally** - Run `pnpm db:generate` in development -2. **Commit migrations** - Add migration files to git -3. **Apply in CI/CD** - Run `pnpm db:migrate` during deployment - -Example deployment script: - -```bash -# Install dependencies -pnpm install - -# Run migrations -cd packages/db && pnpm db:migrate - -# Start the app -pnpm start -``` - -## Rollback Strategy - -Drizzle doesn't auto-generate rollback migrations. For reversibility: - -1. **Create a reverse migration** - Manually write SQL to undo changes -2. **Use a backup** - Restore from a database snapshot -3. **Forward-fix** - Create a new migration to correct issues - -## Common Operations - -### Add a column - -```tsx -// Before -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - title: text("title").notNull() -}) - -// After -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - title: text("title").notNull(), - slug: text("slug") // New column (nullable) -}) -``` - -### Add a table - -```tsx -export const comments = pgTable("Comment", { - id: text("id").primaryKey(), - postId: text("postId") - .notNull() - .references(() => posts.id, { onDelete: "cascade" }), - content: text("content").notNull(), - createdAt: timestamp("createdAt").defaultNow().notNull() -}) -``` - -### Rename a column - -Drizzle detects renames. Just change the column name and generate: - -```tsx -// The TypeScript property name changes -oldName: text("old_name") → newName: text("new_name") -``` - -## Troubleshooting - -### Migration failed - -Check the error message and database state. You may need to: -- Fix the SQL manually -- Roll back partially applied changes -- Sync `_journal.json` with actual database state - -### Schema out of sync - -If your schema and database diverge, use introspection: - -```bash -npx drizzle-kit introspect -``` - -This generates a schema file matching your current database. diff --git a/apps/home/content/docs/db/queries.mdx b/apps/home/content/docs/db/queries.mdx deleted file mode 100644 index 2a855dfb..00000000 --- a/apps/home/content/docs/db/queries.mdx +++ /dev/null @@ -1,381 +0,0 @@ ---- -title: Queries -description: Query patterns with Drizzle ORM ---- - -Drizzle provides two query APIs: the SQL-like query builder and the relational query API. - -## Basic Queries - -### Select all - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" - -const allUsers = await db.select().from(users) -``` - -### Select with columns - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -// ---cut--- -const userEmails = await db - .select({ - id: users.id, - email: users.email - }) - .from(users) -``` - -### Where clause - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -import { eq, and, or, gt, like } from "drizzle-orm" - -// Simple equality -const user = await db - .select() - .from(users) - .where(eq(users.email, "ian@example.com")) - -// Multiple conditions -const activeAdmins = await db - .select() - .from(users) - .where( - and( - eq(users.role, "admin"), - eq(users.emailVerified, true) - ) - ) - -// Pattern matching -const gmailUsers = await db - .select() - .from(users) - .where(like(users.email, "%@gmail.com")) -``` - -### Order and limit - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -import { desc } from "drizzle-orm" -// ---cut--- -const recentUsers = await db - .select() - .from(users) - .orderBy(desc(users.createdAt)) - .limit(10) -``` - -## Relational Queries - -Use the relational API for nested data: - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -import { eq } from "drizzle-orm" -declare const userId: string -// ---cut--- -// Get user with their team memberships -const userWithTeams = await db.query.users.findFirst({ - where: eq(users.id, userId), - with: { - memberships: { - with: { - team: true - } - } - } -}) -``` - -### Find many with relations - -```tsx twoslash -// @noErrors -import { db } from "@repo/db" -// ---cut--- -const teamsWithMembers = await db.query.teams.findMany({ - with: { - members: { - with: { - user: { - columns: { - id: true, - name: true, - email: true - } - } - } - } - } -}) -``` - -## Insert - -### Single insert - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -// ---cut--- -const [newUser] = await db - .insert(users) - .values({ - id: crypto.randomUUID(), - email: "new@example.com", - name: "New User" - }) - .returning() -``` - -### Bulk insert - -```tsx twoslash -// @noErrors -import { db, posts } from "@repo/db" -declare const userId: string -// ---cut--- -await db.insert(posts).values([ - { id: "1", title: "First Post", authorId: userId }, - { id: "2", title: "Second Post", authorId: userId } -]) -``` - -### Insert with conflict handling - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -declare const id: string -declare const email: string -declare const name: string -// ---cut--- -await db - .insert(users) - .values({ id, email, name }) - .onConflictDoUpdate({ - target: users.email, - set: { name, updatedAt: new Date() } - }) -``` - -## Update - -```tsx twoslash -// @noErrors -import { db, users } from "@repo/db" -import { eq } from "drizzle-orm" -declare const userId: string -// ---cut--- -await db - .update(users) - .set({ - name: "Updated Name", - updatedAt: new Date() - }) - .where(eq(users.id, userId)) -``` - -### Update and return - -```tsx twoslash -// @noErrors -import { db, posts } from "@repo/db" -import { eq } from "drizzle-orm" -declare const postId: string -// ---cut--- -const [updated] = await db - .update(posts) - .set({ published: true }) - .where(eq(posts.id, postId)) - .returning() -``` - -## Delete - -```tsx twoslash -// @noErrors -import { db, sessions } from "@repo/db" -import { eq } from "drizzle-orm" -declare const userId: string -// ---cut--- -await db - .delete(sessions) - .where(eq(sessions.userId, userId)) -``` - -### Soft delete pattern - -```tsx twoslash -// @noErrors -import { pgTable, text, timestamp } from "drizzle-orm/pg-core" -import { eq, isNull } from "drizzle-orm" -declare const db: { update: (t: unknown) => { set: (v: unknown) => { where: (c: unknown) => Promise } }; select: () => { from: (t: unknown) => { where: (c: unknown) => Promise } } } -declare const postId: string -// ---cut--- -// Add deletedAt to schema -export const posts = pgTable("Post", { - // ... - deletedAt: timestamp("deletedAt", { mode: "date" }) -}) - -// Soft delete -await db - .update(posts) - .set({ deletedAt: new Date() }) - .where(eq(posts.id, postId)) - -// Query non-deleted only -const activePosts = await db - .select() - .from(posts) - .where(isNull(posts.deletedAt)) -``` - -## Joins - -### Inner join - -```tsx twoslash -// @noErrors -import { db, posts, users } from "@repo/db" -import { eq } from "drizzle-orm" -// ---cut--- -const postsWithAuthors = await db - .select({ - post: posts, - author: users - }) - .from(posts) - .innerJoin(users, eq(posts.authorId, users.id)) -``` - -### Left join - -```tsx twoslash -// @noErrors -import { db, posts, users } from "@repo/db" -import { eq } from "drizzle-orm" -// ---cut--- -const usersWithPosts = await db - .select() - .from(users) - .leftJoin(posts, eq(users.id, posts.authorId)) -``` - -## Aggregations - -```tsx twoslash -// @noErrors -import { db, users, posts } from "@repo/db" -import { count, sum, avg } from "drizzle-orm" - -// Count -const [{ total }] = await db - .select({ total: count() }) - .from(users) - -// Group by -const postsByAuthor = await db - .select({ - authorId: posts.authorId, - postCount: count() - }) - .from(posts) - .groupBy(posts.authorId) -``` - -## Transactions - -```tsx twoslash -// @noErrors -import { db, teams, teamMembers } from "@repo/db" -declare const teamId: string -declare const userId: string -// ---cut--- -await db.transaction(async (tx) => { - const [team] = await tx - .insert(teams) - .values({ id: teamId, name: "New Team", slug: "new-team" }) - .returning() - - await tx.insert(teamMembers).values({ - teamId: team.id, - userId: userId, - role: "owner" - }) -}) -``` - -## Raw SQL - -For complex queries: - -```tsx twoslash -// @noErrors -import { db } from "@repo/db" -import { sql } from "drizzle-orm" - -const result = await db.execute(sql` - SELECT * FROM "User" - WHERE "createdAt" > NOW() - INTERVAL '7 days' -`) -``` - -## Type-Safe Patterns - -### Query wrapper functions - -```tsx twoslash -// @noErrors -import { db, users, type User } from "@repo/db" -import { eq } from "drizzle-orm" - -export async function getUserById(id: string): Promise { - const [user] = await db - .select() - .from(users) - .where(eq(users.id, id)) - return user ?? null -} - -export async function getUserByEmail(email: string): Promise { - const [user] = await db - .select() - .from(users) - .where(eq(users.email, email)) - return user ?? null -} -``` - -### In Server Components - -```tsx twoslash title="app/dashboard/page.tsx" -// @noErrors -import { db, users } from "@repo/db" -import { getSession } from "@repo/auth/server" -import { eq } from "drizzle-orm" - -export default async function DashboardPage() { - const session = await getSession() - if (!session) return null - - const [user] = await db - .select() - .from(users) - .where(eq(users.id, session.user.id)) - - return
Welcome, {user.name}
-} -``` diff --git a/apps/home/content/docs/db/schema.mdx b/apps/home/content/docs/db/schema.mdx deleted file mode 100644 index c4177418..00000000 --- a/apps/home/content/docs/db/schema.mdx +++ /dev/null @@ -1,180 +0,0 @@ ---- -title: Schema Definition -description: Define your database schema with Drizzle ORM ---- - -Your database schema is defined in `packages/db/src/schema.ts`. Drizzle uses TypeScript to define tables, columns, and relations. - -## Defining Tables - -```tsx twoslash title="packages/db/src/schema.ts" -// @noErrors -import { - pgTable, - text, - timestamp, - boolean, - varchar, - index -} from "drizzle-orm/pg-core" -declare const users: { id: unknown } - -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - title: varchar("title", { length: 255 }).notNull(), - content: text("content"), - published: boolean("published").default(false).notNull(), - authorId: text("authorId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow().notNull() -}, (table) => [ - index("Post_authorId_idx").on(table.authorId) -]) -``` - -## Column Types - -Common PostgreSQL column types: - -| Drizzle | PostgreSQL | Example | -|---------|------------|---------| -| `text()` | TEXT | `text("name")` | -| `varchar()` | VARCHAR(n) | `varchar("slug", { length: 64 })` | -| `boolean()` | BOOLEAN | `boolean("active").default(true)` | -| `integer()` | INTEGER | `integer("count")` | -| `timestamp()` | TIMESTAMP | `timestamp("createdAt", { mode: "date" })` | -| `json()` | JSON | `json("metadata")` | -| `jsonb()` | JSONB | `jsonb("settings")` | - -## Enums - -Define PostgreSQL enums for constrained values: - -```tsx twoslash -// @noErrors -import { pgTable, text } from "drizzle-orm/pg-core" -import { pgEnum } from "drizzle-orm/pg-core" - -export const statusEnum = pgEnum("status", ["draft", "published", "archived"]) - -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - status: statusEnum("status").default("draft").notNull() -}) -``` - -## Relations - -Define relations for type-safe joins: - -```tsx twoslash -// @noErrors -import { pgTable, text } from "drizzle-orm/pg-core" -import { relations } from "drizzle-orm" -declare const users: { id: unknown } -declare const comments: unknown - -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - authorId: text("authorId").notNull() -}) - -export const postsRelations = relations(posts, ({ one, many }) => ({ - author: one(users, { - fields: [posts.authorId], - references: [users.id] - }), - comments: many(comments) -})) -``` - -## Indexes - -Add indexes for frequently queried columns: - -```tsx twoslash -// @noErrors -import { pgTable, text, timestamp, index } from "drizzle-orm/pg-core" -// ---cut--- -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - slug: text("slug").notNull(), - authorId: text("authorId").notNull(), - createdAt: timestamp("createdAt").defaultNow().notNull() -}, (table) => [ - index("Post_slug_idx").on(table.slug), - index("Post_authorId_idx").on(table.authorId), - index("Post_createdAt_idx").on(table.createdAt) -]) -``` - -## Unique Constraints - -```tsx twoslash -// @noErrors -import { pgTable, text, uniqueIndex } from "drizzle-orm/pg-core" - -export const posts = pgTable("Post", { - id: text("id").primaryKey(), - slug: text("slug").notNull() -}, (table) => [ - uniqueIndex("Post_slug_key").on(table.slug) -]) -``` - -## Inferred Types - -Export TypeScript types from your schema: - -```tsx twoslash -// @noErrors -import { pgTable, text } from "drizzle-orm/pg-core" -const posts = pgTable("Post", { id: text("id").primaryKey() }) -// ---cut--- -export type Post = typeof posts.$inferSelect -export type NewPost = typeof posts.$inferInsert -``` - -Use these throughout your app: - -```tsx twoslash -// @noErrors -import type { Post, NewPost } from "@repo/db" -import { db, posts } from "@repo/db" - -async function createPost(data: NewPost): Promise { - const [post] = await db.insert(posts).values(data).returning() - return post -} -``` - -## Default Schema - -The starter includes these tables: - -```tsx twoslash -// @noErrors -import { pgTable } from "drizzle-orm/pg-core" -// ---cut--- -// User authentication -export const users = pgTable("User", { /* ... */ }) -export const accounts = pgTable("Account", { /* ... */ }) -export const sessions = pgTable("Session", { /* ... */ }) -export const verifications = pgTable("Verification", { /* ... */ }) - -// Team management -export const teams = pgTable("Team", { /* ... */ }) -export const teamMembers = pgTable("TeamMember", { /* ... */ }) -``` - -All relations are pre-configured for the auth and team systems. - -## Best Practices - -1. **Use descriptive table names** - PascalCase for table names (`"User"`, `"Post"`) -2. **Add timestamps** - Include `createdAt` and `updatedAt` on all tables -3. **Index foreign keys** - Always index columns used in relations -4. **Use references** - Define foreign key constraints with `references()` -5. **Export types** - Create `$inferSelect` and `$inferInsert` types for each table diff --git a/apps/home/content/docs/deployment/cloudflare.mdx b/apps/home/content/docs/deployment/cloudflare.mdx deleted file mode 100644 index 8096b277..00000000 --- a/apps/home/content/docs/deployment/cloudflare.mdx +++ /dev/null @@ -1,112 +0,0 @@ ---- -title: Cloudflare -description: Deploy to Cloudflare Pages ---- - -[Cloudflare Pages](https://pages.cloudflare.com) offers edge-first deployment with excellent global performance. - -## Prerequisites - -- Cloudflare account -- Next.js configured for edge runtime (optional) - -## Setup - -### 1. Connect Repository - -1. Go to [Cloudflare Dashboard](https://dash.cloudflare.com) → Pages -2. Click "Create a project" -3. Connect your GitHub repository - -### 2. Configure Build - -Set the build configuration: - -| Setting | Value | -|---------|-------| -| **Framework preset** | Next.js | -| **Root directory** | `apps/web` | -| **Build command** | `pnpm build` | -| **Build output directory** | `.next` | - -### 3. Environment Variables - -Add environment variables in the Cloudflare dashboard: - -```bash -DATABASE_URL=postgresql://... -BETTER_AUTH_SECRET=your-secret-key -NODE_VERSION=20 -``` - -### 4. Deploy - -Click "Save and Deploy". Cloudflare will: - -1. Clone your repository -2. Install dependencies -3. Build the application -4. Deploy to the edge network - -## @cloudflare/next-on-pages - -For full Next.js support on Cloudflare, use the adapter: - -```bash -pnpm add -D @cloudflare/next-on-pages -``` - -Update your build command: - -```bash -npx @cloudflare/next-on-pages -``` - -## Custom Domain - -1. Go to your Pages project → Custom domains -2. Add your domain -3. Cloudflare automatically provisions SSL - -## Environment Variables - -### Production vs Preview - -Set different values for production and preview environments: - -- Production: Your live database and keys -- Preview: Staging/test values - -### Secrets - -For sensitive values, use encrypted environment variables in the Cloudflare dashboard. - -## Limitations - -Some Next.js features may require configuration: - -- **Image Optimization**: Use Cloudflare Images or external provider -- **ISR**: Supported with caching configuration -- **Middleware**: Runs on the edge - -## Troubleshooting - -### Build Failures - -Check the build logs for: - -- Missing environment variables -- Node.js version issues (set `NODE_VERSION=20`) -- Incompatible Next.js features - -### Edge Runtime - -If using edge runtime, ensure your code is compatible: - -- No Node.js-only APIs -- Database connections use HTTP (Neon, PlanetScale) - -## Next Steps - -- [Cloudflare Pages Docs](https://developers.cloudflare.com/pages) — Full documentation -- [Next.js on Cloudflare](https://developers.cloudflare.com/pages/framework-guides/nextjs/) — Framework guide diff --git a/apps/home/content/docs/deployment/fly.mdx b/apps/home/content/docs/deployment/fly.mdx deleted file mode 100644 index e011312f..00000000 --- a/apps/home/content/docs/deployment/fly.mdx +++ /dev/null @@ -1,216 +0,0 @@ ---- -title: Fly.io -description: Deploy to Fly.io ---- - -[Fly.io](https://fly.io) runs containers close to your users with multi-region deployment. - -## Prerequisites - -Install the Fly CLI: - -```bash -# macOS -brew install flyctl - -# Windows -powershell -Command "iwr https://fly.io/install.ps1 -useb | iex" - -# Linux -curl -L https://fly.io/install.sh | sh -``` - -Login to Fly: - -```bash -fly auth login -``` - -## Setup - -### 1. Initialize App - -From your project root: - -```bash -cd apps/web -fly launch -``` - -This creates a `fly.toml` configuration file. - -### 2. Configure fly.toml - -```toml title="apps/web/fly.toml" -app = "your-app-name" -primary_region = "iad" - -[build] - -[env] - PORT = "3000" - NODE_ENV = "production" - -[http_service] - internal_port = 3000 - force_https = true - auto_stop_machines = true - auto_start_machines = true - min_machines_running = 0 - -[[vm]] - cpu_kind = "shared" - cpus = 1 - memory_mb = 512 -``` - -### 3. Create Dockerfile - -Create a Dockerfile in `apps/web`: - -```dockerfile title="apps/web/Dockerfile" -FROM node:20-alpine AS base - -FROM base AS deps -RUN apk add --no-cache libc6-compat -WORKDIR /app -COPY package.json pnpm-lock.yaml ./ -RUN corepack enable pnpm && pnpm install --frozen-lockfile - -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules -COPY . . -RUN corepack enable pnpm && pnpm build - -FROM base AS runner -WORKDIR /app -ENV NODE_ENV=production -ENV PORT=3000 - -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs - -COPY --from=builder /app/public ./public -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static - -USER nextjs -EXPOSE 3000 -CMD ["node", "server.js"] -``` - -### 4. Set Secrets - -Set environment variables as secrets: - -```bash -fly secrets set DATABASE_URL="postgresql://..." -fly secrets set BETTER_AUTH_SECRET="your-secret-key" -``` - -### 5. Deploy - -```bash -fly deploy -``` - -## Database on Fly.io - -### Fly Postgres - -Create a PostgreSQL cluster: - -```bash -fly postgres create --name myapp-db -``` - -Attach to your app: - -```bash -fly postgres attach myapp-db -``` - -This automatically sets `DATABASE_URL`. - -### External Database - -Use Neon, Supabase, or another provider and set `DATABASE_URL` as a secret. - -## Custom Domain - -```bash -fly certs create yourdomain.com -``` - -Then update your DNS to point to your Fly app. - -## Multi-Region Deployment - -Deploy to multiple regions for lower latency: - -```bash -fly scale count 2 --region iad,lhr -``` - -Or configure in `fly.toml`: - -```toml -primary_region = "iad" - -[[vm]] - cpu_kind = "shared" - cpus = 1 - memory_mb = 512 - -[processes] - app = "node server.js" -``` - -## Scaling - -### Vertical - -```bash -fly scale vm shared-cpu-2x -fly scale memory 1024 -``` - -### Horizontal - -```bash -fly scale count 3 -``` - -## Troubleshooting - -### Build Failures - -Check the build logs: - -```bash -fly logs -``` - -Common issues: - -- Dockerfile syntax errors -- Missing files in build context -- Memory limits during build - -### Connection Issues - -If your app can't connect to the database: - -```bash -# Check secrets are set -fly secrets list - -# Check database is attached -fly postgres list -``` - -## Next Steps - -- [Fly.io Docs](https://fly.io/docs) — Full documentation -- [Fly.io for Next.js](https://fly.io/docs/js/frameworks/nextjs/) — Framework guide diff --git a/apps/home/content/docs/deployment/index.mdx b/apps/home/content/docs/deployment/index.mdx deleted file mode 100644 index fc30e40d..00000000 --- a/apps/home/content/docs/deployment/index.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Overview -description: Deploy your StartupKit application to production ---- - -StartupKit works with any platform that supports Next.js. This guide covers deployment to popular platforms. - -## Supported Platforms - -| Platform | Best For | -|----------|----------| -| [Vercel](/docs/deployment/vercel) | Easiest setup, great for most projects | -| [Cloudflare](/docs/deployment/cloudflare) | Edge-first, global performance | -| [Railway](/docs/deployment/railway) | Simple infrastructure, good DX | -| [Render](/docs/deployment/render) | Straightforward pricing | -| [Fly.io](/docs/deployment/fly) | Multi-region, container-based | - -## Before You Deploy - -### 1. Database - -You'll need a PostgreSQL database. Recommended providers: - -- [Neon](https://neon.tech) — Serverless Postgres, generous free tier -- [Supabase](https://supabase.com) — Postgres with extras -- [Railway](https://railway.app) — Simple managed Postgres -- [PlanetScale](https://planetscale.com) — MySQL (requires adapter) - -### 2. Run Migrations - -Before first deployment, push your schema: - -```bash -pnpm db:push -``` - -Or run migrations: - -```bash -pnpm db:migrate -``` - -### 3. Environment Variables - -Each platform has its own way of setting environment variables. At minimum, you'll need: - -```bash -DATABASE_URL=postgresql://... -BETTER_AUTH_SECRET=your-secret-key -``` - -See [Environment Variables](/docs/getting-started/environment-variables) for the complete list. - -## Production Checklist - -Before going live: - -- [ ] Environment variables are set -- [ ] Database is provisioned and migrated -- [ ] Authentication providers configured with production URLs -- [ ] Analytics is tracking correctly -- [ ] Custom domain configured -- [ ] SSL certificate active - -## Monorepo Configuration - -For all platforms, you'll need to configure the build for your monorepo: - -- **Root Directory**: `apps/web` -- **Build Command**: `pnpm build` (or `turbo run build --filter=web`) -- **Install Command**: `pnpm install` - -## Next Steps - -Choose your deployment platform: - -- [Vercel](/docs/deployment/vercel) — Recommended for most users -- [Cloudflare](/docs/deployment/cloudflare) — Edge deployment -- [Railway](/docs/deployment/railway) — Simple setup -- [Render](/docs/deployment/render) — Predictable pricing -- [Fly.io](/docs/deployment/fly) — Multi-region containers diff --git a/apps/home/content/docs/deployment/railway.mdx b/apps/home/content/docs/deployment/railway.mdx deleted file mode 100644 index a2dfb418..00000000 --- a/apps/home/content/docs/deployment/railway.mdx +++ /dev/null @@ -1,114 +0,0 @@ ---- -title: Railway -description: Deploy to Railway ---- - -[Railway](https://railway.app) offers simple infrastructure with a great developer experience. - -## Setup - -### 1. Create Project - -1. Go to [railway.app/new](https://railway.app/new) -2. Choose "Deploy from GitHub repo" -3. Select your repository - -### 2. Configure Service - -Railway auto-detects Next.js. Configure for monorepo: - -1. Click on your service -2. Go to Settings → Build -3. Set **Root Directory**: `apps/web` -4. Set **Build Command**: `pnpm build` -5. Set **Start Command**: `pnpm start` - -### 3. Environment Variables - -Add environment variables in the Variables tab: - -```bash -DATABASE_URL=postgresql://... -BETTER_AUTH_SECRET=your-secret-key -PORT=3000 -``` - -### 4. Deploy - -Railway automatically deploys on push to your default branch. - -## Database on Railway - -Railway can host your PostgreSQL database: - -### 1. Add PostgreSQL - -1. Click "New" → "Database" → "PostgreSQL" -2. Railway provisions a database - -### 2. Connect - -Use the connection string from the database service: - -1. Click on the PostgreSQL service -2. Go to Variables -3. Copy `DATABASE_URL` -4. Add to your web service variables - -Or use Railway's variable references: - -```bash -DATABASE_URL=${{Postgres.DATABASE_URL}} -``` - -## Custom Domain - -1. Go to your service → Settings → Domains -2. Add your custom domain -3. Update DNS records - -## Networking - -### Private Networking - -Services in the same project can communicate privately: - -```bash -# Reference another service -API_URL=${{api.RAILWAY_PRIVATE_DOMAIN}} -``` - -### Public Access - -Enable public networking for your web service to expose it to the internet. - -## Scaling - -Railway supports horizontal scaling: - -1. Go to Settings → Deploy -2. Adjust replicas -3. Configure health checks - -## Troubleshooting - -### Build Failures - -Check the build logs. Common issues: - -- Missing `PORT` environment variable -- Incorrect root directory -- pnpm not detected (add `packageManager` to package.json) - -### Database Connection - -If database connections fail: - -- Ensure `DATABASE_URL` is set -- Check the database service is running -- Verify connection pooling settings - -## Next Steps - -- [Railway Docs](https://docs.railway.app) — Full documentation -- [Railway Templates](https://railway.app/templates) — Pre-built configurations diff --git a/apps/home/content/docs/deployment/render.mdx b/apps/home/content/docs/deployment/render.mdx deleted file mode 100644 index 1f15ff7f..00000000 --- a/apps/home/content/docs/deployment/render.mdx +++ /dev/null @@ -1,134 +0,0 @@ ---- -title: Render -description: Deploy to Render ---- - -[Render](https://render.com) offers straightforward deployment with predictable pricing. - -## Setup - -### 1. Create Web Service - -1. Go to [dashboard.render.com](https://dashboard.render.com) -2. Click "New" → "Web Service" -3. Connect your GitHub repository - -### 2. Configure Build - -Set the build configuration: - -| Setting | Value | -|---------|-------| -| **Root Directory** | `apps/web` | -| **Build Command** | `pnpm install && pnpm build` | -| **Start Command** | `pnpm start` | -| **Environment** | Node | - -### 3. Environment Variables - -Add environment variables in the Environment tab: - -```bash -DATABASE_URL=postgresql://... -BETTER_AUTH_SECRET=your-secret-key -NODE_VERSION=20 -``` - -### 4. Deploy - -Click "Create Web Service". Render will: - -1. Clone your repository -2. Install dependencies -3. Build and start the application - -## Database on Render - -Render can host your PostgreSQL database: - -### 1. Create Database - -1. Click "New" → "PostgreSQL" -2. Configure size and region -3. Create database - -### 2. Connect - -1. Copy the Internal Database URL -2. Add as `DATABASE_URL` to your web service - -## Custom Domain - -1. Go to your service → Settings → Custom Domains -2. Add your domain -3. Update DNS records -4. Render automatically provisions SSL - -## Blueprint (Infrastructure as Code) - -Define your infrastructure in `render.yaml`: - -```yaml title="render.yaml" -services: - - type: web - name: web - env: node - rootDir: apps/web - buildCommand: pnpm install && pnpm build - startCommand: pnpm start - envVars: - - key: DATABASE_URL - fromDatabase: - name: mydb - property: connectionString - - key: BETTER_AUTH_SECRET - generateValue: true - -databases: - - name: mydb - plan: free -``` - -## Auto-Deploy - -Render automatically deploys on push to your default branch. Configure in Settings: - -- Enable/disable auto-deploy -- Set deploy branch -- Configure deploy hooks - -## Scaling - -### Vertical Scaling - -Upgrade your instance type for more resources: - -- Starter: 512 MB RAM -- Standard: 2 GB RAM -- Pro: 4+ GB RAM - -### Horizontal Scaling - -Available on Team plans: - -1. Go to Settings → Scaling -2. Set number of instances - -## Troubleshooting - -### Build Failures - -Check the build logs. Common issues: - -- Missing Node.js version (set `NODE_VERSION=20`) -- Memory limits (upgrade plan or optimize build) -- Missing dependencies - -### Cold Starts - -Free tier services spin down after inactivity. Upgrade to paid plans for always-on. - -## Next Steps - -- [Render Docs](https://render.com/docs) — Full documentation -- [Render Blueprints](https://render.com/docs/blueprint-spec) — Infrastructure as code diff --git a/apps/home/content/docs/deployment/vercel.mdx b/apps/home/content/docs/deployment/vercel.mdx deleted file mode 100644 index 766e3a7d..00000000 --- a/apps/home/content/docs/deployment/vercel.mdx +++ /dev/null @@ -1,103 +0,0 @@ ---- -title: Vercel -description: Deploy to Vercel ---- - -[Vercel](https://vercel.com) is the recommended deployment platform for StartupKit. It's built by the creators of Next.js and offers the best integration. - -## Setup - -### 1. Connect Repository - -1. Push your code to GitHub -2. Go to [vercel.com/new](https://vercel.com/new) -3. Import your repository -4. Vercel auto-detects Next.js - -### 2. Configure Monorepo - -Set the root directory for your app: - -- **Root Directory**: `apps/web` -- **Framework Preset**: Next.js (auto-detected) -- **Build Command**: Leave default -- **Install Command**: `pnpm install` - -### 3. Environment Variables - -Add your environment variables in the Vercel dashboard: - -```bash -DATABASE_URL=postgresql://... -BETTER_AUTH_SECRET=your-secret-key -``` - -Add any additional variables for auth providers, analytics, etc. - -### 4. Deploy - -Click "Deploy" and Vercel will: - -1. Install dependencies -2. Build the application -3. Deploy to a global edge network - -## Custom Domain - -1. Go to Project Settings → Domains -2. Add your custom domain -3. Update DNS records as instructed - -## Environment Tips - -### Different Values Per Environment - -Use different values for Preview vs Production: - -- Preview deployments can use a staging database -- Production uses your production database - -### Sensitive Variables - -Store secrets in Vercel, never in code: - -- `BETTER_AUTH_SECRET` -- `DATABASE_URL` -- OAuth client secrets - -## Automatic Deployments - -Vercel automatically deploys: - -- **Production**: On push to `main` -- **Preview**: On pull requests - -## Vercel Analytics - -Enable built-in analytics in Project Settings → Analytics for: - -- Web Vitals monitoring -- Page view tracking -- Performance insights - -## Troubleshooting - -### Build Failures - -Check the build logs. Common issues: - -- Missing environment variables -- TypeScript errors -- ESLint errors (run `pnpm lint` locally first) - -### Monorepo Issues - -Ensure the root directory is set correctly: - -- Should be `apps/web`, not the repo root -- Turbo caching may need adjustment - -## Next Steps - -- [Vercel Docs](https://vercel.com/docs) — Full documentation -- [Environment Variables](/docs/getting-started/environment-variables) — Complete variable reference diff --git a/apps/home/content/docs/emails/index.mdx b/apps/home/content/docs/emails/index.mdx deleted file mode 100644 index 0f1d6b6a..00000000 --- a/apps/home/content/docs/emails/index.mdx +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Emails Overview -description: Transactional emails with React Email and Resend ---- - -The `@repo/emails` package provides a type-safe email system using [React Email](https://react.email) for templates and [Resend](https://resend.com) for delivery. - -## Architecture - -Your project imports from `@repo/emails` (a local workspace package): - -``` -your-project/ -├── packages/ -│ └── emails/ # @repo/emails - local package -│ └── src/ -│ ├── index.tsx # sendEmail function -│ ├── lib/ -│ │ └── preview-email.ts -│ └── templates/ # Email templates -│ ├── team-invite.tsx -│ └── verify-code.tsx -└── apps/ - └── web/ # Imports from @repo/emails -``` - -This architecture means: -- **React components as emails** - Build emails with JSX and [Tailwind](https://tailwindcss.com/) -- **Type-safe props** - Full TypeScript support for template data -- **Local preview** - Preview emails in browser during development -- **Easy customization** - Add or modify templates as needed - -## Features - -- **[React Email](https://react.email/)** - Build emails with React components -- **[Tailwind CSS](https://tailwindcss.com/)** - Style emails with familiar utility classes -- **Type-safe templates** - TypeScript props for each email type -- **Dev preview** - Opens emails in browser during development -- **Resend integration** - Production-ready email delivery - -## Usage - -### sendEmail - -The main function for sending emails: - -```ts -import { sendEmail } from "@repo/emails" - -const { data, error } = await sendEmail({ - template: "TeamInvite", // Template name (required) - to: "user@example.com", // Recipient (required) - from: "noreply@myapp.com", // Sender (required) - subject: "You're invited", // Subject line (required) - props: { // Template props (type-safe) - teamName: "Acme Inc", - inviteLink: "https://..." - } -}) -``` - -| Parameter | Type | Description | -|-----------|------|-------------| -| `template` | `string` | Template name matching file in `templates/` | -| `to` | `string` | Recipient email address | -| `from` | `string` | Sender email address | -| `subject` | `string` | Email subject line | -| `props` | `object` | Type-safe props for the template | -| `replyTo` | `string?` | Optional reply-to address | - -### Return value - -```ts -interface SendEmailResult { - data: { id: string } | null // Resend message ID - error: Error | null // Error if failed -} -``` - -### Error handling - -```ts -const { data, error } = await sendEmail({ ... }) - -if (error) { - console.error("Failed to send email:", error.message) - return -} - -console.log("Email sent:", data.id) -``` - -## Quick Start - -### Send an email - -```tsx title="app/api/invite/route.ts" -import { sendEmail } from "@repo/emails" - -export async function POST(request: Request) { - const { email, teamName, inviteLink } = await request.json() - - const { data, error } = await sendEmail({ - template: "TeamInvite", - from: "noreply@myapp.com", - to: email, - subject: `You've been invited to ${teamName}`, - props: { - email, - invitedByName: "Alex", - teamName, - inviteLink - } - }) - - if (error) { - return Response.json({ error: error.message }, { status: 500 }) - } - - return Response.json({ success: true }) -} -``` - -## Environment Setup - -Add your Resend API key to `.env.local`: - -```bash title=".env.local" -RESEND_API_KEY="re_xxxxxxxxxxxxx" -``` - -Get your API key from [resend.com/api-keys](https://resend.com/api-keys). - -## Development Mode - -In development, emails are not sent to Resend. Instead, they open in your browser for preview: - -```bash -📧 Email preview opened in browser: /tmp/email-previews/email-1234567890.html -``` - -This allows you to iterate on templates without sending real emails or using API credits. - -To force sending emails in development: - -```bash title=".env.local" -RESEND_ENABLED=true -``` - -## Preview Templates - -Run the [React Email](https://react.email/) dev server to preview templates with hot reload: - -```bash title="packages/emails" -pnpm dev -``` - -Opens at `http://localhost:5001` with a visual editor for your templates. - -## Included Templates - -| Template | Purpose | -|----------|---------| -| `TeamInvite` | Team invitation email with accept button | -| `VerifyCode` | OTP/verification code email | - -## Next Steps - -- [Email templates](/docs/emails/templates) - Create and customize templates -- [Sending emails](/docs/emails/sending) - API usage and patterns diff --git a/apps/home/content/docs/emails/meta.json b/apps/home/content/docs/emails/meta.json deleted file mode 100644 index 25d586e6..00000000 --- a/apps/home/content/docs/emails/meta.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "title": "Emails", - "icon": "Mail", - "pages": [ - "index", - "templates", - "sending" - ], - "defaultOpen": false -} diff --git a/apps/home/content/docs/emails/sending.mdx b/apps/home/content/docs/emails/sending.mdx deleted file mode 100644 index 228d11fb..00000000 --- a/apps/home/content/docs/emails/sending.mdx +++ /dev/null @@ -1,231 +0,0 @@ ---- -title: Sending Emails -description: Send transactional emails with the sendEmail API ---- - -The `sendEmail` function provides a type-safe way to send emails through Resend. - -## Basic Usage - -```tsx -import { sendEmail } from "@repo/emails" - -const { data, error } = await sendEmail({ - template: "TeamInvite", - from: "MyApp ", - to: "user@example.com", - subject: "You've been invited!", - props: { - email: "user@example.com", - invitedByName: "Alex Johnson", - teamName: "Acme Corp", - inviteLink: "https://myapp.com/invite/abc123" - } -}) -``` - -## Parameters - -| Parameter | Type | Description | -|-----------|------|-------------| -| `template` | string | Template name from the registry | -| `from` | string | Sender address | -| `to` | string | Recipient address | -| `subject` | string | Email subject line | -| `props` | object | Template-specific data (type-safe) | - -## Response - -```tsx -const { data, error } = await sendEmail({ ... }) - -if (error) { - console.error("Failed to send:", error.message) - return -} - -console.log("Email sent:", data?.id) -``` - -## Development Mode - -In development (`NODE_ENV=development`), emails are not sent to Resend. Instead: - -1. The email renders to HTML -2. Opens automatically in your default browser -3. Shows sender, recipient, and subject metadata - -This enables rapid iteration without API calls. - -### Force sending in development - -Set this env var to send real emails in development: - -```bash title=".env.local" -RESEND_ENABLED=true -``` - -## In API Routes - -```tsx title="app/api/auth/verify/route.ts" -import { sendEmail } from "@repo/emails" - -export async function POST(request: Request) { - const { email } = await request.json() - const code = generateVerificationCode() - - const { error } = await sendEmail({ - template: "VerifyCode", - from: "MyApp ", - to: email, - subject: "Your verification code", - props: { - email, - otpCode: code, - expiryTime: "10 minutes" - } - }) - - if (error) { - return Response.json( - { error: "Failed to send verification email" }, - { status: 500 } - ) - } - - return Response.json({ success: true }) -} -``` - -## In Server Actions - -```tsx title="app/actions/invite.ts" -"use server" - -import { sendEmail } from "@repo/emails" - -export async function inviteToTeam(formData: FormData) { - const email = formData.get("email") as string - const teamName = formData.get("teamName") as string - - const inviteToken = generateInviteToken() - const inviteLink = `${process.env.NEXT_PUBLIC_APP_URL}/invite/${inviteToken}` - - const { error } = await sendEmail({ - template: "TeamInvite", - from: "MyApp ", - to: email, - subject: `Join ${teamName} on MyApp`, - props: { - email, - invitedByName: "Current User", - teamName, - inviteLink - } - }) - - if (error) { - return { error: "Failed to send invitation" } - } - - return { success: true } -} -``` - -## Sender Address - -The `from` address should be from a verified domain in Resend. Format options: - -```tsx -// Just email -from: "noreply@myapp.com" - -// Name and email -from: "MyApp " - -// Support address -from: "MyApp Support " -``` - -## Error Handling - -Common errors and handling: - -```tsx -const { error } = await sendEmail({ ... }) - -if (error) { - // Log for debugging - console.error("Email error:", error) - - // Handle specific cases - if (error.message.includes("rate limit")) { - // Retry later - } - - if (error.message.includes("invalid")) { - // Bad email address - } - - // Return user-friendly message - return { error: "Unable to send email. Please try again." } -} -``` - -## Batch Sending - -For multiple recipients, loop and send individually: - -```tsx -const recipients = ["user1@example.com", "user2@example.com"] - -const results = await Promise.all( - recipients.map(email => - sendEmail({ - template: "Newsletter", - from: "MyApp ", - to: email, - subject: "Weekly Update", - props: { email } - }) - ) -) - -const failed = results.filter(r => r.error) -if (failed.length > 0) { - console.error(`${failed.length} emails failed to send`) -} -``` - - -For large batch sends (100+ emails), use Resend's batch API directly or a job queue to avoid rate limits. - - -## Queue Integration - -For production apps, consider using a job queue: - -```tsx title="jobs/send-email.ts" -import { sendEmail } from "@repo/emails" - -export async function sendEmailJob(payload: { - template: string - to: string - subject: string - props: Record -}) { - const { error } = await sendEmail({ - template: payload.template as "TeamInvite" | "VerifyCode", - from: "MyApp ", - to: payload.to, - subject: payload.subject, - props: payload.props - }) - - if (error) { - throw new Error(`Failed to send email: ${error.message}`) - } -} -``` - -This allows retries, rate limiting, and async processing. diff --git a/apps/home/content/docs/emails/templates.mdx b/apps/home/content/docs/emails/templates.mdx deleted file mode 100644 index 4652ecbb..00000000 --- a/apps/home/content/docs/emails/templates.mdx +++ /dev/null @@ -1,238 +0,0 @@ ---- -title: Email Templates -description: Create beautiful emails with React components ---- - -Email templates are React components that render to HTML. Use JSX, TypeScript, and Tailwind CSS to build emails. - -## Template Structure - -Templates live in `packages/emails/src/templates/`: - -```tsx title="packages/emails/src/templates/welcome.tsx" -import { - Body, - Container, - Head, - Heading, - Html, - Preview, - Section, - Tailwind, - Text -} from "@react-email/components" - -export interface WelcomeEmailProps { - name: string -} - -export const WelcomeEmail = ({ name }: WelcomeEmailProps) => { - return ( - - - Welcome to our platform, {name}! - - - -
- - Welcome, {name}! - - - Thanks for signing up. We're excited to have you on board. - -
-
- -
- - ) -} - -WelcomeEmail.PreviewProps = { - name: "John Doe" -} - -export default WelcomeEmail -``` - -## Register the Template - -Add your template to the template registry in `packages/emails/src/index.tsx`: - -```tsx title="packages/emails/src/index.tsx" -import TeamInviteEmail from "./templates/team-invite" -import VerifyCodeEmail from "./templates/verify-code" -import WelcomeEmail from "./templates/welcome" // Add import - -const templates = { - TeamInvite: TeamInviteEmail, - VerifyCode: VerifyCodeEmail, - Welcome: WelcomeEmail // Register template -} as const -``` - -Now you can send it: - -```tsx -await sendEmail({ - template: "Welcome", // Type-safe template name - from: "noreply@myapp.com", - to: user.email, - subject: "Welcome to MyApp!", - props: { name: user.name } // Type-safe props -}) -``` - -## React Email Components - -Common components from `@react-email/components`: - -| Component | Purpose | -|-----------|---------| -| `Html` | Root wrapper | -| `Head` | Document head | -| `Preview` | Email preview text (shown in inbox list) | -| `Body` | Email body | -| `Container` | Centered container | -| `Section` | Content section | -| `Heading` | Headings (h1-h6) | -| `Text` | Paragraphs | -| `Link` | Hyperlinks | -| `Button` | CTA buttons | -| `Img` | Images | -| `Hr` | Horizontal rule | -| `Tailwind` | Enable Tailwind CSS | - -## Styling with Tailwind - -Wrap your email body in `` to use utility classes: - -```tsx - - - - - Hello! - - - - -``` - - -Email clients have limited CSS support. Stick to basic Tailwind utilities—flexbox and grid don't work reliably in emails. - - -## Buttons - -Create clickable buttons with the `Button` component: - -```tsx -import { Button } from "@react-email/components" - - -``` - -## Images - -Reference images with full URLs: - -```tsx -import { Img } from "@react-email/components" - -MyApp Logo -``` - -## Links - -```tsx -import { Link } from "@react-email/components" - - - Need help? - -``` - -## Preview Props - -Define preview props for the dev server: - -```tsx -WelcomeEmail.PreviewProps = { - name: "Jane Smith" -} - -export default WelcomeEmail -``` - -These populate the template when previewing with `pnpm dev`. - -## Template Patterns - -### Header with logo - -```tsx -
- MyApp -
-``` - -### Footer - -```tsx -
- - © {new Date().getFullYear()} MyApp. All rights reserved. - - - 123 Main St, San Francisco, CA 94107 - -
-``` - -### CTA section - -```tsx -
- -
- - - Or copy this link: {ctaLink} - -``` - -## Testing Templates - -Preview templates in the browser: - -```bash title="packages/emails" -pnpm dev -``` - -This starts a local server at `http://localhost:5001` where you can: -- View all templates -- Swap between templates -- See changes with hot reload -- Test with different preview props diff --git a/apps/home/content/docs/getting-started/comparisons.mdx b/apps/home/content/docs/getting-started/comparisons.mdx deleted file mode 100644 index 085f71da..00000000 --- a/apps/home/content/docs/getting-started/comparisons.mdx +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Comparisons -description: How StartupKit compares to other solutions -icon: GitCompareArrows ---- - -See how StartupKit compares to other popular solutions for building applications. - -## vs. Create Next App - -| | StartupKit | Create Next App | -|---|:---:|:---:| -| Authentication | ✅ Built-in | ❌ DIY | -| Analytics | ✅ Built-in | ❌ DIY | -| Database | ✅ Configured | ❌ DIY | -| UI Components | ✅ Shadcn/ui | ❌ DIY | -| Monorepo | ✅ Turborepo | ❌ Single app | -| Email | ✅ React Email | ❌ DIY | -| SEO Utilities | ✅ Built-in | ❌ Basic | - -**Verdict**: Create Next App is great for learning or simple projects. StartupKit is for production applications built with AI assistance. - -## vs. T3 Stack - -| | StartupKit | T3 Stack | -|---|:---:|:---:| -| Authentication | ✅ Better Auth | ✅ NextAuth | -| Analytics | ✅ Built-in | ❌ DIY | -| Database | ✅ Drizzle | ✅ Prisma/Drizzle | -| UI Components | ✅ Shadcn/ui | ❌ DIY | -| Monorepo | ✅ Built-in | ❌ Single app | -| Type Safety | ✅ Full | ✅ Full (tRPC) | -| API Layer | REST/Server Actions | tRPC | - -**Verdict**: T3 is excellent for tRPC-based apps. StartupKit provides more out-of-the-box and is optimized for AI-assisted development. - -## vs. Shipped / Shipfast / Other Boilerplates - -| | StartupKit | Paid Boilerplates | -|---|:---:|:---:| -| Price | Free | $199-499 | -| Open Source | ✅ Yes | ❌ Usually not | -| Updates | ✅ Continuous | ⚠️ Varies | -| Customizable | ✅ Fully | ⚠️ Varies | -| AI-Ready | ✅ Yes | ❌ Usually not | -| Monorepo | ✅ Yes | ❌ Usually not | -| CLI Tools | ✅ Yes | ❌ Usually not | - -**Verdict**: Paid boilerplates can be good, but StartupKit offers more flexibility and is free. - -## vs. Building From Scratch - -| | StartupKit | From Scratch | -|---|:---:|:---:| -| Time to First Feature | Hours | Weeks | -| Auth Setup | 5 minutes | 2-5 days | -| Analytics Setup | 5 minutes | 1-2 days | -| UI Components | Ready | Days to weeks | -| Maintenance | Shared | All on you | -| Best Practices | Built-in | Research required | - -**Verdict**: Building from scratch teaches you a lot, but you'll spend weeks before writing product code. - -## Feature Matrix - -| Feature | StartupKit | Next.js | T3 | Remix | -|---------|:---:|:---:|:---:|:---:| -| React 19 | ✅ | ✅ | ✅ | ✅ | -| Server Components | ✅ | ✅ | ✅ | ⚠️ | -| Authentication | ✅ | ❌ | ✅ | ❌ | -| Analytics | ✅ | ❌ | ❌ | ❌ | -| Database ORM | ✅ | ❌ | ✅ | ❌ | -| UI Library | ✅ | ❌ | ❌ | ❌ | -| Email Templates | ✅ | ❌ | ❌ | ❌ | -| Monorepo | ✅ | ❌ | ❌ | ❌ | -| CLI Tooling | ✅ | ✅ | ✅ | ✅ | -| SEO Utilities | ✅ | ⚠️ | ❌ | ⚠️ | - -## When to Use Something Else - -StartupKit isn't the right choice for every project: - -- **Static sites** → Use Astro or plain Next.js -- **Mobile apps** → Use Expo or React Native directly -- **E-commerce** → Consider Shopify or Medusa -- **Heavy tRPC usage** → T3 Stack may be better -- **Learning React** → Start with Vite or follow the [React tutorial](https://react.dev/learn) - -## Still Have Questions? - -- [Quick Start](/docs/getting-started/quickstart) - Try it yourself -- [GitHub Discussions](https://github.com/ian/startupkit/discussions) - Ask the community diff --git a/apps/home/content/docs/getting-started/environment-variables.mdx b/apps/home/content/docs/getting-started/environment-variables.mdx deleted file mode 100644 index 8cfb0b58..00000000 --- a/apps/home/content/docs/getting-started/environment-variables.mdx +++ /dev/null @@ -1,149 +0,0 @@ ---- -title: Environment Variables -description: Managing environment variables in StartupKit -icon: KeyRound ---- - -This guide covers how environment variables work in StartupKit and the helpers available for managing them. - -## Quick Setup - -After running `npx startupkit init`, create your environment file: - -```bash -cp .env.example .env.local -``` - -The `.env.example` file contains all available variables with documentation. - -## Environment Files - -| File | Purpose | Committed to Git | -|------|---------|------------------| -| `.env.local` | Local development values | No | -| `.env.test` | Test environment values | No | -| `.env.production` | Production overrides | No | - -## Core Variables - -These variables are used across the application: - -| Variable | Required | Description | -|----------|----------|-------------| -| `DATABASE_URL` | Yes | PostgreSQL connection string | -| `BETTER_AUTH_SECRET` | Yes | Secret key for signing auth tokens | - -```bash title=".env.local" -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/myapp -BETTER_AUTH_SECRET=your-secret-key-here -``` - -Generate a secure auth secret: - -```bash -openssl rand -base64 32 -``` - -## Running with Environment Variables - -StartupKit includes helpers for running commands with environment variables loaded: - -### pnpm with-env - -Runs a command with `.env.local` loaded: - -```bash -pnpm with-env -``` - -Examples: - -```bash -# Run database migrations with env vars -pnpm with-env pnpm db:migrate - -# Run a script with env vars -pnpm with-env node scripts/seed.js - -# Start the dev server (already uses with-env internally) -pnpm dev -``` - -### pnpm with-test-env - -Runs a command with `.env.test` loaded: - -```bash -pnpm with-test-env -``` - -Examples: - -```bash -# Run tests with test environment -pnpm with-test-env pnpm test - -# Run a specific test file -pnpm with-test-env vitest run src/lib/auth.test.ts -``` - -This is useful for: -- Using a separate test database -- Mocking external services -- Isolated test configurations - -## Next.js Environment Rules - -Next.js has specific rules for environment variables: - -| Prefix | Availability | Use For | -|--------|--------------|---------| -| `NEXT_PUBLIC_` | Client + Server | Public values (analytics keys) | -| No prefix | Server only | Secrets, credentials | - -```bash -# Exposed to browser - safe for public API keys -NEXT_PUBLIC_POSTHOG_KEY=phc_xxx - -# Server only - never exposed to client -DATABASE_URL=postgresql://... -BETTER_AUTH_SECRET=xxx -``` - -## Package-Specific Variables - -Each package has its own environment variables documented in its respective section: - -- **[Authentication](/docs/auth/configuration)** — OAuth providers, auth settings -- **[Analytics](/docs/analytics)** — PostHog, Google Analytics, OpenPanel -- **[Database](/docs/db)** — Connection strings, pooling -- **[Emails](/docs/emails)** — Resend API keys - -## Deployment - -See the [Deployment guide](/docs/deployment) for platform-specific instructions on setting environment variables in production. - -## Security Best Practices - -1. **Never commit `.env.local`** — It's in `.gitignore` by default -2. **Use different secrets per environment** — Dev, staging, production -3. **Rotate secrets regularly** — Especially after team changes -4. **Limit access** — Only give team members access to secrets they need - -## Troubleshooting - -### Variable Not Available - -1. Check the name matches exactly (case-sensitive) -2. Restart the dev server after changes -3. For client-side, ensure `NEXT_PUBLIC_` prefix -4. In Vercel, check the correct environment is selected - -### "Process is not defined" - -This happens when accessing server-only variables on the client. Keep server-only variables in server-only files: - -```typescript -// lib/db.ts (server-only, never imported by client) -const dbUrl = process.env.DATABASE_URL -``` diff --git a/apps/home/content/docs/getting-started/installation.mdx b/apps/home/content/docs/getting-started/installation.mdx deleted file mode 100644 index e84bb711..00000000 --- a/apps/home/content/docs/getting-started/installation.mdx +++ /dev/null @@ -1,73 +0,0 @@ ---- -title: Manual Installation -description: Get started with StartupKit in minutes -icon: Wrench ---- - -StartupKit uses a CLI to scaffold new projects. The CLI sets up a complete monorepo with all packages pre-configured. - -## Prerequisites - -Before you begin, make sure you have: - -- **Node.js 20+** - [Download](https://nodejs.org) -- **pnpm 9+** - Install with `npm install -g pnpm` - -## Create a New Project - -Run the CLI to create a new project: - -```bash -npx startupkit init -``` - -You'll be prompted to enter a project name: - -``` -? Project name: my-app -``` - -The CLI will: -1. Clone the complete monorepo template -2. Install all dependencies with pnpm -3. Set up workspace packages - -## Start Development - -Navigate to your project and start the development server: - -```bash -cd my-app -pnpm dev -``` - -This starts all apps in the monorepo. Your main app will be available at `http://localhost:3000`. - -## Environment Variables - -Copy the example environment file: - -```bash -cp apps/web/.env.example apps/web/.env.local -``` - -Required environment variables: - -```bash -# Database -DATABASE_URL=postgresql://... - -# Authentication (Better Auth) -BETTER_AUTH_SECRET=your-secret-key -GOOGLE_CLIENT_ID=your-google-client-id -GOOGLE_CLIENT_SECRET=your-google-client-secret - -# Analytics (optional) -NEXT_PUBLIC_POSTHOG_KEY=your-posthog-key -NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com -``` - -## Next Steps - -- [Quickstart](/docs/getting-started/quickstart) - Build your first feature -- [Project Structure](/docs/getting-started/project-structure) - Understand the codebase diff --git a/apps/home/content/docs/getting-started/meta.json b/apps/home/content/docs/getting-started/meta.json deleted file mode 100644 index 6134b484..00000000 --- a/apps/home/content/docs/getting-started/meta.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "title": "Introduction", - "pages": [ - "what-is", - "quickstart", - "project-structure", - "environment-variables", - "comparisons", - "installation" - ], - "defaultOpen": true -} diff --git a/apps/home/content/docs/getting-started/project-structure.mdx b/apps/home/content/docs/getting-started/project-structure.mdx deleted file mode 100644 index 129662ae..00000000 --- a/apps/home/content/docs/getting-started/project-structure.mdx +++ /dev/null @@ -1,229 +0,0 @@ ---- -title: Project Structure -description: Understanding the StartupKit monorepo layout -icon: FolderTree ---- - -StartupKit uses a **monorepo** architecture powered by [Turborepo](https://turbo.build/repo). This allows you to share code between apps while keeping everything in a single repository. - -## Directory Overview - -``` -my-project/ -├── apps/ -│ └── web/ # Main Next.js application -├── packages/ -│ ├── analytics/ # @repo/analytics - Analytics hooks -│ ├── auth/ # @repo/auth - Authentication -│ ├── db/ # @repo/db - Database (Drizzle) -│ ├── ui/ # @repo/ui - UI components -│ └── utils/ # @repo/utils - Shared utilities -├── config/ -│ ├── biome/ # @repo/biome - Linting/formatting config -│ ├── nextjs/ # @repo/nextjs - Shared Next.js config -│ └── typescript/ # @repo/tsconfig - Shared TypeScript configs -├── turbo.json # Turborepo configuration -├── pnpm-workspace.yaml # pnpm workspace configuration -└── package.json # Root package.json -``` - -## Apps - -The `apps/` directory contains deployable applications: - -### `apps/web` - -Your main Next.js application with: - -- **App Router** - Modern Next.js routing -- **Server Components** - RSC by default -- **Authentication** - Better Auth integration -- **Analytics** - PostHog, GA4, etc. -- **Database** - Drizzle ORM with PostgreSQL - -``` -apps/web/ -├── src/ -│ ├── app/ # Next.js App Router -│ │ ├── (auth)/ # Auth routes (sign-in, etc.) -│ │ ├── (main)/ # Main app routes -│ │ └── api/ # API routes -│ ├── components/ # App-specific components -│ └── lib/ # App-specific utilities -├── drizzle/ # Database migrations -└── public/ # Static assets -``` - -## Package Namespaces - -StartupKit uses two package namespaces that serve different purposes: - -### `@startupkit/*` - Published packages - -These are npm packages published by StartupKit that provide **meta-functionality**: - -```tsx -// Core analytics engine with plugin system -import { PostHogPlugin, GoogleAnalyticsPlugin } from "@startupkit/analytics" - -// SEO utilities for Next.js -import { generateMetadata, generateSitemap } from "@startupkit/seo" - -// CLI tools -npx startupkit init -``` - -You typically don't modify these packages—they provide the underlying infrastructure. - -### `@repo/*` - Your local packages - -These are workspace packages in your `packages/` directory that **you own and customize**: - -```tsx -// Your analytics implementation (wraps @startupkit/analytics) -import { useAnalytics, AnalyticsProvider } from "@repo/analytics" - -// Your auth implementation -import { useAuth, AuthProvider } from "@repo/auth" - -// Your database schema and client -import { db, users } from "@repo/db" - -// Your UI components -import { Button, Card } from "@repo/ui" -``` - -The `@repo/*` packages often wrap `@startupkit/*` packages, adding your project-specific configuration and customizations. - -### Why this architecture? - -| Namespace | Purpose | Modifiable | -|-----------|---------|------------| -| `@startupkit/*` | Core infrastructure, plugin systems, utilities | No (npm packages) | -| `@repo/*` | Your implementation, configuration, customizations | Yes (your code) | - -This separation lets you: - -- **Receive updates** to core functionality via npm -- **Customize behavior** in your local packages -- **Add project-specific code** without forking -- **Share code** across multiple apps in your monorepo - -### Example: Analytics - -``` -@startupkit/analytics @repo/analytics -┌─────────────────────┐ ┌─────────────────────────┐ -│ • Plugin system │ │ • Your configured │ -│ • PostHogPlugin │ ──► │ plugins │ -│ • GAPlugin │ │ • Custom event types │ -│ • OpenPanelPlugin │ │ • Server-side track() │ -└─────────────────────┘ └─────────────────────────┘ - (npm package) (your code) -``` - -## Packages - -The `packages/` directory contains shared code: - -### `@repo/analytics` - -Analytics hooks and providers: - -```tsx -import { useAnalytics } from "@repo/analytics" - -const { track, identify } = useAnalytics() -``` - -### `@repo/auth` - -Authentication context and hooks: - -```tsx -import { useAuth } from "@repo/auth" - -const { user, isAuthenticated, logout } = useAuth() -``` - -### `@repo/db` - -Database client and schema: - -```tsx -import { db } from "@repo/db" -import { users } from "@repo/db/schema" -``` - -### `@repo/ui` - -Shadcn-based UI components: - -```tsx -import { Button } from "@repo/ui/button" -import { Card } from "@repo/ui/card" -``` - -### `@repo/utils` - -Shared utilities: - -```tsx -import { cn, getUrl } from "@repo/utils" -``` - -## Configuration - -### `turbo.json` - -Defines the task pipeline: - -```json -{ - "tasks": { - "build": { - "dependsOn": ["^build"], - "outputs": [".next/**", "dist/**"] - }, - "dev": { - "cache": false, - "persistent": true - }, - "lint": {}, - "typecheck": {} - } -} -``` - -### `pnpm-workspace.yaml` - -Defines workspace packages: - -```yaml -packages: - - "apps/*" - - "config/*" - - "packages/*" -``` - -This configuration tells pnpm to look for packages in the `apps`, `config`, and `packages` directories. - -## Adding New Packages - -Use the CLI to add new apps or packages: - -```bash -# Add a new Next.js app -npx startupkit add next --name dashboard - -# Add a new Vite app -npx startupkit add vite --name landing - -# Add a new package -npx startupkit add pkg --name shared-utils -``` - -## Next Steps - -- [Analytics](/docs/analytics) - Explore the analytics package -- [Authentication](/docs/auth) - Set up sign-in flows diff --git a/apps/home/content/docs/getting-started/quickstart.mdx b/apps/home/content/docs/getting-started/quickstart.mdx deleted file mode 100644 index 4fed1896..00000000 --- a/apps/home/content/docs/getting-started/quickstart.mdx +++ /dev/null @@ -1,50 +0,0 @@ ---- -title: Quick Start -description: Get up and running in under 2 minutes -icon: Rocket ---- - -## 1. Create your project - -```bash -npx startupkit@latest init -``` - -Follow the prompts to configure your project name and options, then change into your new project directory: - -```bash -cd -``` - -## 2. Add a Next.js app - -```bash -npx startupkit add next --name web -``` - -This creates a fully configured Next.js app with authentication, analytics, database, and UI components ready to go. - -## 3. Start developing - -```bash -pnpm dev -``` - -Open [http://localhost:3000](http://localhost:3000) — you're ready to build. - -## What's included - -Your new project comes with everything pre-configured: - -- **Authentication** — Sign in with email OTP or OAuth -- **Analytics** — Event tracking with PostHog, Google Analytics -- **Database** — PostgreSQL with Drizzle ORM -- **UI Components** — 40+ Shadcn components -- **TypeScript** — Strict mode, fully typed - -## Next steps - -- [Project Structure](/docs/getting-started/project-structure) — Understand the monorepo layout -- [Environment Variables](/docs/getting-started/environment-variables) — Configure API keys -- [Authentication](/docs/auth) — Customize sign-in flows -- [Analytics](/docs/analytics) — Set up event tracking diff --git a/apps/home/content/docs/getting-started/what-is.mdx b/apps/home/content/docs/getting-started/what-is.mdx deleted file mode 100644 index 7873aa2c..00000000 --- a/apps/home/content/docs/getting-started/what-is.mdx +++ /dev/null @@ -1,85 +0,0 @@ ---- -title: What is StartupKit -description: Learn what StartupKit is and why it was built -icon: CircleHelp ---- - -StartupKit is an **application accelerator** for AI agents. Built as a Turborepo monorepo, it provides a complete foundation with authentication, analytics, database, and UI components pre-configured—all with clear patterns that AI coding tools can understand and extend. - -## The Problem - -Building an application from scratch means spending weeks on boilerplate: - -- Setting up authentication flows -- Configuring analytics providers -- Building and styling UI components -- Optimizing for SEO -- Managing a monorepo structure -- Wiring up databases and ORMs - -By the time you've got the basics working, you've lost momentum and haven't written a single line of product code. - -## The Solution - -StartupKit gives you everything you need out of the box: - -| Feature | What You Get | -|---------|--------------| -| **[Authentication](/docs/auth)** | Email OTP, OAuth providers, session management via [Better Auth](https://www.better-auth.com/) | -| **[Analytics](/docs/analytics)** | Provider-agnostic tracking with [PostHog](https://posthog.com/), [Google Analytics](https://marketingplatform.google.com/about/analytics/), [OpenPanel](https://openpanel.dev/) plugins | -| **[Database](/docs/db)** | [Drizzle ORM](https://orm.drizzle.team/) with [PostgreSQL](https://www.postgresql.org/), ready-to-use schemas | -| **[UI Components](/docs/ui)** | Full [Shadcn/ui](https://ui.shadcn.com/) library pre-configured with [Tailwind](https://tailwindcss.com/) | -| **[Emails](/docs/emails)** | [React Email](https://react.email/) templates with [Resend](https://resend.com/) integration | -| **[SEO](/docs/seo)** | Metadata, sitemaps, robots.txt, structured data utilities | - -## Design Principles - -### 1. No Lock-in - -Every package in StartupKit wraps standard, well-maintained libraries. If you outgrow a component, you can eject and use the underlying library directly: - -- Auth → [Better Auth](https://www.better-auth.com/) -- Analytics → [PostHog](https://posthog.com/) / [Google Analytics](https://marketingplatform.google.com/about/analytics/) / [OpenPanel](https://openpanel.dev/) SDKs -- Database → [Drizzle ORM](https://orm.drizzle.team/) -- UI → [Shadcn/ui](https://ui.shadcn.com/) + [Radix](https://www.radix-ui.com/) - -### 2. AI-Ready - -StartupKit is designed for the AI coding era. Clear patterns, consistent conventions, and well-documented code make it easy for tools like Cursor, GitHub Copilot, and Claude to understand and extend your codebase. - -Every project includes an `AGENTS.md` file that explains your codebase structure to AI tools. - -### 3. Production-Ready - -This isn't a starter template you'll throw away. StartupKit uses the same architecture patterns used by production applications serving millions of users. - -## Who Is This For? - -StartupKit is ideal for: - -- **Solo founders** who want to ship fast without compromising on quality -- **Small teams** who need a shared foundation to build on -- **Developers** who are tired of reinventing the wheel for every project -- **Anyone** building a web application, internal tool, or AI-powered product - -## Tech Stack - -StartupKit is built on proven technologies: - -- **Framework**: [Next.js](https://nextjs.org/) 16 with App Router -- **Language**: [TypeScript](https://www.typescriptlang.org/) (strict mode) -- **React**: [React 19](https://react.dev/) with Server Components -- **Styling**: [Tailwind CSS](https://tailwindcss.com/) v4 -- **Components**: [Shadcn/ui](https://ui.shadcn.com/) + [Radix](https://www.radix-ui.com/) primitives -- **Database**: [PostgreSQL](https://www.postgresql.org/) + [Drizzle ORM](https://orm.drizzle.team/) -- **Auth**: [Better Auth](https://www.better-auth.com/) -- **Monorepo**: [Turborepo](https://turbo.build/) + [pnpm](https://pnpm.io/) workspaces -- **Linting**: [Biome](https://biomejs.dev/) - -## Next Steps - -Ready to get started? - -- [Quick Start](/docs/getting-started/quickstart) - Get running in 5 minutes -- [Manual Installation](/docs/getting-started/installation) - Step-by-step setup guide -- [Comparisons](/docs/getting-started/comparisons) - See how StartupKit compares to alternatives diff --git a/apps/home/content/docs/index.mdx b/apps/home/content/docs/index.mdx deleted file mode 100644 index 07a4bee6..00000000 --- a/apps/home/content/docs/index.mdx +++ /dev/null @@ -1,139 +0,0 @@ ---- -title: Introduction -description: Welcome to StartupKit - The startup stack for the AI era ---- - -StartupKit is an application accelerator for AI agents. It's built on **Next.js**, **React 19**, **TypeScript**, and **Turbo** with authentication, analytics, database, and UI components pre-configured—all designed with clear patterns that AI coding tools can understand and extend. - -## Why StartupKit? - -Building an application from scratch means spending weeks on boilerplate: - -- Setting up authentication -- Configuring analytics -- Building UI components -- Optimizing for SEO -- Managing a monorepo - -StartupKit gives you all of this out of the box, so you can focus on what matters: **building your product**. - -## Quick Start - -Get started with a single command: - -```bash -npx startupkit init -``` - -This will: -1. Clone the complete monorepo template -2. Install all dependencies -3. Set up workspace packages - -Then start the development server: - -```bash -cd my-project -pnpm dev -``` - -## What's Included - -### Packages - -StartupKit includes several packages that work together: - -- **[@repo/analytics](/docs/analytics)** - Provider-agnostic analytics with plugins for PostHog, Google Analytics, and OpenPanel -- **[@repo/auth](/docs/auth)** - Authentication wrappers built on Better Auth -- **[@repo/db](/docs/db)** - Type-safe database access with Drizzle ORM and PostgreSQL -- **[@repo/emails](/docs/emails)** - Transactional emails with React Email and Resend -- **[@repo/seo](/docs/seo)** - SEO utilities including metadata, structured data, and sitemaps -- **[@repo/ui](/docs/ui)** - Pre-built components with Shadcn UI, Radix, and Tailwind - -### Project Structure - -``` -my-project/ -├── apps/ -│ └── web/ # Your Next.js application -├── packages/ -│ ├── analytics/ # @repo/analytics -│ ├── auth/ # @repo/auth -│ ├── db/ # @repo/db (Drizzle ORM) -│ ├── emails/ # @repo/emails (React Email) -│ ├── ui/ # @repo/ui (Shadcn components) -│ └── utils/ # @repo/utils -├── config/ -│ ├── biome/ # @repo/biome -│ ├── nextjs/ # @repo/nextjs -│ └── typescript/ # @repo/tsconfig -├── turbo.json -└── package.json -``` - -## Architecture - -StartupKit uses a **two-layer package architecture** that separates core infrastructure from your customizable code. - -### `@startupkit/*` — Core packages - -Published npm packages that provide the underlying infrastructure: - -```tsx -import { PostHogPlugin, GoogleAnalyticsPlugin } from "@startupkit/analytics" -import { generateMetadata, generateSitemap } from "@startupkit/seo" -``` - -These packages provide: -- Plugin systems and adapters -- Utility functions -- Type definitions -- Provider integrations - -You don't modify these—they're updated via npm. - -### `@repo/*` — Your packages - -Local workspace packages in your `packages/` directory that you own and customize: - -```tsx -import { useAnalytics, AnalyticsProvider } from "@repo/analytics" -import { useAuth, AuthProvider } from "@repo/auth" -import { db, users } from "@repo/db" -import { Button, Card } from "@repo/ui/components/button" -``` - -These packages: -- Wrap `@startupkit/*` packages with your configuration -- Contain your database schema -- Hold your custom components -- Include project-specific logic - -### Why this design? - -| Layer | What it provides | Who maintains it | -|-------|------------------|------------------| -| `@startupkit/*` | Core infrastructure, plugins, utilities | StartupKit (npm updates) | -| `@repo/*` | Your configuration, schema, customizations | You | - -This lets you **receive updates** to core functionality while **keeping full control** over your implementation. When we ship improvements to `@startupkit/analytics`, you get them without losing your custom event types or provider configuration. - -See [Project Structure](/docs/getting-started/project-structure) for more details. - -## AI-Ready - -StartupKit is designed to work seamlessly with AI coding assistants. Clear patterns, consistent conventions, and well-documented code make it easy for tools like Cursor, GitHub Copilot, and Claude to understand and extend your codebase. - -Every project includes an `AGENTS.md` file that explains your codebase structure to AI tools. - -## Next Steps - -- [Installation](/docs/getting-started/installation) - Detailed installation guide -- [Quickstart](/docs/getting-started/quickstart) - Get up and running in 5 minutes -- [CLI](/docs/cli) - Learn the command-line tools -- [Analytics](/docs/analytics) - Set up event tracking -- [Auth](/docs/auth) - Configure authentication -- [Database](/docs/db) - Work with your data -- [Emails](/docs/emails) - Send transactional emails -- [SEO](/docs/seo) - Optimize for search engines -- [UI](/docs/ui) - Use pre-built components diff --git a/apps/home/content/docs/meta.json b/apps/home/content/docs/meta.json deleted file mode 100644 index 059438f3..00000000 --- a/apps/home/content/docs/meta.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "title": "Documentation", - "pages": [ - "index", - "...getting-started", - "---CLI---", - "cli/index", - "cli/init", - "cli/add", - "cli/upgrade", - "cli/agents", - "cli/troubleshooting", - "---Deployment---", - "deployment/index", - "deployment/vercel", - "deployment/cloudflare", - "deployment/railway", - "deployment/render", - "deployment/fly", - "---Packages---", - "analytics", - "auth", - "db", - "emails", - "seo", - "ui" - ] -} diff --git a/apps/home/content/docs/seo/index.mdx b/apps/home/content/docs/seo/index.mdx deleted file mode 100644 index 1cad4b5c..00000000 --- a/apps/home/content/docs/seo/index.mdx +++ /dev/null @@ -1,250 +0,0 @@ ---- -title: SEO Overview -description: Search engine optimization utilities with @startupkit/seo ---- - -`@startupkit/seo` provides utilities for generating Next.js metadata, structured data, sitemaps, and robots.txt files. - -## Features - -- **Metadata generation** - OpenGraph, Twitter Cards, canonical URLs -- **Structured data** - JSON-LD schemas for rich search results -- **Sitemaps** - Dynamic sitemap generation -- **Robots.txt** - Search engine crawling rules - -## Usage - -### Functions - -| Function | Description | -|----------|-------------| -| `generateMetadata()` | Generate Next.js metadata object with OpenGraph, Twitter, canonical URL | -| `generateSitemap()` | Generate sitemap entries for Next.js sitemap.ts | -| `generateRobots()` | Generate robots.txt rules for Next.js robots.ts | -| `generateOrganizationSchema()` | JSON-LD schema for organization | -| `generateWebsiteSchema()` | JSON-LD schema for website | -| `generateBreadcrumbSchema()` | JSON-LD schema for breadcrumbs | -| `generateArticleSchema()` | JSON-LD schema for articles/blog posts | - -### generateMetadata - -For **page-level** metadata (canonical URL, noIndex): - -```ts -import { generateMetadata } from "@startupkit/seo" - -generateMetadata({ - title: "Page Title", - description: "Page description", - baseUrl: "https://myapp.com", - siteName: "My App", - path: "/page", - ogImage: "/og-image.png", - noIndex: false -}) -``` - -For **root layout** metadata (title template, keywords): - -```ts -import { generateMetadata } from "@startupkit/seo" - -generateMetadata({ - title: "My App", - description: "App description", - baseUrl: "https://myapp.com", - siteName: "My App", - titleTemplate: "%s | My App", - keywords: ["saas", "app"], - ogImage: "/og-image.png" -}) -``` - -### generateSitemap - -```ts -import { generateSitemap } from "@startupkit/seo" - -generateSitemap({ - baseUrl: "https://myapp.com", - routes: [ - { path: "/", priority: 1, changeFrequency: "daily" }, - { path: "/about", priority: 0.8 }, - { path: "/blog", priority: 0.9 } - ] -}) -``` - -### generateRobots - -```ts -import { generateRobots } from "@startupkit/seo" - -generateRobots({ - baseUrl: "https://myapp.com", - disallowPaths: ["/api/", "/dashboard/"] -}) -``` - -### Structured Data Schemas - -```ts -import { - generateOrganizationSchema, - generateWebsiteSchema, - generateBreadcrumbSchema, - generateArticleSchema -} from "@startupkit/seo" - -// Organization -generateOrganizationSchema({ - name: "My Company", - url: "https://myapp.com", - logo: "https://myapp.com/logo.png", - sameAs: ["https://twitter.com/mycompany"] -}) - -// Website with search -generateWebsiteSchema({ - name: "My App", - url: "https://myapp.com", - description: "The best app" -}) - -// Breadcrumbs -generateBreadcrumbSchema([ - { name: "Home", url: "https://myapp.com" }, - { name: "Blog", url: "https://myapp.com/blog" } -]) - -// Article -generateArticleSchema({ - headline: "Article Title", - description: "Article description", - datePublished: "2024-01-15", - authorName: "John Doe", - imageUrl: "https://myapp.com/article-image.jpg" -}) -``` - -## Quick Setup - -### Page Metadata - -```tsx twoslash title="app/about/page.tsx" -// @noErrors -import { generateMetadata } from "@startupkit/seo" - -export const metadata = generateMetadata({ - title: "About Us", - description: "Learn about our company and mission", - path: "/about", - baseUrl: "https://myapp.com", - siteName: "My App" -}) - -export default function AboutPage() { - return
About content
-} -``` - -### Root Layout Metadata - -```tsx twoslash title="app/layout.tsx" -// @noErrors -import { generateMetadata } from "@startupkit/seo" - -export const metadata = generateMetadata({ - title: "My App", - description: "The best app for doing things", - baseUrl: "https://myapp.com", - siteName: "My App", - titleTemplate: "%s | My App" -}) -``` - -## What's Generated - -The `generateMetadata` function creates: - -| Field | Description | -|-------|-------------| -| `title` | Page title (with template support) | -| `description` | Meta description | -| `openGraph` | Facebook/LinkedIn sharing | -| `twitter` | Twitter card metadata | -| `alternates.canonical` | Canonical URL | -| `robots` | Index/follow directives | -| `keywords` | SEO keywords (root layout) | - -## Structured Data - -Add JSON-LD for rich search results: - -```tsx twoslash title="app/layout.tsx" -// @noErrors -import type { ReactNode } from "react" -import { - generateOrganizationSchema, - generateWebsiteSchema -} from "@startupkit/seo" - -const orgSchema = generateOrganizationSchema({ - name: "My Company", - url: "https://myapp.com", - logo: "https://myapp.com/logo.png" -}) - -export default function RootLayout({ children }: { children: ReactNode }) { - return ( - - - - - diff --git a/templates/apps/vite/package.json b/templates/apps/vite/package.json deleted file mode 100644 index c7cc1ecf..00000000 --- a/templates/apps/vite/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "PROJECT_VITE", - "private": true, - "version": "0.6.6", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc && vite build", - "preview": "vite preview", - "lint": "biome lint --unsafe", - "lint:fix": "biome lint --write --unsafe", - "typecheck": "tsc --noEmit", - "format": "biome format --write" - }, - "dependencies": { - "@repo/analytics": "workspace:*", - "@repo/auth": "workspace:*", - "@repo/db": "workspace:*", - "@repo/ui": "workspace:*", - "@repo/utils": "workspace:*", - "react": "catalog:react19", - "react-dom": "catalog:react19" - }, - "devDependencies": { - "@repo/biome": "workspace:*", - "@repo/tsconfig": "workspace:*", - "@types/react": "catalog:react19", - "@types/react-dom": "catalog:react19", - "@vitejs/plugin-react": "^4.3.1", - "typescript": "catalog:stack", - "vite": "^5.4.10" - } -} diff --git a/templates/apps/vite/src/App.css b/templates/apps/vite/src/App.css deleted file mode 100644 index e563fc2a..00000000 --- a/templates/apps/vite/src/App.css +++ /dev/null @@ -1,14 +0,0 @@ -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} - -.card { - padding: 2em; -} - -.read-the-docs { - color: #888; -} diff --git a/templates/apps/vite/src/App.tsx b/templates/apps/vite/src/App.tsx deleted file mode 100644 index 28cf7267..00000000 --- a/templates/apps/vite/src/App.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { useState } from "react" -import "./App.css" - -function App() { - const [count, setCount] = useState(0) - - return ( - <> -
-

PROJECT_VITE

-
- -

- Edit src/App.tsx and save to test HMR -

-
-

- Click on the Vite and React logos to learn more -

-
- - ) -} - -export default App diff --git a/templates/apps/vite/src/index.css b/templates/apps/vite/src/index.css deleted file mode 100644 index a951a3e5..00000000 --- a/templates/apps/vite/src/index.css +++ /dev/null @@ -1,69 +0,0 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - color: inherit; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} diff --git a/templates/apps/vite/src/main.tsx b/templates/apps/vite/src/main.tsx deleted file mode 100644 index e89a274c..00000000 --- a/templates/apps/vite/src/main.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from "react" -import ReactDOM from "react-dom/client" -import App from "./App.tsx" -import "./index.css" - -ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( - - - -) diff --git a/templates/apps/vite/startupkit.config.ts b/templates/apps/vite/startupkit.config.ts deleted file mode 100644 index 5e8d9988..00000000 --- a/templates/apps/vite/startupkit.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { StartupKitConfig } from "startupkit/config" - -const config: StartupKitConfig = { - type: "app", - dependencies: { - packages: ["analytics", "auth", "db", "ui", "utils"], - config: ["biome", "typescript"] - } -} - -export default config diff --git a/templates/apps/vite/tsconfig.json b/templates/apps/vite/tsconfig.json deleted file mode 100644 index 004b45f9..00000000 --- a/templates/apps/vite/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "@repo/tsconfig/base.json", - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/templates/apps/vite/tsconfig.node.json b/templates/apps/vite/tsconfig.node.json deleted file mode 100644 index 4f7c8d52..00000000 --- a/templates/apps/vite/tsconfig.node.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "@repo/tsconfig/base.json", - "compilerOptions": { - "composite": true, - "module": "ESNext", - "noEmit": false, - "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/templates/apps/vite/vite.config.ts b/templates/apps/vite/vite.config.ts deleted file mode 100644 index 6d4cd3a1..00000000 --- a/templates/apps/vite/vite.config.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { defineConfig } from "vite" -import react from "@vitejs/plugin-react" - -export default defineConfig({ - plugins: [react()], - server: { - port: 3000 - } -}) diff --git a/templates/packages/analytics/README.md b/templates/packages/analytics/README.md deleted file mode 100644 index 4ff6527e..00000000 --- a/templates/packages/analytics/README.md +++ /dev/null @@ -1,257 +0,0 @@ -# @repo/analytics - -> ✅ **Recommended**: This is the recommended analytics package for product applications. - -Type-safe product analytics with PostHog. Simple, direct integration with both client-side and server-side tracking plus feature flags. - -## Features - -- 🎯 **Type-safe event tracking** - Define events with TypeScript interfaces -- 🖥️ **Server-side tracking** - Accurate metrics for billing and usage -- 🌐 **Client-side tracking** - Track user interactions in real-time -- 🚩 **Feature flags** - Built-in PostHog feature flags -- 📊 **PostHog** - Simple, powerful product analytics -- 🔒 **Privacy-focused** - Full control over your data - -## Why PostHog Only? - -Simple is better. PostHog handles everything you need: - -- ✅ Product analytics (events, funnels, cohorts) -- ✅ Feature flags with targeting -- ✅ Session recordings -- ✅ A/B testing -- ✅ Server-side + client-side SDKs -- ✅ No need for multiple tools (RudderStack, Segment, etc.) - -## Installation - -This package is part of the StartupKit monorepo template. It's already configured in `templates/repo/packages/analytics`. - -## Usage - -### Server-Side Tracking (Recommended) - -We track events at the backend so we can accurately measure and bill for platform usage. - -```typescript -import { track } from "@repo/analytics/server"; - -// Track a sign-in (will handle identifying the user) -await track({ - event: "USER_SIGNED_IN", - user, -}); - -// Track a team switch, will handle grouping user <-> team -await track({ - event: "TEAM_SWITCHED", - userId: user.id, - teamId: team.id, -}); -``` - -### Client-Side Tracking - -```typescript -"use client"; - -import { useAnalytics } from "@repo/analytics"; - -export function MyComponent() { - const { track } = useAnalytics(); - - const handleClick = () => { - track("BUTTON_CLICKED", { buttonId: "cta" }); - }; - - return ; -} -``` - -### Setup Analytics Provider - -```typescript -import { AnalyticsProvider } from "@repo/analytics"; -import { getFeatureFlags } from "@repo/analytics/server"; - -export async function RootLayout({ children }) { - const flags = await getFeatureFlags(userId); - - return ( - - {children} - - ); -} -``` - -### Feature Flags - -```typescript -import { useFlag } from "@repo/analytics"; - -export function MyFeature() { - const isEnabled = useFlag("new-feature"); - - if (!isEnabled) return null; - - return
New Feature!
; -} -``` - -Server-side: -```typescript -import { getFeatureFlags } from "@repo/analytics/server"; - -const flags = await getFeatureFlags(userId); -if (flags["new-feature"]) { - // Show new feature -} -``` - -## Extending Events - -To add a new event, add it to the `types.ts` file: - -```typescript -// src/types.ts -export type MyCustomEvent = { - event: "MY_CUSTOM_EVENT"; - message: string; - userId: string; -}; - -// Update AnalyticsEvent union -export type AnalyticsEvent = - | AuthEvent - | TrackableEvent - | MyCustomEvent; // Add your event -``` - -Then track it: - -```typescript -await track({ - event: "MY_CUSTOM_EVENT", - message: "Hello from my custom event", - userId: user.id, -}); -``` - -TypeScript will enforce that you provide all required properties! - -## Configuration - -Set these environment variables: - -```bash -# PostHog (for everything) -POSTHOG_API_KEY=your_api_key -POSTHOG_HOST=https://app.posthog.com # or your self-hosted instance -``` - -## API Reference - -### Server-Side - -#### `track(event)` - -Track one or more analytics events: - -```typescript -import { track } from "@repo/analytics/server"; - -await track({ - event: "USER_SIGNED_UP", - user: { id: "123", email: "user@example.com" } -}); - -// Track multiple events -await track([event1, event2, event3]); -``` - -#### `getFeatureFlags(userId)` - -Get feature flags for a user: - -```typescript -import { getFeatureFlags } from "@repo/analytics/server"; - -const flags = await getFeatureFlags(userId); -// { "new-feature": true, "beta-feature": false } -``` - -### Client-Side - -#### `useAnalytics()` - -Hook for analytics operations: - -```typescript -const { identify, track, reset, flags } = useAnalytics(); - -// Identify a user -identify(userId, { email: "user@example.com" }); - -// Track an event -track("BUTTON_CLICKED", { buttonId: "cta" }); - -// Reset on logout -reset(); - -// Access flags -const isEnabled = flags["new-feature"]; -``` - -#### `useFlag(flagName)` - -Hook for a specific feature flag: - -```typescript -const isEnabled = useFlag("new-feature"); -``` - -## Architecture - -Simple, direct PostHog integration: - -- **Client-side**: `posthog-js/react` for browser tracking -- **Server-side**: `posthog-node` for backend events -- **Feature flags**: Built-in PostHog API - -One tool, zero complexity. - -## Best Practices - -1. **Track server-side when possible** - More accurate and can't be blocked -2. **Define all events in types.ts** - TypeScript will catch errors -3. **Use meaningful event names** - ALL_CAPS_SNAKE_CASE convention -4. **Include relevant context** - userId, teamId, etc. -5. **Don't track PII without consent** - Be GDPR compliant - -## Why Not Multiple Providers? - -You might be tempted to send data to multiple analytics tools. **Don't.** - -**Problems with multiple providers:** -- ❌ More dependencies to manage -- ❌ More bills to pay -- ❌ Data inconsistencies between platforms -- ❌ Complex setup and maintenance - -**PostHog does it all:** -- ✅ One tool, one bill -- ✅ Consistent data -- ✅ Simple setup -- ✅ Can export to data warehouses if needed - -## Support - -For issues or questions: -- GitHub: https://github.com/ian/startupkit -- Docs: https://startupkit.com/docs - -## License - -ISC diff --git a/templates/packages/analytics/biome.jsonc b/templates/packages/analytics/biome.jsonc deleted file mode 100644 index 8fa79947..00000000 --- a/templates/packages/analytics/biome.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "extends": ["@repo/biome"] -} diff --git a/templates/packages/analytics/package.json b/templates/packages/analytics/package.json deleted file mode 100644 index 629abf0e..00000000 --- a/templates/packages/analytics/package.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "@repo/analytics", - "private": true, - "type": "module", - "exports": { - ".": { - "types": "./src/index.ts", - "default": "./src/index.ts" - }, - "./server": { - "types": "./src/server.ts", - "default": "./src/server.ts" - } - }, - "scripts": { - "lint": "biome lint --unsafe", - "lint:fix": "biome lint --write --unsafe", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@openpanel/sdk": "1.0.0", - "@repo/db": "workspace:*", - "@repo/utils": "workspace:*", - "@startupkit/analytics": "^0.6.6", - "posthog-js": "1.235.5", - "posthog-node": "4.2.1" - }, - "peerDependencies": { - "next": "catalog:stack", - "react": "catalog:react19", - "react-dom": "catalog:react19" - }, - "devDependencies": { - "@repo/biome": "workspace:*", - "@repo/tsconfig": "workspace:*", - "@types/node": "catalog:stack", - "@types/react": "catalog:react19", - "@types/react-dom": "catalog:react19", - "next": "catalog:stack", - "react": "catalog:react19", - "react-dom": "catalog:react19" - }, - "version": "0.6.6" -} diff --git a/templates/packages/analytics/src/components/analytics-provider.tsx b/templates/packages/analytics/src/components/analytics-provider.tsx deleted file mode 100644 index 4bef21b5..00000000 --- a/templates/packages/analytics/src/components/analytics-provider.tsx +++ /dev/null @@ -1,169 +0,0 @@ -/** - * Analytics Provider - Type-safe analytics integration for your application - * - * This module provides a fully typed analytics implementation using @startupkit/analytics - * with support for multiple analytics providers (Google Analytics, PostHog, OpenPanel). - * - * Features: - * - Type-safe event tracking using discriminated unions - * - Multi-provider support (events sent to all providers simultaneously) - * - Feature flag integration - * - Automatic page view tracking - * - * @example - * ```tsx - * // In your root layout - * import { AnalyticsProvider } from '@repo/analytics'; - * - * - * - * - * ``` - * - * @example - * ```tsx - * // In your components - * import { useAnalytics } from '@repo/analytics'; - * - * function MyComponent() { - * const { track, flags } = useAnalytics(); - * - * track({ - * event: "USER_SIGNED_IN", - * user: { id: "123", email: "user@example.com" } - * }); - * } - * ``` - */ -'use client'; - -import type { - AnalyticsContextType, - AnalyticsPlugin, -} from '@startupkit/analytics'; -import { - AhrefsPlugin, - DatafastPlugin, - GoogleAnalyticsPlugin, - GoogleTagManagerPlugin, - AnalyticsProvider as StartupKitAnalyticsProvider, - useAnalytics as useBaseAnalytics, -} from '@startupkit/analytics'; -import { OpenPanelPlugin } from '@startupkit/analytics/openpanel'; -import { PostHogPlugin } from '@startupkit/analytics/posthog'; -import type { ReactNode } from 'react'; -import type { Flags } from '../types'; - -/** - * Analytics plugins configuration - * - * Configures all analytics providers to receive events simultaneously. - * Add or remove providers by modifying this array. - * - * Providers are initialized with environment variables: - * - NEXT_PUBLIC_AHREFS_API_KEY - Ahrefs API key - * - NEXT_PUBLIC_DATAFAST_WEBSITE_ID - Datafast website ID (dfid_*) - * - NEXT_PUBLIC_DATAFAST_DOMAIN - Datafast domain (optional) - * - NEXT_PUBLIC_GOOGLE_ANALYTICS_ID - Google Analytics measurement ID - * - NEXT_PUBLIC_GTM_CONTAINER_ID - Google Tag Manager container ID (GTM-XXXXXXX) - * - NEXT_PUBLIC_OPENPANEL_CLIENT_ID - OpenPanel client ID - * - NEXT_PUBLIC_POSTHOG_KEY - PostHog API key - * - POSTHOG_HOST (optional) - Custom PostHog host URL (defaults to https://app.posthog.com) - */ -const plugins: AnalyticsPlugin[] = [ - AhrefsPlugin({ - key: process.env.NEXT_PUBLIC_AHREFS_API_KEY as string, - }), - DatafastPlugin({ - websiteId: process.env.NEXT_PUBLIC_DATAFAST_WEBSITE_ID as string, - domain: process.env.NEXT_PUBLIC_DATAFAST_DOMAIN, - }), - GoogleAnalyticsPlugin({ - measurementId: process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID as string, - }), - GoogleTagManagerPlugin({ - containerId: process.env.NEXT_PUBLIC_GTM_CONTAINER_ID as string, - }), - OpenPanelPlugin({ - clientId: process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID as string, - }), - PostHogPlugin({ - apiKey: process.env.NEXT_PUBLIC_POSTHOG_KEY as string, - }), -]; - -/** - * Type-safe analytics hook - * - * Returns an analytics context with properly typed events and feature flags. - * All events are validated at compile-time using TypeScript discriminated unions. - * - * @returns Analytics context with track, identify, page, reset methods and feature flags - * - * @example - * ```tsx - * const { track, identify, flags } = useAnalytics(); - * - * // Type-safe event tracking - * track({ - * event: "TEAM_CREATED", - * teamId: "team_123" - * }); - * - * // Identify users - * identify("user_123", { - * email: "user@example.com", - * name: "John Doe" - * }); - * - * // Check feature flags - * if (flags["secret-flag"]) { - * // Show secret feature - * } - * ``` - */ -export function useAnalytics(): AnalyticsContextType { - return useBaseAnalytics(); -} -interface AnalyticsProviderProps { - children: ReactNode; - flags: Flags; -} - -/** - * Analytics Provider component - * - * Wraps your application to provide analytics tracking and feature flags. - * Should be placed near the root of your component tree. - * - * @param children - React children to wrap - * @param flags - Feature flags object (from PostHog or other feature flag provider) - * - * @example - * ```tsx - * // In your root layout - * import { AnalyticsProvider } from '@repo/analytics'; - * import { getFeatureFlags } from '@repo/analytics/server'; - * - * export default async function RootLayout({ children }) { - * const flags = await getFeatureFlags(); - * - * return ( - * - * - * - * {children} - * - * - * - * ); - * } - * ``` - */ -export function AnalyticsProvider({ children, flags }: AnalyticsProviderProps) { - return ( - - {children} - - ); -} diff --git a/templates/packages/analytics/src/hooks/use-flag.ts b/templates/packages/analytics/src/hooks/use-flag.ts deleted file mode 100644 index 76ef7b53..00000000 --- a/templates/packages/analytics/src/hooks/use-flag.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { useFlag as useStartupKitFlag } from "@startupkit/analytics" -import type { FlagName, Flags } from "../vendor/posthog" - -export function useFlag(name: T) { - return useStartupKitFlag(name) -} diff --git a/templates/packages/analytics/src/index.ts b/templates/packages/analytics/src/index.ts deleted file mode 100644 index 41cabedc..00000000 --- a/templates/packages/analytics/src/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -// @repo/analytics - Your customizable analytics implementation -// This code is generated and YOU OWN IT - customize as needed - -export * from "./components/analytics-provider"; -export * from "./hooks/use-flag"; -export * from "./types"; - diff --git a/templates/packages/analytics/src/server.ts b/templates/packages/analytics/src/server.ts deleted file mode 100644 index 8e0f3a1f..00000000 --- a/templates/packages/analytics/src/server.ts +++ /dev/null @@ -1,45 +0,0 @@ -// @repo/analytics/server - Server-side analytics (PostHog only) -// Imports directly from posthog-node - -import { PostHog } from "posthog-node" -import type { TrackableEvent } from "./types" -export { getFeatureFlags } from "./vendor/posthog" - -export type { Flags } from "./types" - -function client() { - const apiKey = process.env.POSTHOG_API_KEY - - if (!apiKey) { - return null - } - - return new PostHog(apiKey, { - host: process.env.POSTHOG_HOST || "https://app.posthog.com" - }) -} - -/** - * Tracks one or more analytics events server-side using PostHog. - * - * @param eventData - An AnalyticsEvent object or an array of AnalyticsEvent objects to track. - */ -export async function track(eventData: TrackableEvent | TrackableEvent[]) { - const posthog = client() - - if (!posthog) { - return - } - - const events = Array.isArray(eventData) ? eventData : [eventData] - - events.forEach(({ event, userId, anonymousId, ...properties }) => { - posthog.capture({ - distinctId: userId ?? anonymousId, - event, - properties - }) - }) - - await posthog.flush() -} diff --git a/templates/packages/analytics/src/types.ts b/templates/packages/analytics/src/types.ts deleted file mode 100644 index c7db116f..00000000 --- a/templates/packages/analytics/src/types.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { User } from "@repo/db" - -export type AnalyticsEvent = AuthEvent | TrackableEvent - -export type AuthUser = Pick -export type AuthEvent = UserSignedIn | UserSignedUp | UserSignedOut - -export type IdentityOptions = { userId: string; anonymousId?: string } - -export type TrackableEvent = IdentityOptions & - ( - | TeamCreated - | TeamJoined - | TeamSwitched - | { event: "USER_SIGNED_IN"; user: AuthUser } - | { event: "USER_SIGNED_UP"; user: AuthUser } - | { event: "USER_SIGNED_OUT"; user: AuthUser } - ) - -// #################################### -// Auth - -export type UserSignedIn = { - event: "USER_SIGNED_IN" - user: AuthUser -} - -export type UserSignedUp = { - event: "USER_SIGNED_UP" - user: AuthUser -} - -export type UserSignedOut = { - event: "USER_SIGNED_OUT" - user: AuthUser -} - -// #################################### -// Teams - -export type TeamCreated = { - event: "TEAM_CREATED" - teamId: string -} - -export type TeamJoined = { - event: "TEAM_JOINED" - teamId: string -} - -export type TeamSwitched = { - event: "TEAM_SWITCHED" - teamId: string -} - -// #################################### -// Feature Flags (from PostHog) - -export type Flags = { - "secret-flag"?: true | false | undefined -} - -export type FlagName = keyof Flags - -// #################################### -// TODO - Add more custom tracking events diff --git a/templates/packages/analytics/src/vendor/posthog.ts b/templates/packages/analytics/src/vendor/posthog.ts deleted file mode 100644 index 39390cbe..00000000 --- a/templates/packages/analytics/src/vendor/posthog.ts +++ /dev/null @@ -1,51 +0,0 @@ -const POSTHOG_HOST = process.env.POSTHOG_HOST -const POSTHOG_API_KEY = process.env.POSTHOG_API_KEY - -export const DISTINCT_ID_COOKIE_NAME = "distinct_id" - -// Match these to the flags in PostHog -export type Flags = { - "secret-flag"?: true | false | undefined -} - -// export type FlagValue = boolean | string | undefined; -export type FlagName = keyof Flags - -/** - * Fetches feature flags for the specified user from the PostHog service. - * - * Returns an object mapping feature flag names to their values for the given user. - * If PostHog is not configured (missing API key or host), returns an empty object. - * - * @param distinctUserId - The unique identifier for the user whose feature flags are being retrieved. - * @returns An object containing feature flag values for the user. Boolean values indicate A/B test flags; string values indicate multivariate flags. - * - * @throws {Error} If the request to the PostHog service fails. - */ -export async function getFeatureFlags(distinctUserId: string): Promise { - if (!POSTHOG_HOST || !POSTHOG_API_KEY) { - return {} - } - - if (!distinctUserId) { - return {} as Flags - } - - const res = await fetch(`${POSTHOG_HOST}/decide?v=2`, { - method: "POST", - body: JSON.stringify({ - api_key: POSTHOG_API_KEY, - distinct_id: distinctUserId - }) - }) - - if (!res.ok) { - throw new Error( - `Fetch request to retrieve flag status failed with: (${res.status}) ${res.statusText}` - ) - } - - const data = (await res.json()) as { featureFlags: Flags } - - return data.featureFlags as Flags -} diff --git a/templates/packages/analytics/startupkit.config.ts b/templates/packages/analytics/startupkit.config.ts deleted file mode 100644 index 4de26f09..00000000 --- a/templates/packages/analytics/startupkit.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { StartupKitConfig } from 'startupkit/config'; - -const config: StartupKitConfig = { - type: 'package', - dependencies: { - config: ['biome', 'typescript'], - }, -}; - -export default config; diff --git a/templates/packages/analytics/tsconfig.json b/templates/packages/analytics/tsconfig.json deleted file mode 100644 index af751cd9..00000000 --- a/templates/packages/analytics/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@repo/tsconfig/react-library.json", - "include": ["src/**/*.ts", "src/**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/templates/packages/auth/README.md b/templates/packages/auth/README.md deleted file mode 100644 index c677f393..00000000 --- a/templates/packages/auth/README.md +++ /dev/null @@ -1,314 +0,0 @@ -# @repo/auth - -Authentication package built on [better-auth](https://better-auth.com) with dual client/server exports for Next.js applications. - -## Installation - -This package is part of the monorepo workspace and is already configured with Drizzle ORM adapter, PostgreSQL, and Google OAuth support. - -The package exports two entry points: -- `@repo/auth` - Client-side authentication utilities (AuthProvider, useAuth, authClient) -- `@repo/auth/server` - Server-side authentication helpers (withAuth, handler, auth) - -## Client-Side Usage - -### Setup AuthProvider - -Wrap your application with the `AuthProvider` component to enable authentication context throughout your app: - -```tsx -import { AuthProvider } from "@repo/auth" - -export function Providers({ children, user }) { - return ( - - {children} - - ) -} -``` - -### Using the useAuth Hook - -Access authentication state and methods in your components: - -```tsx -import { useAuth } from "@repo/auth" - -export function Header() { - const { isAuthenticated, user, logout } = useAuth() - - return ( -
- {isAuthenticated ? ( -
- Welcome, {user?.name} - -
- ) : ( - Sign In - )} -
- ) -} -``` - -### Authentication Methods - -The `useAuth` hook provides the following methods: - -```tsx -const { - isAuthenticated, - isLoading, - user, - sendAuthCode, - verifyAuthCode, - googleAuth, - logout -} = useAuth() -``` - -#### Email OTP Flow - -```tsx -import { useAuth } from "@repo/auth" - -export function SignIn() { - const { sendAuthCode, verifyAuthCode } = useAuth() - const [email, setEmail] = useState("") - const [code, setCode] = useState("") - const [step, setStep] = useState<"email" | "code">("email") - - const handleSendCode = async () => { - await sendAuthCode(email) - setStep("code") - } - - const handleVerifyCode = async () => { - await verifyAuthCode(email, code) - } - - return step === "email" ? ( -
- setEmail(e.target.value)} - placeholder="Enter your email" - /> - -
- ) : ( -
- setCode(e.target.value)} - placeholder="Enter code" - /> - -
- ) -} -``` - -#### Google OAuth - -```tsx -import { useAuth } from "@repo/auth" - -export function SocialSignIn() { - const { googleAuth } = useAuth() - - return ( - - ) -} -``` - -## Server-Side Usage - -### Using withAuth Helper - -Access the authenticated user in Server Components: - -```tsx -import { withAuth } from "@repo/auth/server" - -export default async function RootLayout({ children }) { - const { user, session } = await withAuth() - - return ( - - - - {children} - - - - ) -} -``` - -The `withAuth` helper returns: -- `user` - The authenticated user or null -- `session` - The session object or null -- `flags` - Optional feature flags (when `opts.flags: true`) - -```tsx -const { user, session, flags } = await withAuth({ flags: true }) -``` - -### API Route Handler - -Export the auth handler in your Next.js API route: - -```ts -import { handler } from "@repo/auth/server" - -export const { GET, POST } = handler() -``` - -### Accessing Core Auth Instance - -For advanced use cases, access the core better-auth instance: - -```ts -import { auth } from "@repo/auth/server" -``` - -## Configuration - -### Environment Variables - -Required environment variables for authentication: - -```bash -GOOGLE_CLIENT_ID=your_google_client_id -GOOGLE_CLIENT_SECRET=your_google_client_secret -``` - -### Base Path - -All authentication routes are served at `/auth`: -- `/auth/sign-in/google` - Google OAuth -- `/auth/sign-in/email-otp` - Email OTP sign in -- `/auth/sign-out` - Sign out - -### Integration - -The auth package integrates with other workspace packages: -- `@repo/emails` - Sends OTP verification emails -- `@repo/analytics` - Tracks user authentication events -- `@repo/db` - Drizzle ORM database adapter for user and session storage - -## Authentication Methods - -### Google OAuth - -Configured with automatic profile mapping to extract: -- First name and last name from Google profile -- Profile picture -- Email address - -### Email OTP Verification - -One-time password authentication via email: -- OTP codes expire after 10 minutes -- Verification emails sent via `@repo/emails` package -- Supports both sign-up and sign-in flows - -### Session Management - -Sessions are configured with: -- **Expiration**: 7 days (604,800 seconds) -- **Update Age**: 24 hours (86,400 seconds) - -Sessions automatically refresh their expiration time every 24 hours when the user is active. - -## API Reference - -### Types - -```ts -import type { User, Session } from "@repo/auth" -``` - -#### User Type - -Inferred from better-auth with additional fields: - -```ts -type User = { - id: string - email: string - name: string - firstName?: string - lastName?: string - phone?: string - image?: string - emailVerified: boolean - createdAt: Date - updatedAt: Date -} -``` - -#### Session Type - -```ts -type Session = { - id: string - userId: string - expiresAt: Date - token: string - ipAddress?: string - userAgent?: string -} -``` - -### AuthContext Interface - -The context provided by `AuthProvider` and accessed via `useAuth`: - -```ts -interface AuthContextType { - isAuthenticated: boolean - isLoading: boolean - user: User | null | undefined - logout: () => Promise - sendAuthCode: (email: string) => Promise - verifyAuthCode: (email: string, code: string) => Promise - googleAuth: () => Promise -} -``` - -## Dependencies - -### Core Dependencies - -- **better-auth** (1.2.5) - Modern authentication library for TypeScript -- **swr** (2.2.5) - React hooks for data fetching - -### Workspace Dependencies - -- **@repo/analytics** - User tracking and feature flags -- **@repo/db** - Drizzle ORM database client -- **@repo/emails** - Email sending service -- **@repo/utils** - Shared utilities - -### Peer Dependencies - -- **next** - Next.js framework -- **react** - React 19 -- **react-dom** - React DOM 19 - -### Integration Points - -The package leverages workspace dependencies for: -- **Database**: Drizzle ORM adapter connects to PostgreSQL via `@repo/db` -- **Email**: OTP codes sent through `@repo/emails` with custom templates -- **Analytics**: User identification and event tracking via `@repo/analytics` -- **Middleware**: Updates user's `lastSeenAt` timestamp on each session creation diff --git a/templates/packages/auth/biome.jsonc b/templates/packages/auth/biome.jsonc deleted file mode 100644 index 928f2597..00000000 --- a/templates/packages/auth/biome.jsonc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "extends": ["@repo/biome"], - "files": { - "ignore": ["dist", ".cache"] - } -} diff --git a/templates/packages/auth/package.json b/templates/packages/auth/package.json deleted file mode 100644 index 088cd0c7..00000000 --- a/templates/packages/auth/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@repo/auth", - "private": true, - "scripts": { - "typecheck": "tsc --noEmit", - "lint": "biome check", - "lint:fix": "biome check --write" - }, - "type": "module", - "main": "./src/node.ts", - "exports": { - ".": "./src/index.ts", - "./server": "./src/server.ts" - }, - "dependencies": { - "@repo/analytics": "workspace:*", - "@repo/db": "workspace:*", - "@repo/emails": "workspace:*", - "@repo/utils": "workspace:*", - "@startupkit/auth": "^0.6.6", - "better-auth": "1.3.27", - "drizzle-orm": "0.38.4", - "swr": "2.2.5" - }, - "peerDependencies": { - "next": "catalog:stack", - "react": "catalog:react19", - "react-dom": "catalog:react19" - }, - "devDependencies": { - "@repo/biome": "workspace:*", - "@repo/tsconfig": "workspace:*", - "@types/node": "catalog:stack", - "@types/react": "catalog:react19", - "@types/react-dom": "catalog:react19", - "next": "catalog:stack" - }, - "version": "0.6.6" -} diff --git a/templates/packages/auth/src/components/index.ts b/templates/packages/auth/src/components/index.ts deleted file mode 100644 index de7133f6..00000000 --- a/templates/packages/auth/src/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useAuth } from "../hooks/use-auth" -export { AuthProvider } from "./provider" diff --git a/templates/packages/auth/src/components/provider.tsx b/templates/packages/auth/src/components/provider.tsx deleted file mode 100644 index 8cd31b2d..00000000 --- a/templates/packages/auth/src/components/provider.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client" - -import { useAnalytics } from "@repo/analytics" -import { AuthProvider as StartupKitAuthProvider } from "@startupkit/auth" -import type React from "react" -import { authClient } from "../index" -import type { User } from "../types" - -interface AuthProviderProps { - children: React.ReactNode - user?: User -} - -/** - * Auth Provider - Wraps @startupkit/auth provider with analytics integration - * - * Uses better-auth directly (via authClient) and integrates with your analytics - */ -export function AuthProvider({ children, user }: AuthProviderProps) { - const { identify, reset } = useAnalytics() - - return ( - { - const analyticsProps: Record = {} - for (const [key, value] of Object.entries(user)) { - if ( - key !== "id" && - (typeof value === "string" || - typeof value === "number" || - typeof value === "boolean") - ) { - analyticsProps[key] = value - } - } - - identify(user.id, analyticsProps) - }} - onReset={() => { - reset() - }} - > - {children} - - ) -} diff --git a/templates/packages/auth/src/hooks/use-auth.ts b/templates/packages/auth/src/hooks/use-auth.ts deleted file mode 100644 index 46fe1209..00000000 --- a/templates/packages/auth/src/hooks/use-auth.ts +++ /dev/null @@ -1,18 +0,0 @@ -"use client" - -import { useAuth as useStartupKitAuth } from "@startupkit/auth" -import type { User } from "../types" - -interface UseAuthReturn { - isAuthenticated: boolean - isLoading: boolean - user: User | null | undefined - logout: () => Promise - sendAuthCode: (email: string) => Promise - verifyAuthCode: (email: string, otp: string) => Promise - googleAuth: () => Promise -} - -export function useAuth(): UseAuthReturn { - return useStartupKitAuth() as UseAuthReturn -} diff --git a/templates/packages/auth/src/index.ts b/templates/packages/auth/src/index.ts deleted file mode 100644 index db5a04c8..00000000 --- a/templates/packages/auth/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -// @repo/auth - Your customizable auth implementation -// Imports directly from better-auth - you control the version! - -import { emailOTPClient } from "better-auth/client/plugins" -import { createAuthClient } from "better-auth/react" - -export * from "./components" -export * from "./types" - -export const authClient = createAuthClient({ - basePath: "/auth", - plugins: [emailOTPClient()] -}) diff --git a/templates/packages/auth/src/lib/auth.ts b/templates/packages/auth/src/lib/auth.ts deleted file mode 100644 index e3d86bac..00000000 --- a/templates/packages/auth/src/lib/auth.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { track } from "@repo/analytics/server" -import * as dbSchema from "@repo/db" -import { sendEmail } from "@repo/emails" -import { betterAuth } from "better-auth" -import { drizzleAdapter } from "better-auth/adapters/drizzle" -import { nextCookies } from "better-auth/next-js" -import { createAuthMiddleware, emailOTP } from "better-auth/plugins" -import { eq } from "drizzle-orm" - -type BetterAuthInstance = ReturnType - -const globalForAuth = globalThis as unknown as { auth?: BetterAuthInstance } - -async function sendVerificationOTP({ - email, - otp -}: { email: string; otp: string }) { - await sendEmail({ - template: "VerifyCode", - from: "hello@startupkit.com", - to: email, - subject: "Verify your email", - props: { - email, - otpCode: otp, - expiryTime: "10 minutes" - } - }) -} - -function createAuth(): BetterAuthInstance { - return betterAuth({ - secret: process.env.BETTER_AUTH_SECRET, - basePath: "/auth", - trustedOrigins: process.env.BETTER_AUTH_TRUSTED_ORIGINS - ? process.env.BETTER_AUTH_TRUSTED_ORIGINS.split(",") - : ["http://localhost:2274"], - advanced: { - generateId: () => crypto.randomUUID() - }, - logger: { - disabled: false, - disableColors: false, - level: "error", - log: (level, message, ...args) => { - console.log(`[${level}] ${message}`, ...args) - } - }, - database: drizzleAdapter(dbSchema.db, { - provider: "pg", - schema: { - user: dbSchema.users, - account: dbSchema.accounts, - session: dbSchema.sessions, - verification: dbSchema.verifications - } - }), - account: { - accountLinking: { - enabled: true - } - }, - user: { - additionalFields: { - firstName: { - type: "string", - required: false - }, - lastName: { - type: "string", - required: false - }, - phone: { - type: "string", - required: false - } - } - }, - hooks: { - after: createAuthMiddleware(async (ctx) => { - const { newSession, newUser } = ctx.context - - if (newSession) { - await dbSchema.db - .update(dbSchema.users) - .set({ - lastSeenAt: new Date() - } as unknown as typeof dbSchema.users.$inferInsert) - .where(eq(dbSchema.users.id, newSession.user.id)) - - const [user] = await dbSchema.db - .select({ - id: dbSchema.users.id, - email: dbSchema.users.email, - firstName: dbSchema.users.firstName, - lastName: dbSchema.users.lastName - }) - .from(dbSchema.users) - .where(eq(dbSchema.users.id, newSession.user.id)) - .limit(1) - - if (user?.email) { - await track({ - event: newUser ? "USER_SIGNED_UP" : "USER_SIGNED_IN", - userId: user.id, - user: { - id: user.id, - email: user.email, - firstName: user.firstName, - lastName: user.lastName - } - }) - } - } - }) - }, - plugins: [ - emailOTP({ - sendVerificationOTP - }), - nextCookies() - ], - session: { - expiresIn: 60 * 60 * 24 * 7, - updateAge: 60 * 60 * 24, - cookieCache: { - enabled: true, - maxAge: 60 * 5 - } - }, - socialProviders: { - google: { - clientId: process.env.GOOGLE_CLIENT_ID || "", - clientSecret: process.env.GOOGLE_CLIENT_SECRET || "", - enabled: Boolean( - process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET - ), - mapProfileToUser: async (profile) => { - return { - name: `${profile.given_name} ${profile.family_name}`, - firstName: profile.given_name, - lastName: profile.family_name, - image: profile.picture - } - } - } - } - }) -} - -export const auth = globalForAuth.auth ?? createAuth() - -if (process.env.NODE_ENV !== "production") { - globalForAuth.auth = auth -} diff --git a/templates/packages/auth/src/server.ts b/templates/packages/auth/src/server.ts deleted file mode 100644 index d97e2ef4..00000000 --- a/templates/packages/auth/src/server.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { getFeatureFlags } from "@repo/analytics/server" -import { toNextJsHandler } from "better-auth/next-js" -import { headers } from "next/headers" -import { auth } from "./lib/auth" - -export { auth } - -export type Session = typeof auth.$Infer.Session.session -export type User = typeof auth.$Infer.Session.user - -export function handler() { - return toNextJsHandler(auth.handler) -} - -export async function withAuth(opts?: { flags?: boolean }) { - const session = await auth.api.getSession({ - headers: await headers() - }) - - const flags = - opts?.flags && session?.user - ? await getFeatureFlags(session.user.id) - : undefined - - return { - user: session?.user ?? null, - session: session?.session ?? null, - flags - } -} diff --git a/templates/packages/auth/src/types.ts b/templates/packages/auth/src/types.ts deleted file mode 100644 index 8b7117ed..00000000 --- a/templates/packages/auth/src/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { auth } from "./lib/auth" - -export type User = typeof auth.$Infer.Session.user -export type Session = typeof auth.$Infer.Session.session diff --git a/templates/packages/auth/startupkit.config.ts b/templates/packages/auth/startupkit.config.ts deleted file mode 100644 index 46a73e46..00000000 --- a/templates/packages/auth/startupkit.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { StartupKitConfig } from "startupkit/config" - -const config: StartupKitConfig = { - type: "package", - dependencies: { - packages: ["db", "emails"], - config: ["biome", "typescript"] - } -} - -export default config diff --git a/templates/packages/auth/tsconfig.json b/templates/packages/auth/tsconfig.json deleted file mode 100644 index af751cd9..00000000 --- a/templates/packages/auth/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@repo/tsconfig/react-library.json", - "include": ["src/**/*.ts", "src/**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/templates/packages/db/biome.jsonc b/templates/packages/db/biome.jsonc deleted file mode 100644 index 8fa79947..00000000 --- a/templates/packages/db/biome.jsonc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", - "extends": ["@repo/biome"] -} diff --git a/templates/packages/db/drizzle.config.ts b/templates/packages/db/drizzle.config.ts deleted file mode 100644 index 6e064d48..00000000 --- a/templates/packages/db/drizzle.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { defineConfig } from "drizzle-kit" - -export default defineConfig({ - dialect: "postgresql", - schema: "./src/schema.ts", - out: "./drizzle", - dbCredentials: { - url: process.env.DATABASE_URL || "" - } -}) - diff --git a/templates/packages/db/drizzle/0000_initial.sql b/templates/packages/db/drizzle/0000_initial.sql deleted file mode 100644 index b817027f..00000000 --- a/templates/packages/db/drizzle/0000_initial.sql +++ /dev/null @@ -1,86 +0,0 @@ -CREATE TYPE "public"."team_role" AS ENUM('owner', 'member');--> statement-breakpoint -CREATE TABLE "Account" ( - "id" text PRIMARY KEY NOT NULL, - "userId" text NOT NULL, - "accountId" text NOT NULL, - "providerId" text NOT NULL, - "accessToken" text, - "refreshToken" text, - "accessTokenExpiresAt" timestamp, - "refreshTokenExpiresAt" timestamp, - "scope" text, - "idToken" text, - "password" text, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE "Session" ( - "id" text PRIMARY KEY NOT NULL, - "userId" text NOT NULL, - "token" text NOT NULL, - "expiresAt" timestamp NOT NULL, - "ipAddress" text, - "userAgent" text, - "impersonatedBy" text, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp DEFAULT now() NOT NULL, - CONSTRAINT "Session_token_unique" UNIQUE("token") -); ---> statement-breakpoint -CREATE TABLE "TeamMember" ( - "teamId" text NOT NULL, - "userId" text NOT NULL, - "role" "team_role" DEFAULT 'member' NOT NULL, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp, - "joinedAt" timestamp -); ---> statement-breakpoint -CREATE TABLE "Team" ( - "id" text PRIMARY KEY NOT NULL, - "name" varchar(64) NOT NULL, - "slug" text NOT NULL, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp, - CONSTRAINT "Team_slug_unique" UNIQUE("slug") -); ---> statement-breakpoint -CREATE TABLE "User" ( - "id" text PRIMARY KEY NOT NULL, - "firstName" text, - "lastName" text, - "name" text, - "image" text, - "email" text, - "emailVerified" boolean DEFAULT false NOT NULL, - "phone" text, - "role" text DEFAULT 'user' NOT NULL, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp DEFAULT now() NOT NULL, - "lastSeenAt" timestamp, - CONSTRAINT "User_email_unique" UNIQUE("email"), - CONSTRAINT "User_phone_unique" UNIQUE("phone") -); ---> statement-breakpoint -CREATE TABLE "Verification" ( - "id" text PRIMARY KEY NOT NULL, - "identifier" text NOT NULL, - "value" text NOT NULL, - "expiresAt" timestamp NOT NULL, - "createdAt" timestamp DEFAULT now() NOT NULL, - "updatedAt" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_User_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_User_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_teamId_Team_id_fk" FOREIGN KEY ("teamId") REFERENCES "public"."Team"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "TeamMember" ADD CONSTRAINT "TeamMember_userId_User_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint -CREATE INDEX "Account_userId_idx" ON "Account" USING btree ("userId");--> statement-breakpoint -CREATE UNIQUE INDEX "Account_providerId_accountId_key" ON "Account" USING btree ("providerId","accountId");--> statement-breakpoint -CREATE UNIQUE INDEX "Session_token_key" ON "Session" USING btree ("token");--> statement-breakpoint -CREATE UNIQUE INDEX "TeamMember_teamId_userId_key" ON "TeamMember" USING btree ("teamId","userId");--> statement-breakpoint -CREATE INDEX "TeamMember_teamId_idx" ON "TeamMember" USING btree ("teamId");--> statement-breakpoint -CREATE INDEX "TeamMember_userId_idx" ON "TeamMember" USING btree ("userId");--> statement-breakpoint -CREATE INDEX "Team_slug_idx" ON "Team" USING btree ("slug");--> statement-breakpoint -CREATE INDEX "Verification_identifier_value_idx" ON "Verification" USING btree ("identifier","value"); \ No newline at end of file diff --git a/templates/packages/db/drizzle/meta/0000_snapshot.json b/templates/packages/db/drizzle/meta/0000_snapshot.json deleted file mode 100644 index 5860008a..00000000 --- a/templates/packages/db/drizzle/meta/0000_snapshot.json +++ /dev/null @@ -1,655 +0,0 @@ -{ - "id": "7a1129e4-a4cd-47a2-b232-bb8718867c6f", - "prevId": "00000000-0000-0000-0000-000000000000", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.Account": { - "name": "Account", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accountId": { - "name": "accountId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "providerId": { - "name": "providerId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "accessToken": { - "name": "accessToken", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "refreshToken": { - "name": "refreshToken", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "accessTokenExpiresAt": { - "name": "accessTokenExpiresAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "refreshTokenExpiresAt": { - "name": "refreshTokenExpiresAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "scope": { - "name": "scope", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "idToken": { - "name": "idToken", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "Account_userId_idx": { - "name": "Account_userId_idx", - "columns": [ - { - "expression": "userId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "Account_providerId_accountId_key": { - "name": "Account_providerId_accountId_key", - "columns": [ - { - "expression": "providerId", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "accountId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "Account_userId_User_id_fk": { - "name": "Account_userId_User_id_fk", - "tableFrom": "Account", - "tableTo": "User", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.Session": { - "name": "Session", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "token": { - "name": "token", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expiresAt": { - "name": "expiresAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "ipAddress": { - "name": "ipAddress", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "userAgent": { - "name": "userAgent", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "impersonatedBy": { - "name": "impersonatedBy", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "Session_token_key": { - "name": "Session_token_key", - "columns": [ - { - "expression": "token", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "Session_userId_User_id_fk": { - "name": "Session_userId_User_id_fk", - "tableFrom": "Session", - "tableTo": "User", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "Session_token_unique": { - "name": "Session_token_unique", - "nullsNotDistinct": false, - "columns": [ - "token" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.TeamMember": { - "name": "TeamMember", - "schema": "", - "columns": { - "teamId": { - "name": "teamId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "role": { - "name": "role", - "type": "team_role", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'member'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - }, - "joinedAt": { - "name": "joinedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "TeamMember_teamId_userId_key": { - "name": "TeamMember_teamId_userId_key", - "columns": [ - { - "expression": "teamId", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "userId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - }, - "TeamMember_teamId_idx": { - "name": "TeamMember_teamId_idx", - "columns": [ - { - "expression": "teamId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - }, - "TeamMember_userId_idx": { - "name": "TeamMember_userId_idx", - "columns": [ - { - "expression": "userId", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "TeamMember_teamId_Team_id_fk": { - "name": "TeamMember_teamId_Team_id_fk", - "tableFrom": "TeamMember", - "tableTo": "Team", - "columnsFrom": [ - "teamId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "TeamMember_userId_User_id_fk": { - "name": "TeamMember_userId_User_id_fk", - "tableFrom": "TeamMember", - "tableTo": "User", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.Team": { - "name": "Team", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "name": { - "name": "name", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "Team_slug_idx": { - "name": "Team_slug_idx", - "columns": [ - { - "expression": "slug", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "Team_slug_unique": { - "name": "Team_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.User": { - "name": "User", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "firstName": { - "name": "firstName", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "lastName": { - "name": "lastName", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "emailVerified": { - "name": "emailVerified", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "phone": { - "name": "phone", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'user'" - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "lastSeenAt": { - "name": "lastSeenAt", - "type": "timestamp", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "User_email_unique": { - "name": "User_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - }, - "User_phone_unique": { - "name": "User_phone_unique", - "nullsNotDistinct": false, - "columns": [ - "phone" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.Verification": { - "name": "Verification", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "identifier": { - "name": "identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "value": { - "name": "value", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expiresAt": { - "name": "expiresAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true - }, - "createdAt": { - "name": "createdAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updatedAt": { - "name": "updatedAt", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": { - "Verification_identifier_value_idx": { - "name": "Verification_identifier_value_idx", - "columns": [ - { - "expression": "identifier", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "value", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.team_role": { - "name": "team_role", - "schema": "public", - "values": [ - "owner", - "member" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file diff --git a/templates/packages/db/drizzle/meta/_journal.json b/templates/packages/db/drizzle/meta/_journal.json deleted file mode 100644 index d1b77103..00000000 --- a/templates/packages/db/drizzle/meta/_journal.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1762370105573, - "tag": "0000_initial", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/templates/packages/db/package.json b/templates/packages/db/package.json deleted file mode 100644 index 2a370149..00000000 --- a/templates/packages/db/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "@repo/db", - "private": true, - "type": "module", - "exports": { - ".": { - "types": "./dist/index.ts", - "default": "./src/index.ts" - } - }, - "scripts": { - "lint": "biome lint --unsafe", - "lint:fix": "biome lint --write --unsafe", - "typecheck": "tsc --noEmit", - "drizzle": "npx drizzle-kit", - "db:generate": "drizzle-kit generate", - "db:migrate": "drizzle-kit migrate", - "db:push": "drizzle-kit push", - "db:studio": "drizzle-kit studio" - }, - "dependencies": { - "drizzle-orm": "^0.38.3", - "pg": "^8.13.1" - }, - "devDependencies": { - "@repo/biome": "workspace:*", - "@repo/tsconfig": "workspace:*", - "@types/node": "catalog:stack", - "@types/pg": "^8.11.10", - "drizzle-kit": "^0.30.2" - }, - "version": "0.6.6" -} diff --git a/templates/packages/db/src/index.ts b/templates/packages/db/src/index.ts deleted file mode 100644 index 96233846..00000000 --- a/templates/packages/db/src/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { drizzle } from "drizzle-orm/node-postgres" -import { Pool } from "pg" -import * as schema from "./schema" - -if (!process.env.DATABASE_URL) { - throw new Error( - "DATABASE_URL environment variable is not set. Please add it to your .env.local file." - ) -} - -const globalForDb = globalThis as unknown as { - pool: Pool | undefined -} - -const pool = - globalForDb.pool ?? - new Pool({ - connectionString: process.env.DATABASE_URL, - max: 20, - idleTimeoutMillis: 30000, - connectionTimeoutMillis: 2000, - allowExitOnIdle: true - }) - -// Only attach error listener when creating a new pool, not when reusing cached one -if (!globalForDb.pool) { - pool.on("error", (err) => { - console.error("Unexpected database pool error", err) - }) -} - -if (process.env.NODE_ENV !== "production") globalForDb.pool = pool - -export const db = drizzle({ client: pool, schema }) - -export * from "./schema" diff --git a/templates/packages/db/src/schema.ts b/templates/packages/db/src/schema.ts deleted file mode 100644 index 64803aab..00000000 --- a/templates/packages/db/src/schema.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { relations } from "drizzle-orm" -import { - boolean, - index, - pgEnum, - pgTable, - text, - timestamp, - uniqueIndex, - varchar -} from "drizzle-orm/pg-core" - -export const teamRoleEnum = pgEnum("team_role", ["owner", "member"]) - -export const users = pgTable("User", { - id: text("id").primaryKey(), - firstName: text("firstName"), - lastName: text("lastName"), - name: text("name"), - image: text("image"), - email: text("email").unique(), - emailVerified: boolean("emailVerified").default(false).notNull(), - phone: text("phone").unique(), - role: text("role").default("user").notNull(), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow().notNull(), - lastSeenAt: timestamp("lastSeenAt", { mode: "date" }) -}) - -export const accounts = pgTable( - "Account", - { - id: text("id").primaryKey(), - userId: text("userId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - accountId: text("accountId").notNull(), - providerId: text("providerId").notNull(), - accessToken: text("accessToken"), - refreshToken: text("refreshToken"), - accessTokenExpiresAt: timestamp("accessTokenExpiresAt", { mode: "date" }), - refreshTokenExpiresAt: timestamp("refreshTokenExpiresAt", { - mode: "date" - }), - scope: text("scope"), - idToken: text("idToken"), - password: text("password"), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow().notNull() - }, - (table) => [ - index("Account_userId_idx").on(table.userId), - uniqueIndex("Account_providerId_accountId_key").on( - table.providerId, - table.accountId - ) - ] -) - -export const sessions = pgTable( - "Session", - { - id: text("id").primaryKey(), - userId: text("userId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - token: text("token").notNull().unique(), - expiresAt: timestamp("expiresAt", { mode: "date" }).notNull(), - ipAddress: text("ipAddress"), - userAgent: text("userAgent"), - impersonatedBy: text("impersonatedBy"), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow().notNull() - }, - (table) => [uniqueIndex("Session_token_key").on(table.token)] -) - -export const teams = pgTable( - "Team", - { - id: text("id").primaryKey(), - name: varchar("name", { length: 64 }).notNull(), - slug: text("slug").notNull().unique(), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }) - }, - (table) => [index("Team_slug_idx").on(table.slug)] -) - -export const teamMembers = pgTable( - "TeamMember", - { - teamId: text("teamId") - .notNull() - .references(() => teams.id), - userId: text("userId") - .notNull() - .references(() => users.id), - role: teamRoleEnum("role").default("member").notNull(), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }), - joinedAt: timestamp("joinedAt", { mode: "date" }) - }, - (table) => [ - uniqueIndex("TeamMember_teamId_userId_key").on(table.teamId, table.userId), - index("TeamMember_teamId_idx").on(table.teamId), - index("TeamMember_userId_idx").on(table.userId) - ] -) - -export const verifications = pgTable( - "Verification", - { - id: text("id").primaryKey(), - identifier: text("identifier").notNull(), - value: text("value").notNull(), - expiresAt: timestamp("expiresAt", { mode: "date" }).notNull(), - createdAt: timestamp("createdAt", { mode: "date" }).defaultNow().notNull(), - updatedAt: timestamp("updatedAt", { mode: "date" }).defaultNow().notNull() - }, - (table) => [ - index("Verification_identifier_value_idx").on( - table.identifier, - table.value - ) - ] -) - -export const usersRelations = relations(users, ({ many }) => ({ - accounts: many(accounts), - sessions: many(sessions), - memberships: many(teamMembers) -})) - -export const accountsRelations = relations(accounts, ({ one }) => ({ - user: one(users, { - fields: [accounts.userId], - references: [users.id] - }) -})) - -export const sessionsRelations = relations(sessions, ({ one }) => ({ - user: one(users, { - fields: [sessions.userId], - references: [users.id] - }) -})) - -export const teamsRelations = relations(teams, ({ many }) => ({ - members: many(teamMembers) -})) - -export const teamMembersRelations = relations(teamMembers, ({ one }) => ({ - team: one(teams, { - fields: [teamMembers.teamId], - references: [teams.id] - }), - user: one(users, { - fields: [teamMembers.userId], - references: [users.id] - }) -})) - -export type User = typeof users.$inferSelect -export type NewUser = typeof users.$inferInsert - -export type Account = typeof accounts.$inferSelect -export type NewAccount = typeof accounts.$inferInsert - -export type Session = typeof sessions.$inferSelect -export type NewSession = typeof sessions.$inferInsert - -export type Team = typeof teams.$inferSelect -export type NewTeam = typeof teams.$inferInsert - -export type TeamMember = typeof teamMembers.$inferSelect -export type NewTeamMember = typeof teamMembers.$inferInsert - -export type Verification = typeof verifications.$inferSelect -export type NewVerification = typeof verifications.$inferInsert - diff --git a/templates/packages/db/startupkit.config.ts b/templates/packages/db/startupkit.config.ts deleted file mode 100644 index 4de26f09..00000000 --- a/templates/packages/db/startupkit.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { StartupKitConfig } from 'startupkit/config'; - -const config: StartupKitConfig = { - type: 'package', - dependencies: { - config: ['biome', 'typescript'], - }, -}; - -export default config; diff --git a/templates/packages/db/tsconfig.json b/templates/packages/db/tsconfig.json deleted file mode 100644 index 476474be..00000000 --- a/templates/packages/db/tsconfig.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "@repo/tsconfig/internal-package.json", - "include": ["src/**/*.ts", "src/**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/templates/packages/emails/.gitignore b/templates/packages/emails/.gitignore deleted file mode 100644 index 9e9e8799..00000000 --- a/templates/packages/emails/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.react-email \ No newline at end of file diff --git a/templates/packages/emails/package.json b/templates/packages/emails/package.json deleted file mode 100644 index 991c79b7..00000000 --- a/templates/packages/emails/package.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "name": "@repo/emails", - "private": true, - "main": "src/index.tsx", - "scripts": { - "clean": "rm -rf .turbo .react-email", - "build": "email build -d ./src/templates", - "dev": "email dev -p 5001 -d ./src/templates", - "typecheck": "tsc --noEmit" - }, - "dependencies": { - "@react-email/components": "0.0.35", - "@react-email/render": "1.0.5", - "open": "^10.2.0", - "react": "catalog:react19", - "react-dom": "catalog:react19", - "resend": "^4.5.0" - }, - "devDependencies": { - "@repo/biome": "workspace:*", - "@repo/tsconfig": "workspace:*", - "@types/node": "catalog:stack", - "@types/react": "catalog:react19", - "@types/react-dom": "catalog:react19", - "react-email": "4.0.3" - }, - "version": "0.6.6" -} diff --git a/templates/packages/emails/src/index.tsx b/templates/packages/emails/src/index.tsx deleted file mode 100644 index dd7275e2..00000000 --- a/templates/packages/emails/src/index.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { render } from '@react-email/render'; -import { Resend } from 'resend'; -import { previewEmailInBrowser } from './lib/preview-email'; -import TeamInviteEmail from './templates/team-invite'; -import VerifyCodeEmail from './templates/verify-code'; - -const templates = { - TeamInvite: TeamInviteEmail, - VerifyCode: VerifyCodeEmail, -} as const; - -export async function sendEmail({ - template, - from, - to, - subject, - props, -}: { - template: T; - from: string; - to: string; - subject: string; - props: Parameters<(typeof templates)[T]>[0]; -}) { - const Template = templates[template] as React.ComponentType; - const html = await render(