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
|
|
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#
|
|
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
|
|
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
|
-
//
|
|
1441
|
-
|
|
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
|
|
1446
|
-
content = content.split('\\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
|
|
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
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
|
|
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
|
-
//
|
|
261
|
-
|
|
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
|
|
266
|
-
content = content.split('\\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
|
|
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-
|
|
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
|
|
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#
|
|
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,9 +1,9 @@
|
|
|
1
1
|
# Convex - These values are synced from packages/backend/.env.local after running convex dev
|
|
2
|
-
#
|
|
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
|
|