kofi-stack-template-generator 2.1.5 → 2.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -6
- package/dist/index.js +5 -29
- package/package.json +1 -1
- package/src/templates.generated.ts +7 -6
- package/templates/convex/convex/auth.ts.hbs +19 -11
- package/templates/web/_env.local.hbs +18 -0
- package/templates/web/src/lib/auth-server.ts.hbs +1 -1
- package/templates/web/src/proxy.ts.hbs +15 -5
- package/templates/web/tsconfig.json.hbs +1 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
|
|
2
|
-
> kofi-stack-template-generator@2.1.
|
|
2
|
+
> kofi-stack-template-generator@2.1.11 build /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
|
|
3
3
|
> pnpm run prebuild && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
> kofi-stack-template-generator@2.1.
|
|
6
|
+
> kofi-stack-template-generator@2.1.11 prebuild /Users/theodenanyoh/Documents/Krumalabs/create-kofi-stack-v2/packages/template-generator
|
|
7
7
|
> node scripts/generate-templates.js
|
|
8
8
|
|
|
9
9
|
Generating templates.generated.ts...
|
|
10
|
-
Generated templates.generated.ts with
|
|
10
|
+
Generated templates.generated.ts with 90 templates
|
|
11
11
|
CLI Building entry: src/index.ts
|
|
12
12
|
CLI Using tsconfig: tsconfig.json
|
|
13
13
|
CLI tsup v8.5.1
|
|
14
14
|
CLI Target: es2022
|
|
15
15
|
ESM Build start
|
|
16
|
-
ESM dist/index.js
|
|
17
|
-
ESM ⚡️ Build success in
|
|
16
|
+
ESM dist/index.js 101.26 KB
|
|
17
|
+
ESM ⚡️ Build success in 35ms
|
|
18
18
|
DTS Build start
|
|
19
|
-
DTS ⚡️ Build success in
|
|
19
|
+
DTS ⚡️ Build success in 473ms
|
|
20
20
|
DTS dist/index.d.ts 2.96 KB
|
package/dist/index.js
CHANGED
|
@@ -245,7 +245,7 @@ var EMBEDDED_TEMPLATES = {
|
|
|
245
245
|
"base/biome.json.hbs": '{\n "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",\n "organizeImports": {\n "enabled": true\n },\n "linter": {\n "enabled": true,\n "rules": {\n "recommended": true\n }\n },\n "formatter": {\n "enabled": true,\n "indentStyle": "space",\n "indentWidth": 2\n },\n "javascript": {\n "formatter": {\n "quoteStyle": "single",\n "semicolons": "asNeeded"\n }\n },\n "files": {\n "ignore": [\n "node_modules",\n ".next",\n "dist",\n ".turbo",\n "coverage",\n ".vercel",\n "_generated"\n ]\n }\n}\n',
|
|
246
246
|
"convex/_env.local.hbs": "# Convex\nCONVEX_DEPLOYMENT=\nNEXT_PUBLIC_CONVEX_URL=\nNEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000\n\n# Site URL (used for auth redirects)\nSITE_URL=http://localhost:3000\nNEXT_PUBLIC_SITE_URL=http://localhost:3000\n\n# Better Auth Secret (generate with: openssl rand -base64 32)\nBETTER_AUTH_SECRET=\n\n# Auth - GitHub OAuth\nGITHUB_CLIENT_ID=\nGITHUB_CLIENT_SECRET=\n\n# Auth - Google OAuth\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\n\n# Email (Resend) - https://resend.com\nRESEND_API_KEY=\nRESEND_FROM_EMAIL=\n{{#if (eq integrations.analytics 'posthog')}}\n\n# PostHog\nNEXT_PUBLIC_POSTHOG_KEY=\nNEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com\n{{/if}}\n{{#if (eq integrations.uploads 'convex-fs')}}\n\n# Convex FS - Built-in file storage (no additional config needed)\n{{/if}}\n{{#if (eq integrations.uploads 'r2')}}\n\n# Cloudflare R2\nR2_ACCESS_KEY_ID=\nR2_SECRET_ACCESS_KEY=\nR2_BUCKET=\nR2_ENDPOINT=\n{{/if}}\n{{#if (eq integrations.uploads 'uploadthing')}}\n\n# UploadThing\nUPLOADTHING_TOKEN=\n{{/if}}\n{{#if (eq integrations.uploads 's3')}}\n\n# AWS S3\nAWS_ACCESS_KEY_ID=\nAWS_SECRET_ACCESS_KEY=\nAWS_REGION=\nAWS_S3_BUCKET=\n{{/if}}\n{{#if (eq integrations.uploads 'vercel-blob')}}\n\n# Vercel Blob\nBLOB_READ_WRITE_TOKEN=\n{{/if}}\n{{#if (eq integrations.payments 'stripe')}}\n\n# Stripe\nSTRIPE_SECRET_KEY=\nSTRIPE_WEBHOOK_SECRET=\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\n{{/if}}\n{{#if (eq integrations.payments 'polar')}}\n\n# Polar\nPOLAR_ACCESS_TOKEN=\nPOLAR_WEBHOOK_SECRET=\nPOLAR_ORGANIZATION_ID=\n{{/if}}\n{{#if (includes addons 'rate-limiting')}}\n\n# Convex Rate Limiter - No additional config needed (uses Convex backend)\n{{/if}}\n{{#if (includes addons 'monitoring')}}\n\n# Sentry\nSENTRY_DSN=\nSENTRY_AUTH_TOKEN=\n{{/if}}\n",
|
|
247
247
|
"convex/convex/auth.config.ts.hbs": "import { getAuthConfigProvider } from '@convex-dev/better-auth/auth-config'\nimport type { AuthConfig } from 'convex/server'\n\nexport default {\n providers: [getAuthConfigProvider()],\n} satisfies AuthConfig\n",
|
|
248
|
-
"convex/convex/auth.ts.hbs": "import { createClient, type GenericCtx } from '@convex-dev/better-auth'\nimport { convex } from '@convex-dev/better-auth/plugins'\nimport { betterAuth } from 'better-auth/minimal'\nimport { components } from './_generated/api'\nimport type { DataModel } from './_generated/dataModel'\nimport { query } from './_generated/server'\nimport authConfig from './auth.config'\n\nconst siteUrl = process.env.SITE_URL
|
|
248
|
+
"convex/convex/auth.ts.hbs": "import { createClient, type GenericCtx } from '@convex-dev/better-auth'\nimport { convex } from '@convex-dev/better-auth/plugins'\nimport { betterAuth } from 'better-auth/minimal'\nimport { components } from './_generated/api'\nimport type { DataModel } from './_generated/dataModel'\nimport { query } from './_generated/server'\nimport authConfig from './auth.config'\n\nconst siteUrl = process.env.SITE_URL || 'http://localhost:3000'\n\nexport const authComponent = createClient<DataModel>(components.betterAuth)\n\n// Build social providers only if credentials are configured\nconst socialProviders: Record<string, { clientId: string; clientSecret: string }> = {}\n\nif (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {\n socialProviders.github = {\n clientId: process.env.GITHUB_CLIENT_ID,\n clientSecret: process.env.GITHUB_CLIENT_SECRET,\n }\n}\n\nif (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {\n socialProviders.google = {\n clientId: process.env.GOOGLE_CLIENT_ID,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET,\n }\n}\n\nexport const createAuth = (ctx: GenericCtx<DataModel>) => {\n return betterAuth({\n baseURL: siteUrl,\n database: authComponent.adapter(ctx),\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: false,\n },\n socialProviders: Object.keys(socialProviders).length > 0 ? socialProviders : undefined,\n plugins: [convex({ authConfig })],\n })\n}\n\nexport const getCurrentUser = query({\n args: {},\n handler: async (ctx) => {\n return authComponent.getAuthUser(ctx)\n },\n})\n",
|
|
249
249
|
"convex/convex/convex.config.ts.hbs": "import { defineApp } from 'convex/server'\nimport betterAuth from '@convex-dev/better-auth/convex.config'\n\nconst app = defineApp()\napp.use(betterAuth)\n\nexport default app\n",
|
|
250
250
|
"convex/convex/http.ts.hbs": "import { httpRouter } from 'convex/server'\nimport { authComponent, createAuth } from './auth'\n\nconst http = httpRouter()\n\n// Register Better Auth routes\nauthComponent.registerRoutes(http, createAuth)\n\nexport default http\n",
|
|
251
251
|
"convex/convex/schema.ts.hbs": "import { defineSchema, defineTable } from 'convex/server'\nimport { v } from 'convex/values'\n\n// Better Auth manages its own tables via the betterAuth component\n// Add your custom application tables here\nexport default defineSchema({\n // Example:\n // posts: defineTable({\n // title: v.string(),\n // content: v.string(),\n // userId: v.string(), // Better Auth user ID\n // createdAt: v.number(),\n // }).index('by_user', ['userId']),\n})\n",
|
|
@@ -586,6 +586,7 @@ export default function RootLayout({
|
|
|
586
586
|
"packages/ui/src/index.ts.hbs": "export { cn } from './lib/utils'\n// Export components as they are added\n// export * from './components/ui/button'\n",
|
|
587
587
|
"packages/ui/src/lib/utils.ts.hbs": "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
|
|
588
588
|
"packages/ui/tsconfig.json.hbs": '{\n "compilerOptions": {\n "target": "ES2020",\n "lib": ["dom", "dom.iterable", "esnext"],\n "allowJs": true,\n "skipLibCheck": true,\n "strict": true,\n "noEmit": true,\n "esModuleInterop": true,\n "module": "esnext",\n "moduleResolution": "bundler",\n "resolveJsonModule": true,\n "isolatedModules": true,\n "jsx": "react-jsx",\n "incremental": true,\n "paths": {\n "@/*": ["./src/*"]\n }\n },\n "include": ["src/**/*"],\n "exclude": ["node_modules"]\n}\n',
|
|
589
|
+
"web/_env.local.hbs": "# Convex - These values are synced from packages/backend/.env.local after running convex dev\n# Copy NEXT_PUBLIC_CONVEX_URL from packages/backend/.env.local after Convex setup\nNEXT_PUBLIC_CONVEX_URL=\n\n# Site URL for auth\nNEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000\nNEXT_PUBLIC_SITE_URL=http://localhost:3000\n{{#if (eq integrations.analytics 'posthog')}}\n\n# PostHog\nNEXT_PUBLIC_POSTHOG_KEY=\nNEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com\n{{/if}}\n{{#if (eq integrations.payments 'stripe')}}\n\n# Stripe\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\n{{/if}}\n",
|
|
589
590
|
"web/components.json.hbs": '{\n "$schema": "https://ui.shadcn.com/schema.json",\n "style": "new-york",\n "rsc": true,\n "tsx": true,\n "tailwind": {\n "config": "",\n "css": "src/app/globals.css",\n "baseColor": "{{shadcn.baseColor}}",\n "cssVariables": true,\n "prefix": ""\n },\n "aliases": {\n "components": "@/components",\n "utils": "@/lib/utils",\n "ui": "@/components/ui",\n "lib": "@/lib",\n "hooks": "@/hooks"\n },\n "iconLibrary": "{{shadcn.iconLibrary}}"\n}\n',
|
|
590
591
|
"web/next.config.ts.hbs": "import type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n{{#if (eq structure 'monorepo')}}\n transpilePackages: ['@repo/ui', '@repo/backend'],\n{{/if}}\n}\n\nexport default nextConfig\n",
|
|
591
592
|
"web/package.json.hbs": `{
|
|
@@ -1266,36 +1267,11 @@ export function DashboardLayout({ children, title = 'Dashboard' }: DashboardLayo
|
|
|
1266
1267
|
}
|
|
1267
1268
|
`,
|
|
1268
1269
|
"web/src/components/providers/convex-provider.tsx.hbs": "'use client'\n\nimport { ConvexReactClient } from 'convex/react'\nimport { ConvexBetterAuthProvider } from '@convex-dev/better-auth/react'\nimport { authClient } from '@/lib/auth'\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!)\n\nexport function ConvexClientProvider({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n {children}\n </ConvexBetterAuthProvider>\n )\n}\n",
|
|
1269
|
-
"web/src/lib/auth-server.ts.hbs": "import { convexBetterAuthNextJs } from '@convex-dev/better-auth/nextjs'\n\nexport const {\n handler,\n preloadAuthQuery,\n isAuthenticated,\n getToken,\n fetchAuthQuery,\n fetchAuthMutation,\n fetchAuthAction,\n} = convexBetterAuthNextJs({\n convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,\n convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL
|
|
1270
|
+
"web/src/lib/auth-server.ts.hbs": "import { convexBetterAuthNextJs } from '@convex-dev/better-auth/nextjs'\n\nexport const {\n handler,\n preloadAuthQuery,\n isAuthenticated,\n getToken,\n fetchAuthQuery,\n fetchAuthMutation,\n fetchAuthAction,\n} = convexBetterAuthNextJs({\n convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,\n convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL || process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',\n})\n",
|
|
1270
1271
|
"web/src/lib/auth.ts.hbs": "'use client'\n\nimport { createAuthClient } from 'better-auth/react'\nimport { convexClient } from '@convex-dev/better-auth/client/plugins'\n\nexport const authClient = createAuthClient({\n plugins: [convexClient()],\n})\n\nexport const {\n signIn,\n signUp,\n signOut,\n useSession,\n getSession,\n} = authClient\n",
|
|
1271
1272
|
"web/src/lib/utils.ts.hbs": "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
|
|
1272
|
-
"web/src/proxy.ts.hbs": "import { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\nimport { isAuthenticated } from '@/lib/auth-server'\n\nconst publicRoutes = ['/sign-in', '/sign-up', '/api/auth']\n\nfunction isPublicRoute(pathname: string) {\n return publicRoutes.some(route => pathname.startsWith(route))\n}\n\nexport async function proxy(request: NextRequest) {\n const { pathname } = request.nextUrl\n\n // Allow public routes\n if (isPublicRoute(pathname)) {\n return NextResponse.next()\n }\n\n // Check authentication\n const authenticated = await isAuthenticated()\n\n
|
|
1273
|
-
"web/tsconfig.json.hbs":
|
|
1274
|
-
"compilerOptions": {
|
|
1275
|
-
"target": "ES2017",
|
|
1276
|
-
"lib": ["dom", "dom.iterable", "esnext"],
|
|
1277
|
-
"allowJs": true,
|
|
1278
|
-
"skipLibCheck": true,
|
|
1279
|
-
"strict": true,
|
|
1280
|
-
"noEmit": true,
|
|
1281
|
-
"esModuleInterop": true,
|
|
1282
|
-
"module": "esnext",
|
|
1283
|
-
"moduleResolution": "bundler",
|
|
1284
|
-
"resolveJsonModule": true,
|
|
1285
|
-
"isolatedModules": true,
|
|
1286
|
-
"jsx": "react-jsx",
|
|
1287
|
-
"incremental": true,
|
|
1288
|
-
"plugins": [{ "name": "next" }],
|
|
1289
|
-
"paths": {
|
|
1290
|
-
{{#if (eq structure 'monorepo')}} "@/components/ui/*": ["../../packages/ui/src/components/ui/*"],
|
|
1291
|
-
"@/lib/utils": ["../../packages/ui/src/lib/utils"],
|
|
1292
|
-
{{/if}} "@/*": ["./src/*"]
|
|
1293
|
-
}
|
|
1294
|
-
},
|
|
1295
|
-
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
|
|
1296
|
-
"exclude": ["node_modules"]
|
|
1297
|
-
}
|
|
1298
|
-
`
|
|
1273
|
+
"web/src/proxy.ts.hbs": "import { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\nimport { isAuthenticated } from '@/lib/auth-server'\n\nconst publicRoutes = ['/sign-in', '/sign-up', '/api/auth']\n\nfunction isPublicRoute(pathname: string) {\n return publicRoutes.some(route => pathname.startsWith(route))\n}\n\nexport async function proxy(request: NextRequest) {\n const { pathname } = request.nextUrl\n\n // Allow public routes\n if (isPublicRoute(pathname)) {\n return NextResponse.next()\n }\n\n // Skip auth check if Convex URL isn't configured yet\n if (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n return NextResponse.next()\n }\n\n // Check authentication\n try {\n const authenticated = await isAuthenticated()\n\n // Redirect unauthenticated users to /sign-up\n if (!authenticated) {\n return NextResponse.redirect(new URL('/sign-up', request.url))\n }\n } catch {\n // If auth check fails (e.g., Convex not running), allow request through\n return NextResponse.next()\n }\n\n return NextResponse.next()\n}\n\nexport const config = {\n matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],\n}\n",
|
|
1274
|
+
"web/tsconfig.json.hbs": '{\n "compilerOptions": {\n "target": "ES2017",\n "lib": ["dom", "dom.iterable", "esnext"],\n "allowJs": true,\n "skipLibCheck": true,\n "strict": true,\n "noEmit": true,\n "esModuleInterop": true,\n "module": "esnext",\n "moduleResolution": "bundler",\n "resolveJsonModule": true,\n "isolatedModules": true,\n "jsx": "react-jsx",\n "incremental": true,\n "plugins": [{ "name": "next" }],\n "paths": {\n "@/*": ["./src/*"]\n }\n },\n "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],\n "exclude": ["node_modules"]\n}\n'
|
|
1299
1275
|
};
|
|
1300
1276
|
|
|
1301
1277
|
// src/generator.ts
|
package/package.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Auto-generated file. Do not edit manually.
|
|
2
2
|
// Run 'pnpm prebuild' to regenerate.
|
|
3
|
-
// Generated: 2026-01-
|
|
4
|
-
// Template count:
|
|
3
|
+
// Generated: 2026-01-14T04:39:40.149Z
|
|
4
|
+
// Template count: 90
|
|
5
5
|
|
|
6
6
|
export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
7
7
|
"base/_gitignore.hbs": "# Dependencies\nnode_modules\n.pnpm-store\n\n# Build outputs\n.next\ndist\n.turbo\nout\n\n# Testing\ncoverage\nplaywright-report\ntest-results\n\n# Environment\n.env\n.env.local\n.env.*.local\n\n# IDE\n.idea\n.vscode\n*.swp\n*.swo\n.DS_Store\n\n# Convex\n.convex\n\n# Vercel\n.vercel\n\n# Debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# TypeScript\n*.tsbuildinfo\n\n# Misc\n*.pem\n.cache\n",
|
|
8
8
|
"base/biome.json.hbs": "{\n \"$schema\": \"https://biomejs.dev/schemas/1.9.4/schema.json\",\n \"organizeImports\": {\n \"enabled\": true\n },\n \"linter\": {\n \"enabled\": true,\n \"rules\": {\n \"recommended\": true\n }\n },\n \"formatter\": {\n \"enabled\": true,\n \"indentStyle\": \"space\",\n \"indentWidth\": 2\n },\n \"javascript\": {\n \"formatter\": {\n \"quoteStyle\": \"single\",\n \"semicolons\": \"asNeeded\"\n }\n },\n \"files\": {\n \"ignore\": [\n \"node_modules\",\n \".next\",\n \"dist\",\n \".turbo\",\n \"coverage\",\n \".vercel\",\n \"_generated\"\n ]\n }\n}\n",
|
|
9
9
|
"convex/_env.local.hbs": "# Convex\nCONVEX_DEPLOYMENT=\nNEXT_PUBLIC_CONVEX_URL=\nNEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000\n\n# Site URL (used for auth redirects)\nSITE_URL=http://localhost:3000\nNEXT_PUBLIC_SITE_URL=http://localhost:3000\n\n# Better Auth Secret (generate with: openssl rand -base64 32)\nBETTER_AUTH_SECRET=\n\n# Auth - GitHub OAuth\nGITHUB_CLIENT_ID=\nGITHUB_CLIENT_SECRET=\n\n# Auth - Google OAuth\nGOOGLE_CLIENT_ID=\nGOOGLE_CLIENT_SECRET=\n\n# Email (Resend) - https://resend.com\nRESEND_API_KEY=\nRESEND_FROM_EMAIL=\n{{#if (eq integrations.analytics 'posthog')}}\n\n# PostHog\nNEXT_PUBLIC_POSTHOG_KEY=\nNEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com\n{{/if}}\n{{#if (eq integrations.uploads 'convex-fs')}}\n\n# Convex FS - Built-in file storage (no additional config needed)\n{{/if}}\n{{#if (eq integrations.uploads 'r2')}}\n\n# Cloudflare R2\nR2_ACCESS_KEY_ID=\nR2_SECRET_ACCESS_KEY=\nR2_BUCKET=\nR2_ENDPOINT=\n{{/if}}\n{{#if (eq integrations.uploads 'uploadthing')}}\n\n# UploadThing\nUPLOADTHING_TOKEN=\n{{/if}}\n{{#if (eq integrations.uploads 's3')}}\n\n# AWS S3\nAWS_ACCESS_KEY_ID=\nAWS_SECRET_ACCESS_KEY=\nAWS_REGION=\nAWS_S3_BUCKET=\n{{/if}}\n{{#if (eq integrations.uploads 'vercel-blob')}}\n\n# Vercel Blob\nBLOB_READ_WRITE_TOKEN=\n{{/if}}\n{{#if (eq integrations.payments 'stripe')}}\n\n# Stripe\nSTRIPE_SECRET_KEY=\nSTRIPE_WEBHOOK_SECRET=\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\n{{/if}}\n{{#if (eq integrations.payments 'polar')}}\n\n# Polar\nPOLAR_ACCESS_TOKEN=\nPOLAR_WEBHOOK_SECRET=\nPOLAR_ORGANIZATION_ID=\n{{/if}}\n{{#if (includes addons 'rate-limiting')}}\n\n# Convex Rate Limiter - No additional config needed (uses Convex backend)\n{{/if}}\n{{#if (includes addons 'monitoring')}}\n\n# Sentry\nSENTRY_DSN=\nSENTRY_AUTH_TOKEN=\n{{/if}}\n",
|
|
10
10
|
"convex/convex/auth.config.ts.hbs": "import { getAuthConfigProvider } from '@convex-dev/better-auth/auth-config'\nimport type { AuthConfig } from 'convex/server'\n\nexport default {\n providers: [getAuthConfigProvider()],\n} satisfies AuthConfig\n",
|
|
11
|
-
"convex/convex/auth.ts.hbs": "import { createClient, type GenericCtx } from '@convex-dev/better-auth'\nimport { convex } from '@convex-dev/better-auth/plugins'\nimport { betterAuth } from 'better-auth/minimal'\nimport { components } from './_generated/api'\nimport type { DataModel } from './_generated/dataModel'\nimport { query } from './_generated/server'\nimport authConfig from './auth.config'\n\nconst siteUrl = process.env.SITE_URL
|
|
11
|
+
"convex/convex/auth.ts.hbs": "import { createClient, type GenericCtx } from '@convex-dev/better-auth'\nimport { convex } from '@convex-dev/better-auth/plugins'\nimport { betterAuth } from 'better-auth/minimal'\nimport { components } from './_generated/api'\nimport type { DataModel } from './_generated/dataModel'\nimport { query } from './_generated/server'\nimport authConfig from './auth.config'\n\nconst siteUrl = process.env.SITE_URL || 'http://localhost:3000'\n\nexport const authComponent = createClient<DataModel>(components.betterAuth)\n\n// Build social providers only if credentials are configured\nconst socialProviders: Record<string, { clientId: string; clientSecret: string }> = {}\n\nif (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {\n socialProviders.github = {\n clientId: process.env.GITHUB_CLIENT_ID,\n clientSecret: process.env.GITHUB_CLIENT_SECRET,\n }\n}\n\nif (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {\n socialProviders.google = {\n clientId: process.env.GOOGLE_CLIENT_ID,\n clientSecret: process.env.GOOGLE_CLIENT_SECRET,\n }\n}\n\nexport const createAuth = (ctx: GenericCtx<DataModel>) => {\n return betterAuth({\n baseURL: siteUrl,\n database: authComponent.adapter(ctx),\n emailAndPassword: {\n enabled: true,\n requireEmailVerification: false,\n },\n socialProviders: Object.keys(socialProviders).length > 0 ? socialProviders : undefined,\n plugins: [convex({ authConfig })],\n })\n}\n\nexport const getCurrentUser = query({\n args: {},\n handler: async (ctx) => {\n return authComponent.getAuthUser(ctx)\n },\n})\n",
|
|
12
12
|
"convex/convex/convex.config.ts.hbs": "import { defineApp } from 'convex/server'\nimport betterAuth from '@convex-dev/better-auth/convex.config'\n\nconst app = defineApp()\napp.use(betterAuth)\n\nexport default app\n",
|
|
13
13
|
"convex/convex/http.ts.hbs": "import { httpRouter } from 'convex/server'\nimport { authComponent, createAuth } from './auth'\n\nconst http = httpRouter()\n\n// Register Better Auth routes\nauthComponent.registerRoutes(http, createAuth)\n\nexport default http\n",
|
|
14
14
|
"convex/convex/schema.ts.hbs": "import { defineSchema, defineTable } from 'convex/server'\nimport { v } from 'convex/values'\n\n// Better Auth manages its own tables via the betterAuth component\n// Add your custom application tables here\nexport default defineSchema({\n // Example:\n // posts: defineTable({\n // title: v.string(),\n // content: v.string(),\n // userId: v.string(), // Better Auth user ID\n // createdAt: v.number(),\n // }).index('by_user', ['userId']),\n})\n",
|
|
@@ -72,6 +72,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
|
72
72
|
"packages/ui/src/index.ts.hbs": "export { cn } from './lib/utils'\n// Export components as they are added\n// export * from './components/ui/button'\n",
|
|
73
73
|
"packages/ui/src/lib/utils.ts.hbs": "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
|
|
74
74
|
"packages/ui/tsconfig.json.hbs": "{\n \"compilerOptions\": {\n \"target\": \"ES2020\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n }\n },\n \"include\": [\"src/**/*\"],\n \"exclude\": [\"node_modules\"]\n}\n",
|
|
75
|
+
"web/_env.local.hbs": "# Convex - These values are synced from packages/backend/.env.local after running convex dev\n# Copy NEXT_PUBLIC_CONVEX_URL from packages/backend/.env.local after Convex setup\nNEXT_PUBLIC_CONVEX_URL=\n\n# Site URL for auth\nNEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000\nNEXT_PUBLIC_SITE_URL=http://localhost:3000\n{{#if (eq integrations.analytics 'posthog')}}\n\n# PostHog\nNEXT_PUBLIC_POSTHOG_KEY=\nNEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com\n{{/if}}\n{{#if (eq integrations.payments 'stripe')}}\n\n# Stripe\nNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=\n{{/if}}\n",
|
|
75
76
|
"web/components.json.hbs": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"new-york\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {\n \"config\": \"\",\n \"css\": \"src/app/globals.css\",\n \"baseColor\": \"{{shadcn.baseColor}}\",\n \"cssVariables\": true,\n \"prefix\": \"\"\n },\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\",\n \"ui\": \"@/components/ui\",\n \"lib\": \"@/lib\",\n \"hooks\": \"@/hooks\"\n },\n \"iconLibrary\": \"{{shadcn.iconLibrary}}\"\n}\n",
|
|
76
77
|
"web/next.config.ts.hbs": "import type { NextConfig } from 'next'\n\nconst nextConfig: NextConfig = {\n{{#if (eq structure 'monorepo')}}\n transpilePackages: ['@repo/ui', '@repo/backend'],\n{{/if}}\n}\n\nexport default nextConfig\n",
|
|
77
78
|
"web/package.json.hbs": "{\n \"name\": \"{{#if (eq structure 'monorepo')}}@repo/web{{else}}{{projectName}}{{/if}}\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"type\": \"module\",\n \"scripts\": {\n \"dev\": \"{{#if (eq structure 'monorepo')}}next dev --turbopack{{else}}node scripts/dev.mjs{{/if}}\",\n \"dev:next\": \"next dev --turbopack\",\n{{#unless (eq structure 'monorepo')}} \"dev:setup\": \"npx convex dev --configure --until-success\",\n{{/unless}} \"build\": \"next build\",\n \"start\": \"next start\",\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"vitest run\",\n \"test:watch\": \"vitest\",\n \"test:e2e\": \"playwright test\"\n },\n \"dependencies\": {\n{{#if (eq structure 'monorepo')}} \"@repo/backend\": \"workspace:*\",\n \"@repo/ui\": \"workspace:*\",\n{{/if}} \"next\": \"^16.0.0\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"convex\": \"^1.25.0\",\n \"@convex-dev/better-auth\": \"^0.10.0\",\n \"better-auth\": \"1.4.9\",\n{{#unless (eq structure 'monorepo')}}{{#if (eq shadcn.iconLibrary \"hugeicons\")}} \"@hugeicons/react\": \"^0.3.0\",\n{{/if}}{{#if (eq shadcn.iconLibrary \"lucide\")}} \"lucide-react\": \"^0.469.0\",\n{{/if}}{{#if (eq shadcn.iconLibrary \"tabler\")}} \"@tabler/icons-react\": \"^3.31.0\",\n{{/if}}{{#if (eq shadcn.iconLibrary \"phosphor\")}} \"@phosphor-icons/react\": \"^2.1.7\",\n{{/if}}{{/unless}} \"class-variance-authority\": \"^0.7.0\",\n \"clsx\": \"^2.1.0\",\n \"tailwind-merge\": \"^2.5.0\",\n \"tw-animate-css\": \"^1.3.0\",\n \"resend\": \"^4.0.0\",\n \"react-email\": \"^3.0.0\",\n \"@react-email/components\": \"^0.0.36\"{{#if (eq integrations.analytics 'posthog')}},\n \"posthog-js\": \"^1.200.0\",\n \"posthog-node\": \"^5.0.0\"{{/if}}{{#if (eq integrations.analytics 'vercel')}},\n \"@vercel/analytics\": \"^1.4.0\",\n \"@vercel/speed-insights\": \"^1.1.0\"{{/if}}{{#if (eq integrations.uploads 'uploadthing')}},\n \"uploadthing\": \"^7.0.0\",\n \"@uploadthing/react\": \"^7.0.0\"{{/if}}{{#if (eq integrations.uploads 's3')}},\n \"@aws-sdk/client-s3\": \"^3.700.0\",\n \"@aws-sdk/s3-request-presigner\": \"^3.700.0\"{{/if}}{{#if (eq integrations.uploads 'vercel-blob')}},\n \"@vercel/blob\": \"^2.0.0\"{{/if}}{{#if (includes addons 'rate-limiting')}},\n \"@arcjet/next\": \"^1.0.0-beta.16\"{{/if}}{{#if (includes addons 'monitoring')}},\n \"@sentry/nextjs\": \"^8.0.0\"{{/if}}\n },\n \"devDependencies\": {\n{{#if (eq structure 'monorepo')}} \"@repo/config-typescript\": \"workspace:*\",\n{{/if}} \"@types/node\": \"^20.0.0\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"tailwindcss\": \"^4.0.0\",\n \"@tailwindcss/postcss\": \"^4.0.0\",\n \"postcss\": \"^8.4.0\",\n \"typescript\": \"^5.0.0\",\n \"vitest\": \"^3.0.0\",\n \"@vitejs/plugin-react\": \"^4.3.0\",\n \"@testing-library/react\": \"^16.0.0\",\n \"jsdom\": \"^26.0.0\",\n \"playwright\": \"^1.50.0\",\n \"@playwright/test\": \"^1.50.0\"\n }\n}\n",
|
|
@@ -88,9 +89,9 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
|
88
89
|
"web/src/components/dashboard/app-sidebar.tsx.hbs": "'use client'\n\nimport {\n GalleryVerticalEnd,\n Home,\n Settings,\n ChevronsUpDown,\n LogOut,\n User,\n} from 'lucide-react'\nimport { useRouter } from 'next/navigation'\nimport { signOut, useSession } from '@/lib/auth'\nimport {\n Sidebar,\n SidebarContent,\n SidebarFooter,\n SidebarGroup,\n SidebarGroupContent,\n SidebarGroupLabel,\n SidebarHeader,\n SidebarMenu,\n SidebarMenuButton,\n SidebarMenuItem,\n} from '@/components/ui/sidebar'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '@/components/ui/dropdown-menu'\nimport { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'\n\nconst navigation = [\n {\n title: 'Home',\n url: '/',\n icon: Home,\n },\n {\n title: 'Settings',\n url: '/settings',\n icon: Settings,\n },\n]\n\nexport function AppSidebar() {\n const router = useRouter()\n const { data: session } = useSession()\n const user = session?.user\n\n const handleSignOut = async () => {\n await signOut()\n router.push('/sign-in')\n }\n\n const getInitials = (name?: string | null) => {\n if (!name) return 'U'\n return name\n .split(' ')\n .map((n) => n[0])\n .join('')\n .toUpperCase()\n .slice(0, 2)\n }\n\n return (\n <Sidebar>\n <SidebarHeader>\n <SidebarMenu>\n <SidebarMenuItem>\n <SidebarMenuButton size=\"lg\" asChild>\n <a href=\"/\">\n <div className=\"flex aspect-square size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground\">\n <GalleryVerticalEnd className=\"size-4\" />\n </div>\n <div className=\"flex flex-col gap-0.5 leading-none\">\n <span className=\"font-semibold\">{{projectName}}</span>\n <span className=\"text-xs text-muted-foreground\">Dashboard</span>\n </div>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarHeader>\n <SidebarContent>\n <SidebarGroup>\n <SidebarGroupLabel>Navigation</SidebarGroupLabel>\n <SidebarGroupContent>\n <SidebarMenu>\n {navigation.map((item) => (\n <SidebarMenuItem key={item.title}>\n <SidebarMenuButton asChild>\n <a href={item.url}>\n <item.icon className=\"size-4\" />\n <span>{item.title}</span>\n </a>\n </SidebarMenuButton>\n </SidebarMenuItem>\n ))}\n </SidebarMenu>\n </SidebarGroupContent>\n </SidebarGroup>\n </SidebarContent>\n <SidebarFooter>\n <SidebarMenu>\n <SidebarMenuItem>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <SidebarMenuButton\n size=\"lg\"\n className=\"data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground\"\n >\n <Avatar className=\"h-8 w-8 rounded-lg\">\n <AvatarImage src={user?.image ?? undefined} alt={user?.name ?? 'User'} />\n <AvatarFallback className=\"rounded-lg\">\n {getInitials(user?.name)}\n </AvatarFallback>\n </Avatar>\n <div className=\"grid flex-1 text-left text-sm leading-tight\">\n <span className=\"truncate font-semibold\">{user?.name ?? 'User'}</span>\n <span className=\"truncate text-xs text-muted-foreground\">\n {user?.email ?? ''}\n </span>\n </div>\n <ChevronsUpDown className=\"ml-auto size-4\" />\n </SidebarMenuButton>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n className=\"w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg\"\n side=\"bottom\"\n align=\"end\"\n sideOffset={4}\n >\n <DropdownMenuItem asChild>\n <a href=\"/settings\">\n <User className=\"mr-2 size-4\" />\n Profile\n </a>\n </DropdownMenuItem>\n <DropdownMenuItem asChild>\n <a href=\"/settings\">\n <Settings className=\"mr-2 size-4\" />\n Settings\n </a>\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem onClick={handleSignOut}>\n <LogOut className=\"mr-2 size-4\" />\n Sign out\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </SidebarMenuItem>\n </SidebarMenu>\n </SidebarFooter>\n </Sidebar>\n )\n}\n",
|
|
89
90
|
"web/src/components/dashboard/dashboard-layout.tsx.hbs": "'use client'\n\nimport { SidebarInset, SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar'\nimport { Separator } from '@/components/ui/separator'\nimport {\n Breadcrumb,\n BreadcrumbItem,\n BreadcrumbList,\n BreadcrumbPage,\n} from '@/components/ui/breadcrumb'\nimport { AppSidebar } from './app-sidebar'\n\ninterface DashboardLayoutProps {\n children: React.ReactNode\n title?: string\n}\n\nexport function DashboardLayout({ children, title = 'Dashboard' }: DashboardLayoutProps) {\n return (\n <SidebarProvider>\n <AppSidebar />\n <SidebarInset>\n <header className=\"flex h-16 shrink-0 items-center gap-2 border-b px-4\">\n <SidebarTrigger className=\"-ml-1\" />\n <Separator orientation=\"vertical\" className=\"mr-2 h-4\" />\n <Breadcrumb>\n <BreadcrumbList>\n <BreadcrumbItem>\n <BreadcrumbPage>{title}</BreadcrumbPage>\n </BreadcrumbItem>\n </BreadcrumbList>\n </Breadcrumb>\n </header>\n <main className=\"flex-1 p-4 md:p-6\">{children}</main>\n </SidebarInset>\n </SidebarProvider>\n )\n}\n",
|
|
90
91
|
"web/src/components/providers/convex-provider.tsx.hbs": "'use client'\n\nimport { ConvexReactClient } from 'convex/react'\nimport { ConvexBetterAuthProvider } from '@convex-dev/better-auth/react'\nimport { authClient } from '@/lib/auth'\n\nconst convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!)\n\nexport function ConvexClientProvider({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <ConvexBetterAuthProvider client={convex} authClient={authClient}>\n {children}\n </ConvexBetterAuthProvider>\n )\n}\n",
|
|
91
|
-
"web/src/lib/auth-server.ts.hbs": "import { convexBetterAuthNextJs } from '@convex-dev/better-auth/nextjs'\n\nexport const {\n handler,\n preloadAuthQuery,\n isAuthenticated,\n getToken,\n fetchAuthQuery,\n fetchAuthMutation,\n fetchAuthAction,\n} = convexBetterAuthNextJs({\n convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,\n convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL
|
|
92
|
+
"web/src/lib/auth-server.ts.hbs": "import { convexBetterAuthNextJs } from '@convex-dev/better-auth/nextjs'\n\nexport const {\n handler,\n preloadAuthQuery,\n isAuthenticated,\n getToken,\n fetchAuthQuery,\n fetchAuthMutation,\n fetchAuthAction,\n} = convexBetterAuthNextJs({\n convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,\n convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL || process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',\n})\n",
|
|
92
93
|
"web/src/lib/auth.ts.hbs": "'use client'\n\nimport { createAuthClient } from 'better-auth/react'\nimport { convexClient } from '@convex-dev/better-auth/client/plugins'\n\nexport const authClient = createAuthClient({\n plugins: [convexClient()],\n})\n\nexport const {\n signIn,\n signUp,\n signOut,\n useSession,\n getSession,\n} = authClient\n",
|
|
93
94
|
"web/src/lib/utils.ts.hbs": "import { clsx, type ClassValue } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n",
|
|
94
|
-
"web/src/proxy.ts.hbs": "import { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\nimport { isAuthenticated } from '@/lib/auth-server'\n\nconst publicRoutes = ['/sign-in', '/sign-up', '/api/auth']\n\nfunction isPublicRoute(pathname: string) {\n return publicRoutes.some(route => pathname.startsWith(route))\n}\n\nexport async function proxy(request: NextRequest) {\n const { pathname } = request.nextUrl\n\n // Allow public routes\n if (isPublicRoute(pathname)) {\n return NextResponse.next()\n }\n\n // Check authentication\n const authenticated = await isAuthenticated()\n\n
|
|
95
|
-
"web/tsconfig.json.hbs": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"paths\": {\n
|
|
95
|
+
"web/src/proxy.ts.hbs": "import { NextResponse } from 'next/server'\nimport type { NextRequest } from 'next/server'\nimport { isAuthenticated } from '@/lib/auth-server'\n\nconst publicRoutes = ['/sign-in', '/sign-up', '/api/auth']\n\nfunction isPublicRoute(pathname: string) {\n return publicRoutes.some(route => pathname.startsWith(route))\n}\n\nexport async function proxy(request: NextRequest) {\n const { pathname } = request.nextUrl\n\n // Allow public routes\n if (isPublicRoute(pathname)) {\n return NextResponse.next()\n }\n\n // Skip auth check if Convex URL isn't configured yet\n if (!process.env.NEXT_PUBLIC_CONVEX_URL) {\n return NextResponse.next()\n }\n\n // Check authentication\n try {\n const authenticated = await isAuthenticated()\n\n // Redirect unauthenticated users to /sign-up\n if (!authenticated) {\n return NextResponse.redirect(new URL('/sign-up', request.url))\n }\n } catch {\n // If auth check fails (e.g., Convex not running), allow request through\n return NextResponse.next()\n }\n\n return NextResponse.next()\n}\n\nexport const config = {\n matcher: ['/((?!.*\\\\..*|_next).*)', '/', '/(api|trpc)(.*)'],\n}\n",
|
|
96
|
+
"web/tsconfig.json.hbs": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"skipLibCheck\": true,\n \"strict\": true,\n \"noEmit\": true,\n \"esModuleInterop\": true,\n \"module\": \"esnext\",\n \"moduleResolution\": \"bundler\",\n \"resolveJsonModule\": true,\n \"isolatedModules\": true,\n \"jsx\": \"react-jsx\",\n \"incremental\": true,\n \"plugins\": [{ \"name\": \"next\" }],\n \"paths\": {\n \"@/*\": [\"./src/*\"]\n }\n },\n \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\", \".next/dev/types/**/*.ts\"],\n \"exclude\": [\"node_modules\"]\n}\n"
|
|
96
97
|
}
|
|
@@ -6,10 +6,27 @@ import type { DataModel } from './_generated/dataModel'
|
|
|
6
6
|
import { query } from './_generated/server'
|
|
7
7
|
import authConfig from './auth.config'
|
|
8
8
|
|
|
9
|
-
const siteUrl = process.env.SITE_URL
|
|
9
|
+
const siteUrl = process.env.SITE_URL || 'http://localhost:3000'
|
|
10
10
|
|
|
11
11
|
export const authComponent = createClient<DataModel>(components.betterAuth)
|
|
12
12
|
|
|
13
|
+
// Build social providers only if credentials are configured
|
|
14
|
+
const socialProviders: Record<string, { clientId: string; clientSecret: string }> = {}
|
|
15
|
+
|
|
16
|
+
if (process.env.GITHUB_CLIENT_ID && process.env.GITHUB_CLIENT_SECRET) {
|
|
17
|
+
socialProviders.github = {
|
|
18
|
+
clientId: process.env.GITHUB_CLIENT_ID,
|
|
19
|
+
clientSecret: process.env.GITHUB_CLIENT_SECRET,
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (process.env.GOOGLE_CLIENT_ID && process.env.GOOGLE_CLIENT_SECRET) {
|
|
24
|
+
socialProviders.google = {
|
|
25
|
+
clientId: process.env.GOOGLE_CLIENT_ID,
|
|
26
|
+
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
export const createAuth = (ctx: GenericCtx<DataModel>) => {
|
|
14
31
|
return betterAuth({
|
|
15
32
|
baseURL: siteUrl,
|
|
@@ -18,16 +35,7 @@ export const createAuth = (ctx: GenericCtx<DataModel>) => {
|
|
|
18
35
|
enabled: true,
|
|
19
36
|
requireEmailVerification: false,
|
|
20
37
|
},
|
|
21
|
-
socialProviders:
|
|
22
|
-
github: {
|
|
23
|
-
clientId: process.env.GITHUB_CLIENT_ID!,
|
|
24
|
-
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
|
|
25
|
-
},
|
|
26
|
-
google: {
|
|
27
|
-
clientId: process.env.GOOGLE_CLIENT_ID!,
|
|
28
|
-
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
|
|
29
|
-
},
|
|
30
|
-
},
|
|
38
|
+
socialProviders: Object.keys(socialProviders).length > 0 ? socialProviders : undefined,
|
|
31
39
|
plugins: [convex({ authConfig })],
|
|
32
40
|
})
|
|
33
41
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Convex - These values are synced from packages/backend/.env.local after running convex dev
|
|
2
|
+
# Copy NEXT_PUBLIC_CONVEX_URL from packages/backend/.env.local after Convex setup
|
|
3
|
+
NEXT_PUBLIC_CONVEX_URL=
|
|
4
|
+
|
|
5
|
+
# Site URL for auth
|
|
6
|
+
NEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000
|
|
7
|
+
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
8
|
+
{{#if (eq integrations.analytics 'posthog')}}
|
|
9
|
+
|
|
10
|
+
# PostHog
|
|
11
|
+
NEXT_PUBLIC_POSTHOG_KEY=
|
|
12
|
+
NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
|
|
13
|
+
{{/if}}
|
|
14
|
+
{{#if (eq integrations.payments 'stripe')}}
|
|
15
|
+
|
|
16
|
+
# Stripe
|
|
17
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
|
|
18
|
+
{{/if}}
|
|
@@ -10,5 +10,5 @@ export const {
|
|
|
10
10
|
fetchAuthAction,
|
|
11
11
|
} = convexBetterAuthNextJs({
|
|
12
12
|
convexUrl: process.env.NEXT_PUBLIC_CONVEX_URL!,
|
|
13
|
-
convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL
|
|
13
|
+
convexSiteUrl: process.env.NEXT_PUBLIC_CONVEX_SITE_URL || process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000',
|
|
14
14
|
})
|
|
@@ -16,12 +16,22 @@ export async function proxy(request: NextRequest) {
|
|
|
16
16
|
return NextResponse.next()
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
//
|
|
20
|
-
|
|
19
|
+
// Skip auth check if Convex URL isn't configured yet
|
|
20
|
+
if (!process.env.NEXT_PUBLIC_CONVEX_URL) {
|
|
21
|
+
return NextResponse.next()
|
|
22
|
+
}
|
|
21
23
|
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
// Check authentication
|
|
25
|
+
try {
|
|
26
|
+
const authenticated = await isAuthenticated()
|
|
27
|
+
|
|
28
|
+
// Redirect unauthenticated users to /sign-up
|
|
29
|
+
if (!authenticated) {
|
|
30
|
+
return NextResponse.redirect(new URL('/sign-up', request.url))
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// If auth check fails (e.g., Convex not running), allow request through
|
|
34
|
+
return NextResponse.next()
|
|
25
35
|
}
|
|
26
36
|
|
|
27
37
|
return NextResponse.next()
|
|
@@ -15,9 +15,7 @@
|
|
|
15
15
|
"incremental": true,
|
|
16
16
|
"plugins": [{ "name": "next" }],
|
|
17
17
|
"paths": {
|
|
18
|
-
|
|
19
|
-
"@/lib/utils": ["../../packages/ui/src/lib/utils"],
|
|
20
|
-
{{/if}} "@/*": ["./src/*"]
|
|
18
|
+
"@/*": ["./src/*"]
|
|
21
19
|
}
|
|
22
20
|
},
|
|
23
21
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
|