kofi-stack-template-generator 2.0.0

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.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +20 -0
  2. package/dist/index.d.ts +94 -0
  3. package/dist/index.js +744 -0
  4. package/package.json +29 -0
  5. package/scripts/generate-templates.js +104 -0
  6. package/src/core/index.ts +7 -0
  7. package/src/core/template-processor.ts +127 -0
  8. package/src/core/virtual-fs.ts +189 -0
  9. package/src/generator.ts +429 -0
  10. package/src/index.ts +19 -0
  11. package/src/templates.generated.ts +39 -0
  12. package/templates/base/_gitignore.hbs +45 -0
  13. package/templates/base/biome.json.hbs +34 -0
  14. package/templates/convex/_env.local.hbs +52 -0
  15. package/templates/convex/convex/auth.ts.hbs +7 -0
  16. package/templates/convex/convex/http.ts.hbs +8 -0
  17. package/templates/convex/convex/schema.ts.hbs +15 -0
  18. package/templates/convex/convex/users.ts.hbs +13 -0
  19. package/templates/integrations/posthog/src/components/providers/posthog-provider.tsx.hbs +17 -0
  20. package/templates/monorepo/package.json.hbs +29 -0
  21. package/templates/monorepo/pnpm-workspace.yaml.hbs +3 -0
  22. package/templates/monorepo/turbo.json.hbs +42 -0
  23. package/templates/packages/config-biome/biome.json.hbs +4 -0
  24. package/templates/packages/config-biome/package.json.hbs +6 -0
  25. package/templates/packages/config-typescript/base.json.hbs +17 -0
  26. package/templates/packages/config-typescript/nextjs.json.hbs +7 -0
  27. package/templates/packages/config-typescript/package.json.hbs +10 -0
  28. package/templates/packages/ui/components.json.hbs +20 -0
  29. package/templates/packages/ui/package.json.hbs +34 -0
  30. package/templates/packages/ui/src/index.ts.hbs +3 -0
  31. package/templates/packages/ui/src/lib/utils.ts.hbs +6 -0
  32. package/templates/packages/ui/tsconfig.json.hbs +22 -0
  33. package/templates/web/components.json.hbs +20 -0
  34. package/templates/web/next.config.ts.hbs +9 -0
  35. package/templates/web/package.json.hbs +62 -0
  36. package/templates/web/postcss.config.mjs.hbs +5 -0
  37. package/templates/web/src/app/globals.css.hbs +122 -0
  38. package/templates/web/src/app/layout.tsx.hbs +55 -0
  39. package/templates/web/src/app/page.tsx.hbs +74 -0
  40. package/templates/web/src/components/providers/convex-provider.tsx.hbs +18 -0
  41. package/templates/web/src/lib/auth.ts.hbs +23 -0
  42. package/templates/web/src/lib/utils.ts.hbs +6 -0
  43. package/templates/web/tsconfig.json.hbs +23 -0
  44. package/tsconfig.json +15 -0
@@ -0,0 +1,429 @@
1
+ import type {
2
+ ProjectConfig,
3
+ VirtualFileTree,
4
+ GeneratorResult,
5
+ } from 'kofi-stack-types'
6
+ import { VirtualFileSystem } from './core/virtual-fs.js'
7
+ import {
8
+ processTemplateString,
9
+ transformFilename,
10
+ isBinaryFile,
11
+ shouldIncludeFile,
12
+ } from './core/template-processor.js'
13
+ import { EMBEDDED_TEMPLATES } from './templates.generated.js'
14
+ import path from 'path'
15
+
16
+ /**
17
+ * Generate a virtual project based on the provided configuration.
18
+ * Returns a VirtualFileTree that can be written to disk.
19
+ */
20
+ export async function generateVirtualProject(
21
+ config: ProjectConfig
22
+ ): Promise<GeneratorResult> {
23
+ const vfs = new VirtualFileSystem()
24
+ const errors: string[] = []
25
+
26
+ try {
27
+ // Process templates in order
28
+ await processBaseTemplates(vfs, config)
29
+ await processWebAppTemplates(vfs, config)
30
+ await processConvexTemplates(vfs, config)
31
+ await processBetterAuthTemplates(vfs, config)
32
+
33
+ if (config.structure === 'monorepo') {
34
+ await processMonorepoTemplates(vfs, config)
35
+ if (config.marketingSite === 'payload') {
36
+ await processPayloadTemplates(vfs, config)
37
+ } else if (config.marketingSite === 'nextjs') {
38
+ await processMarketingTemplates(vfs, config)
39
+ }
40
+ }
41
+
42
+ await processIntegrationTemplates(vfs, config)
43
+ await processAddonTemplates(vfs, config)
44
+
45
+ // Post-processing: Generate package.json, README, etc.
46
+ await postProcess(vfs, config)
47
+
48
+ const tree = vfs.toTree(config)
49
+
50
+ return {
51
+ tree,
52
+ success: true,
53
+ }
54
+ } catch (error) {
55
+ errors.push(error instanceof Error ? error.message : String(error))
56
+ return {
57
+ tree: vfs.toTree(config),
58
+ success: false,
59
+ errors,
60
+ }
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Process templates from a given prefix path
66
+ */
67
+ function processTemplatesFromPrefix(
68
+ vfs: VirtualFileSystem,
69
+ prefix: string,
70
+ outputPrefix: string,
71
+ config: ProjectConfig
72
+ ): void {
73
+ for (const [templatePath, content] of Object.entries(EMBEDDED_TEMPLATES)) {
74
+ if (!templatePath.startsWith(prefix)) continue
75
+ if (!shouldIncludeFile(templatePath, config)) continue
76
+
77
+ // Calculate output path
78
+ const relativePath = templatePath.slice(prefix.length)
79
+ const outputPath = path.join(outputPrefix, relativePath)
80
+
81
+ // Transform filename
82
+ const dir = path.dirname(outputPath)
83
+ const filename = path.basename(outputPath)
84
+ const transformedFilename = transformFilename(filename, config)
85
+ const finalPath = path.join(dir, transformedFilename)
86
+
87
+ // Process content
88
+ if (isBinaryFile(filename)) {
89
+ // For binary files, we store the source path for later copying
90
+ vfs.writeFile(finalPath, Buffer.from(content, 'base64'))
91
+ } else {
92
+ // Process as template
93
+ const processedContent = processTemplateString(content, config)
94
+ vfs.writeFile(finalPath, processedContent)
95
+ }
96
+ }
97
+ }
98
+
99
+ async function processBaseTemplates(
100
+ vfs: VirtualFileSystem,
101
+ config: ProjectConfig
102
+ ): Promise<void> {
103
+ processTemplatesFromPrefix(vfs, 'base/', '/', config)
104
+ }
105
+
106
+ async function processWebAppTemplates(
107
+ vfs: VirtualFileSystem,
108
+ config: ProjectConfig
109
+ ): Promise<void> {
110
+ const appPath = config.structure === 'monorepo' ? '/apps/web' : '/'
111
+ processTemplatesFromPrefix(vfs, 'web/', appPath, config)
112
+ }
113
+
114
+ async function processConvexTemplates(
115
+ vfs: VirtualFileSystem,
116
+ config: ProjectConfig
117
+ ): Promise<void> {
118
+ const convexPath = config.structure === 'monorepo' ? '/packages/backend' : '/'
119
+ processTemplatesFromPrefix(vfs, 'convex/', convexPath, config)
120
+ }
121
+
122
+ async function processBetterAuthTemplates(
123
+ vfs: VirtualFileSystem,
124
+ config: ProjectConfig
125
+ ): Promise<void> {
126
+ const webPath = config.structure === 'monorepo' ? '/apps/web' : '/'
127
+ processTemplatesFromPrefix(vfs, 'auth/', webPath, config)
128
+ }
129
+
130
+ async function processMonorepoTemplates(
131
+ vfs: VirtualFileSystem,
132
+ config: ProjectConfig
133
+ ): Promise<void> {
134
+ processTemplatesFromPrefix(vfs, 'monorepo/', '/', config)
135
+ processTemplatesFromPrefix(vfs, 'packages/ui/', '/packages/ui', config)
136
+ processTemplatesFromPrefix(vfs, 'packages/config/', '/packages/config', config)
137
+ }
138
+
139
+ async function processPayloadTemplates(
140
+ vfs: VirtualFileSystem,
141
+ config: ProjectConfig
142
+ ): Promise<void> {
143
+ processTemplatesFromPrefix(vfs, 'marketing/payload/', '/apps/marketing', config)
144
+ }
145
+
146
+ async function processMarketingTemplates(
147
+ vfs: VirtualFileSystem,
148
+ config: ProjectConfig
149
+ ): Promise<void> {
150
+ processTemplatesFromPrefix(vfs, 'marketing/nextjs/', '/apps/marketing', config)
151
+ }
152
+
153
+ async function processIntegrationTemplates(
154
+ vfs: VirtualFileSystem,
155
+ config: ProjectConfig
156
+ ): Promise<void> {
157
+ const webPath = config.structure === 'monorepo' ? '/apps/web' : '/'
158
+
159
+ if (config.integrations.analytics === 'posthog') {
160
+ processTemplatesFromPrefix(vfs, 'integrations/posthog/', webPath, config)
161
+ }
162
+ if (config.integrations.analytics === 'vercel') {
163
+ processTemplatesFromPrefix(vfs, 'integrations/vercel-analytics/', webPath, config)
164
+ }
165
+ if (config.integrations.uploads === 'uploadthing') {
166
+ processTemplatesFromPrefix(vfs, 'integrations/uploadthing/', webPath, config)
167
+ }
168
+ if (config.integrations.uploads === 's3') {
169
+ processTemplatesFromPrefix(vfs, 'integrations/s3/', webPath, config)
170
+ }
171
+ if (config.integrations.uploads === 'vercel-blob') {
172
+ processTemplatesFromPrefix(vfs, 'integrations/vercel-blob/', webPath, config)
173
+ }
174
+ }
175
+
176
+ async function processAddonTemplates(
177
+ vfs: VirtualFileSystem,
178
+ config: ProjectConfig
179
+ ): Promise<void> {
180
+ for (const addon of config.addons) {
181
+ processTemplatesFromPrefix(vfs, `addons/${addon}/`, '/', config)
182
+ }
183
+ }
184
+
185
+ async function postProcess(
186
+ vfs: VirtualFileSystem,
187
+ config: ProjectConfig
188
+ ): Promise<void> {
189
+ // Generate scripts directory with dev.mjs and setup scripts
190
+ const scriptsPath = config.structure === 'monorepo' ? '/scripts' : '/scripts'
191
+ const webScriptsPath = config.structure === 'monorepo' ? '/apps/web/scripts' : '/scripts'
192
+
193
+ generateDevScript(vfs, scriptsPath, config)
194
+ generateSetupConvexScript(vfs, webScriptsPath, config)
195
+
196
+ // Generate README
197
+ generateReadme(vfs, config)
198
+ }
199
+
200
+ function generateDevScript(
201
+ vfs: VirtualFileSystem,
202
+ scriptsPath: string,
203
+ config: ProjectConfig
204
+ ): void {
205
+ const isMonorepo = config.structure === 'monorepo'
206
+ const webAppDir = isMonorepo ? 'apps/web' : '.'
207
+
208
+ const devScript = `#!/usr/bin/env node
209
+ /**
210
+ * Dev Script - Starts Next.js and Convex dev servers
211
+ */
212
+
213
+ import { spawn, execSync } from 'child_process'
214
+ import { existsSync, readFileSync } from 'fs'
215
+ import { resolve, dirname } from 'path'
216
+ import { fileURLToPath } from 'url'
217
+
218
+ const __dirname = dirname(fileURLToPath(import.meta.url))
219
+ const rootDir = resolve(__dirname, '..')
220
+ const webAppDir = resolve(rootDir, '${webAppDir}')
221
+
222
+ function loadEnvFile(dir) {
223
+ const envPath = resolve(dir, '.env.local')
224
+ if (!existsSync(envPath)) return {}
225
+
226
+ const content = readFileSync(envPath, 'utf-8')
227
+ const env = {}
228
+
229
+ for (const line of content.split('\\n')) {
230
+ const trimmed = line.trim()
231
+ if (!trimmed || trimmed.startsWith('#')) continue
232
+ const eqIndex = trimmed.indexOf('=')
233
+ if (eqIndex === -1) continue
234
+ const key = trimmed.slice(0, eqIndex)
235
+ let value = trimmed.slice(eqIndex + 1)
236
+ if ((value.startsWith('"') && value.endsWith('"')) ||
237
+ (value.startsWith("'") && value.endsWith("'"))) {
238
+ value = value.slice(1, -1)
239
+ }
240
+ if (value) env[key] = value
241
+ }
242
+ return env
243
+ }
244
+
245
+ async function checkAndInstall() {
246
+ if (!existsSync(resolve(rootDir, 'node_modules'))) {
247
+ console.log('📦 Installing dependencies...\\n')
248
+ execSync('pnpm install', { cwd: rootDir, stdio: 'inherit' })
249
+ }
250
+ }
251
+
252
+ function startDevServers() {
253
+ const localEnv = loadEnvFile(webAppDir)
254
+
255
+ if (!localEnv.CONVEX_DEPLOYMENT) {
256
+ console.log('⚠️ Convex not configured. Run: pnpm dev:setup\\n')
257
+ console.log('Starting Next.js only...\\n')
258
+ spawn('pnpm', ['${isMonorepo ? 'dev:turbo' : 'dev:next'}'], {
259
+ cwd: rootDir, stdio: 'inherit', shell: true
260
+ })
261
+ return
262
+ }
263
+
264
+ console.log('🚀 Starting development servers...\\n')
265
+
266
+ const nextProcess = spawn('pnpm', ['${isMonorepo ? 'dev:turbo' : 'dev:next'}'], {
267
+ cwd: rootDir, stdio: 'inherit', shell: true
268
+ })
269
+
270
+ const convexProcess = spawn('npx', ['convex', 'dev'], {
271
+ cwd: webAppDir, stdio: 'inherit', shell: true
272
+ })
273
+
274
+ const cleanup = () => {
275
+ nextProcess.kill()
276
+ convexProcess.kill()
277
+ process.exit(0)
278
+ }
279
+
280
+ process.on('SIGINT', cleanup)
281
+ process.on('SIGTERM', cleanup)
282
+ }
283
+
284
+ async function main() {
285
+ await checkAndInstall()
286
+ startDevServers()
287
+ }
288
+
289
+ main()
290
+ `
291
+
292
+ vfs.writeFile(`${scriptsPath}/dev.mjs`, devScript)
293
+ }
294
+
295
+ function generateSetupConvexScript(
296
+ vfs: VirtualFileSystem,
297
+ scriptsPath: string,
298
+ config: ProjectConfig
299
+ ): void {
300
+ const setupScript = `#!/usr/bin/env node
301
+ /**
302
+ * Setup Convex - Interactive setup wizard for Convex
303
+ */
304
+
305
+ import { execSync, spawnSync } from 'child_process'
306
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
307
+ import { resolve, dirname } from 'path'
308
+ import { fileURLToPath } from 'url'
309
+ import * as readline from 'readline'
310
+
311
+ const __dirname = dirname(fileURLToPath(import.meta.url))
312
+ const projectDir = resolve(__dirname, '..')
313
+
314
+ function prompt(question) {
315
+ const rl = readline.createInterface({
316
+ input: process.stdin,
317
+ output: process.stdout
318
+ })
319
+
320
+ return new Promise((resolve) => {
321
+ rl.question(question, (answer) => {
322
+ rl.close()
323
+ resolve(answer.trim())
324
+ })
325
+ })
326
+ }
327
+
328
+ async function main() {
329
+ console.log('\\n🔧 Convex Setup Wizard\\n')
330
+
331
+ // Check if already configured
332
+ const envPath = resolve(projectDir, '.env.local')
333
+ if (existsSync(envPath)) {
334
+ const content = readFileSync(envPath, 'utf-8')
335
+ if (content.includes('CONVEX_DEPLOYMENT=') && !content.includes('CONVEX_DEPLOYMENT=\\n')) {
336
+ console.log('✅ Convex is already configured!')
337
+ console.log(' Run "pnpm dev" to start development.\\n')
338
+ return
339
+ }
340
+ }
341
+
342
+ console.log('This wizard will help you set up Convex for your project.\\n')
343
+
344
+ // Run convex dev to trigger authentication and project creation
345
+ console.log('Running Convex setup (this will open your browser if needed)...\\n')
346
+
347
+ try {
348
+ spawnSync('npx', ['convex', 'dev', '--once'], {
349
+ cwd: projectDir,
350
+ stdio: 'inherit',
351
+ shell: true
352
+ })
353
+
354
+ console.log('\\n✅ Convex setup complete!')
355
+ console.log(' Run "pnpm dev" to start development.\\n')
356
+ } catch (error) {
357
+ console.error('\\n❌ Convex setup failed:', error.message)
358
+ console.error(' Try running "npx convex dev" manually.\\n')
359
+ process.exit(1)
360
+ }
361
+ }
362
+
363
+ main()
364
+ `
365
+
366
+ vfs.writeFile(`${scriptsPath}/setup-convex.mjs`, setupScript)
367
+ }
368
+
369
+ function generateReadme(vfs: VirtualFileSystem, config: ProjectConfig): void {
370
+ const readme = `# ${config.projectName}
371
+
372
+ Built with [create-kofi-stack](https://github.com/theodenanyoh11/create-kofi-stack)
373
+
374
+ ## Tech Stack
375
+
376
+ - **Framework**: Next.js 16 with App Router
377
+ - **Backend**: Convex (reactive backend-as-a-service)
378
+ - **Auth**: Better-Auth with Convex adapter
379
+ - **UI**: shadcn/ui with ${config.shadcn.componentLibrary}
380
+ - **Styling**: Tailwind CSS v4
381
+
382
+ ## Getting Started
383
+
384
+ \`\`\`bash
385
+ cd ${config.projectName}
386
+ pnpm dev
387
+ \`\`\`
388
+
389
+ This will:
390
+ - Install dependencies (if needed)
391
+ - Set up Convex (if not configured)
392
+ - Start Next.js and Convex dev servers
393
+
394
+ ## Project Structure
395
+
396
+ ${config.structure === 'monorepo' ? `
397
+ \`\`\`
398
+ ├── apps/
399
+ │ ├── web/ # Main Next.js application
400
+ │ ${config.marketingSite !== 'none' ? '├── marketing/ # Marketing site' : ''}
401
+ │ └── design-system/ # Component showcase
402
+ ├── packages/
403
+ │ ├── backend/ # Convex functions
404
+ │ └── ui/ # Shared UI components
405
+ └── ...
406
+ \`\`\`
407
+ ` : `
408
+ \`\`\`
409
+ ├── src/
410
+ │ ├── app/ # Next.js App Router
411
+ │ ├── components/ # React components
412
+ │ └── lib/ # Utilities
413
+ ├── convex/ # Convex functions
414
+ └── ...
415
+ \`\`\`
416
+ `}
417
+
418
+ ## Documentation
419
+
420
+ - [Convex](https://docs.convex.dev)
421
+ - [Better-Auth](https://www.better-auth.com)
422
+ - [shadcn/ui](https://ui.shadcn.com)
423
+ - [Next.js](https://nextjs.org/docs)
424
+ `
425
+
426
+ vfs.writeFile('/README.md', readme)
427
+ }
428
+
429
+ export { VirtualFileSystem }
package/src/index.ts ADDED
@@ -0,0 +1,19 @@
1
+ // Main generator
2
+ export { generateVirtualProject, VirtualFileSystem } from './generator.js'
3
+
4
+ // Core utilities
5
+ export {
6
+ processTemplateString,
7
+ transformFilename,
8
+ isBinaryFile,
9
+ shouldIncludeFile,
10
+ } from './core/template-processor.js'
11
+
12
+ // Re-export types
13
+ export type {
14
+ ProjectConfig,
15
+ VirtualFileTree,
16
+ VirtualFile,
17
+ VirtualDirectory,
18
+ GeneratorResult,
19
+ } from 'kofi-stack-types'
@@ -0,0 +1,39 @@
1
+ // Auto-generated file. Do not edit manually.
2
+ // Run 'pnpm prebuild' to regenerate.
3
+ // Generated: 2026-01-13T20:11:02.957Z
4
+ // Template count: 32
5
+
6
+ export const EMBEDDED_TEMPLATES: Record<string, string> = {
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
+ "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=\n\n# Auth - GitHub OAuth\nAUTH_GITHUB_ID=\nAUTH_GITHUB_SECRET=\n\n# Auth - Google OAuth\nAUTH_GOOGLE_ID=\nAUTH_GOOGLE_SECRET=\n\n# Better Auth Secret (generate with: openssl rand -base64 32)\nBETTER_AUTH_SECRET=\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 '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 (includes addons 'rate-limiting')}}\n\n# Arcjet\nARCJET_KEY=\n{{/if}}\n{{#if (includes addons 'monitoring')}}\n\n# Sentry\nSENTRY_DSN=\nSENTRY_AUTH_TOKEN=\n{{/if}}\n\n# Email (Resend)\nRESEND_API_KEY=\n",
10
+ "convex/convex/auth.ts.hbs": "import GitHub from '@auth/core/providers/github'\nimport Google from '@auth/core/providers/google'\nimport { convexAuth } from '@convex-dev/auth/server'\n\nexport const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({\n providers: [GitHub, Google],\n})\n",
11
+ "convex/convex/http.ts.hbs": "import { httpRouter } from 'convex/server'\nimport { auth } from './auth'\n\nconst http = httpRouter()\n\nauth.addHttpRoutes(http)\n\nexport default http\n",
12
+ "convex/convex/schema.ts.hbs": "import { defineSchema, defineTable } from 'convex/server'\nimport { authTables } from '@convex-dev/auth/server'\nimport { v } from 'convex/values'\n\nexport default defineSchema({\n ...authTables,\n // Add your custom tables here\n // Example:\n // posts: defineTable({\n // title: v.string(),\n // content: v.string(),\n // authorId: v.id('users'),\n // createdAt: v.number(),\n // }).index('by_author', ['authorId']),\n})\n",
13
+ "convex/convex/users.ts.hbs": "import { query } from './_generated/server'\nimport { auth } from './auth'\n\nexport const current = query({\n args: {},\n handler: async (ctx) => {\n const userId = await auth.getUserId(ctx)\n if (!userId) return null\n\n const user = await ctx.db.get(userId)\n return user\n },\n})\n",
14
+ "integrations/posthog/src/components/providers/posthog-provider.tsx.hbs": "'use client'\n\nimport posthog from 'posthog-js'\nimport { PostHogProvider as PHProvider } from 'posthog-js/react'\nimport { useEffect } from 'react'\n\nexport function PostHogProvider({ children }: { children: React.ReactNode }) {\n useEffect(() => {\n posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {\n api_host: process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://app.posthog.com',\n person_profiles: 'identified_only',\n capture_pageview: false, // We capture pageviews manually\n })\n }, [])\n\n return <PHProvider client={posthog}>{children}</PHProvider>\n}\n",
15
+ "monorepo/package.json.hbs": "{\n \"name\": \"{{projectName}}\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"node scripts/dev.mjs\",\n \"dev:turbo\": \"turbo dev\",\n \"build\": \"turbo build\",\n \"lint\": \"turbo lint\",\n \"lint:fix\": \"turbo lint:fix\",\n \"format\": \"turbo format\",\n \"typecheck\": \"turbo typecheck\",\n \"test\": \"turbo test\",\n \"test:e2e\": \"turbo test:e2e\",\n \"clean\": \"turbo clean && rm -rf node_modules\",\n \"prepare\": \"husky\",\n \"setup:convex\": \"node apps/web/scripts/setup-convex.mjs\"\n },\n \"devDependencies\": {\n \"turbo\": \"^2.0.0\",\n \"husky\": \"^9.0.0\",\n \"lint-staged\": \"^15.0.0\"\n },\n \"packageManager\": \"pnpm@9.0.0\",\n \"lint-staged\": {\n \"*.{js,ts,jsx,tsx}\": [\"biome check --apply\"],\n \"*.{json,md}\": [\"biome format --write\"]\n }\n}\n",
16
+ "monorepo/pnpm-workspace.yaml.hbs": "packages:\n - \"apps/*\"\n - \"packages/*\"\n",
17
+ "monorepo/turbo.json.hbs": "{\n \"$schema\": \"https://turbo.build/schema.json\",\n \"ui\": \"tui\",\n \"tasks\": {\n \"build\": {\n \"dependsOn\": [\"^build\"],\n \"inputs\": [\"$TURBO_DEFAULT$\", \".env*\"],\n \"outputs\": [\".next/**\", \"!.next/cache/**\", \"dist/**\"]\n },\n \"lint\": {\n \"dependsOn\": [\"^lint\"],\n \"inputs\": [\"$TURBO_DEFAULT$\"]\n },\n \"lint:fix\": {\n \"dependsOn\": [\"^lint:fix\"],\n \"inputs\": [\"$TURBO_DEFAULT$\"]\n },\n \"format\": {\n \"dependsOn\": [\"^format\"],\n \"inputs\": [\"$TURBO_DEFAULT$\"]\n },\n \"typecheck\": {\n \"dependsOn\": [\"^typecheck\"],\n \"inputs\": [\"$TURBO_DEFAULT$\"]\n },\n \"dev\": {\n \"cache\": false,\n \"persistent\": true\n },\n \"test\": {\n \"dependsOn\": [\"^build\"],\n \"inputs\": [\"$TURBO_DEFAULT$\"]\n },\n \"test:e2e\": {\n \"dependsOn\": [\"build\"],\n \"inputs\": [\"$TURBO_DEFAULT$\"]\n },\n \"clean\": {\n \"cache\": false\n }\n }\n}\n",
18
+ "packages/config-biome/biome.json.hbs": "{\n \"$schema\": \"https://biomejs.dev/schemas/1.9.4/schema.json\",\n \"extends\": [\"../../biome.json\"]\n}\n",
19
+ "packages/config-biome/package.json.hbs": "{\n \"name\": \"@repo/config-biome\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"files\": [\"biome.json\"]\n}\n",
20
+ "packages/config-typescript/base.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 \"incremental\": true\n },\n \"exclude\": [\"node_modules\"]\n}\n",
21
+ "packages/config-typescript/nextjs.json.hbs": "{\n \"extends\": \"./base.json\",\n \"compilerOptions\": {\n \"jsx\": \"react-jsx\",\n \"plugins\": [{ \"name\": \"next\" }]\n }\n}\n",
22
+ "packages/config-typescript/package.json.hbs": "{\n \"name\": \"@repo/config-typescript\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"exports\": {\n \"./base.json\": \"./base.json\",\n \"./nextjs.json\": \"./nextjs.json\"\n },\n \"files\": [\"base.json\", \"nextjs.json\"]\n}\n",
23
+ "packages/ui/components.json.hbs": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"{{shadcn.componentLibrary}}-{{shadcn.styleVariant}}\",\n \"rsc\": false,\n \"tsx\": true,\n \"tailwind\": {\n \"config\": \"\",\n \"css\": \"\",\n \"baseColor\": \"{{shadcn.baseColor}}\",\n \"cssVariables\": true\n },\n \"iconLibrary\": \"{{shadcn.iconLibrary}}\",\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\",\n \"ui\": \"@/components/ui\",\n \"lib\": \"@/lib\",\n \"hooks\": \"@/hooks\"\n }\n}\n",
24
+ "packages/ui/package.json.hbs": "{\n \"name\": \"@repo/ui\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"main\": \"./src/index.ts\",\n \"types\": \"./src/index.ts\",\n \"exports\": {\n \".\": \"./src/index.ts\",\n \"./components/*\": \"./src/components/*.tsx\",\n \"./lib/*\": \"./src/lib/*.ts\"\n },\n \"scripts\": {\n \"lint\": \"biome check .\",\n \"lint:fix\": \"biome check --write .\",\n \"typecheck\": \"tsc --noEmit\"\n },\n \"dependencies\": {\n \"@hugeicons/react\": \"^0.3.0\",\n \"class-variance-authority\": \"^0.7.0\",\n \"clsx\": \"^2.1.0\",\n \"tailwind-merge\": \"^2.5.0\"\n },\n \"devDependencies\": {\n \"@repo/config-typescript\": \"workspace:*\",\n \"@types/react\": \"^19.0.0\",\n \"@types/react-dom\": \"^19.0.0\",\n \"react\": \"^19.0.0\",\n \"tailwindcss\": \"^4.0.0\",\n \"typescript\": \"^5.0.0\"\n },\n \"peerDependencies\": {\n \"react\": \"^19.0.0\"\n }\n}\n",
25
+ "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",
26
+ "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",
27
+ "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",
28
+ "web/components.json.hbs": "{\n \"$schema\": \"https://ui.shadcn.com/schema.json\",\n \"style\": \"{{shadcn.componentLibrary}}-{{shadcn.styleVariant}}\",\n \"rsc\": true,\n \"tsx\": true,\n \"tailwind\": {\n \"config\": \"\",\n \"css\": \"src/app/globals.css\",\n \"baseColor\": \"{{shadcn.baseColor}}\",\n \"cssVariables\": true\n },\n \"iconLibrary\": \"{{shadcn.iconLibrary}}\",\n \"aliases\": {\n \"components\": \"@/components\",\n \"utils\": \"@/lib/utils\",\n \"ui\": \"@/components/ui\",\n \"lib\": \"@/lib\",\n \"hooks\": \"@/hooks\"\n }\n}\n",
29
+ "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",
30
+ "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 \"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 \"setup:convex\": \"node scripts/setup-convex.mjs\"\n },\n \"dependencies\": {\n \"next\": \"^16.0.0\",\n \"react\": \"^19.0.0\",\n \"react-dom\": \"^19.0.0\",\n \"convex\": \"^1.25.0\",\n \"@convex-dev/auth\": \"^0.0.90\",\n \"@auth/core\": \"^0.37.0\",\n \"@hugeicons/react\": \"^0.3.0\",\n \"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",
31
+ "web/postcss.config.mjs.hbs": "export default {\n plugins: {\n '@tailwindcss/postcss': {},\n },\n}\n",
32
+ "web/src/app/globals.css.hbs": "@import \"tailwindcss\";\n@import \"tw-animate-css\";\n\n@custom-variant dark (&:is(.dark *));\n\n@theme inline {\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --font-sans: var(--font-geist-sans);\n --font-mono: var(--font-geist-mono);\n --color-sidebar-ring: var(--sidebar-ring);\n --color-sidebar-border: var(--sidebar-border);\n --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);\n --color-sidebar-accent: var(--sidebar-accent);\n --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);\n --color-sidebar-primary: var(--sidebar-primary);\n --color-sidebar-foreground: var(--sidebar-foreground);\n --color-sidebar: var(--sidebar);\n --color-chart-5: var(--chart-5);\n --color-chart-4: var(--chart-4);\n --color-chart-3: var(--chart-3);\n --color-chart-2: var(--chart-2);\n --color-chart-1: var(--chart-1);\n --color-ring: var(--ring);\n --color-input: var(--input);\n --color-border: var(--border);\n --color-destructive: var(--destructive);\n --color-accent-foreground: var(--accent-foreground);\n --color-accent: var(--accent);\n --color-muted-foreground: var(--muted-foreground);\n --color-muted: var(--muted);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-secondary: var(--secondary);\n --color-primary-foreground: var(--primary-foreground);\n --color-primary: var(--primary);\n --color-popover-foreground: var(--popover-foreground);\n --color-popover: var(--popover);\n --color-card-foreground: var(--card-foreground);\n --color-card: var(--card);\n --radius-sm: calc(var(--radius) - 4px);\n --radius-md: calc(var(--radius) - 2px);\n --radius-lg: var(--radius);\n --radius-xl: calc(var(--radius) + 4px);\n}\n\n:root {\n --radius: 0.625rem;\n --background: oklch(1 0 0);\n --foreground: oklch(0.145 0 0);\n --card: oklch(1 0 0);\n --card-foreground: oklch(0.145 0 0);\n --popover: oklch(1 0 0);\n --popover-foreground: oklch(0.145 0 0);\n --primary: oklch(0.205 0 0);\n --primary-foreground: oklch(0.985 0 0);\n --secondary: oklch(0.97 0 0);\n --secondary-foreground: oklch(0.205 0 0);\n --muted: oklch(0.97 0 0);\n --muted-foreground: oklch(0.556 0 0);\n --accent: oklch(0.97 0 0);\n --accent-foreground: oklch(0.205 0 0);\n --destructive: oklch(0.577 0.245 27.325);\n --border: oklch(0.922 0 0);\n --input: oklch(0.922 0 0);\n --ring: oklch(0.708 0 0);\n --chart-1: oklch(0.646 0.222 41.116);\n --chart-2: oklch(0.6 0.118 184.704);\n --chart-3: oklch(0.398 0.07 227.392);\n --chart-4: oklch(0.828 0.189 84.429);\n --chart-5: oklch(0.769 0.188 70.08);\n --sidebar: oklch(0.985 0 0);\n --sidebar-foreground: oklch(0.145 0 0);\n --sidebar-primary: oklch(0.205 0 0);\n --sidebar-primary-foreground: oklch(0.985 0 0);\n --sidebar-accent: oklch(0.97 0 0);\n --sidebar-accent-foreground: oklch(0.205 0 0);\n --sidebar-border: oklch(0.922 0 0);\n --sidebar-ring: oklch(0.708 0 0);\n}\n\n.dark {\n --background: oklch(0.145 0 0);\n --foreground: oklch(0.985 0 0);\n --card: oklch(0.205 0 0);\n --card-foreground: oklch(0.985 0 0);\n --popover: oklch(0.205 0 0);\n --popover-foreground: oklch(0.985 0 0);\n --primary: oklch(0.922 0 0);\n --primary-foreground: oklch(0.205 0 0);\n --secondary: oklch(0.269 0 0);\n --secondary-foreground: oklch(0.985 0 0);\n --muted: oklch(0.269 0 0);\n --muted-foreground: oklch(0.708 0 0);\n --accent: oklch(0.269 0 0);\n --accent-foreground: oklch(0.985 0 0);\n --destructive: oklch(0.704 0.191 22.216);\n --border: oklch(1 0 0 / 10%);\n --input: oklch(1 0 0 / 15%);\n --ring: oklch(0.556 0 0);\n --chart-1: oklch(0.488 0.243 264.376);\n --chart-2: oklch(0.696 0.17 162.48);\n --chart-3: oklch(0.769 0.188 70.08);\n --chart-4: oklch(0.627 0.265 303.9);\n --chart-5: oklch(0.645 0.246 16.439);\n --sidebar: oklch(0.205 0 0);\n --sidebar-foreground: oklch(0.985 0 0);\n --sidebar-primary: oklch(0.488 0.243 264.376);\n --sidebar-primary-foreground: oklch(0.985 0 0);\n --sidebar-accent: oklch(0.269 0 0);\n --sidebar-accent-foreground: oklch(0.985 0 0);\n --sidebar-border: oklch(1 0 0 / 10%);\n --sidebar-ring: oklch(0.556 0 0);\n}\n\n@layer base {\n * {\n @apply border-border outline-ring/50;\n }\n body {\n @apply bg-background text-foreground;\n }\n}\n",
33
+ "web/src/app/layout.tsx.hbs": "import type { Metadata } from 'next'\nimport { Geist, Geist_Mono } from 'next/font/google'\nimport { ConvexAuthNextjsServerProvider } from '@convex-dev/auth/nextjs/server'\nimport { ConvexClientProvider } from '@/components/providers/convex-provider'\n{{#if (eq integrations.analytics 'posthog')}}\nimport { PostHogProvider } from '@/components/providers/posthog-provider'\n{{/if}}\n{{#if (eq integrations.analytics 'vercel')}}\nimport { Analytics } from '@vercel/analytics/react'\nimport { SpeedInsights } from '@vercel/speed-insights/next'\n{{/if}}\nimport './globals.css'\n\nconst geistSans = Geist({\n variable: '--font-geist-sans',\n subsets: ['latin'],\n})\n\nconst geistMono = Geist_Mono({\n variable: '--font-geist-mono',\n subsets: ['latin'],\n})\n\nexport const metadata: Metadata = {\n title: '{{projectName}}',\n description: 'Built with create-kofi-stack',\n}\n\nexport default function RootLayout({\n children,\n}: {\n children: React.ReactNode\n}) {\n return (\n <ConvexAuthNextjsServerProvider>\n <html lang=\"en\" suppressHydrationWarning>\n <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>\n <ConvexClientProvider>\n {{#if (eq integrations.analytics 'posthog')}}\n <PostHogProvider>\n {children}\n </PostHogProvider>\n {{else}}\n {children}\n {{/if}}\n </ConvexClientProvider>\n {{#if (eq integrations.analytics 'vercel')}}\n <Analytics />\n <SpeedInsights />\n {{/if}}\n </body>\n </html>\n </ConvexAuthNextjsServerProvider>\n )\n}\n",
34
+ "web/src/app/page.tsx.hbs": "'use client'\n\nimport { useAuth } from '@/lib/auth'\n\nexport default function HomePage() {\n const { isAuthenticated, isLoading, signIn, signOut, user } = useAuth()\n\n if (isLoading) {\n return (\n <main className=\"min-h-screen flex items-center justify-center\">\n <div className=\"animate-pulse\">Loading...</div>\n </main>\n )\n }\n\n return (\n <main className=\"min-h-screen flex flex-col items-center justify-center p-8\">\n <div className=\"max-w-2xl text-center space-y-8\">\n <h1 className=\"text-4xl font-bold\">{{projectName}}</h1>\n <p className=\"text-xl text-muted-foreground\">\n Built with Next.js, Convex, Better-Auth, and shadcn/ui\n </p>\n\n {isAuthenticated ? (\n <div className=\"space-y-4\">\n <p className=\"text-lg\">\n Welcome, <span className=\"font-semibold\">{user?.name || user?.email}</span>!\n </p>\n <button\n onClick={() => signOut()}\n className=\"px-6 py-2 bg-secondary text-secondary-foreground rounded-lg hover:opacity-90 transition\"\n >\n Sign Out\n </button>\n </div>\n ) : (\n <div className=\"space-y-4\">\n <p className=\"text-muted-foreground\">\n Sign in to get started\n </p>\n <div className=\"flex gap-4 justify-center\">\n <button\n onClick={() => signIn('github')}\n className=\"px-6 py-2 bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition\"\n >\n Sign in with GitHub\n </button>\n <button\n onClick={() => signIn('google')}\n className=\"px-6 py-2 bg-secondary text-secondary-foreground rounded-lg hover:opacity-90 transition\"\n >\n Sign in with Google\n </button>\n </div>\n </div>\n )}\n\n <div className=\"pt-8 border-t border-border\">\n <p className=\"text-sm text-muted-foreground\">\n Created with{' '}\n <a\n href=\"https://github.com/theodenanyoh11/create-kofi-stack\"\n className=\"text-primary hover:underline\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n create-kofi-stack\n </a>\n </p>\n </div>\n </div>\n </main>\n )\n}\n",
35
+ "web/src/components/providers/convex-provider.tsx.hbs": "'use client'\n\nimport { ConvexAuthNextjsProvider } from '@convex-dev/auth/nextjs'\nimport { ConvexReactClient } from 'convex/react'\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 <ConvexAuthNextjsProvider client={convex}>\n {children}\n </ConvexAuthNextjsProvider>\n )\n}\n",
36
+ "web/src/lib/auth.ts.hbs": "'use client'\n\nimport { useConvexAuth, useMutation, useQuery } from 'convex/react'\nimport { useAuthActions } from '@convex-dev/auth/react'\nimport { api } from '{{#if (eq structure 'monorepo')}}@repo/backend{{else}}../../convex/_generated/api{{/if}}'\n\nexport function useAuth() {\n const { isAuthenticated, isLoading } = useConvexAuth()\n const { signIn, signOut } = useAuthActions()\n const user = useQuery(api.users.current)\n\n return {\n isAuthenticated,\n isLoading,\n user,\n signIn: (provider: 'github' | 'google') => {\n void signIn(provider)\n },\n signOut: () => {\n void signOut()\n },\n }\n}\n",
37
+ "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",
38
+ "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"
39
+ }
@@ -0,0 +1,45 @@
1
+ # Dependencies
2
+ node_modules
3
+ .pnpm-store
4
+
5
+ # Build outputs
6
+ .next
7
+ dist
8
+ .turbo
9
+ out
10
+
11
+ # Testing
12
+ coverage
13
+ playwright-report
14
+ test-results
15
+
16
+ # Environment
17
+ .env
18
+ .env.local
19
+ .env.*.local
20
+
21
+ # IDE
22
+ .idea
23
+ .vscode
24
+ *.swp
25
+ *.swo
26
+ .DS_Store
27
+
28
+ # Convex
29
+ .convex
30
+
31
+ # Vercel
32
+ .vercel
33
+
34
+ # Debug
35
+ npm-debug.log*
36
+ yarn-debug.log*
37
+ yarn-error.log*
38
+ .pnpm-debug.log*
39
+
40
+ # TypeScript
41
+ *.tsbuildinfo
42
+
43
+ # Misc
44
+ *.pem
45
+ .cache
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
+ "organizeImports": {
4
+ "enabled": true
5
+ },
6
+ "linter": {
7
+ "enabled": true,
8
+ "rules": {
9
+ "recommended": true
10
+ }
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "indentWidth": 2
16
+ },
17
+ "javascript": {
18
+ "formatter": {
19
+ "quoteStyle": "single",
20
+ "semicolons": "asNeeded"
21
+ }
22
+ },
23
+ "files": {
24
+ "ignore": [
25
+ "node_modules",
26
+ ".next",
27
+ "dist",
28
+ ".turbo",
29
+ "coverage",
30
+ ".vercel",
31
+ "_generated"
32
+ ]
33
+ }
34
+ }
@@ -0,0 +1,52 @@
1
+ # Convex
2
+ CONVEX_DEPLOYMENT=
3
+ NEXT_PUBLIC_CONVEX_URL=
4
+
5
+ # Auth - GitHub OAuth
6
+ AUTH_GITHUB_ID=
7
+ AUTH_GITHUB_SECRET=
8
+
9
+ # Auth - Google OAuth
10
+ AUTH_GOOGLE_ID=
11
+ AUTH_GOOGLE_SECRET=
12
+
13
+ # Better Auth Secret (generate with: openssl rand -base64 32)
14
+ BETTER_AUTH_SECRET=
15
+ {{#if (eq integrations.analytics 'posthog')}}
16
+
17
+ # PostHog
18
+ NEXT_PUBLIC_POSTHOG_KEY=
19
+ NEXT_PUBLIC_POSTHOG_HOST=https://app.posthog.com
20
+ {{/if}}
21
+ {{#if (eq integrations.uploads 'uploadthing')}}
22
+
23
+ # UploadThing
24
+ UPLOADTHING_TOKEN=
25
+ {{/if}}
26
+ {{#if (eq integrations.uploads 's3')}}
27
+
28
+ # AWS S3
29
+ AWS_ACCESS_KEY_ID=
30
+ AWS_SECRET_ACCESS_KEY=
31
+ AWS_REGION=
32
+ AWS_S3_BUCKET=
33
+ {{/if}}
34
+ {{#if (eq integrations.uploads 'vercel-blob')}}
35
+
36
+ # Vercel Blob
37
+ BLOB_READ_WRITE_TOKEN=
38
+ {{/if}}
39
+ {{#if (includes addons 'rate-limiting')}}
40
+
41
+ # Arcjet
42
+ ARCJET_KEY=
43
+ {{/if}}
44
+ {{#if (includes addons 'monitoring')}}
45
+
46
+ # Sentry
47
+ SENTRY_DSN=
48
+ SENTRY_AUTH_TOKEN=
49
+ {{/if}}
50
+
51
+ # Email (Resend)
52
+ RESEND_API_KEY=