kofi-stack-template-generator 2.1.10 → 2.1.12
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 +5 -5
- package/dist/index.js +78 -15
- package/package.json +10 -10
- package/src/generator.ts +78 -14
- package/src/templates.generated.ts +2 -2
- package/templates/convex/convex/auth.ts.hbs +19 -11
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
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...
|
|
@@ -13,8 +13,8 @@ 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",
|
|
@@ -1503,6 +1503,8 @@ main()
|
|
|
1503
1503
|
vfs.writeFile(`${scriptsPath}/dev.mjs`, devScript);
|
|
1504
1504
|
}
|
|
1505
1505
|
function generateSetupConvexScript(vfs, scriptsPath, config) {
|
|
1506
|
+
const isMonorepo = config.structure === "monorepo";
|
|
1507
|
+
const backendDir = isMonorepo ? "../packages/backend" : ".";
|
|
1506
1508
|
const setupScript = `#!/usr/bin/env node
|
|
1507
1509
|
/**
|
|
1508
1510
|
* Setup Convex - Interactive setup wizard for Convex
|
|
@@ -1516,30 +1518,86 @@ import * as readline from 'readline'
|
|
|
1516
1518
|
|
|
1517
1519
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
1518
1520
|
const projectDir = resolve(__dirname, '..')
|
|
1521
|
+
const backendDir = resolve(projectDir, '${backendDir}')
|
|
1519
1522
|
|
|
1520
|
-
function
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
output: process.stdout
|
|
1524
|
-
})
|
|
1523
|
+
function loadEnvFile(dir) {
|
|
1524
|
+
const envPath = resolve(dir, '.env.local')
|
|
1525
|
+
if (!existsSync(envPath)) return {}
|
|
1525
1526
|
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1527
|
+
const content = readFileSync(envPath, 'utf-8')
|
|
1528
|
+
const env = {}
|
|
1529
|
+
|
|
1530
|
+
for (const line of content.split('\\n')) {
|
|
1531
|
+
const trimmed = line.trim()
|
|
1532
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
1533
|
+
const eqIndex = trimmed.indexOf('=')
|
|
1534
|
+
if (eqIndex === -1) continue
|
|
1535
|
+
const key = trimmed.slice(0, eqIndex)
|
|
1536
|
+
let value = trimmed.slice(eqIndex + 1)
|
|
1537
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
1538
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
1539
|
+
value = value.slice(1, -1)
|
|
1540
|
+
}
|
|
1541
|
+
if (value) env[key] = value
|
|
1542
|
+
}
|
|
1543
|
+
return env
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function syncEnvToConvex(envVars) {
|
|
1547
|
+
// Required env vars that Better Auth needs in Convex cloud
|
|
1548
|
+
const requiredVars = ['BETTER_AUTH_SECRET', 'SITE_URL']
|
|
1549
|
+
|
|
1550
|
+
for (const varName of requiredVars) {
|
|
1551
|
+
if (envVars[varName]) {
|
|
1552
|
+
try {
|
|
1553
|
+
console.log(\` Setting \${varName} in Convex...\`)
|
|
1554
|
+
execSync(\`npx convex env set \${varName} "\${envVars[varName]}"\`, {
|
|
1555
|
+
cwd: backendDir,
|
|
1556
|
+
stdio: 'pipe'
|
|
1557
|
+
})
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
console.warn(\` \u26A0\uFE0F Could not set \${varName}: \${error.message}\`)
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// Optional OAuth env vars - only sync if they have values
|
|
1565
|
+
const optionalVars = [
|
|
1566
|
+
'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET',
|
|
1567
|
+
'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET',
|
|
1568
|
+
'RESEND_API_KEY', 'RESEND_FROM_EMAIL'
|
|
1569
|
+
]
|
|
1570
|
+
|
|
1571
|
+
for (const varName of optionalVars) {
|
|
1572
|
+
if (envVars[varName]) {
|
|
1573
|
+
try {
|
|
1574
|
+
execSync(\`npx convex env set \${varName} "\${envVars[varName]}"\`, {
|
|
1575
|
+
cwd: backendDir,
|
|
1576
|
+
stdio: 'pipe'
|
|
1577
|
+
})
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
// Silent fail for optional vars
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1532
1583
|
}
|
|
1533
1584
|
|
|
1534
1585
|
async function main() {
|
|
1535
1586
|
console.log('\\n\u{1F527} Convex Setup Wizard\\n')
|
|
1536
1587
|
|
|
1537
1588
|
// Check if already configured
|
|
1538
|
-
const envPath = resolve(
|
|
1589
|
+
const envPath = resolve(backendDir, '.env.local')
|
|
1539
1590
|
if (existsSync(envPath)) {
|
|
1540
1591
|
const content = readFileSync(envPath, 'utf-8')
|
|
1541
1592
|
if (content.includes('CONVEX_DEPLOYMENT=') && !content.includes('CONVEX_DEPLOYMENT=\\n')) {
|
|
1542
|
-
console.log('\u2705 Convex
|
|
1593
|
+
console.log('\u2705 Convex deployment already configured!')
|
|
1594
|
+
|
|
1595
|
+
// Sync env vars even if deployment exists
|
|
1596
|
+
console.log('\\n\u{1F4E4} Syncing environment variables to Convex cloud...')
|
|
1597
|
+
const envVars = loadEnvFile(backendDir)
|
|
1598
|
+
syncEnvToConvex(envVars)
|
|
1599
|
+
|
|
1600
|
+
console.log('\\n\u2705 Setup complete!')
|
|
1543
1601
|
console.log(' Run "pnpm dev" to start development.\\n')
|
|
1544
1602
|
return
|
|
1545
1603
|
}
|
|
@@ -1552,11 +1610,16 @@ async function main() {
|
|
|
1552
1610
|
|
|
1553
1611
|
try {
|
|
1554
1612
|
spawnSync('npx', ['convex', 'dev', '--once'], {
|
|
1555
|
-
cwd:
|
|
1613
|
+
cwd: backendDir,
|
|
1556
1614
|
stdio: 'inherit',
|
|
1557
1615
|
shell: true
|
|
1558
1616
|
})
|
|
1559
1617
|
|
|
1618
|
+
// After setup, sync env vars to Convex cloud
|
|
1619
|
+
console.log('\\n\u{1F4E4} Syncing environment variables to Convex cloud...')
|
|
1620
|
+
const envVars = loadEnvFile(backendDir)
|
|
1621
|
+
syncEnvToConvex(envVars)
|
|
1622
|
+
|
|
1560
1623
|
console.log('\\n\u2705 Convex setup complete!')
|
|
1561
1624
|
console.log(' Run "pnpm dev" to start development.\\n')
|
|
1562
1625
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kofi-stack-template-generator",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -10,20 +10,20 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts"
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "pnpm run prebuild && tsup src/index.ts --format esm --dts",
|
|
15
|
+
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
16
|
+
"prebuild": "node scripts/generate-templates.js",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
13
19
|
"dependencies": {
|
|
20
|
+
"kofi-stack-types": "workspace:^",
|
|
14
21
|
"handlebars": "^4.7.8",
|
|
15
|
-
"memfs": "^4.9.0"
|
|
16
|
-
"kofi-stack-types": "^2.1.0"
|
|
22
|
+
"memfs": "^4.9.0"
|
|
17
23
|
},
|
|
18
24
|
"devDependencies": {
|
|
19
25
|
"@types/node": "^20.0.0",
|
|
20
26
|
"tsup": "^8.0.0",
|
|
21
27
|
"typescript": "^5.0.0"
|
|
22
|
-
},
|
|
23
|
-
"scripts": {
|
|
24
|
-
"build": "pnpm run prebuild && tsup src/index.ts --format esm --dts",
|
|
25
|
-
"dev": "tsup src/index.ts --format esm --dts --watch",
|
|
26
|
-
"prebuild": "node scripts/generate-templates.js",
|
|
27
|
-
"typecheck": "tsc --noEmit"
|
|
28
28
|
}
|
|
29
|
-
}
|
|
29
|
+
}
|
package/src/generator.ts
CHANGED
|
@@ -329,6 +329,9 @@ function generateSetupConvexScript(
|
|
|
329
329
|
scriptsPath: string,
|
|
330
330
|
config: ProjectConfig
|
|
331
331
|
): void {
|
|
332
|
+
const isMonorepo = config.structure === 'monorepo'
|
|
333
|
+
const backendDir = isMonorepo ? '../packages/backend' : '.'
|
|
334
|
+
|
|
332
335
|
const setupScript = `#!/usr/bin/env node
|
|
333
336
|
/**
|
|
334
337
|
* Setup Convex - Interactive setup wizard for Convex
|
|
@@ -342,30 +345,86 @@ import * as readline from 'readline'
|
|
|
342
345
|
|
|
343
346
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
344
347
|
const projectDir = resolve(__dirname, '..')
|
|
348
|
+
const backendDir = resolve(projectDir, '${backendDir}')
|
|
345
349
|
|
|
346
|
-
function
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
output: process.stdout
|
|
350
|
-
})
|
|
350
|
+
function loadEnvFile(dir) {
|
|
351
|
+
const envPath = resolve(dir, '.env.local')
|
|
352
|
+
if (!existsSync(envPath)) return {}
|
|
351
353
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
354
|
+
const content = readFileSync(envPath, 'utf-8')
|
|
355
|
+
const env = {}
|
|
356
|
+
|
|
357
|
+
for (const line of content.split('\\n')) {
|
|
358
|
+
const trimmed = line.trim()
|
|
359
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
360
|
+
const eqIndex = trimmed.indexOf('=')
|
|
361
|
+
if (eqIndex === -1) continue
|
|
362
|
+
const key = trimmed.slice(0, eqIndex)
|
|
363
|
+
let value = trimmed.slice(eqIndex + 1)
|
|
364
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
365
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
366
|
+
value = value.slice(1, -1)
|
|
367
|
+
}
|
|
368
|
+
if (value) env[key] = value
|
|
369
|
+
}
|
|
370
|
+
return env
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function syncEnvToConvex(envVars) {
|
|
374
|
+
// Required env vars that Better Auth needs in Convex cloud
|
|
375
|
+
const requiredVars = ['BETTER_AUTH_SECRET', 'SITE_URL']
|
|
376
|
+
|
|
377
|
+
for (const varName of requiredVars) {
|
|
378
|
+
if (envVars[varName]) {
|
|
379
|
+
try {
|
|
380
|
+
console.log(\` Setting \${varName} in Convex...\`)
|
|
381
|
+
execSync(\`npx convex env set \${varName} "\${envVars[varName]}"\`, {
|
|
382
|
+
cwd: backendDir,
|
|
383
|
+
stdio: 'pipe'
|
|
384
|
+
})
|
|
385
|
+
} catch (error) {
|
|
386
|
+
console.warn(\` ⚠️ Could not set \${varName}: \${error.message}\`)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Optional OAuth env vars - only sync if they have values
|
|
392
|
+
const optionalVars = [
|
|
393
|
+
'GITHUB_CLIENT_ID', 'GITHUB_CLIENT_SECRET',
|
|
394
|
+
'GOOGLE_CLIENT_ID', 'GOOGLE_CLIENT_SECRET',
|
|
395
|
+
'RESEND_API_KEY', 'RESEND_FROM_EMAIL'
|
|
396
|
+
]
|
|
397
|
+
|
|
398
|
+
for (const varName of optionalVars) {
|
|
399
|
+
if (envVars[varName]) {
|
|
400
|
+
try {
|
|
401
|
+
execSync(\`npx convex env set \${varName} "\${envVars[varName]}"\`, {
|
|
402
|
+
cwd: backendDir,
|
|
403
|
+
stdio: 'pipe'
|
|
404
|
+
})
|
|
405
|
+
} catch (error) {
|
|
406
|
+
// Silent fail for optional vars
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
358
410
|
}
|
|
359
411
|
|
|
360
412
|
async function main() {
|
|
361
413
|
console.log('\\n🔧 Convex Setup Wizard\\n')
|
|
362
414
|
|
|
363
415
|
// Check if already configured
|
|
364
|
-
const envPath = resolve(
|
|
416
|
+
const envPath = resolve(backendDir, '.env.local')
|
|
365
417
|
if (existsSync(envPath)) {
|
|
366
418
|
const content = readFileSync(envPath, 'utf-8')
|
|
367
419
|
if (content.includes('CONVEX_DEPLOYMENT=') && !content.includes('CONVEX_DEPLOYMENT=\\n')) {
|
|
368
|
-
console.log('✅ Convex
|
|
420
|
+
console.log('✅ Convex deployment already configured!')
|
|
421
|
+
|
|
422
|
+
// Sync env vars even if deployment exists
|
|
423
|
+
console.log('\\n📤 Syncing environment variables to Convex cloud...')
|
|
424
|
+
const envVars = loadEnvFile(backendDir)
|
|
425
|
+
syncEnvToConvex(envVars)
|
|
426
|
+
|
|
427
|
+
console.log('\\n✅ Setup complete!')
|
|
369
428
|
console.log(' Run "pnpm dev" to start development.\\n')
|
|
370
429
|
return
|
|
371
430
|
}
|
|
@@ -378,11 +437,16 @@ async function main() {
|
|
|
378
437
|
|
|
379
438
|
try {
|
|
380
439
|
spawnSync('npx', ['convex', 'dev', '--once'], {
|
|
381
|
-
cwd:
|
|
440
|
+
cwd: backendDir,
|
|
382
441
|
stdio: 'inherit',
|
|
383
442
|
shell: true
|
|
384
443
|
})
|
|
385
444
|
|
|
445
|
+
// After setup, sync env vars to Convex cloud
|
|
446
|
+
console.log('\\n📤 Syncing environment variables to Convex cloud...')
|
|
447
|
+
const envVars = loadEnvFile(backendDir)
|
|
448
|
+
syncEnvToConvex(envVars)
|
|
449
|
+
|
|
386
450
|
console.log('\\n✅ Convex setup complete!')
|
|
387
451
|
console.log(' Run "pnpm dev" to start development.\\n')
|
|
388
452
|
} catch (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Auto-generated file. Do not edit manually.
|
|
2
2
|
// Run 'pnpm prebuild' to regenerate.
|
|
3
|
-
// Generated: 2026-01-14T04:
|
|
3
|
+
// Generated: 2026-01-14T04:49:12.028Z
|
|
4
4
|
// Template count: 90
|
|
5
5
|
|
|
6
6
|
export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
@@ -8,7 +8,7 @@ export const EMBEDDED_TEMPLATES: Record<string, string> = {
|
|
|
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",
|
|
@@ -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
|
}
|