From 54bcdf1cbc7ecb9d280ae5dcb60ebe4fb54dfe02 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Fri, 29 Aug 2025 00:21:08 +0530 Subject: [PATCH] feat: add clerk auth support with convex (#548) --- apps/cli/src/constants.ts | 7 +- apps/cli/src/helpers/core/add-addons.ts | 2 +- apps/cli/src/helpers/core/add-deployment.ts | 2 +- .../helpers/{addons => core}/auth-setup.ts | 71 ++- apps/cli/src/helpers/core/command-handlers.ts | 6 +- apps/cli/src/helpers/core/create-project.ts | 6 +- apps/cli/src/helpers/core/create-readme.ts | 28 +- apps/cli/src/helpers/core/env-setup.ts | 46 +- .../cli/src/helpers/core/post-installation.ts | 17 + apps/cli/src/helpers/core/template-manager.ts | 125 ++++- apps/cli/src/index.ts | 5 +- apps/cli/src/prompts/auth.ts | 49 +- apps/cli/src/prompts/config-prompts.ts | 16 +- apps/cli/src/prompts/frontend.ts | 3 +- apps/cli/src/types.ts | 11 +- apps/cli/src/utils/compatibility-rules.ts | 21 +- apps/cli/src/utils/config-processing.ts | 3 +- apps/cli/src/utils/config-validation.ts | 43 +- apps/cli/src/utils/display-config.ts | 8 +- .../utils/generate-reproducible-command.ts | 2 +- .../templates/addons/ruler/.ruler/bts.md.hbs | 4 +- .../api/orpc/native/utils/orpc.ts.hbs | 4 +- .../orpc/server/base/src/lib/context.ts.hbs | 20 +- .../api/orpc/server/base/src/lib/orpc.ts.hbs | 2 +- .../next/src/app/rpc/[...all]/route.ts.hbs | 4 +- .../api/orpc/web/nuxt/app/plugins/orpc.ts.hbs | 2 +- .../orpc/web/react/base/src/utils/orpc.ts.hbs | 2 +- .../api/orpc/web/solid/src/utils/orpc.ts.hbs | 2 +- .../api/orpc/web/svelte/src/lib/orpc.ts.hbs | 2 +- .../api/trpc/native/utils/trpc.ts.hbs | 4 +- .../trpc/server/base/src/lib/context.ts.hbs | 20 +- .../api/trpc/server/base/src/lib/trpc.ts.hbs | 2 +- .../trpc/web/react/base/src/utils/trpc.ts.hbs | 4 +- .../native/native-base/lib/auth-client.ts.hbs | 0 .../nativewind/app/(drawer)/index.tsx.hbs | 0 .../nativewind/components/sign-in.tsx.hbs | 0 .../nativewind/components/sign-up.tsx.hbs | 0 .../unistyles/app/(drawer)/index.tsx.hbs | 0 .../unistyles/components/sign-in.tsx.hbs | 0 .../unistyles/components/sign-up.tsx.hbs | 0 .../server/base/src/lib/auth.ts.hbs | 0 .../db/drizzle/mysql/src/db/schema/auth.ts | 0 .../db/drizzle/postgres/src/db/schema/auth.ts | 0 .../db/drizzle/sqlite/src/db/schema/auth.ts | 0 .../mongodb/src/db/models/auth.model.ts | 0 .../prisma/mongodb/prisma/schema/auth.prisma | 0 .../db/prisma/mysql/prisma/schema/auth.prisma | 0 .../prisma/postgres/prisma/schema/auth.prisma | 0 .../prisma/sqlite/prisma/schema/auth.prisma | 0 .../next/src/app/api/auth/[...all]/route.ts | 0 .../web/nuxt/app/components/SignInForm.vue | 0 .../web/nuxt/app/components/SignUpForm.vue | 0 .../web/nuxt/app/components/UserMenu.vue | 0 .../web/nuxt/app/middleware/auth.ts | 0 .../web/nuxt/app/pages/dashboard.vue.hbs | 0 .../web/nuxt/app/pages/login.vue | 0 .../web/nuxt/app/plugins/auth-client.ts | 0 .../web/react/base/src/lib/auth-client.ts.hbs | 0 .../react/next/src/app/dashboard/page.tsx.hbs | 0 .../web/react/next/src/app/login/page.tsx | 0 .../next/src/components/sign-in-form.tsx | 0 .../next/src/components/sign-up-form.tsx | 0 .../next/src/components/theme-provider.tsx | 0 .../react/next/src/components/user-menu.tsx | 0 .../src/components/sign-in-form.tsx | 0 .../src/components/sign-up-form.tsx | 0 .../react-router/src/components/user-menu.tsx | 0 .../react-router/src/routes/dashboard.tsx.hbs | 0 .../react/react-router/src/routes/login.tsx | 0 .../src/components/sign-in-form.tsx | 0 .../src/components/sign-up-form.tsx | 0 .../src/components/user-menu.tsx | 0 .../src/routes/dashboard.tsx.hbs | 0 .../tanstack-router/src/routes/login.tsx | 0 .../src/components/sign-in-form.tsx | 0 .../src/components/sign-up-form.tsx | 0 .../src/components/user-menu.tsx | 0 .../src/routes/dashboard.tsx.hbs | 0 .../react/tanstack-start/src/routes/login.tsx | 0 .../web/solid/src/components/sign-in-form.tsx | 0 .../web/solid/src/components/sign-up-form.tsx | 0 .../solid/src/components/user-menu.tsx.hbs | 0 .../web/solid/src/lib/auth-client.ts | 0 .../web/solid/src/routes/dashboard.tsx.hbs | 0 .../web/solid/src/routes/login.tsx | 0 .../svelte/src/components/SignInForm.svelte | 0 .../svelte/src/components/SignUpForm.svelte | 0 .../web/svelte/src/components/UserMenu.svelte | 0 .../web/svelte/src/lib/auth-client.ts | 0 .../src/routes/dashboard/+page.svelte.hbs | 0 .../web/svelte/src/routes/login/+page.svelte | 0 .../convex/backend/convex/auth.config.ts.hbs | 12 + .../convex/backend/convex/privateData.ts.hbs | 16 + .../native/base/app/(auth)/_layout.tsx.hbs | 12 + .../native/base/app/(auth)/sign-in.tsx.hbs | 67 +++ .../native/base/app/(auth)/sign-out.tsx.hbs | 110 ++++ .../base/components/sign-out-button.tsx.hbs | 27 + .../react/next/src/app/dashboard/page.tsx.hbs | 29 + .../web/react/next/src/middleware.ts.hbs | 12 + .../react-router/src/routes/dashboard.tsx.hbs | 32 ++ .../src/routes/dashboard.tsx.hbs | 37 ++ .../src/routes/dashboard.tsx.hbs | 37 ++ .../react/tanstack-start/src/server.ts.hbs | 18 + .../convex/packages/backend/package.json.hbs | 1 + .../backend/server/elysia/src/index.ts.hbs | 6 +- .../backend/server/express/src/index.ts.hbs | 12 +- .../backend/server/fastify/src/index.ts.hbs | 8 +- .../backend/server/hono/src/index.ts.hbs | 8 +- .../server-base/src/routers/index.ts.hbs | 8 +- .../deploy/alchemy/alchemy.run.ts.hbs | 4 +- .../nativewind/app/(drawer)/index.tsx.hbs | 38 +- .../native/nativewind/app/_layout.tsx.hbs | 28 + .../native/nativewind/package.json.hbs | 1 + .../unistyles/app/(drawer)/index.tsx.hbs | 32 ++ .../native/unistyles/app/_layout.tsx.hbs | 35 ++ .../native/unistyles/package.json.hbs | 1 + .../nuxt/app/components/Header.vue.hbs | 6 +- .../react/next/src/app/layout.tsx.hbs | 38 +- .../next/src/components/providers.tsx.hbs | 12 + .../react/react-router/src/root.tsx.hbs | 29 +- .../react/tanstack-router/src/main.tsx.hbs | 20 +- .../react/tanstack-start/src/router.tsx.hbs | 12 +- .../tanstack-start/src/routes/__root.tsx.hbs | 57 +- .../web-base/src/components/header.tsx.hbs | 6 +- .../solid/src/components/header.tsx.hbs | 6 +- .../svelte/src/components/Header.svelte.hbs | 6 +- apps/cli/test/cli.smoke.test.ts | 506 +++++++++++++++--- apps/cli/test/programmatic-api.test.ts | 6 +- apps/web/content/docs/analytics.mdx | 4 +- apps/web/content/docs/bts-config.mdx | 2 +- apps/web/content/docs/cli/compatibility.mdx | 38 +- apps/web/content/docs/cli/index.mdx | 1 + apps/web/content/docs/cli/options.mdx | 20 +- .../web/content/docs/cli/programmatic-api.mdx | 8 +- apps/web/content/docs/compatibility.mdx | 5 +- apps/web/content/docs/index.mdx | 24 +- apps/web/content/docs/project-structure.mdx | 6 +- apps/web/scripts/generate-analytics.ts | 2 +- .../(home)/_components/command-section.tsx | 1 + .../(home)/_components/sponsors-section.tsx | 83 +-- .../app/(home)/_components/stack-builder.tsx | 92 +++- .../app/(home)/_components/stats-section.tsx | 30 +- .../app/(home)/_components/testimonials.tsx | 109 +--- .../_components/analytics-header.tsx | 21 +- .../analytics/_components/analytics-page.tsx | 56 ++ .../app/(home)/analytics/_components/index.ts | 8 - apps/web/src/app/(home)/analytics/page.tsx | 82 +-- apps/web/src/app/(home)/layout.tsx | 21 +- apps/web/src/app/(home)/page.tsx | 28 +- .../showcase/_components/showcase-page.tsx | 72 +++ apps/web/src/app/(home)/showcase/page.tsx | 142 +---- apps/web/src/lib/constant.ts | 30 +- apps/web/src/lib/stack-url-state.ts | 2 +- 153 files changed, 1954 insertions(+), 771 deletions(-) rename apps/cli/src/helpers/{addons => core}/auth-setup.ts (52%) rename apps/cli/templates/auth/{ => better-auth}/native/native-base/lib/auth-client.ts.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/native/nativewind/app/(drawer)/index.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/native/nativewind/components/sign-in.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/native/nativewind/components/sign-up.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/native/unistyles/app/(drawer)/index.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/native/unistyles/components/sign-in.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/native/unistyles/components/sign-up.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/server/base/src/lib/auth.ts.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/drizzle/mysql/src/db/schema/auth.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/drizzle/postgres/src/db/schema/auth.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/drizzle/sqlite/src/db/schema/auth.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/mongoose/mongodb/src/db/models/auth.model.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/prisma/mongodb/prisma/schema/auth.prisma (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/prisma/mysql/prisma/schema/auth.prisma (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/prisma/postgres/prisma/schema/auth.prisma (100%) rename apps/cli/templates/auth/{ => better-auth}/server/db/prisma/sqlite/prisma/schema/auth.prisma (100%) rename apps/cli/templates/auth/{ => better-auth}/server/next/src/app/api/auth/[...all]/route.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/components/SignInForm.vue (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/components/SignUpForm.vue (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/components/UserMenu.vue (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/middleware/auth.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/pages/dashboard.vue.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/pages/login.vue (100%) rename apps/cli/templates/auth/{ => better-auth}/web/nuxt/app/plugins/auth-client.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/base/src/lib/auth-client.ts.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/next/src/app/dashboard/page.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/next/src/app/login/page.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/next/src/components/sign-in-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/next/src/components/sign-up-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/next/src/components/theme-provider.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/next/src/components/user-menu.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/react-router/src/components/sign-in-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/react-router/src/components/sign-up-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/react-router/src/components/user-menu.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/react-router/src/routes/dashboard.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/react-router/src/routes/login.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-router/src/components/sign-in-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-router/src/components/sign-up-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-router/src/components/user-menu.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-router/src/routes/dashboard.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-router/src/routes/login.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-start/src/components/sign-in-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-start/src/components/sign-up-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-start/src/components/user-menu.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-start/src/routes/dashboard.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/react/tanstack-start/src/routes/login.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/solid/src/components/sign-in-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/solid/src/components/sign-up-form.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/solid/src/components/user-menu.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/solid/src/lib/auth-client.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/web/solid/src/routes/dashboard.tsx.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/solid/src/routes/login.tsx (100%) rename apps/cli/templates/auth/{ => better-auth}/web/svelte/src/components/SignInForm.svelte (100%) rename apps/cli/templates/auth/{ => better-auth}/web/svelte/src/components/SignUpForm.svelte (100%) rename apps/cli/templates/auth/{ => better-auth}/web/svelte/src/components/UserMenu.svelte (100%) rename apps/cli/templates/auth/{ => better-auth}/web/svelte/src/lib/auth-client.ts (100%) rename apps/cli/templates/auth/{ => better-auth}/web/svelte/src/routes/dashboard/+page.svelte.hbs (100%) rename apps/cli/templates/auth/{ => better-auth}/web/svelte/src/routes/login/+page.svelte (100%) create mode 100644 apps/cli/templates/auth/clerk/convex/backend/convex/auth.config.ts.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/backend/convex/privateData.ts.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-out.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/web/react/next/src/middleware.ts.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs create mode 100644 apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs create mode 100644 apps/web/src/app/(home)/analytics/_components/analytics-page.tsx delete mode 100644 apps/web/src/app/(home)/analytics/_components/index.ts create mode 100644 apps/web/src/app/(home)/showcase/_components/showcase-page.tsx diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index 2e1b16b..82c1f56 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -13,7 +13,7 @@ export const DEFAULT_CONFIG_BASE = { frontend: ["tanstack-router"], database: "sqlite", orm: "drizzle", - auth: true, + auth: "better-auth", addons: ["turborepo"], examples: [], git: true, @@ -43,6 +43,11 @@ export const dependencyVersionMap = { "better-auth": "^1.3.7", "@better-auth/expo": "^1.3.7", + "@clerk/nextjs": "^6.31.5", + "@clerk/clerk-react": "^5.45.0", + "@clerk/tanstack-react-start": "^0.23.1", + "@clerk/clerk-expo": "^2.14.25", + "drizzle-orm": "^0.44.2", "drizzle-kit": "^0.31.2", diff --git a/apps/cli/src/helpers/core/add-addons.ts b/apps/cli/src/helpers/core/add-addons.ts index a3279e9..b5062c1 100644 --- a/apps/cli/src/helpers/core/add-addons.ts +++ b/apps/cli/src/helpers/core/add-addons.ts @@ -44,7 +44,7 @@ export async function addAddonsToProject( frontend: detectedConfig.frontend || [], addons: input.addons, examples: detectedConfig.examples || [], - auth: detectedConfig.auth || false, + auth: detectedConfig.auth || "none", git: false, packageManager: input.packageManager || detectedConfig.packageManager || "npm", diff --git a/apps/cli/src/helpers/core/add-deployment.ts b/apps/cli/src/helpers/core/add-deployment.ts index 62b0f32..0158642 100644 --- a/apps/cli/src/helpers/core/add-deployment.ts +++ b/apps/cli/src/helpers/core/add-deployment.ts @@ -68,7 +68,7 @@ export async function addDeploymentToProject( frontend: detectedConfig.frontend || [], addons: detectedConfig.addons || [], examples: detectedConfig.examples || [], - auth: detectedConfig.auth || false, + auth: detectedConfig.auth || "none", git: false, packageManager: input.packageManager || detectedConfig.packageManager || "npm", diff --git a/apps/cli/src/helpers/addons/auth-setup.ts b/apps/cli/src/helpers/core/auth-setup.ts similarity index 52% rename from apps/cli/src/helpers/addons/auth-setup.ts rename to apps/cli/src/helpers/core/auth-setup.ts index 8d6c86b..a7163c3 100644 --- a/apps/cli/src/helpers/addons/auth-setup.ts +++ b/apps/cli/src/helpers/core/auth-setup.ts @@ -7,7 +7,7 @@ import { addPackageDependency } from "../../utils/add-package-deps"; export async function setupAuth(config: ProjectConfig) { const { auth, frontend, backend, projectDir } = config; - if (backend === "convex" || !auth) { + if (!auth || auth === "none") { return; } @@ -20,7 +20,48 @@ export async function setupAuth(config: ProjectConfig) { const serverDirExists = await fs.pathExists(serverDir); try { - if (serverDirExists) { + if (backend === "convex") { + if (auth === "clerk" && clientDirExists) { + const hasNextJs = frontend.includes("next"); + const hasTanStackStart = frontend.includes("tanstack-start"); + const hasViteReactOther = frontend.some((f) => + ["tanstack-router", "react-router"].includes(f), + ); + + if (hasNextJs) { + await addPackageDependency({ + dependencies: ["@clerk/nextjs"], + projectDir: clientDir, + }); + } else if (hasTanStackStart) { + await addPackageDependency({ + dependencies: ["@clerk/tanstack-react-start"], + projectDir: clientDir, + }); + } else if (hasViteReactOther) { + await addPackageDependency({ + dependencies: ["@clerk/clerk-react"], + projectDir: clientDir, + }); + } + } + + const hasNativeWind = frontend.includes("native-nativewind"); + const hasUnistyles = frontend.includes("native-unistyles"); + if ( + auth === "clerk" && + nativeDirExists && + (hasNativeWind || hasUnistyles) + ) { + await addPackageDependency({ + dependencies: ["@clerk/clerk-expo"], + projectDir: nativeDir, + }); + } + return; + } + + if (serverDirExists && auth === "better-auth") { await addPackageDependency({ dependencies: ["better-auth"], projectDir: serverDir, @@ -40,10 +81,12 @@ export async function setupAuth(config: ProjectConfig) { ); if (hasWebFrontend && clientDirExists) { - await addPackageDependency({ - dependencies: ["better-auth"], - projectDir: clientDir, - }); + if (auth === "better-auth") { + await addPackageDependency({ + dependencies: ["better-auth"], + projectDir: clientDir, + }); + } } if ( @@ -51,15 +94,17 @@ export async function setupAuth(config: ProjectConfig) { frontend.includes("native-unistyles")) && nativeDirExists ) { - await addPackageDependency({ - dependencies: ["better-auth", "@better-auth/expo"], - projectDir: nativeDir, - }); - if (serverDirExists) { + if (auth === "better-auth") { await addPackageDependency({ - dependencies: ["@better-auth/expo"], - projectDir: serverDir, + dependencies: ["better-auth", "@better-auth/expo"], + projectDir: nativeDir, }); + if (serverDirExists) { + await addPackageDependency({ + dependencies: ["@better-auth/expo"], + projectDir: serverDir, + }); + } } } } catch (error) { diff --git a/apps/cli/src/helpers/core/command-handlers.ts b/apps/cli/src/helpers/core/command-handlers.ts index f6d59ce..fc97645 100644 --- a/apps/cli/src/helpers/core/command-handlers.ts +++ b/apps/cli/src/helpers/core/command-handlers.ts @@ -103,7 +103,7 @@ export async function createProjectHandler( frontend: [], addons: [], examples: [], - auth: false, + auth: "none", git: false, packageManager: "npm", install: false, @@ -154,11 +154,11 @@ export async function createProjectHandler( if (config.backend === "convex") { log.info( - "Due to '--backend convex' flag, the following options have been automatically set: auth=false, database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo", + `Due to '--backend convex' flag, the following options have been automatically set: database=none, orm=none, api=none, runtime=none, dbSetup=none, examples=todo`, ); } else if (config.backend === "none") { log.info( - "Due to '--backend none', the following options have been automatically set: --auth=false, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none", + "Due to '--backend none', the following options have been automatically set: --auth none, --database=none, --orm=none, --api=none, --runtime=none, --db-setup=none, --examples=none", ); } diff --git a/apps/cli/src/helpers/core/create-project.ts b/apps/cli/src/helpers/core/create-project.ts index 8fd797f..e02a7b6 100644 --- a/apps/cli/src/helpers/core/create-project.ts +++ b/apps/cli/src/helpers/core/create-project.ts @@ -5,7 +5,6 @@ import { writeBtsConfig } from "../../utils/bts-config"; import { exitWithError } from "../../utils/errors"; import { formatProjectWithBiome } from "../../utils/format-with-biome"; import { setupAddons } from "../addons/addons-setup"; -import { setupAuth } from "../addons/auth-setup"; import { setupExamples } from "../addons/examples-setup"; import { setupApi } from "../core/api-setup"; import { setupBackendDependencies } from "../core/backend-setup"; @@ -13,6 +12,7 @@ import { setupDatabase } from "../core/db-setup"; import { setupRuntime } from "../core/runtime-setup"; import { setupServerDeploy } from "../deployment/server-deploy-setup"; import { setupWebDeploy } from "../deployment/web-deploy-setup"; +import { setupAuth } from "./auth-setup"; import { runConvexCodegen } from "./convex-codegen"; import { createReadme } from "./create-readme"; import { setupEnvironmentVariables } from "./env-setup"; @@ -46,8 +46,8 @@ export async function createProject(options: ProjectConfig) { if (!isConvex) { await setupDbOrmTemplates(projectDir, options); await setupDockerComposeTemplates(projectDir, options); - await setupAuthTemplate(projectDir, options); } + await setupAuthTemplate(projectDir, options); if (options.examples.length > 0 && options.examples[0] !== "none") { await setupExamplesTemplate(projectDir, options); } @@ -70,7 +70,7 @@ export async function createProject(options: ProjectConfig) { await setupAddons(options); } - if (!isConvex && options.auth) { + if (options.auth && options.auth !== "none") { await setupAuth(options); } diff --git a/apps/cli/src/helpers/core/create-readme.ts b/apps/cli/src/helpers/core/create-readme.ts index b0957de..5cb8430 100644 --- a/apps/cli/src/helpers/core/create-readme.ts +++ b/apps/cli/src/helpers/core/create-readme.ts @@ -4,6 +4,7 @@ import fs from "fs-extra"; import type { Addons, API, + Auth, Database, DatabaseSetup, Frontend, @@ -98,7 +99,11 @@ This project uses Convex as a backend. You'll need to set up Convex before runni ${packageManagerRunCmd} dev:setup \`\`\` -Follow the prompts to create a new Convex project and connect it to your application.` +Follow the prompts to create a new Convex project and connect it to your application.${ + auth === "clerk" + ? " See [Convex + Clerk guide](https://docs.convex.dev/auth/clerk) for auth setup." + : "" + }` : generateDatabaseSetup( database, auth, @@ -135,6 +140,7 @@ ${generateProjectStructure( addons, isConvex, api, + auth, )} \`\`\` @@ -258,6 +264,7 @@ function generateProjectStructure( addons: Addons[], isConvex: boolean, api: API, + auth: Auth, ): string { const structure: string[] = [`${projectName}/`, "├── apps/"]; @@ -317,6 +324,12 @@ function generateProjectStructure( structure.push( "│ └── backend/ # Convex backend functions and schema", ); + if (auth === "clerk") { + structure.push( + "│ ├── convex/ # Convex functions and schema", + "│ └── .env.local # Convex environment variables", + ); + } } else if (!isBackendNone) { const backendName = backend[0].toUpperCase() + backend.slice(1); const apiName = api !== "none" ? api.toUpperCase() : ""; @@ -329,7 +342,7 @@ function generateProjectStructure( function generateFeaturesList( database: Database, - auth: boolean, + auth: Auth, addons: Addons[], orm: ORM, runtime: Runtime, @@ -449,10 +462,9 @@ function generateFeaturesList( ); } - if (auth && !isConvex) { - addonsList.push( - "- **Authentication** - Email & password authentication with Better Auth", - ); + if (auth !== "none") { + const authLabel = auth === "clerk" ? "Clerk" : "Better-Auth"; + addonsList.push(`- **Authentication** - ${authLabel}`); } for (const addon of addons) { @@ -476,7 +488,7 @@ function generateFeaturesList( function generateDatabaseSetup( database: Database, - _auth: boolean, + _auth: Auth, packageManagerRunCmd: string, orm: ORM, dbSetup: DatabaseSetup, @@ -575,7 +587,7 @@ function generateScriptsList( packageManagerRunCmd: string, database: Database, orm: ORM, - _auth: boolean, + _auth: Auth, hasNative: boolean, addons: Addons[], backend: string, diff --git a/apps/cli/src/helpers/core/env-setup.ts b/apps/cli/src/helpers/core/env-setup.ts index 0431965..4578de9 100644 --- a/apps/cli/src/helpers/core/env-setup.ts +++ b/apps/cli/src/helpers/core/env-setup.ts @@ -1,7 +1,7 @@ import path from "node:path"; import fs from "fs-extra"; import type { ProjectConfig } from "../../types"; -import { generateAuthSecret } from "../addons/auth-setup"; +import { generateAuthSecret } from "./auth-setup"; export interface EnvVariable { key: string; @@ -143,6 +143,42 @@ export async function setupEnvironmentVariables(config: ProjectConfig) { condition: true, }, ]; + + if (backend === "convex" && auth === "clerk") { + if (hasNextJs) { + clientVars.push( + { + key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL", + value: "", + condition: true, + }, + { + key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY", + value: "", + condition: true, + }, + { + key: "CLERK_SECRET_KEY", + value: "", + condition: true, + }, + ); + } else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) { + clientVars.push({ + key: "VITE_CLERK_PUBLISHABLE_KEY", + value: "", + condition: true, + }); + if (hasTanStackStart) { + clientVars.push({ + key: "CLERK_SECRET_KEY", + value: "", + condition: true, + }); + } + } + } + await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars); } } @@ -168,6 +204,14 @@ export async function setupEnvironmentVariables(config: ProjectConfig) { condition: true, }, ]; + + if (backend === "convex" && auth === "clerk") { + nativeVars.push({ + key: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY", + value: "", + condition: true, + }); + } await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars); } } diff --git a/apps/cli/src/helpers/core/post-installation.ts b/apps/cli/src/helpers/core/post-installation.ts index 1fe012d..d161d2e 100644 --- a/apps/cli/src/helpers/core/post-installation.ts +++ b/apps/cli/src/helpers/core/post-installation.ts @@ -69,6 +69,8 @@ export async function displayPostInstallInstructions( const starlightInstructions = addons?.includes("starlight") ? getStarlightInstructions(runCmd) : ""; + const clerkInstructions = + isConvex && config.auth === "clerk" ? getClerkInstructions() : ""; const wranglerDeployInstructions = getWranglerDeployInstructions( runCmd, webDeploy, @@ -119,6 +121,16 @@ export async function displayPostInstallInstructions( output += `${pc.cyan(`${stepCounter++}.`)} ${runCmd} dev:setup\n${pc.dim( " (this will guide you through Convex project setup)", )}\n`; + + if (config.auth === "clerk") { + output += `${pc.cyan(`${stepCounter++}.`)} ${pc.bold("Clerk Setup:")}\n${pc.dim( + " Follow the Convex + Clerk guide to configure authentication:", + )}\n${pc.cyan(" ")}${pc.underline("https://docs.convex.dev/auth/clerk")}\n\n`; + output += `${pc.cyan(`${stepCounter++}.`)} ${pc.bold("Required Environment Variables:")}\n`; + output += `${pc.dim(" •")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n`; + output += `${pc.dim(" •")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env\n`; + } + output += `${pc.cyan( `${stepCounter++}.`, )} Copy environment variables from\n${pc.white( @@ -175,6 +187,7 @@ export async function displayPostInstallInstructions( if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`; if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`; + if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`; if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`; if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`; @@ -403,6 +416,10 @@ function getWranglerDeployInstructions( return instructions.length ? `\n${instructions.join("\n")}` : ""; } +function getClerkInstructions(): string { + return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("1.")} Sign up for Clerk at ${pc.underline("https://clerk.com/sign-up")}\n${pc.cyan("2.")} Create a new application in Clerk Dashboard\n${pc.cyan("3.")} Create a JWT template named ${pc.bold("'convex'")} (exact name required)\n${pc.cyan("4.")} Copy your Clerk Frontend API URL (Issuer URL)\n${pc.cyan("5.")} Set environment variables:\n${pc.dim(" •")} CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.dim(" •")} CLERK_PUBLISHABLE_KEY in apps/*/.env\n${pc.cyan("6.")} Follow the complete guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.yellow("NOTE:")} Use Convex's components instead of Clerk's `; +} + function getAlchemyDeployInstructions( runCmd?: string, webDeploy?: string, diff --git a/apps/cli/src/helpers/core/template-manager.ts b/apps/cli/src/helpers/core/template-manager.ts index 1cfa235..f352e44 100644 --- a/apps/cli/src/helpers/core/template-manager.ts +++ b/apps/cli/src/helpers/core/template-manager.ts @@ -362,7 +362,7 @@ export async function setupAuthTemplate( projectDir: string, context: ProjectConfig, ) { - if (context.backend === "convex" || !context.auth) return; + if (!context.auth || context.auth === "none") return; const serverAppDir = path.join(projectDir, "apps/server"); const webAppDir = path.join(projectDir, "apps/web"); @@ -382,8 +382,88 @@ export async function setupAuthTemplate( const hasUnistyles = context.frontend.includes("native-unistyles"); const hasNative = hasNativeWind || hasUnistyles; - if (serverAppDirExists) { - const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base"); + const authProvider = context.auth; + + if (context.backend === "convex" && authProvider === "clerk") { + const convexBackendDestDir = path.join(projectDir, "packages/backend"); + const convexClerkBackendSrc = path.join( + PKG_ROOT, + "templates/auth/clerk/convex/backend", + ); + if (await fs.pathExists(convexClerkBackendSrc)) { + await fs.ensureDir(convexBackendDestDir); + await processAndCopyFiles( + "**/*", + convexClerkBackendSrc, + convexBackendDestDir, + context, + ); + } + + if (webAppDirExists) { + const reactFramework = context.frontend.find((f) => + ["tanstack-router", "react-router", "tanstack-start", "next"].includes( + f, + ), + ); + if (reactFramework) { + const convexClerkWebSrc = path.join( + PKG_ROOT, + `templates/auth/clerk/convex/web/react/${reactFramework}`, + ); + if (await fs.pathExists(convexClerkWebSrc)) { + await processAndCopyFiles( + "**/*", + convexClerkWebSrc, + webAppDir, + context, + ); + } + } + } + + if (nativeAppDirExists) { + const convexClerkNativeBaseSrc = path.join( + PKG_ROOT, + "templates/auth/clerk/convex/native/base", + ); + if (await fs.pathExists(convexClerkNativeBaseSrc)) { + await processAndCopyFiles( + "**/*", + convexClerkNativeBaseSrc, + nativeAppDir, + context, + ); + } + + const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasUnistyles = context.frontend.includes("native-unistyles"); + let nativeFrameworkPath = ""; + if (hasNativeWind) nativeFrameworkPath = "nativewind"; + else if (hasUnistyles) nativeFrameworkPath = "unistyles"; + if (nativeFrameworkPath) { + const convexClerkNativeFrameworkSrc = path.join( + PKG_ROOT, + `templates/auth/clerk/convex/native/${nativeFrameworkPath}`, + ); + if (await fs.pathExists(convexClerkNativeFrameworkSrc)) { + await processAndCopyFiles( + "**/*", + convexClerkNativeFrameworkSrc, + nativeAppDir, + context, + ); + } + } + } + return; + } + + if (serverAppDirExists && context.backend !== "convex") { + const authServerBaseSrc = path.join( + PKG_ROOT, + `templates/auth/${authProvider}/server/base`, + ); if (await fs.pathExists(authServerBaseSrc)) { await processAndCopyFiles( "**/*", @@ -391,13 +471,12 @@ export async function setupAuthTemplate( serverAppDir, context, ); - } else { } if (context.backend === "next") { const authServerNextSrc = path.join( PKG_ROOT, - "templates/auth/server/next", + `templates/auth/${authProvider}/server/next`, ); if (await fs.pathExists(authServerNextSrc)) { await processAndCopyFiles( @@ -406,7 +485,6 @@ export async function setupAuthTemplate( serverAppDir, context, ); - } else { } } @@ -417,22 +495,21 @@ export async function setupAuthTemplate( if (orm === "drizzle") { authDbSrc = path.join( PKG_ROOT, - `templates/auth/server/db/drizzle/${db}`, + `templates/auth/${authProvider}/server/db/drizzle/${db}`, ); } else if (orm === "prisma") { authDbSrc = path.join( PKG_ROOT, - `templates/auth/server/db/prisma/${db}`, + `templates/auth/${authProvider}/server/db/prisma/${db}`, ); } else if (orm === "mongoose") { authDbSrc = path.join( PKG_ROOT, - `templates/auth/server/db/mongoose/${db}`, + `templates/auth/${authProvider}/server/db/mongoose/${db}`, ); } if (authDbSrc && (await fs.pathExists(authDbSrc))) { await processAndCopyFiles("**/*", authDbSrc, serverAppDir, context); - } else if (authDbSrc) { } } } @@ -444,11 +521,10 @@ export async function setupAuthTemplate( if (hasReactWeb) { const authWebBaseSrc = path.join( PKG_ROOT, - "templates/auth/web/react/base", + `templates/auth/${authProvider}/web/react/base`, ); if (await fs.pathExists(authWebBaseSrc)) { await processAndCopyFiles("**/*", authWebBaseSrc, webAppDir, context); - } else { } const reactFramework = context.frontend.find((f) => @@ -459,7 +535,7 @@ export async function setupAuthTemplate( if (reactFramework) { const authWebFrameworkSrc = path.join( PKG_ROOT, - `templates/auth/web/react/${reactFramework}`, + `templates/auth/${authProvider}/web/react/${reactFramework}`, ); if (await fs.pathExists(authWebFrameworkSrc)) { await processAndCopyFiles( @@ -468,26 +544,31 @@ export async function setupAuthTemplate( webAppDir, context, ); - } else { } } } else if (hasNuxtWeb) { - const authWebNuxtSrc = path.join(PKG_ROOT, "templates/auth/web/nuxt"); + const authWebNuxtSrc = path.join( + PKG_ROOT, + `templates/auth/${authProvider}/web/nuxt`, + ); if (await fs.pathExists(authWebNuxtSrc)) { await processAndCopyFiles("**/*", authWebNuxtSrc, webAppDir, context); - } else { } } else if (hasSvelteWeb) { - const authWebSvelteSrc = path.join(PKG_ROOT, "templates/auth/web/svelte"); + const authWebSvelteSrc = path.join( + PKG_ROOT, + `templates/auth/${authProvider}/web/svelte`, + ); if (await fs.pathExists(authWebSvelteSrc)) { await processAndCopyFiles("**/*", authWebSvelteSrc, webAppDir, context); - } else { } } else if (hasSolidWeb) { - const authWebSolidSrc = path.join(PKG_ROOT, "templates/auth/web/solid"); + const authWebSolidSrc = path.join( + PKG_ROOT, + `templates/auth/${authProvider}/web/solid`, + ); if (await fs.pathExists(authWebSolidSrc)) { await processAndCopyFiles("**/*", authWebSolidSrc, webAppDir, context); - } else { } } } @@ -495,7 +576,7 @@ export async function setupAuthTemplate( if (hasNative && nativeAppDirExists) { const authNativeBaseSrc = path.join( PKG_ROOT, - "templates/auth/native/native-base", + `templates/auth/${authProvider}/native/native-base`, ); if (await fs.pathExists(authNativeBaseSrc)) { await processAndCopyFiles( @@ -516,7 +597,7 @@ export async function setupAuthTemplate( if (nativeFrameworkAuthPath) { const authNativeFrameworkSrc = path.join( PKG_ROOT, - `templates/auth/native/${nativeFrameworkAuthPath}`, + `templates/auth/${authProvider}/native/${nativeFrameworkAuthPath}`, ); if (await fs.pathExists(authNativeFrameworkSrc)) { await processAndCopyFiles( diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts index 6f142cb..2fde6e4 100644 --- a/apps/cli/src/index.ts +++ b/apps/cli/src/index.ts @@ -12,6 +12,7 @@ import { AddonsSchema, type API, APISchema, + AuthSchema, type Backend, BackendSchema, type BetterTStackConfig, @@ -78,7 +79,7 @@ export const router = t.router({ .describe("Show detailed result information"), database: DatabaseSchema.optional(), orm: ORMSchema.optional(), - auth: z.boolean().optional(), + auth: AuthSchema.optional(), frontend: z.array(FrontendSchema).optional(), addons: z.array(AddonsSchema).optional(), examples: z.array(ExamplesSchema).optional(), @@ -202,7 +203,7 @@ export function createBtsCli() { * backend: "hono", * database: "sqlite", * orm: "drizzle", - * auth: true, + * auth: "better-auth", * addons: ["biome", "turborepo"], * packageManager: "bun", * install: false, diff --git a/apps/cli/src/prompts/auth.ts b/apps/cli/src/prompts/auth.ts index 46a79aa..cc3b17b 100644 --- a/apps/cli/src/prompts/auth.ts +++ b/apps/cli/src/prompts/auth.ts @@ -1,27 +1,56 @@ -import { confirm, isCancel } from "@clack/prompts"; +import { isCancel, select } from "@clack/prompts"; import { DEFAULT_CONFIG } from "../constants"; -import type { Backend } from "../types"; +import type { Auth, Backend } from "../types"; import { exitCancelled } from "../utils/errors"; export async function getAuthChoice( - auth: boolean | undefined, + auth: Auth | undefined, hasDatabase: boolean, backend?: Backend, + frontend?: string[], ) { + if (auth !== undefined) return auth; if (backend === "convex") { - return false; + const unsupportedFrontends = frontend?.filter((f) => + ["nuxt", "svelte", "solid"].includes(f), + ); + + if (unsupportedFrontends && unsupportedFrontends.length > 0) { + return "none"; + } + + const response = await select({ + message: "Select authentication provider", + options: [ + { + value: "clerk", + label: "Clerk", + hint: "More than auth, Complete User Management", + }, + { value: "none", label: "None" }, + ], + initialValue: "clerk", + }); + if (isCancel(response)) return exitCancelled("Operation cancelled"); + return response as Auth; } - if (!hasDatabase) return false; + if (!hasDatabase) return "none"; - if (auth !== undefined) return auth; - - const response = await confirm({ - message: "Add authentication with Better-Auth?", + const response = await select({ + message: "Select authentication provider", + options: [ + { + value: "better-auth", + label: "Better-Auth", + hint: "comprehensive auth framework for TypeScript", + }, + { value: "none", label: "None" }, + ], initialValue: DEFAULT_CONFIG.auth, }); if (isCancel(response)) return exitCancelled("Operation cancelled"); - return response; + return response as Auth; } diff --git a/apps/cli/src/prompts/config-prompts.ts b/apps/cli/src/prompts/config-prompts.ts index 50deae9..cc20b58 100644 --- a/apps/cli/src/prompts/config-prompts.ts +++ b/apps/cli/src/prompts/config-prompts.ts @@ -2,6 +2,7 @@ import { group } from "@clack/prompts"; import type { Addons, API, + Auth, Backend, Database, DatabaseSetup, @@ -38,7 +39,7 @@ type PromptGroupResults = { database: Database; orm: ORM; api: API; - auth: boolean; + auth: Auth; addons: Addons[]; examples: Examples[]; dbSetup: DatabaseSetup; @@ -57,7 +58,8 @@ export async function gatherConfig( ): Promise { const result = await group( { - frontend: () => getFrontendChoice(flags.frontend, flags.backend), + frontend: () => + getFrontendChoice(flags.frontend, flags.backend, flags.auth), backend: ({ results }) => getBackendFrameworkChoice(flags.backend, results.frontend), runtime: ({ results }) => @@ -75,7 +77,12 @@ export async function gatherConfig( api: ({ results }) => getApiChoice(flags.api, results.frontend, results.backend), auth: ({ results }) => - getAuthChoice(flags.auth, results.database !== "none", results.backend), + getAuthChoice( + flags.auth as import("../types").Auth | undefined, + results.database !== "none", + results.backend, + results.frontend, + ), addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend), examples: ({ results }) => getExamplesChoice( @@ -121,7 +128,6 @@ export async function gatherConfig( result.database = "none"; result.orm = "none"; result.api = "none"; - result.auth = false; result.dbSetup = "none"; result.examples = ["todo"]; } @@ -131,7 +137,7 @@ export async function gatherConfig( result.database = "none"; result.orm = "none"; result.api = "none"; - result.auth = false; + result.auth = "none"; result.dbSetup = "none"; result.examples = []; } diff --git a/apps/cli/src/prompts/frontend.ts b/apps/cli/src/prompts/frontend.ts index 7bc2cb7..9b9b60f 100644 --- a/apps/cli/src/prompts/frontend.ts +++ b/apps/cli/src/prompts/frontend.ts @@ -7,6 +7,7 @@ import { exitCancelled } from "../utils/errors"; export async function getFrontendChoice( frontendOptions?: Frontend[], backend?: Backend, + auth?: string, ): Promise { if (frontendOptions !== undefined) return frontendOptions; @@ -72,7 +73,7 @@ export async function getFrontendChoice( ]; const webOptions = allWebOptions.filter((option) => - isFrontendAllowedWithBackend(option.value, backend), + isFrontendAllowedWithBackend(option.value, backend, auth), ); const webFramework = await select({ diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index d33aabb..fce9a9b 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -80,6 +80,11 @@ export type DatabaseSetup = z.infer; export const APISchema = z.enum(["trpc", "orpc", "none"]).describe("API type"); export type API = z.infer; +export const AuthSchema = z + .enum(["better-auth", "clerk", "none"]) + .describe("Authentication provider"); +export type Auth = z.infer; + export const ProjectNameSchema = z .string() .min(1, "Project name cannot be empty") @@ -125,7 +130,7 @@ export type CreateInput = { verbose?: boolean; database?: Database; orm?: ORM; - auth?: boolean; + auth?: Auth; frontend?: Frontend[]; addons?: Addons[]; examples?: Examples[]; @@ -167,7 +172,7 @@ export interface ProjectConfig { frontend: Frontend[]; addons: Addons[]; examples: Examples[]; - auth: boolean; + auth: Auth; git: boolean; packageManager: PackageManager; install: boolean; @@ -187,7 +192,7 @@ export interface BetterTStackConfig { frontend: Frontend[]; addons: Addons[]; examples: Examples[]; - auth: boolean; + auth: Auth; packageManager: PackageManager; dbSetup: DatabaseSetup; api: API; diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index ca4abc4..8f3eaad 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -135,7 +135,6 @@ export function validateWorkersCompatibility( export function coerceBackendPresets(config: Partial) { if (config.backend === "convex") { - config.auth = false; config.database = "none"; config.orm = "none"; config.api = "none"; @@ -144,7 +143,7 @@ export function coerceBackendPresets(config: Partial) { config.examples = ["todo"] as ProjectConfig["examples"]; } if (config.backend === "none") { - config.auth = false; + config.auth = "none" as ProjectConfig["auth"]; config.database = "none"; config.orm = "none"; config.api = "none"; @@ -161,7 +160,13 @@ export function incompatibleFlagsForBackend( ): string[] { const list: string[] = []; if (backend === "convex") { - if (providedFlags.has("auth") && options.auth === true) list.push("--auth"); + if ( + providedFlags.has("auth") && + options.auth && + options.auth !== "none" && + options.auth !== "clerk" + ) + list.push(`--auth ${options.auth}`); if (providedFlags.has("database") && options.database !== "none") list.push(`--database ${options.database}`); if (providedFlags.has("orm") && options.orm !== "none") @@ -174,7 +179,8 @@ export function incompatibleFlagsForBackend( list.push(`--db-setup ${options.dbSetup}`); } if (backend === "none") { - if (providedFlags.has("auth") && options.auth === true) list.push("--auth"); + if (providedFlags.has("auth") && options.auth && options.auth !== "none") + list.push(`--auth ${options.auth}`); if (providedFlags.has("database") && options.database !== "none") list.push(`--database ${options.database}`); if (providedFlags.has("orm") && options.orm !== "none") @@ -210,8 +216,15 @@ export function validateApiFrontendCompatibility( export function isFrontendAllowedWithBackend( frontend: Frontend, backend?: ProjectConfig["backend"], + auth?: string, ) { if (backend === "convex" && frontend === "solid") return false; + + if (auth === "clerk" && backend === "convex") { + const incompatibleFrontends = ["nuxt", "svelte", "solid"]; + if (incompatibleFrontends.includes(frontend)) return false; + } + return true; } diff --git a/apps/cli/src/utils/config-processing.ts b/apps/cli/src/utils/config-processing.ts index a57299b..255c0ca 100644 --- a/apps/cli/src/utils/config-processing.ts +++ b/apps/cli/src/utils/config-processing.ts @@ -1,6 +1,7 @@ import path from "node:path"; import type { API, + Auth, Backend, CLIInput, Database, @@ -57,7 +58,7 @@ export function processFlags( } if (options.auth !== undefined) { - config.auth = options.auth; + config.auth = options.auth as Auth; } if (options.git !== undefined) { diff --git a/apps/cli/src/utils/config-validation.ts b/apps/cli/src/utils/config-validation.ts index e1e4d52..ce83fde 100644 --- a/apps/cli/src/utils/config-validation.ts +++ b/apps/cli/src/utils/config-validation.ts @@ -66,15 +66,21 @@ export function validateDatabaseOrmAuth( ); } - if (has("auth") && has("database") && cfg.auth && db === "none") { + if ( + has("auth") && + has("database") && + cfg.auth !== "none" && + db === "none" && + cfg.backend !== "convex" + ) { exitWithError( - "Authentication requires a database. Please choose a database or set '--no-auth'.", + "Authentication requires a database. Please choose a database or set '--auth none'.", ); } - if (cfg.auth && db === "none") { + if (cfg.auth !== "none" && db === "none" && cfg.backend !== "convex") { exitWithError( - "Authentication requires a database. Please choose a database or set '--no-auth'.", + "Authentication requires a database. Please choose a database or set '--auth none'.", ); } @@ -178,6 +184,35 @@ export function validateBackendConstraints( ): void { const { backend } = config; + if (config.auth === "clerk" && backend !== "convex") { + exitWithError( + "Clerk authentication is only supported with the Convex backend. Please use '--backend convex' or choose a different auth provider.", + ); + } + + if (backend === "convex" && config.auth === "clerk" && config.frontend) { + const incompatibleFrontends = config.frontend.filter((f) => + ["nuxt", "svelte", "solid"].includes(f), + ); + if (incompatibleFrontends.length > 0) { + exitWithError( + `Clerk authentication is not compatible with the following frontends: ${incompatibleFrontends.join( + ", ", + )}. Please choose a different frontend or auth provider.`, + ); + } + } + + if ( + backend === "convex" && + config.auth === "better-auth" && + providedFlags.has("auth") + ) { + exitWithError( + "Better-Auth is not compatible with the Convex backend. Please use '--auth clerk' or '--auth none'.", + ); + } + if ( providedFlags.has("backend") && backend && diff --git a/apps/cli/src/utils/display-config.ts b/apps/cli/src/utils/display-config.ts index 8a92d05..971c5d9 100644 --- a/apps/cli/src/utils/display-config.ts +++ b/apps/cli/src/utils/display-config.ts @@ -40,13 +40,7 @@ export function displayConfig(config: Partial) { } if (config.auth !== undefined) { - const authText = - typeof config.auth === "boolean" - ? config.auth - ? "Yes" - : "No" - : String(config.auth); - configDisplay.push(`${pc.blue("Authentication:")} ${authText}`); + configDisplay.push(`${pc.blue("Auth:")} ${String(config.auth)}`); } if (config.addons !== undefined) { diff --git a/apps/cli/src/utils/generate-reproducible-command.ts b/apps/cli/src/utils/generate-reproducible-command.ts index 330fe71..942c705 100644 --- a/apps/cli/src/utils/generate-reproducible-command.ts +++ b/apps/cli/src/utils/generate-reproducible-command.ts @@ -14,7 +14,7 @@ export function generateReproducibleCommand(config: ProjectConfig): string { flags.push(`--database ${config.database}`); flags.push(`--orm ${config.orm}`); flags.push(`--api ${config.api}`); - flags.push(config.auth ? "--auth" : "--no-auth"); + flags.push(`--auth ${config.auth}`); if (config.addons && config.addons.length > 0) { flags.push(`--addons ${config.addons.join(" ")}`); diff --git a/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs b/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs index 2ad0229..2e7a059 100644 --- a/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs +++ b/apps/cli/templates/addons/ruler/.ruler/bts.md.hbs @@ -77,7 +77,7 @@ Database models are located in `apps/server/src/db/models/` {{/if}} {{/if}} -{{#if auth}} +{{#if (eq auth "better-auth")}} ## Authentication Authentication is enabled in this project: @@ -129,4 +129,4 @@ This project includes a `bts.jsonc` configuration file that stores your Better-T - Turborepo handles build caching and parallel execution {{/if}} - Use `{{#if (eq packageManager "bun")}}bunx{{else if (eq packageManager "pnpm")}}pnpx{{else}}npx{{/if}} -create-better-t-stack add` to add more features later \ No newline at end of file +create-better-t-stack add` to add more features later diff --git a/apps/cli/templates/api/orpc/native/utils/orpc.ts.hbs b/apps/cli/templates/api/orpc/native/utils/orpc.ts.hbs index 01a1aa2..e1e6f4e 100644 --- a/apps/cli/templates/api/orpc/native/utils/orpc.ts.hbs +++ b/apps/cli/templates/api/orpc/native/utils/orpc.ts.hbs @@ -3,7 +3,7 @@ import { RPCLink } from "@orpc/client/fetch"; import { createTanstackQueryUtils } from "@orpc/tanstack-query"; import { QueryCache, QueryClient } from "@tanstack/react-query"; import type { AppRouterClient } from "../../server/src/routers"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { authClient } from "@/lib/auth-client"; {{/if}} @@ -17,7 +17,7 @@ export const queryClient = new QueryClient({ export const link = new RPCLink({ url: `${process.env.EXPO_PUBLIC_SERVER_URL}/rpc`, - {{#if auth}} + {{#if (eq auth "better-auth")}} headers() { const headers = new Map(); const cookies = authClient.getCookie(); diff --git a/apps/cli/templates/api/orpc/server/base/src/lib/context.ts.hbs b/apps/cli/templates/api/orpc/server/base/src/lib/context.ts.hbs index d14560c..8dcd19a 100644 --- a/apps/cli/templates/api/orpc/server/base/src/lib/context.ts.hbs +++ b/apps/cli/templates/api/orpc/server/base/src/lib/context.ts.hbs @@ -1,11 +1,11 @@ {{#if (eq backend 'next')}} import type { NextRequest } from "next/server"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./auth"; {{/if}} export async function createContext(req: NextRequest) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: req.headers, }); @@ -19,7 +19,7 @@ export async function createContext(req: NextRequest) { {{else if (eq backend 'hono')}} import type { Context as HonoContext } from "hono"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./auth"; {{/if}} @@ -28,7 +28,7 @@ export type CreateContextOptions = { }; export async function createContext({ context }: CreateContextOptions) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: context.req.raw.headers, }); @@ -45,7 +45,7 @@ export async function createContext({ context }: CreateContextOptions) { {{else if (eq backend 'elysia')}} import type { Context as ElysiaContext } from "elysia"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./auth"; {{/if}} @@ -54,7 +54,7 @@ export type CreateContextOptions = { }; export async function createContext({ context }: CreateContextOptions) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: context.request.headers, }); @@ -70,13 +70,13 @@ export async function createContext({ context }: CreateContextOptions) { } {{else if (eq backend 'express')}} -{{#if auth}} +{{#if (eq auth "better-auth")}} import { fromNodeHeaders } from "better-auth/node"; import { auth } from "./auth"; {{/if}} export async function createContext(opts: any) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: fromNodeHeaders(opts.req.headers), }); @@ -93,13 +93,13 @@ export async function createContext(opts: any) { {{else if (eq backend 'fastify')}} import type { IncomingHttpHeaders } from "node:http"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { fromNodeHeaders } from "better-auth/node"; import { auth } from "./auth"; {{/if}} export async function createContext(req: IncomingHttpHeaders) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: fromNodeHeaders(req), }); diff --git a/apps/cli/templates/api/orpc/server/base/src/lib/orpc.ts.hbs b/apps/cli/templates/api/orpc/server/base/src/lib/orpc.ts.hbs index 973532c..0f8ec9f 100644 --- a/apps/cli/templates/api/orpc/server/base/src/lib/orpc.ts.hbs +++ b/apps/cli/templates/api/orpc/server/base/src/lib/orpc.ts.hbs @@ -5,7 +5,7 @@ export const o = os.$context(); export const publicProcedure = o; -{{#if auth}} +{{#if (eq auth "better-auth")}} const requireAuth = o.middleware(async ({ context, next }) => { if (!context.session?.user) { throw new ORPCError("UNAUTHORIZED"); diff --git a/apps/cli/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs b/apps/cli/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs index 94b5da5..5250db4 100644 --- a/apps/cli/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs +++ b/apps/cli/templates/api/orpc/server/next/src/app/rpc/[...all]/route.ts.hbs @@ -1,4 +1,4 @@ -{{#if auth}} +{{#if (eq auth "better-auth")}} import { createContext } from '@/lib/context' {{/if}} import { appRouter } from '@/routers' @@ -10,7 +10,7 @@ const handler = new RPCHandler(appRouter) async function handleRequest(req: NextRequest) { const { response } = await handler.handle(req, { prefix: '/rpc', - context: {{#if auth}}await createContext(req){{else}}{}{{/if}}, + context: {{#if (eq auth "better-auth")}}await createContext(req){{else}}{}{{/if}}, }) return response ?? new Response('Not found', { status: 404 }) diff --git a/apps/cli/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs b/apps/cli/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs index ab831df..43257cf 100644 --- a/apps/cli/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +++ b/apps/cli/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs @@ -12,7 +12,7 @@ export default defineNuxtPlugin(() => { const rpcLink = new RPCLink({ url: rpcUrl, - {{#if auth}} + {{#if (eq auth "better-auth")}} fetch(url, options) { return fetch(url, { ...options, diff --git a/apps/cli/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs b/apps/cli/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs index 4060212..b135648 100644 --- a/apps/cli/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs +++ b/apps/cli/templates/api/orpc/web/react/base/src/utils/orpc.ts.hbs @@ -26,7 +26,7 @@ export const link = new RPCLink({ {{else}} url: `${import.meta.env.VITE_SERVER_URL}/rpc`, {{/if}} - {{#if auth}} + {{#if (eq auth "better-auth")}} fetch(url, options) { return fetch(url, { ...options, diff --git a/apps/cli/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs b/apps/cli/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs index ca885fc..8dabcd0 100644 --- a/apps/cli/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs +++ b/apps/cli/templates/api/orpc/web/solid/src/utils/orpc.ts.hbs @@ -14,7 +14,7 @@ export const queryClient = new QueryClient({ export const link = new RPCLink({ url: `${import.meta.env.VITE_SERVER_URL}/rpc`, - {{#if auth}} + {{#if (eq auth "better-auth")}} fetch(url, options) { return fetch(url, { ...options, diff --git a/apps/cli/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs b/apps/cli/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs index d05d5e5..f95cdab 100644 --- a/apps/cli/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs +++ b/apps/cli/templates/api/orpc/web/svelte/src/lib/orpc.ts.hbs @@ -15,7 +15,7 @@ export const queryClient = new QueryClient({ export const link = new RPCLink({ url: `${PUBLIC_SERVER_URL}/rpc`, - {{#if auth}} + {{#if (eq auth "better-auth")}} fetch(url, options) { return fetch(url, { ...options, diff --git a/apps/cli/templates/api/trpc/native/utils/trpc.ts.hbs b/apps/cli/templates/api/trpc/native/utils/trpc.ts.hbs index f4560fe..7f8e413 100644 --- a/apps/cli/templates/api/trpc/native/utils/trpc.ts.hbs +++ b/apps/cli/templates/api/trpc/native/utils/trpc.ts.hbs @@ -1,4 +1,4 @@ -{{#if auth}} +{{#if (eq auth "better-auth")}} import { authClient } from "@/lib/auth-client"; {{/if}} import { QueryClient } from "@tanstack/react-query"; @@ -12,7 +12,7 @@ const trpcClient = createTRPCClient({ links: [ httpBatchLink({ url: `${process.env.EXPO_PUBLIC_SERVER_URL}/trpc`, - {{#if auth}} + {{#if (eq auth "better-auth")}} headers() { const headers = new Map(); const cookies = authClient.getCookie(); diff --git a/apps/cli/templates/api/trpc/server/base/src/lib/context.ts.hbs b/apps/cli/templates/api/trpc/server/base/src/lib/context.ts.hbs index 927c893..cc816e1 100644 --- a/apps/cli/templates/api/trpc/server/base/src/lib/context.ts.hbs +++ b/apps/cli/templates/api/trpc/server/base/src/lib/context.ts.hbs @@ -1,11 +1,11 @@ {{#if (eq backend 'next')}} import type { NextRequest } from "next/server"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./auth"; {{/if}} export async function createContext(req: NextRequest) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: req.headers, }); @@ -22,7 +22,7 @@ export async function createContext(req: NextRequest) { {{else if (eq backend 'hono')}} import type { Context as HonoContext } from "hono"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./auth"; {{/if}} @@ -31,7 +31,7 @@ export type CreateContextOptions = { }; export async function createContext({ context }: CreateContextOptions) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: context.req.raw.headers, }); @@ -48,7 +48,7 @@ export async function createContext({ context }: CreateContextOptions) { {{else if (eq backend 'elysia')}} import type { Context as ElysiaContext } from "elysia"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./auth"; {{/if}} @@ -57,7 +57,7 @@ export type CreateContextOptions = { }; export async function createContext({ context }: CreateContextOptions) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: context.request.headers, }); @@ -74,13 +74,13 @@ export async function createContext({ context }: CreateContextOptions) { {{else if (eq backend 'express')}} import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { fromNodeHeaders } from "better-auth/node"; import { auth } from "./auth"; {{/if}} export async function createContext(opts: CreateExpressContextOptions) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: fromNodeHeaders(opts.req.headers), }); @@ -97,13 +97,13 @@ export async function createContext(opts: CreateExpressContextOptions) { {{else if (eq backend 'fastify')}} import type { CreateFastifyContextOptions } from "@trpc/server/adapters/fastify"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { fromNodeHeaders } from "better-auth/node"; import { auth } from "./auth"; {{/if}} export async function createContext({ req, res }: CreateFastifyContextOptions) { -{{#if auth}} +{{#if (eq auth "better-auth")}} const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers), }); diff --git a/apps/cli/templates/api/trpc/server/base/src/lib/trpc.ts.hbs b/apps/cli/templates/api/trpc/server/base/src/lib/trpc.ts.hbs index f3dade3..717c019 100644 --- a/apps/cli/templates/api/trpc/server/base/src/lib/trpc.ts.hbs +++ b/apps/cli/templates/api/trpc/server/base/src/lib/trpc.ts.hbs @@ -7,7 +7,7 @@ export const router = t.router; export const publicProcedure = t.procedure; -{{#if auth}} +{{#if (eq auth "better-auth")}} export const protectedProcedure = t.procedure.use(({ ctx, next }) => { if (!ctx.session) { throw new TRPCError({ diff --git a/apps/cli/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs b/apps/cli/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs index 90e0e56..8132362 100644 --- a/apps/cli/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs +++ b/apps/cli/templates/api/trpc/web/react/base/src/utils/trpc.ts.hbs @@ -28,7 +28,7 @@ const trpcClient = createTRPCClient({ {{else}} url: `${import.meta.env.VITE_SERVER_URL}/trpc`, {{/if}} - {{#if auth}} + {{#if (eq auth "better-auth")}} fetch(url, options) { return fetch(url, { ...options, @@ -78,7 +78,7 @@ export const trpcClient = createTRPCClient({ links: [ httpBatchLink({ url: `${import.meta.env.VITE_SERVER_URL}/trpc`, - {{#if auth}} + {{#if (eq auth "better-auth")}} fetch(url, options) { return fetch(url, { ...options, diff --git a/apps/cli/templates/auth/native/native-base/lib/auth-client.ts.hbs b/apps/cli/templates/auth/better-auth/native/native-base/lib/auth-client.ts.hbs similarity index 100% rename from apps/cli/templates/auth/native/native-base/lib/auth-client.ts.hbs rename to apps/cli/templates/auth/better-auth/native/native-base/lib/auth-client.ts.hbs diff --git a/apps/cli/templates/auth/native/nativewind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/nativewind/app/(drawer)/index.tsx.hbs rename to apps/cli/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs diff --git a/apps/cli/templates/auth/native/nativewind/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/nativewind/components/sign-in.tsx.hbs rename to apps/cli/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs diff --git a/apps/cli/templates/auth/native/nativewind/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/nativewind/components/sign-up.tsx.hbs rename to apps/cli/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs diff --git a/apps/cli/templates/auth/native/unistyles/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/unistyles/app/(drawer)/index.tsx.hbs rename to apps/cli/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs diff --git a/apps/cli/templates/auth/native/unistyles/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/unistyles/components/sign-in.tsx.hbs rename to apps/cli/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs diff --git a/apps/cli/templates/auth/native/unistyles/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/native/unistyles/components/sign-up.tsx.hbs rename to apps/cli/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs diff --git a/apps/cli/templates/auth/server/base/src/lib/auth.ts.hbs b/apps/cli/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs similarity index 100% rename from apps/cli/templates/auth/server/base/src/lib/auth.ts.hbs rename to apps/cli/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs diff --git a/apps/cli/templates/auth/server/db/drizzle/mysql/src/db/schema/auth.ts b/apps/cli/templates/auth/better-auth/server/db/drizzle/mysql/src/db/schema/auth.ts similarity index 100% rename from apps/cli/templates/auth/server/db/drizzle/mysql/src/db/schema/auth.ts rename to apps/cli/templates/auth/better-auth/server/db/drizzle/mysql/src/db/schema/auth.ts diff --git a/apps/cli/templates/auth/server/db/drizzle/postgres/src/db/schema/auth.ts b/apps/cli/templates/auth/better-auth/server/db/drizzle/postgres/src/db/schema/auth.ts similarity index 100% rename from apps/cli/templates/auth/server/db/drizzle/postgres/src/db/schema/auth.ts rename to apps/cli/templates/auth/better-auth/server/db/drizzle/postgres/src/db/schema/auth.ts diff --git a/apps/cli/templates/auth/server/db/drizzle/sqlite/src/db/schema/auth.ts b/apps/cli/templates/auth/better-auth/server/db/drizzle/sqlite/src/db/schema/auth.ts similarity index 100% rename from apps/cli/templates/auth/server/db/drizzle/sqlite/src/db/schema/auth.ts rename to apps/cli/templates/auth/better-auth/server/db/drizzle/sqlite/src/db/schema/auth.ts diff --git a/apps/cli/templates/auth/server/db/mongoose/mongodb/src/db/models/auth.model.ts b/apps/cli/templates/auth/better-auth/server/db/mongoose/mongodb/src/db/models/auth.model.ts similarity index 100% rename from apps/cli/templates/auth/server/db/mongoose/mongodb/src/db/models/auth.model.ts rename to apps/cli/templates/auth/better-auth/server/db/mongoose/mongodb/src/db/models/auth.model.ts diff --git a/apps/cli/templates/auth/server/db/prisma/mongodb/prisma/schema/auth.prisma b/apps/cli/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/auth.prisma similarity index 100% rename from apps/cli/templates/auth/server/db/prisma/mongodb/prisma/schema/auth.prisma rename to apps/cli/templates/auth/better-auth/server/db/prisma/mongodb/prisma/schema/auth.prisma diff --git a/apps/cli/templates/auth/server/db/prisma/mysql/prisma/schema/auth.prisma b/apps/cli/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/auth.prisma similarity index 100% rename from apps/cli/templates/auth/server/db/prisma/mysql/prisma/schema/auth.prisma rename to apps/cli/templates/auth/better-auth/server/db/prisma/mysql/prisma/schema/auth.prisma diff --git a/apps/cli/templates/auth/server/db/prisma/postgres/prisma/schema/auth.prisma b/apps/cli/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/auth.prisma similarity index 100% rename from apps/cli/templates/auth/server/db/prisma/postgres/prisma/schema/auth.prisma rename to apps/cli/templates/auth/better-auth/server/db/prisma/postgres/prisma/schema/auth.prisma diff --git a/apps/cli/templates/auth/server/db/prisma/sqlite/prisma/schema/auth.prisma b/apps/cli/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/auth.prisma similarity index 100% rename from apps/cli/templates/auth/server/db/prisma/sqlite/prisma/schema/auth.prisma rename to apps/cli/templates/auth/better-auth/server/db/prisma/sqlite/prisma/schema/auth.prisma diff --git a/apps/cli/templates/auth/server/next/src/app/api/auth/[...all]/route.ts b/apps/cli/templates/auth/better-auth/server/next/src/app/api/auth/[...all]/route.ts similarity index 100% rename from apps/cli/templates/auth/server/next/src/app/api/auth/[...all]/route.ts rename to apps/cli/templates/auth/better-auth/server/next/src/app/api/auth/[...all]/route.ts diff --git a/apps/cli/templates/auth/web/nuxt/app/components/SignInForm.vue b/apps/cli/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/components/SignInForm.vue rename to apps/cli/templates/auth/better-auth/web/nuxt/app/components/SignInForm.vue diff --git a/apps/cli/templates/auth/web/nuxt/app/components/SignUpForm.vue b/apps/cli/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/components/SignUpForm.vue rename to apps/cli/templates/auth/better-auth/web/nuxt/app/components/SignUpForm.vue diff --git a/apps/cli/templates/auth/web/nuxt/app/components/UserMenu.vue b/apps/cli/templates/auth/better-auth/web/nuxt/app/components/UserMenu.vue similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/components/UserMenu.vue rename to apps/cli/templates/auth/better-auth/web/nuxt/app/components/UserMenu.vue diff --git a/apps/cli/templates/auth/web/nuxt/app/middleware/auth.ts b/apps/cli/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/middleware/auth.ts rename to apps/cli/templates/auth/better-auth/web/nuxt/app/middleware/auth.ts diff --git a/apps/cli/templates/auth/web/nuxt/app/pages/dashboard.vue.hbs b/apps/cli/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/pages/dashboard.vue.hbs rename to apps/cli/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs diff --git a/apps/cli/templates/auth/web/nuxt/app/pages/login.vue b/apps/cli/templates/auth/better-auth/web/nuxt/app/pages/login.vue similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/pages/login.vue rename to apps/cli/templates/auth/better-auth/web/nuxt/app/pages/login.vue diff --git a/apps/cli/templates/auth/web/nuxt/app/plugins/auth-client.ts b/apps/cli/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts similarity index 100% rename from apps/cli/templates/auth/web/nuxt/app/plugins/auth-client.ts rename to apps/cli/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts diff --git a/apps/cli/templates/auth/web/react/base/src/lib/auth-client.ts.hbs b/apps/cli/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs similarity index 100% rename from apps/cli/templates/auth/web/react/base/src/lib/auth-client.ts.hbs rename to apps/cli/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs diff --git a/apps/cli/templates/auth/web/react/next/src/app/dashboard/page.tsx.hbs b/apps/cli/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/web/react/next/src/app/dashboard/page.tsx.hbs rename to apps/cli/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs diff --git a/apps/cli/templates/auth/web/react/next/src/app/login/page.tsx b/apps/cli/templates/auth/better-auth/web/react/next/src/app/login/page.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/next/src/app/login/page.tsx rename to apps/cli/templates/auth/better-auth/web/react/next/src/app/login/page.tsx diff --git a/apps/cli/templates/auth/web/react/next/src/components/sign-in-form.tsx b/apps/cli/templates/auth/better-auth/web/react/next/src/components/sign-in-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/next/src/components/sign-in-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/next/src/components/sign-in-form.tsx diff --git a/apps/cli/templates/auth/web/react/next/src/components/sign-up-form.tsx b/apps/cli/templates/auth/better-auth/web/react/next/src/components/sign-up-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/next/src/components/sign-up-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/next/src/components/sign-up-form.tsx diff --git a/apps/cli/templates/auth/web/react/next/src/components/theme-provider.tsx b/apps/cli/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/next/src/components/theme-provider.tsx rename to apps/cli/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx diff --git a/apps/cli/templates/auth/web/react/next/src/components/user-menu.tsx b/apps/cli/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/next/src/components/user-menu.tsx rename to apps/cli/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx diff --git a/apps/cli/templates/auth/web/react/react-router/src/components/sign-in-form.tsx b/apps/cli/templates/auth/better-auth/web/react/react-router/src/components/sign-in-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/react-router/src/components/sign-in-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/react-router/src/components/sign-in-form.tsx diff --git a/apps/cli/templates/auth/web/react/react-router/src/components/sign-up-form.tsx b/apps/cli/templates/auth/better-auth/web/react/react-router/src/components/sign-up-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/react-router/src/components/sign-up-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/react-router/src/components/sign-up-form.tsx diff --git a/apps/cli/templates/auth/web/react/react-router/src/components/user-menu.tsx b/apps/cli/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/react-router/src/components/user-menu.tsx rename to apps/cli/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx diff --git a/apps/cli/templates/auth/web/react/react-router/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/web/react/react-router/src/routes/dashboard.tsx.hbs rename to apps/cli/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs diff --git a/apps/cli/templates/auth/web/react/react-router/src/routes/login.tsx b/apps/cli/templates/auth/better-auth/web/react/react-router/src/routes/login.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/react-router/src/routes/login.tsx rename to apps/cli/templates/auth/better-auth/web/react/react-router/src/routes/login.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-in-form.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-in-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-in-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-in-form.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-up-form.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-up-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-router/src/components/sign-up-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/components/sign-up-form.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/components/user-menu.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/components/user-menu.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-router/src/components/user-menu.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/components/user-menu.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs rename to apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs diff --git a/apps/cli/templates/auth/web/react/tanstack-router/src/routes/login.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/routes/login.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-router/src/routes/login.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-router/src/routes/login.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-start/src/components/sign-in-form.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-in-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-start/src/components/sign-in-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-in-form.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-start/src/components/sign-up-form.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-up-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-start/src/components/sign-up-form.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/components/sign-up-form.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-start/src/components/user-menu.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/components/user-menu.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-start/src/components/user-menu.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/components/user-menu.tsx diff --git a/apps/cli/templates/auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs rename to apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs diff --git a/apps/cli/templates/auth/web/react/tanstack-start/src/routes/login.tsx b/apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/routes/login.tsx similarity index 100% rename from apps/cli/templates/auth/web/react/tanstack-start/src/routes/login.tsx rename to apps/cli/templates/auth/better-auth/web/react/tanstack-start/src/routes/login.tsx diff --git a/apps/cli/templates/auth/web/solid/src/components/sign-in-form.tsx b/apps/cli/templates/auth/better-auth/web/solid/src/components/sign-in-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/solid/src/components/sign-in-form.tsx rename to apps/cli/templates/auth/better-auth/web/solid/src/components/sign-in-form.tsx diff --git a/apps/cli/templates/auth/web/solid/src/components/sign-up-form.tsx b/apps/cli/templates/auth/better-auth/web/solid/src/components/sign-up-form.tsx similarity index 100% rename from apps/cli/templates/auth/web/solid/src/components/sign-up-form.tsx rename to apps/cli/templates/auth/better-auth/web/solid/src/components/sign-up-form.tsx diff --git a/apps/cli/templates/auth/web/solid/src/components/user-menu.tsx.hbs b/apps/cli/templates/auth/better-auth/web/solid/src/components/user-menu.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/web/solid/src/components/user-menu.tsx.hbs rename to apps/cli/templates/auth/better-auth/web/solid/src/components/user-menu.tsx.hbs diff --git a/apps/cli/templates/auth/web/solid/src/lib/auth-client.ts b/apps/cli/templates/auth/better-auth/web/solid/src/lib/auth-client.ts similarity index 100% rename from apps/cli/templates/auth/web/solid/src/lib/auth-client.ts rename to apps/cli/templates/auth/better-auth/web/solid/src/lib/auth-client.ts diff --git a/apps/cli/templates/auth/web/solid/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs similarity index 100% rename from apps/cli/templates/auth/web/solid/src/routes/dashboard.tsx.hbs rename to apps/cli/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs diff --git a/apps/cli/templates/auth/web/solid/src/routes/login.tsx b/apps/cli/templates/auth/better-auth/web/solid/src/routes/login.tsx similarity index 100% rename from apps/cli/templates/auth/web/solid/src/routes/login.tsx rename to apps/cli/templates/auth/better-auth/web/solid/src/routes/login.tsx diff --git a/apps/cli/templates/auth/web/svelte/src/components/SignInForm.svelte b/apps/cli/templates/auth/better-auth/web/svelte/src/components/SignInForm.svelte similarity index 100% rename from apps/cli/templates/auth/web/svelte/src/components/SignInForm.svelte rename to apps/cli/templates/auth/better-auth/web/svelte/src/components/SignInForm.svelte diff --git a/apps/cli/templates/auth/web/svelte/src/components/SignUpForm.svelte b/apps/cli/templates/auth/better-auth/web/svelte/src/components/SignUpForm.svelte similarity index 100% rename from apps/cli/templates/auth/web/svelte/src/components/SignUpForm.svelte rename to apps/cli/templates/auth/better-auth/web/svelte/src/components/SignUpForm.svelte diff --git a/apps/cli/templates/auth/web/svelte/src/components/UserMenu.svelte b/apps/cli/templates/auth/better-auth/web/svelte/src/components/UserMenu.svelte similarity index 100% rename from apps/cli/templates/auth/web/svelte/src/components/UserMenu.svelte rename to apps/cli/templates/auth/better-auth/web/svelte/src/components/UserMenu.svelte diff --git a/apps/cli/templates/auth/web/svelte/src/lib/auth-client.ts b/apps/cli/templates/auth/better-auth/web/svelte/src/lib/auth-client.ts similarity index 100% rename from apps/cli/templates/auth/web/svelte/src/lib/auth-client.ts rename to apps/cli/templates/auth/better-auth/web/svelte/src/lib/auth-client.ts diff --git a/apps/cli/templates/auth/web/svelte/src/routes/dashboard/+page.svelte.hbs b/apps/cli/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs similarity index 100% rename from apps/cli/templates/auth/web/svelte/src/routes/dashboard/+page.svelte.hbs rename to apps/cli/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs diff --git a/apps/cli/templates/auth/web/svelte/src/routes/login/+page.svelte b/apps/cli/templates/auth/better-auth/web/svelte/src/routes/login/+page.svelte similarity index 100% rename from apps/cli/templates/auth/web/svelte/src/routes/login/+page.svelte rename to apps/cli/templates/auth/better-auth/web/svelte/src/routes/login/+page.svelte diff --git a/apps/cli/templates/auth/clerk/convex/backend/convex/auth.config.ts.hbs b/apps/cli/templates/auth/clerk/convex/backend/convex/auth.config.ts.hbs new file mode 100644 index 0000000..add0f61 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/backend/convex/auth.config.ts.hbs @@ -0,0 +1,12 @@ +export default { + providers: [ + { + // Replace with your own Clerk Issuer URL from your "convex" JWT template + // or with `process.env.CLERK_JWT_ISSUER_DOMAIN` + // and configure CLERK_JWT_ISSUER_DOMAIN on the Convex Dashboard + // See https://docs.convex.dev/auth/clerk#configuring-dev-and-prod-instances + domain: process.env.CLERK_JWT_ISSUER_DOMAIN, + applicationID: "convex", + }, + ], +}; diff --git a/apps/cli/templates/auth/clerk/convex/backend/convex/privateData.ts.hbs b/apps/cli/templates/auth/clerk/convex/backend/convex/privateData.ts.hbs new file mode 100644 index 0000000..975c48f --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/backend/convex/privateData.ts.hbs @@ -0,0 +1,16 @@ +import { query } from "./_generated/server"; + +export const get = query({ + args: {}, + handler: async (ctx) => { + const identity = await ctx.auth.getUserIdentity(); + if (identity === null) { + return { + message: "Not authenticated", + }; + } + return { + message: "This is private", + }; + }, +}); diff --git a/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs b/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs new file mode 100644 index 0000000..a36f42a --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs @@ -0,0 +1,12 @@ +import { Redirect, Stack } from "expo-router"; +import { useAuth } from "@clerk/clerk-expo"; + +export default function AuthRoutesLayout() { + const { isSignedIn } = useAuth(); + + if (isSignedIn) { + return ; + } + + return ; +} diff --git a/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs b/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs new file mode 100644 index 0000000..6f4b4e1 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs @@ -0,0 +1,67 @@ +import { useSignIn } from "@clerk/clerk-expo"; +import { Link, useRouter } from "expo-router"; +import { Text, TextInput, TouchableOpacity, View } from "react-native"; +import React from "react"; + +export default function Page() { + const { signIn, setActive, isLoaded } = useSignIn(); + const router = useRouter(); + + const [emailAddress, setEmailAddress] = React.useState(""); + const [password, setPassword] = React.useState(""); + + // Handle the submission of the sign-in form + const onSignInPress = async () => { + if (!isLoaded) return; + + // Start the sign-in process using the email and password provided + try { + const signInAttempt = await signIn.create({ + identifier: emailAddress, + password, + }); + + // If sign-in process is complete, set the created session as active + // and redirect the user + if (signInAttempt.status === "complete") { + await setActive({ session: signInAttempt.createdSessionId }); + router.replace("/"); + } else { + // If the status isn't complete, check why. User might need to + // complete further steps. + console.error(JSON.stringify(signInAttempt, null, 2)); + } + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error(JSON.stringify(err, null, 2)); + } + }; + + return ( + + Sign in + setEmailAddress(emailAddress)} + /> + setPassword(password)} + /> + + Continue + + + Don't have an account? + + Sign up + + + + ); +} diff --git a/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-out.tsx.hbs b/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-out.tsx.hbs new file mode 100644 index 0000000..d50f341 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/native/base/app/(auth)/sign-out.tsx.hbs @@ -0,0 +1,110 @@ +import * as React from "react"; +import { Text, TextInput, TouchableOpacity, View } from "react-native"; +import { useSignUp } from "@clerk/clerk-expo"; +import { Link, useRouter } from "expo-router"; + +export default function SignUpScreen() { + const { isLoaded, signUp, setActive } = useSignUp(); + const router = useRouter(); + + const [emailAddress, setEmailAddress] = React.useState(""); + const [password, setPassword] = React.useState(""); + const [pendingVerification, setPendingVerification] = React.useState(false); + const [code, setCode] = React.useState(""); + + // Handle submission of sign-up form + const onSignUpPress = async () => { + if (!isLoaded) return; + + console.log(emailAddress, password); + + // Start sign-up process using email and password provided + try { + await signUp.create({ + emailAddress, + password, + }); + + // Send user an email with verification code + await signUp.prepareEmailAddressVerification({ strategy: "email_code" }); + + // Set 'pendingVerification' to true to display second form + // and capture OTP code + setPendingVerification(true); + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error(JSON.stringify(err, null, 2)); + } + }; + + // Handle submission of verification form + const onVerifyPress = async () => { + if (!isLoaded) return; + + try { + // Use the code the user provided to attempt verification + const signUpAttempt = await signUp.attemptEmailAddressVerification({ + code, + }); + + // If verification was completed, set the session to active + // and redirect the user + if (signUpAttempt.status === "complete") { + await setActive({ session: signUpAttempt.createdSessionId }); + router.replace("/"); + } else { + // If the status is not complete, check why. User may need to + // complete further steps. + console.error(JSON.stringify(signUpAttempt, null, 2)); + } + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error(JSON.stringify(err, null, 2)); + } + }; + + if (pendingVerification) { + return ( + <> + Verify your email + setCode(code)} + /> + + Verify + + + ); + } + + return ( + + Sign up + setEmailAddress(email)} + /> + setPassword(password)} + /> + + Continue + + + Already have an account? + + Sign in + + + + ); +} diff --git a/apps/cli/templates/auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs b/apps/cli/templates/auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs new file mode 100644 index 0000000..a40e476 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs @@ -0,0 +1,27 @@ +import { useClerk } from "@clerk/clerk-expo"; +import { useRouter } from "expo-router"; +import { Text, TouchableOpacity } from "react-native"; + +export const SignOutButton = () => { + // Use `useClerk()` to access the `signOut()` function + const { signOut } = useClerk(); + const router = useRouter(); + + const handleSignOut = async () => { + try { + await signOut(); + // Redirect to your desired page + router.replace("/"); + } catch (err) { + // See https://clerk.com/docs/custom-flows/error-handling + // for more info on error handling + console.error(JSON.stringify(err, null, 2)); + } + }; + + return ( + + Sign out + + ); +}; diff --git a/apps/cli/templates/auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs b/apps/cli/templates/auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs new file mode 100644 index 0000000..32ec3bb --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs @@ -0,0 +1,29 @@ +"use client"; + +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import { SignInButton, UserButton, useUser } from "@clerk/nextjs"; +import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react"; + +export default function Dashboard() { + const user = useUser(); + const privateData = useQuery(api.privateData.get); + + return ( + <> + +
+

Dashboard

+

Welcome {user.user?.fullName}

+

privateData: {privateData?.message}

+ +
+
+ + + + +
Loading...
+
+ + ); +} diff --git a/apps/cli/templates/auth/clerk/convex/web/react/next/src/middleware.ts.hbs b/apps/cli/templates/auth/clerk/convex/web/react/next/src/middleware.ts.hbs new file mode 100644 index 0000000..9b730c5 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/web/react/next/src/middleware.ts.hbs @@ -0,0 +1,12 @@ +import { clerkMiddleware } from "@clerk/nextjs/server"; + +export default clerkMiddleware(); + +export const config = { + matcher: [ + // Skip Next.js internals and all static files, unless found in search params + "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)", + // Always run for API routes + "/(api|trpc)(.*)", + ], +}; diff --git a/apps/cli/templates/auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs new file mode 100644 index 0000000..be2eaf5 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs @@ -0,0 +1,32 @@ +import { SignInButton, UserButton, useUser } from "@clerk/clerk-react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import { + Authenticated, + AuthLoading, + Unauthenticated, + useQuery, +} from "convex/react"; + +export default function Dashboard() { + const privateData = useQuery(api.privateData.get); + const user = useUser(); + + return ( + <> + +
+

Dashboard

+

Welcome {user.user?.fullName}

+

privateData: {privateData?.message}

+ +
+
+ + + + +
Loading...
+
+ + ); +} diff --git a/apps/cli/templates/auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs new file mode 100644 index 0000000..7d889df --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs @@ -0,0 +1,37 @@ +import { SignInButton, UserButton, useUser } from "@clerk/clerk-react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import { createFileRoute } from "@tanstack/react-router"; +import { + Authenticated, + AuthLoading, + Unauthenticated, + useQuery, +} from "convex/react"; + +export const Route = createFileRoute("/dashboard")({ + component: RouteComponent, +}); + +function RouteComponent() { + const privateData = useQuery(api.privateData.get); + const user = useUser() + + return ( + <> + +
+

Dashboard

+

Welcome {user.user?.fullName}

+

privateData: {privateData?.message}

+ +
+
+ + + + +
Loading...
+
+ + ); +} diff --git a/apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs b/apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs new file mode 100644 index 0000000..6bef28b --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs @@ -0,0 +1,37 @@ +import { SignInButton, UserButton, useUser } from "@clerk/tanstack-react-start"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import { createFileRoute } from "@tanstack/react-router"; +import { + Authenticated, + AuthLoading, + Unauthenticated, + useQuery, +} from "convex/react"; + +export const Route = createFileRoute("/dashboard")({ + component: RouteComponent, +}); + +function RouteComponent() { + const privateData = useQuery(api.privateData.get); + const user = useUser(); + + return ( + <> + +
+

Dashboard

+

Welcome {user.user?.fullName}

+

privateData: {privateData?.message}

+ +
+
+ + + + +
Loading...
+
+ + ); +} diff --git a/apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs b/apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs new file mode 100644 index 0000000..bcd16f1 --- /dev/null +++ b/apps/cli/templates/auth/clerk/convex/web/react/tanstack-start/src/server.ts.hbs @@ -0,0 +1,18 @@ +import { createClerkHandler } from "@clerk/tanstack-react-start/server"; +import { + createStartHandler, + defaultStreamHandler, + defineHandlerCallback, +} from "@tanstack/react-start/server"; +import { createRouter } from "./router"; + +const handlerFactory = createClerkHandler( + createStartHandler({ + createRouter, + }), +); + +export default defineHandlerCallback(async (event) => { + const startHandler = await handlerFactory(defaultStreamHandler); + return startHandler(event); +}); diff --git a/apps/cli/templates/backend/convex/packages/backend/package.json.hbs b/apps/cli/templates/backend/convex/packages/backend/package.json.hbs index 4a46e81..8a4b9fb 100644 --- a/apps/cli/templates/backend/convex/packages/backend/package.json.hbs +++ b/apps/cli/templates/backend/convex/packages/backend/package.json.hbs @@ -9,6 +9,7 @@ "license": "ISC", "description": "", "devDependencies": { + "@types/node": "^24.3.0", "typescript": "^5.9.2" }, "dependencies": { diff --git a/apps/cli/templates/backend/server/elysia/src/index.ts.hbs b/apps/cli/templates/backend/server/elysia/src/index.ts.hbs index dfdf3c8..10b9a9f 100644 --- a/apps/cli/templates/backend/server/elysia/src/index.ts.hbs +++ b/apps/cli/templates/backend/server/elysia/src/index.ts.hbs @@ -14,7 +14,7 @@ import { RPCHandler } from "@orpc/server/fetch"; import { appRouter } from "./routers"; import { createContext } from "./lib/context"; {{/if}} -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./lib/auth"; {{/if}} @@ -31,13 +31,13 @@ const app = new Elysia() cors({ origin: process.env.CORS_ORIGIN || "", methods: ["GET", "POST", "OPTIONS"], - {{#if auth}} + {{#if (eq auth "better-auth")}} allowedHeaders: ["Content-Type", "Authorization"], credentials: true, {{/if}} }), ) - {{#if auth}} + {{#if (eq auth "better-auth")}} .all("/api/auth/*", async (context) => { const { request } = context; if (["POST", "GET"].includes(request.method)) { diff --git a/apps/cli/templates/backend/server/express/src/index.ts.hbs b/apps/cli/templates/backend/server/express/src/index.ts.hbs index 1e3127c..2a269e4 100644 --- a/apps/cli/templates/backend/server/express/src/index.ts.hbs +++ b/apps/cli/templates/backend/server/express/src/index.ts.hbs @@ -7,7 +7,7 @@ import { appRouter } from "./routers/index"; {{#if (eq api "orpc")}} import { RPCHandler } from "@orpc/server/node"; import { appRouter } from "./routers"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { createContext } from "./lib/context"; {{/if}} {{/if}} @@ -17,7 +17,7 @@ import express from "express"; import { streamText, type UIMessage, convertToModelMessages } from "ai"; import { google } from "@ai-sdk/google"; {{/if}} -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./lib/auth"; import { toNodeHandler } from "better-auth/node"; {{/if}} @@ -28,14 +28,14 @@ app.use( cors({ origin: process.env.CORS_ORIGIN || "", methods: ["GET", "POST", "OPTIONS"], - {{#if auth}} + {{#if (eq auth "better-auth")}} allowedHeaders: ["Content-Type", "Authorization"], credentials: true, {{/if}} }) ); -{{#if auth}} +{{#if (eq auth "better-auth")}} app.all("/api/auth{/*path}", toNodeHandler(auth)); {{/if}} @@ -54,7 +54,7 @@ const handler = new RPCHandler(appRouter); app.use("/rpc{*path}", async (req, res, next) => { const { matched } = await handler.handle(req, res, { prefix: "/rpc", - {{#if auth}} + {{#if (eq auth "better-auth")}} context: await createContext({ req }), {{else}} context: {}, @@ -85,4 +85,4 @@ app.get("/", (_req, res) => { const port = process.env.PORT || 3000; app.listen(port, () => { console.log(`Server is running on port ${port}`); -}); \ No newline at end of file +}); diff --git a/apps/cli/templates/backend/server/fastify/src/index.ts.hbs b/apps/cli/templates/backend/server/fastify/src/index.ts.hbs index b59b50e..936ed93 100644 --- a/apps/cli/templates/backend/server/fastify/src/index.ts.hbs +++ b/apps/cli/templates/backend/server/fastify/src/index.ts.hbs @@ -13,7 +13,7 @@ import { RPCHandler } from "@orpc/server/node"; import { CORSPlugin } from "@orpc/server/plugins"; import { appRouter } from "./routers/index"; import { createServer } from "node:http"; -{{#if auth}} +{{#if (eq auth "better-auth")}} import { createContext } from "./lib/context"; {{/if}} {{/if}} @@ -23,7 +23,7 @@ import { streamText, type UIMessage, convertToModelMessages } from "ai"; import { google } from "@ai-sdk/google"; {{/if}} -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./lib/auth"; {{/if}} @@ -77,7 +77,7 @@ const fastify = Fastify({ fastify.register(fastifyCors, baseCorsConfig); -{{#if auth}} +{{#if (eq auth "better-auth")}} fastify.route({ method: ["GET", "POST"], url: "/api/auth/*", @@ -149,4 +149,4 @@ fastify.listen({ port: 3000 }, (err) => { process.exit(1); } console.log("Server running on port 3000"); -}); \ No newline at end of file +}); diff --git a/apps/cli/templates/backend/server/hono/src/index.ts.hbs b/apps/cli/templates/backend/server/hono/src/index.ts.hbs index 86d09fa..33a8589 100644 --- a/apps/cli/templates/backend/server/hono/src/index.ts.hbs +++ b/apps/cli/templates/backend/server/hono/src/index.ts.hbs @@ -14,7 +14,7 @@ import { trpcServer } from "@hono/trpc-server"; import { createContext } from "./lib/context"; import { appRouter } from "./routers/index"; {{/if}} -{{#if auth}} +{{#if (eq auth "better-auth")}} import { auth } from "./lib/auth"; {{/if}} import { Hono } from "hono"; @@ -42,14 +42,14 @@ app.use( origin: env.CORS_ORIGIN || "", {{/if}} allowMethods: ["GET", "POST", "OPTIONS"], - {{#if auth}} + {{#if (eq auth "better-auth")}} allowHeaders: ["Content-Type", "Authorization"], credentials: true, {{/if}} }) ); -{{#if auth}} +{{#if (eq auth "better-auth")}} app.on(["POST", "GET"], "/api/auth/**", (c) => auth.handler(c.req.raw)); {{/if}} @@ -133,4 +133,4 @@ export default app; {{#if (eq runtime "workers")}} export default app; {{/if}} -{{/if}} \ No newline at end of file +{{/if}} diff --git a/apps/cli/templates/backend/server/server-base/src/routers/index.ts.hbs b/apps/cli/templates/backend/server/server-base/src/routers/index.ts.hbs index f0fe30b..2804e11 100644 --- a/apps/cli/templates/backend/server/server-base/src/routers/index.ts.hbs +++ b/apps/cli/templates/backend/server/server-base/src/routers/index.ts.hbs @@ -1,5 +1,5 @@ {{#if (eq api "orpc")}} -import { {{#if auth}}protectedProcedure, {{/if}}publicProcedure } from "../lib/orpc"; +import { {{#if (eq auth "better-auth")}}protectedProcedure, {{/if}}publicProcedure } from "../lib/orpc"; import type { RouterClient } from "@orpc/server"; {{#if (includes examples "todo")}} import { todoRouter } from "./todo"; @@ -9,7 +9,7 @@ export const appRouter = { healthCheck: publicProcedure.handler(() => { return "OK"; }), - {{#if auth}} + {{#if (eq auth "better-auth")}} privateData: protectedProcedure.handler(({ context }) => { return { message: "This is private", @@ -25,7 +25,7 @@ export type AppRouter = typeof appRouter; export type AppRouterClient = RouterClient; {{else if (eq api "trpc")}} import { - {{#if auth}}protectedProcedure, {{/if}}publicProcedure, + {{#if (eq auth "better-auth")}}protectedProcedure, {{/if}}publicProcedure, router, } from "../lib/trpc"; {{#if (includes examples "todo")}} @@ -36,7 +36,7 @@ export const appRouter = router({ healthCheck: publicProcedure.query(() => { return "OK"; }), - {{#if auth}} + {{#if (eq auth "better-auth")}} privateData: protectedProcedure.query(({ ctx }) => { return { message: "This is private", diff --git a/apps/cli/templates/deploy/alchemy/alchemy.run.ts.hbs b/apps/cli/templates/deploy/alchemy/alchemy.run.ts.hbs index 891d446..027eb77 100644 --- a/apps/cli/templates/deploy/alchemy/alchemy.run.ts.hbs +++ b/apps/cli/templates/deploy/alchemy/alchemy.run.ts.hbs @@ -164,7 +164,7 @@ export const server = await Worker("server", { DATABASE_URL: alchemy.secret(process.env.DATABASE_URL), {{/if}} CORS_ORIGIN: process.env.CORS_ORIGIN || "", - {{#if auth}} + {{#if (eq auth "better-auth")}} BETTER_AUTH_SECRET: alchemy.secret(process.env.BETTER_AUTH_SECRET), BETTER_AUTH_URL: process.env.BETTER_AUTH_URL || "", {{/if}} @@ -190,4 +190,4 @@ console.log(`Web -> ${web.url}`); console.log(`Server -> ${server.url}`); {{/if}} -await app.finalize(); \ No newline at end of file +await app.finalize(); diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs index 4db82d5..2183f86 100644 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +++ b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs @@ -9,9 +9,17 @@ import { useQuery } from "@tanstack/react-query"; import { trpc } from "@/utils/trpc"; {{/if}} {{#if (eq backend "convex")}} +{{#if (eq auth "clerk")}} +import { Link } from "expo-router"; +import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react"; +import { api } from "@{{ projectName }}/backend/convex/_generated/api"; +import { useUser } from "@clerk/clerk-expo"; +import { SignOutButton } from "@/components/sign-out-button"; +{{else}} import { useQuery } from "convex/react"; import { api } from "@{{ projectName }}/backend/convex/_generated/api"; {{/if}} +{{/if}} export default function Home() { {{#if (eq api "orpc")}} @@ -21,21 +29,27 @@ export default function Home() { const healthCheck = useQuery(trpc.healthCheck.queryOptions()); {{/if}} {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + const { user } = useUser(); const healthCheck = useQuery(api.healthCheck.get); + const privateData = useQuery(api.privateData.get); + {{else}} + const healthCheck = useQuery(api.healthCheck.get); + {{/if}} {{/if}} return ( - BETTER T STACK - + BETTER T STACK + {{#if (eq backend "convex")}} @@ -89,6 +103,24 @@ export default function Home() { {{/unless}} {{/if}} + {{#if (and (eq backend "convex") (eq auth "clerk"))}} + + Hello {user?.emailAddresses[0].emailAddress} + Private Data: {privateData?.message} + + + + + Sign in + + + Sign up + + + + Loading... + + {{/if}} ); diff --git a/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs index 132328a..2f11930 100644 --- a/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs @@ -3,6 +3,11 @@ import "@/polyfills"; {{/if}} {{#if (eq backend "convex")}} import { ConvexProvider, ConvexReactClient } from "convex/react"; +{{#if (eq auth "clerk")}} +import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; +import { ConvexProviderWithClerk } from "convex/react-clerk"; +import { tokenCache } from "@clerk/clerk-expo/token-cache"; +{{/if}} {{else}} {{#unless (eq api "none")}} import { QueryClientProvider } from "@tanstack/react-query"; @@ -72,6 +77,28 @@ export default function RootLayout() { } return ( {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + + + + + + + + + + + + + + + {{else}} @@ -86,6 +113,7 @@ export default function RootLayout() { + {{/if}} {{else}} {{#unless (eq api "none")}} diff --git a/apps/cli/templates/frontend/native/nativewind/package.json.hbs b/apps/cli/templates/frontend/native/nativewind/package.json.hbs index 66c473e..0778013 100644 --- a/apps/cli/templates/frontend/native/nativewind/package.json.hbs +++ b/apps/cli/templates/frontend/native/nativewind/package.json.hbs @@ -22,6 +22,7 @@ {{/if}} "expo": "^53.0.4", "expo-constants": "~17.1.4", + "expo-crypto": "~14.1.5", "expo-linking": "~7.1.4", "expo-navigation-bar": "~4.2.3", "expo-router": "~5.0.3", diff --git a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs index d356ee7..0078fab 100644 --- a/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs +++ b/apps/cli/templates/frontend/native/unistyles/app/(drawer)/index.tsx.hbs @@ -11,9 +11,17 @@ import { useQuery } from "@tanstack/react-query"; import { trpc } from "@/utils/trpc"; {{/if}} {{#if (eq backend "convex")}} +{{#if (eq auth "clerk")}} +import { Link } from "expo-router"; +import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react"; +import { api } from "@{{ projectName }}/backend/convex/_generated/api"; +import { useUser } from "@clerk/clerk-expo"; +import { SignOutButton } from "@/components/sign-out-button"; +{{else}} import { useQuery } from "convex/react"; import { api } from "@{{ projectName }}/backend/convex/_generated/api"; {{/if}} +{{/if}} export default function Home() { {{#if (eq api "orpc")}} @@ -23,7 +31,13 @@ export default function Home() { const healthCheck = useQuery(trpc.healthCheck.queryOptions()); {{/if}} {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + const { user } = useUser(); const healthCheck = useQuery(api.healthCheck.get); + const privateData = useQuery(api.privateData.get); + {{else}} + const healthCheck = useQuery(api.healthCheck.get); + {{/if}} {{/if}} return ( @@ -105,6 +119,24 @@ export default function Home() { {{/unless}} {{/if}} + {{#if (and (eq backend "convex") (eq auth "clerk"))}} + + Hello {user?.emailAddresses[0].emailAddress} + Private Data: {privateData?.message} + + + + + Sign in + + + Sign up + + + + Loading... + + {{/if}} ); diff --git a/apps/cli/templates/frontend/native/unistyles/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/unistyles/app/_layout.tsx.hbs index d625a5a..0509a7a 100644 --- a/apps/cli/templates/frontend/native/unistyles/app/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/unistyles/app/_layout.tsx.hbs @@ -9,6 +9,11 @@ import { queryClient } from "@/utils/orpc"; {{/if}} {{#if (eq backend "convex")}} import { ConvexProvider, ConvexReactClient } from "convex/react"; +{{#if (eq auth "clerk")}} +import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; +import { ConvexProviderWithClerk } from "convex/react-clerk"; +import { tokenCache } from "@clerk/clerk-expo/token-cache"; +{{/if}} {{else}} {{#unless (eq api "none")}} import { QueryClientProvider } from "@tanstack/react-query"; @@ -34,6 +39,35 @@ export default function RootLayout() { return ( {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + + + + + + + + + + + + {{else}} + {{/if}} {{else}} {{#unless (eq api "none")}} diff --git a/apps/cli/templates/frontend/native/unistyles/package.json.hbs b/apps/cli/templates/frontend/native/unistyles/package.json.hbs index 2f76f3b..33f4ffc 100644 --- a/apps/cli/templates/frontend/native/unistyles/package.json.hbs +++ b/apps/cli/templates/frontend/native/unistyles/package.json.hbs @@ -21,6 +21,7 @@ "babel-plugin-react-compiler": "^19.1.0-rc.2", "expo": "^53.0.17", "expo-constants": "~17.1.7", + "expo-crypto": "~14.1.5", "expo-linking": "~7.1.7", "expo-router": "~5.1.3", "expo-secure-store": "~14.2.3", diff --git a/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs b/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs index c1e8daa..4a3f1e7 100644 --- a/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs +++ b/apps/cli/templates/frontend/nuxt/app/components/Header.vue.hbs @@ -1,12 +1,12 @@