kofi-stack-template-generator 2.1.13 → 2.1.15

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/dist/index.js CHANGED
@@ -243,7 +243,7 @@ function shouldIncludeFile(templatePath, config) {
243
243
  var EMBEDDED_TEMPLATES = {
244
244
  "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",
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
- "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",
246
+ "convex/_env.local.hbs": "# Convex\nCONVEX_DEPLOYMENT=\nNEXT_PUBLIC_CONVEX_URL=\nNEXT_PUBLIC_CONVEX_SITE_URL=\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
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",
@@ -586,7 +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
+ "web/_env.local.hbs": "# Convex - These values are synced from packages/backend/.env.local after running convex dev\n# NEXT_PUBLIC_CONVEX_URL and NEXT_PUBLIC_CONVEX_SITE_URL are auto-synced by dev script\nNEXT_PUBLIC_CONVEX_URL=\nNEXT_PUBLIC_CONVEX_SITE_URL=\n\n# Site URL for auth (your frontend URL)\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",
590
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',
591
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",
592
592
  "web/package.json.hbs": `{
@@ -1430,25 +1430,33 @@ function loadEnvFile(dir) {
1430
1430
 
1431
1431
  function syncEnvToWebApp() {
1432
1432
  // In monorepo, Convex creates .env.local in backend package
1433
- // Web app needs NEXT_PUBLIC_CONVEX_URL to connect to Convex
1433
+ // Web app needs NEXT_PUBLIC_CONVEX_URL and NEXT_PUBLIC_CONVEX_SITE_URL
1434
1434
  const backendEnv = loadEnvFile(backendDir)
1435
1435
  const webEnvPath = resolve(webAppDir, '.env.local')
1436
1436
 
1437
1437
  if (backendEnv.NEXT_PUBLIC_CONVEX_URL) {
1438
1438
  const webEnv = loadEnvFile(webAppDir)
1439
+ // Derive site URL from cloud URL (.convex.cloud -> .convex.site)
1440
+ const convexSiteUrl = backendEnv.NEXT_PUBLIC_CONVEX_URL.replace('.convex.cloud', '.convex.site')
1439
1441
 
1440
- // Only sync if web app doesn't have the URL or it's different
1441
- if (webEnv.NEXT_PUBLIC_CONVEX_URL !== backendEnv.NEXT_PUBLIC_CONVEX_URL) {
1442
+ // Check if sync is needed
1443
+ const needsSync = webEnv.NEXT_PUBLIC_CONVEX_URL !== backendEnv.NEXT_PUBLIC_CONVEX_URL ||
1444
+ webEnv.NEXT_PUBLIC_CONVEX_SITE_URL !== convexSiteUrl
1445
+
1446
+ if (needsSync) {
1442
1447
  let content = ''
1443
1448
  if (existsSync(webEnvPath)) {
1444
1449
  content = readFileSync(webEnvPath, 'utf-8')
1445
- // Remove existing NEXT_PUBLIC_CONVEX_URL line if present
1446
- content = content.split('\\n').filter(line => !line.startsWith('NEXT_PUBLIC_CONVEX_URL=')).join('\\n')
1450
+ // Remove existing lines
1451
+ content = content.split('\\n')
1452
+ .filter(line => !line.startsWith('NEXT_PUBLIC_CONVEX_URL=') && !line.startsWith('NEXT_PUBLIC_CONVEX_SITE_URL='))
1453
+ .join('\\n')
1447
1454
  if (content && !content.endsWith('\\n')) content += '\\n'
1448
1455
  }
1449
1456
  content += \`NEXT_PUBLIC_CONVEX_URL=\${backendEnv.NEXT_PUBLIC_CONVEX_URL}\\n\`
1457
+ content += \`NEXT_PUBLIC_CONVEX_SITE_URL=\${convexSiteUrl}\\n\`
1450
1458
  writeFileSync(webEnvPath, content)
1451
- console.log('\u2713 Synced NEXT_PUBLIC_CONVEX_URL to web app\\n')
1459
+ console.log('\u2713 Synced Convex URLs to web app\\n')
1452
1460
  }
1453
1461
  }
1454
1462
 
@@ -1462,6 +1470,42 @@ async function checkAndInstall() {
1462
1470
  }
1463
1471
  }
1464
1472
 
1473
+ function syncEnvToConvex(envVars) {
1474
+ // Sync required env vars to Convex cloud (only if not already synced)
1475
+ const requiredVars = ['BETTER_AUTH_SECRET', 'SITE_URL']
1476
+ let needsSync = false
1477
+
1478
+ // Check if env vars need syncing by trying to list them
1479
+ try {
1480
+ const result = execSync('npx convex env list', { cwd: backendDir, stdio: 'pipe' }).toString()
1481
+ for (const varName of requiredVars) {
1482
+ if (envVars[varName] && !result.includes(varName + '=')) {
1483
+ needsSync = true
1484
+ break
1485
+ }
1486
+ }
1487
+ } catch {
1488
+ needsSync = true
1489
+ }
1490
+
1491
+ if (needsSync) {
1492
+ console.log('\u{1F4E4} Syncing environment variables to Convex cloud...\\n')
1493
+ for (const varName of requiredVars) {
1494
+ if (envVars[varName]) {
1495
+ try {
1496
+ execSync(\`npx convex env set \${varName} "\${envVars[varName]}"\`, {
1497
+ cwd: backendDir,
1498
+ stdio: 'pipe'
1499
+ })
1500
+ console.log(\` \u2713 \${varName}\\n\`)
1501
+ } catch (error) {
1502
+ console.warn(\` \u26A0\uFE0F Could not set \${varName}\\n\`)
1503
+ }
1504
+ }
1505
+ }
1506
+ }
1507
+ }
1508
+
1465
1509
  function startDevServers() {
1466
1510
  ${isMonorepo ? "const backendEnv = syncEnvToWebApp()" : "const backendEnv = loadEnvFile(webAppDir)"}
1467
1511
 
@@ -1474,6 +1518,9 @@ function startDevServers() {
1474
1518
  return
1475
1519
  }
1476
1520
 
1521
+ // Sync env vars to Convex cloud if needed
1522
+ syncEnvToConvex(backendEnv)
1523
+
1477
1524
  console.log('\u{1F680} Starting development servers...\\n')
1478
1525
  spawn('pnpm', ['${isMonorepo ? "dev:all" : "dev:next"}'], {
1479
1526
  cwd: rootDir, stdio: 'inherit', shell: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kofi-stack-template-generator",
3
- "version": "2.1.13",
3
+ "version": "2.1.15",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/generator.ts CHANGED
@@ -250,25 +250,33 @@ function loadEnvFile(dir) {
250
250
 
251
251
  function syncEnvToWebApp() {
252
252
  // In monorepo, Convex creates .env.local in backend package
253
- // Web app needs NEXT_PUBLIC_CONVEX_URL to connect to Convex
253
+ // Web app needs NEXT_PUBLIC_CONVEX_URL and NEXT_PUBLIC_CONVEX_SITE_URL
254
254
  const backendEnv = loadEnvFile(backendDir)
255
255
  const webEnvPath = resolve(webAppDir, '.env.local')
256
256
 
257
257
  if (backendEnv.NEXT_PUBLIC_CONVEX_URL) {
258
258
  const webEnv = loadEnvFile(webAppDir)
259
+ // Derive site URL from cloud URL (.convex.cloud -> .convex.site)
260
+ const convexSiteUrl = backendEnv.NEXT_PUBLIC_CONVEX_URL.replace('.convex.cloud', '.convex.site')
259
261
 
260
- // Only sync if web app doesn't have the URL or it's different
261
- if (webEnv.NEXT_PUBLIC_CONVEX_URL !== backendEnv.NEXT_PUBLIC_CONVEX_URL) {
262
+ // Check if sync is needed
263
+ const needsSync = webEnv.NEXT_PUBLIC_CONVEX_URL !== backendEnv.NEXT_PUBLIC_CONVEX_URL ||
264
+ webEnv.NEXT_PUBLIC_CONVEX_SITE_URL !== convexSiteUrl
265
+
266
+ if (needsSync) {
262
267
  let content = ''
263
268
  if (existsSync(webEnvPath)) {
264
269
  content = readFileSync(webEnvPath, 'utf-8')
265
- // Remove existing NEXT_PUBLIC_CONVEX_URL line if present
266
- content = content.split('\\n').filter(line => !line.startsWith('NEXT_PUBLIC_CONVEX_URL=')).join('\\n')
270
+ // Remove existing lines
271
+ content = content.split('\\n')
272
+ .filter(line => !line.startsWith('NEXT_PUBLIC_CONVEX_URL=') && !line.startsWith('NEXT_PUBLIC_CONVEX_SITE_URL='))
273
+ .join('\\n')
267
274
  if (content && !content.endsWith('\\n')) content += '\\n'
268
275
  }
269
276
  content += \`NEXT_PUBLIC_CONVEX_URL=\${backendEnv.NEXT_PUBLIC_CONVEX_URL}\\n\`
277
+ content += \`NEXT_PUBLIC_CONVEX_SITE_URL=\${convexSiteUrl}\\n\`
270
278
  writeFileSync(webEnvPath, content)
271
- console.log('✓ Synced NEXT_PUBLIC_CONVEX_URL to web app\\n')
279
+ console.log('✓ Synced Convex URLs to web app\\n')
272
280
  }
273
281
  }
274
282
 
@@ -282,6 +290,42 @@ async function checkAndInstall() {
282
290
  }
283
291
  }
284
292
 
293
+ function syncEnvToConvex(envVars) {
294
+ // Sync required env vars to Convex cloud (only if not already synced)
295
+ const requiredVars = ['BETTER_AUTH_SECRET', 'SITE_URL']
296
+ let needsSync = false
297
+
298
+ // Check if env vars need syncing by trying to list them
299
+ try {
300
+ const result = execSync('npx convex env list', { cwd: backendDir, stdio: 'pipe' }).toString()
301
+ for (const varName of requiredVars) {
302
+ if (envVars[varName] && !result.includes(varName + '=')) {
303
+ needsSync = true
304
+ break
305
+ }
306
+ }
307
+ } catch {
308
+ needsSync = true
309
+ }
310
+
311
+ if (needsSync) {
312
+ console.log('📤 Syncing environment variables to Convex cloud...\\n')
313
+ for (const varName of requiredVars) {
314
+ if (envVars[varName]) {
315
+ try {
316
+ execSync(\`npx convex env set \${varName} "\${envVars[varName]}"\`, {
317
+ cwd: backendDir,
318
+ stdio: 'pipe'
319
+ })
320
+ console.log(\` ✓ \${varName}\\n\`)
321
+ } catch (error) {
322
+ console.warn(\` ⚠️ Could not set \${varName}\\n\`)
323
+ }
324
+ }
325
+ }
326
+ }
327
+ }
328
+
285
329
  function startDevServers() {
286
330
  ${isMonorepo ? 'const backendEnv = syncEnvToWebApp()' : 'const backendEnv = loadEnvFile(webAppDir)'}
287
331
 
@@ -294,6 +338,9 @@ function startDevServers() {
294
338
  return
295
339
  }
296
340
 
341
+ // Sync env vars to Convex cloud if needed
342
+ syncEnvToConvex(backendEnv)
343
+
297
344
  console.log('🚀 Starting development servers...\\n')
298
345
  spawn('pnpm', ['${isMonorepo ? 'dev:all' : 'dev:next'}'], {
299
346
  cwd: rootDir, stdio: 'inherit', shell: true
@@ -1,12 +1,12 @@
1
1
  // Auto-generated file. Do not edit manually.
2
2
  // Run 'pnpm prebuild' to regenerate.
3
- // Generated: 2026-01-15T00:46:15.242Z
3
+ // Generated: 2026-01-15T02:00:44.663Z
4
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
- "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",
9
+ "convex/_env.local.hbs": "# Convex\nCONVEX_DEPLOYMENT=\nNEXT_PUBLIC_CONVEX_URL=\nNEXT_PUBLIC_CONVEX_SITE_URL=\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
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",
@@ -72,7 +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
+ "web/_env.local.hbs": "# Convex - These values are synced from packages/backend/.env.local after running convex dev\n# NEXT_PUBLIC_CONVEX_URL and NEXT_PUBLIC_CONVEX_SITE_URL are auto-synced by dev script\nNEXT_PUBLIC_CONVEX_URL=\nNEXT_PUBLIC_CONVEX_SITE_URL=\n\n# Site URL for auth (your frontend URL)\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",
76
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",
77
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",
78
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",
@@ -1,7 +1,7 @@
1
1
  # Convex
2
2
  CONVEX_DEPLOYMENT=
3
3
  NEXT_PUBLIC_CONVEX_URL=
4
- NEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000
4
+ NEXT_PUBLIC_CONVEX_SITE_URL=
5
5
 
6
6
  # Site URL (used for auth redirects)
7
7
  SITE_URL=http://localhost:3000
@@ -1,9 +1,9 @@
1
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
2
+ # NEXT_PUBLIC_CONVEX_URL and NEXT_PUBLIC_CONVEX_SITE_URL are auto-synced by dev script
3
3
  NEXT_PUBLIC_CONVEX_URL=
4
+ NEXT_PUBLIC_CONVEX_SITE_URL=
4
5
 
5
- # Site URL for auth
6
- NEXT_PUBLIC_CONVEX_SITE_URL=http://localhost:3000
6
+ # Site URL for auth (your frontend URL)
7
7
  NEXT_PUBLIC_SITE_URL=http://localhost:3000
8
8
  {{#if (eq integrations.analytics 'posthog')}}
9
9