create-ncf 0.1.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/README.md +145 -0
  2. package/dist/index.js +813 -0
  3. package/dist/index.js.map +1 -0
  4. package/package.json +28 -0
  5. package/templates/auth/src/app/(auth)/sign-in/page.tsx +99 -0
  6. package/templates/auth/src/app/(auth)/sign-up/page.tsx +118 -0
  7. package/templates/auth/src/app/api/auth/[...all]/route.ts +11 -0
  8. package/templates/auth/src/middleware.ts +74 -0
  9. package/templates/auth/src/server/auth/auth-client.ts +8 -0
  10. package/templates/auth/src/server/auth/auth.ts +77 -0
  11. package/templates/auth/src/server/db/schema/auth.ts +115 -0
  12. package/templates/base/biome.jsonc +68 -0
  13. package/templates/base/open-next.config.ts +3 -0
  14. package/templates/base/postcss.config.mjs +5 -0
  15. package/templates/base/src/app/layout.tsx +30 -0
  16. package/templates/base/src/app/page.tsx +24 -0
  17. package/templates/base/src/app/robots.ts +13 -0
  18. package/templates/base/src/app/sitemap.ts +12 -0
  19. package/templates/base/src/lib/utils.ts +6 -0
  20. package/templates/base/src/middleware.ts +9 -0
  21. package/templates/base/src/styles/globals.css +121 -0
  22. package/templates/base/tsconfig.json +37 -0
  23. package/templates/drizzle/drizzle.config.ts +7 -0
  24. package/templates/drizzle/migrations/.gitkeep +0 -0
  25. package/templates/drizzle/src/server/db/index.ts +9 -0
  26. package/templates/drizzle/src/server/db/schema/example.ts +15 -0
  27. package/templates/drizzle/src/server/db/schema/index.ts +1 -0
  28. package/templates/image-loader/src/lib/image-loader.ts +34 -0
  29. package/templates/posthog/instrumentation-client.ts +15 -0
  30. package/templates/queues/src/server/queues/handler.ts +43 -0
  31. package/templates/r2/src/server/services/storage.ts +53 -0
  32. package/templates/shadcn/components.json +22 -0
  33. package/templates/shadcn/src/components/ui/button.tsx +59 -0
  34. package/templates/shadcn/src/components/ui/card.tsx +92 -0
  35. package/templates/shadcn/src/components/ui/input.tsx +21 -0
  36. package/templates/shadcn/src/components/ui/skeleton.tsx +13 -0
  37. package/templates/shadcn/src/components/ui/sonner.tsx +28 -0
  38. package/templates/trpc/src/app/api/trpc/[trpc]/route.ts +29 -0
  39. package/templates/trpc/src/server/api/root.ts +10 -0
  40. package/templates/trpc/src/server/api/routes/example.ts +12 -0
  41. package/templates/trpc/src/server/api/trpc.ts +45 -0
  42. package/templates/trpc/src/server/api/trpc.with-auth.ts +67 -0
  43. package/templates/trpc/src/trpc/query-client.ts +23 -0
  44. package/templates/trpc/src/trpc/react.tsx +65 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts","../src/prompts.ts","../src/types.ts","../src/scaffolder.ts","../src/utils.ts","../src/builders/cloudflare-env.ts","../src/builders/env.ts","../src/builders/next-config.ts","../src/builders/package-json.ts","../src/builders/wrangler.ts","../src/builders/worker.ts","../src/index.ts"],"sourcesContent":["import * as p from \"@clack/prompts\";\nimport { execSync } from \"node:child_process\";\nimport fs from \"fs-extra\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\nimport { runPrompts } from \"./prompts.js\";\nimport { scaffold } from \"./scaffolder.js\";\nimport { FEATURE_LABELS } from \"./types.js\";\nimport { detectPackageManager, getInstallCommand, getRunCommand } from \"./utils.js\";\n\nexport async function cli() {\n\tconst args = process.argv.slice(2);\n\tconst cliProjectName = args[0] && !args[0].startsWith(\"-\") ? args[0] : undefined;\n\n\tconst selections = await runPrompts(cliProjectName);\n\tconst targetDir = path.resolve(process.cwd(), selections.projectName);\n\n\t// Check if directory exists and is not empty\n\tif (await fs.pathExists(targetDir)) {\n\t\tconst files = await fs.readdir(targetDir);\n\t\tif (files.length > 0) {\n\t\t\tp.log.error(`Directory ${pc.cyan(selections.projectName)} is not empty.`);\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\tconst spinner = p.spinner();\n\n\t// Scaffold project\n\tspinner.start(\"Creating project structure...\");\n\tawait scaffold(targetDir, selections);\n\tspinner.stop(\"Project structure created.\");\n\n\t// Install dependencies\n\tconst pm = detectPackageManager();\n\tspinner.start(\"Installing dependencies...\");\n\ttry {\n\t\texecSync(getInstallCommand(pm), {\n\t\t\tcwd: targetDir,\n\t\t\tstdio: \"ignore\",\n\t\t});\n\t\tspinner.stop(\"Dependencies installed.\");\n\t} catch {\n\t\tspinner.stop(\"Failed to install dependencies. You can install them manually.\");\n\t}\n\n\t// Git init\n\tspinner.start(\"Initializing git repository...\");\n\ttry {\n\t\texecSync(\"git init && git add -A && git commit -m 'Initial commit from create-ncf'\", {\n\t\t\tcwd: targetDir,\n\t\t\tstdio: \"ignore\",\n\t\t});\n\t\tspinner.stop(\"Git repository initialized.\");\n\t} catch {\n\t\tspinner.stop(\"Failed to initialize git. You can do this manually.\");\n\t}\n\n\t// Print next steps\n\tconst runCmd = getRunCommand(pm);\n\n\tp.note(\n\t\t[\n\t\t\t`cd ${selections.projectName}`,\n\t\t\t\"cp .env.example .env.local\",\n\t\t\t\"# Edit .env.local with your values\",\n\t\t\t`${runCmd} dev`,\n\t\t].join(\"\\n\"),\n\t\t\"Next steps\",\n\t);\n\n\t// Print Cloudflare setup instructions based on features\n\tconst cfSteps: string[] = [];\n\n\tif (selections.features.includes(\"drizzle\")) {\n\t\tcfSteps.push(\n\t\t\t`wrangler d1 create ${selections.projectName}`,\n\t\t\t\"# Update wrangler.jsonc with your database_id\",\n\t\t\t`${runCmd} db:generate`,\n\t\t\t`${runCmd} db:migrate`,\n\t\t);\n\t}\n\n\tif (selections.features.includes(\"r2\")) {\n\t\tcfSteps.push(\n\t\t\t`wrangler r2 bucket create ${selections.projectName}`,\n\t\t\t\"# Update wrangler.jsonc with your bucket name\",\n\t\t);\n\t}\n\n\tif (selections.features.includes(\"queues\")) {\n\t\tcfSteps.push(\n\t\t\t`wrangler queues create ${selections.projectName}`,\n\t\t\t`wrangler queues create ${selections.projectName}-dlq`,\n\t\t);\n\t}\n\n\tif (cfSteps.length > 0) {\n\t\tp.note(cfSteps.join(\"\\n\"), \"Cloudflare setup\");\n\t}\n\n\tif (selections.features.includes(\"imageLoader\")) {\n\t\tp.log.warn(\n\t\t\tpc.yellow(\n\t\t\t\t\"Remember to enable Cloudflare Image Transformations on your zone:\\nCloudflare Dashboard → Speed → Image Transformations → Enable\",\n\t\t\t),\n\t\t);\n\t}\n\n\tp.note(`${runCmd} deploy`, \"Deploy\");\n\n\tp.outro(pc.green(\"Happy building!\"));\n}\n","import * as p from \"@clack/prompts\";\nimport pc from \"picocolors\";\nimport { FEATURE_LABELS, type Feature, type UserSelections } from \"./types.js\";\n\nexport async function runPrompts(\n\tcliProjectName?: string,\n): Promise<UserSelections> {\n\tp.intro(pc.bgCyan(pc.black(\" create-ncf \")));\n\n\tconst projectName =\n\t\tcliProjectName ??\n\t\t((await p.text({\n\t\t\tmessage: \"Where should we create your project?\",\n\t\t\tplaceholder: \"./my-app\",\n\t\t\tdefaultValue: \"./my-app\",\n\t\t\tvalidate(value) {\n\t\t\t\tif (!value.trim()) return \"Please enter a project name.\";\n\t\t\t},\n\t\t})) as string);\n\n\tif (p.isCancel(projectName)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\tconst selectedFeatures = (await p.multiselect({\n\t\tmessage: \"Which features would you like to include?\",\n\t\toptions: [\n\t\t\t{\n\t\t\t\tvalue: \"trpc\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.trpc.label,\n\t\t\t\thint: FEATURE_LABELS.trpc.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"drizzle\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.drizzle.label,\n\t\t\t\thint: FEATURE_LABELS.drizzle.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"auth\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.auth.label,\n\t\t\t\thint: FEATURE_LABELS.auth.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"r2\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.r2.label,\n\t\t\t\thint: FEATURE_LABELS.r2.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"queues\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.queues.label,\n\t\t\t\thint: FEATURE_LABELS.queues.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"imageLoader\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.imageLoader.label,\n\t\t\t\thint: FEATURE_LABELS.imageLoader.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"posthog\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.posthog.label,\n\t\t\t\thint: FEATURE_LABELS.posthog.hint,\n\t\t\t},\n\t\t\t{\n\t\t\t\tvalue: \"shadcn\" as Feature,\n\t\t\t\tlabel: FEATURE_LABELS.shadcn.label,\n\t\t\t\thint: FEATURE_LABELS.shadcn.hint,\n\t\t\t},\n\t\t],\n\t\tinitialValues: [\n\t\t\t\"trpc\" as Feature,\n\t\t\t\"drizzle\" as Feature,\n\t\t\t\"auth\" as Feature,\n\t\t\t\"shadcn\" as Feature,\n\t\t],\n\t\trequired: false,\n\t})) as Feature[];\n\n\tif (p.isCancel(selectedFeatures)) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\t// Auto-enable drizzle if auth is selected\n\tconst features = [...selectedFeatures];\n\tif (features.includes(\"auth\") && !features.includes(\"drizzle\")) {\n\t\tfeatures.push(\"drizzle\");\n\t\tp.log.info(\"better-auth requires Drizzle + D1. It has been automatically included.\");\n\t}\n\n\t// Show summary\n\tconst featureList =\n\t\tfeatures.length > 0\n\t\t\t? features.map((f) => FEATURE_LABELS[f].label).join(\", \")\n\t\t\t: \"None (base only)\";\n\n\tp.log.info(`Project: ${pc.cyan(projectName)}`);\n\tp.log.info(`Features: ${pc.cyan(featureList)}`);\n\n\tconst confirmed = await p.confirm({\n\t\tmessage: \"Continue with these settings?\",\n\t});\n\n\tif (p.isCancel(confirmed) || !confirmed) {\n\t\tp.cancel(\"Operation cancelled.\");\n\t\tprocess.exit(0);\n\t}\n\n\treturn {\n\t\tprojectName: projectName.replace(/^\\.\\//, \"\"),\n\t\tfeatures,\n\t};\n}\n","export const FEATURES = [\n\t\"trpc\",\n\t\"drizzle\",\n\t\"auth\",\n\t\"r2\",\n\t\"queues\",\n\t\"imageLoader\",\n\t\"posthog\",\n\t\"shadcn\",\n] as const;\n\nexport type Feature = (typeof FEATURES)[number];\n\nexport interface UserSelections {\n\tprojectName: string;\n\tfeatures: Feature[];\n}\n\nexport const FEATURE_LABELS: Record<Feature, { label: string; hint: string }> =\n\t{\n\t\ttrpc: {\n\t\t\tlabel: \"tRPC\",\n\t\t\thint: \"Type-safe API with React Query\",\n\t\t},\n\t\tdrizzle: {\n\t\t\tlabel: \"Drizzle ORM + D1\",\n\t\t\thint: \"Database with SQLite on Cloudflare\",\n\t\t},\n\t\tauth: {\n\t\t\tlabel: \"better-auth\",\n\t\t\thint: \"Authentication with sign-in/sign-up pages\",\n\t\t},\n\t\tr2: {\n\t\t\tlabel: \"Cloudflare R2\",\n\t\t\thint: \"Object storage helper\",\n\t\t},\n\t\tqueues: {\n\t\t\tlabel: \"Cloudflare Queues\",\n\t\t\thint: \"Background job processing with Worker consumer\",\n\t\t},\n\t\timageLoader: {\n\t\t\tlabel: \"Cloudflare Image Loader\",\n\t\t\thint: \"Custom Next.js image loader for CF Image Transformations\",\n\t\t},\n\t\tposthog: {\n\t\t\tlabel: \"PostHog Analytics\",\n\t\t\thint: \"Product analytics\",\n\t\t},\n\t\tshadcn: {\n\t\t\tlabel: \"shadcn/ui\",\n\t\t\thint: \"UI component library\",\n\t\t},\n\t};\n","import fs from \"fs-extra\";\nimport path from \"node:path\";\nimport { buildCloudflareEnvDts, buildLibEnvDts } from \"./builders/cloudflare-env.js\";\nimport { buildEnvExample, buildEnvJs } from \"./builders/env.js\";\nimport { buildNextConfig } from \"./builders/next-config.js\";\nimport { buildPackageJson } from \"./builders/package-json.js\";\nimport { buildWrangler } from \"./builders/wrangler.js\";\nimport { buildWorkerTs } from \"./builders/worker.js\";\nimport type { UserSelections } from \"./types.js\";\nimport { copyDirectory, getTemplatesDir, hasFeature } from \"./utils.js\";\n\nexport async function scaffold(\n\ttargetDir: string,\n\tselections: UserSelections,\n): Promise<void> {\n\tconst { projectName, features } = selections;\n\tconst templatesDir = getTemplatesDir();\n\n\t// Ensure target directory exists\n\tawait fs.ensureDir(targetDir);\n\n\t// 1. Copy base template\n\tawait copyDirectory(path.join(templatesDir, \"base\"), targetDir);\n\n\t// 2. Overlay selected feature templates\n\tconst featureDirs: string[] = [];\n\n\tif (hasFeature(features, \"trpc\")) {\n\t\t// Choose the right variant based on other features\n\t\tfeatureDirs.push(\"trpc\");\n\t}\n\n\tif (hasFeature(features, \"drizzle\")) {\n\t\tfeatureDirs.push(\"drizzle\");\n\t}\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tfeatureDirs.push(\"auth\");\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tfeatureDirs.push(\"r2\");\n\t}\n\n\tif (hasFeature(features, \"queues\")) {\n\t\tfeatureDirs.push(\"queues\");\n\t}\n\n\tif (hasFeature(features, \"imageLoader\")) {\n\t\tfeatureDirs.push(\"image-loader\");\n\t}\n\n\tif (hasFeature(features, \"posthog\")) {\n\t\tfeatureDirs.push(\"posthog\");\n\t}\n\n\tif (hasFeature(features, \"shadcn\")) {\n\t\tfeatureDirs.push(\"shadcn\");\n\t}\n\n\tfor (const dir of featureDirs) {\n\t\tconst featurePath = path.join(templatesDir, dir);\n\t\tif (await fs.pathExists(featurePath)) {\n\t\t\tawait copyDirectory(featurePath, targetDir);\n\t\t}\n\t}\n\n\t// 3. Handle tRPC variant files (with-auth vs without-auth)\n\tif (hasFeature(features, \"trpc\")) {\n\t\tconst trpcDir = path.join(targetDir, \"src\", \"server\", \"api\");\n\n\t\tif (hasFeature(features, \"auth\") && hasFeature(features, \"drizzle\")) {\n\t\t\t// Use the with-auth variant\n\t\t\tconst withAuthPath = path.join(trpcDir, \"trpc.with-auth.ts\");\n\t\t\tconst trpcPath = path.join(trpcDir, \"trpc.ts\");\n\t\t\tif (await fs.pathExists(withAuthPath)) {\n\t\t\t\tawait fs.remove(trpcPath);\n\t\t\t\tawait fs.rename(withAuthPath, trpcPath);\n\t\t\t}\n\t\t} else {\n\t\t\t// Remove the with-auth variant\n\t\t\tconst withAuthPath = path.join(trpcDir, \"trpc.with-auth.ts\");\n\t\t\tif (await fs.pathExists(withAuthPath)) {\n\t\t\t\tawait fs.remove(withAuthPath);\n\t\t\t}\n\t\t}\n\t}\n\n\t// 4. Handle drizzle schema index based on auth\n\tif (hasFeature(features, \"drizzle\")) {\n\t\tconst schemaIndexPath = path.join(\n\t\t\ttargetDir,\n\t\t\t\"src\",\n\t\t\t\"server\",\n\t\t\t\"db\",\n\t\t\t\"schema\",\n\t\t\t\"index.ts\",\n\t\t);\n\n\t\tif (hasFeature(features, \"auth\")) {\n\t\t\tawait fs.writeFile(\n\t\t\t\tschemaIndexPath,\n\t\t\t\t'export * from \"./example\";\\nexport * from \"./auth\";\\n',\n\t\t\t);\n\t\t}\n\t\t// Otherwise the default from drizzle template is fine (just example)\n\t}\n\n\t// 5. Generate dynamic files\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"package.json\"),\n\t\tbuildPackageJson(projectName, features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"wrangler.jsonc\"),\n\t\tbuildWrangler(projectName, features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"next.config.ts\"),\n\t\tbuildNextConfig(features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"worker.ts\"),\n\t\tbuildWorkerTs(features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"cloudflare-env.d.ts\"),\n\t\tbuildCloudflareEnvDts(features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"lib.env.d.ts\"),\n\t\tbuildLibEnvDts(features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \"src\", \"env.js\"),\n\t\tbuildEnvJs(features),\n\t);\n\n\tawait fs.writeFile(\n\t\tpath.join(targetDir, \".env.example\"),\n\t\tbuildEnvExample(features),\n\t);\n}\n","import fs from \"fs-extra\";\nimport path from \"node:path\";\nimport type { Feature } from \"./types.js\";\n\nexport function detectPackageManager(): \"bun\" | \"npm\" | \"pnpm\" | \"yarn\" {\n\tconst userAgent = process.env.npm_config_user_agent ?? \"\";\n\tif (userAgent.startsWith(\"bun\")) return \"bun\";\n\tif (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n\tif (userAgent.startsWith(\"yarn\")) return \"yarn\";\n\treturn \"npm\";\n}\n\nexport function getRunCommand(pm: string): string {\n\treturn pm === \"npm\" ? \"npm run\" : `${pm} run`;\n}\n\nexport function getInstallCommand(pm: string): string {\n\tif (pm === \"yarn\") return \"yarn\";\n\treturn `${pm} install`;\n}\n\nexport async function copyDirectory(\n\tsrc: string,\n\tdest: string,\n\tfilter?: (filePath: string) => boolean,\n): Promise<void> {\n\tawait fs.copy(src, dest, {\n\t\toverwrite: true,\n\t\tfilter: (srcPath) => {\n\t\t\t// Always skip _dependencies.json files\n\t\t\tif (path.basename(srcPath) === \"_dependencies.json\") return false;\n\t\t\tif (filter) return filter(srcPath);\n\t\t\treturn true;\n\t\t},\n\t});\n}\n\nexport function hasFeature(features: Feature[], feature: Feature): boolean {\n\treturn features.includes(feature);\n}\n\nexport function getTemplatesDir(): string {\n\t// In the built CLI, templates are at ../templates relative to dist/index.js\n\treturn path.resolve(new URL(\".\", import.meta.url).pathname, \"..\", \"templates\");\n}\n","import type { Feature } from \"../types.js\";\nimport { hasFeature } from \"../utils.js\";\n\nexport function buildCloudflareEnvDts(features: Feature[]): string {\n\tconst bindings: string[] = [\"\\tASSETS: Fetcher;\"];\n\n\tif (hasFeature(features, \"drizzle\")) {\n\t\tbindings.push(\"\\tDB: D1Database;\");\n\t}\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tbindings.push(\"\\tKV: KVNamespace;\");\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tbindings.push(\"\\tSTORAGE: R2Bucket;\");\n\t}\n\n\tif (hasFeature(features, \"queues\")) {\n\t\tbindings.push(\"\\tQUEUE: Queue;\");\n\t}\n\n\treturn `// Generated by create-ncf. Run \\`wrangler types\\` to regenerate with full Cloudflare types.\n/// <reference types=\"@cloudflare/workers-types\" />\n\ninterface CloudflareEnv {\n${bindings.join(\"\\n\")}\n}\n`;\n}\n\nexport function buildLibEnvDts(features: Feature[]): string {\n\tconst entries: string[] = [];\n\n\tif (hasFeature(features, \"drizzle\")) {\n\t\tentries.push(\"\\t\\tDB: D1Database;\");\n\t}\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tentries.push(\"\\t\\tKV: KVNamespace;\");\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tentries.push(\"\\t\\tSTORAGE: R2Bucket;\");\n\t}\n\n\tif (hasFeature(features, \"queues\")) {\n\t\tentries.push(\"\\t\\tQUEUE: Queue;\");\n\t}\n\n\tif (entries.length === 0) {\n\t\treturn `declare namespace NodeJS {\n\\tinterface ProcessEnv {\n\\t\\t[key: string]: string | undefined;\n\\t}\n}\n`;\n\t}\n\n\treturn `declare namespace NodeJS {\n\\tinterface ProcessEnv {\n${entries.join(\"\\n\")}\n\\t\\t[key: string]: string | undefined;\n\\t}\n}\n`;\n}\n","import type { Feature } from \"../types.js\";\nimport { hasFeature } from \"../utils.js\";\n\nexport function buildEnvJs(features: Feature[]): string {\n\tconst serverVars: string[] = [\n\t\t\"\\t\\tNODE_ENV: z\",\n\t\t'\\t\\t\\t.enum([\"development\", \"test\", \"production\"])',\n\t\t'\\t\\t\\t.default(\"development\"),',\n\t\t\"\\t\\tSITE_URL: z.string().url(),\",\n\t];\n\n\tconst clientVars: string[] = [];\n\n\tconst runtimeEnvServer: string[] = [\n\t\t\"\\t\\tNODE_ENV: process.env.NODE_ENV,\",\n\t\t\"\\t\\tSITE_URL: process.env.SITE_URL,\",\n\t];\n\n\tconst runtimeEnvClient: string[] = [];\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tserverVars.push(\n\t\t\t\"\\t\\tBETTER_AUTH_SECRET: z.string().min(1),\",\n\t\t\t\"\\t\\tBETTER_AUTH_URL: z.string().url(),\",\n\t\t);\n\t\truntimeEnvServer.push(\n\t\t\t\"\\t\\tBETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,\",\n\t\t\t\"\\t\\tBETTER_AUTH_URL: process.env.BETTER_AUTH_URL,\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tclientVars.push(\"\\t\\tNEXT_PUBLIC_R2_DOMAIN: z.string().url().optional(),\");\n\t\truntimeEnvClient.push(\n\t\t\t\"\\t\\tNEXT_PUBLIC_R2_DOMAIN: process.env.NEXT_PUBLIC_R2_DOMAIN,\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"imageLoader\")) {\n\t\tclientVars.push(\n\t\t\t\"\\t\\tNEXT_PUBLIC_CDN_DOMAIN: z.string().url().optional(),\",\n\t\t);\n\t\truntimeEnvClient.push(\n\t\t\t\"\\t\\tNEXT_PUBLIC_CDN_DOMAIN: process.env.NEXT_PUBLIC_CDN_DOMAIN,\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"posthog\")) {\n\t\tclientVars.push(\n\t\t\t\"\\t\\tNEXT_PUBLIC_POSTHOG_KEY: z.string().min(1).optional(),\",\n\t\t\t\"\\t\\tNEXT_PUBLIC_POSTHOG_HOST: z.string().url().optional(),\",\n\t\t);\n\t\truntimeEnvClient.push(\n\t\t\t\"\\t\\tNEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,\",\n\t\t\t\"\\t\\tNEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,\",\n\t\t);\n\t}\n\n\tconst serverSection = serverVars.join(\"\\n\");\n\tconst clientSection =\n\t\tclientVars.length > 0 ? clientVars.join(\"\\n\") : \"\\t\\t// Add NEXT_PUBLIC_ client vars here\";\n\tconst runtimeSection = [\n\t\t...runtimeEnvServer,\n\t\t...runtimeEnvClient,\n\t].join(\"\\n\");\n\n\treturn `import { createEnv } from \"@t3-oss/env-nextjs\";\nimport { z } from \"zod\";\n\nexport const env = createEnv({\n\\tserver: {\n${serverSection}\n\\t},\n\\tclient: {\n${clientSection}\n\\t},\n\\truntimeEnv: {\n${runtimeSection}\n\\t},\n\\tskipValidation: !!process.env.SKIP_ENV_VALIDATION,\n\\temptyStringAsUndefined: true,\n});\n`;\n}\n\nexport function buildEnvExample(features: Feature[]): string {\n\tconst lines: string[] = [\n\t\t\"NODE_ENV=development\",\n\t\t\"SITE_URL=http://localhost:3000\",\n\t\t\"\",\n\t];\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tlines.push(\n\t\t\t\"# Authentication (better-auth)\",\n\t\t\t\"BETTER_AUTH_SECRET=replace-with-a-secure-random-string\",\n\t\t\t\"BETTER_AUTH_URL=http://localhost:3000\",\n\t\t\t\"\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tlines.push(\n\t\t\t\"# Cloudflare R2 Storage\",\n\t\t\t\"NEXT_PUBLIC_R2_DOMAIN=https://your-r2-domain.com\",\n\t\t\t\"\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"imageLoader\")) {\n\t\tlines.push(\n\t\t\t\"# Cloudflare Image Loader\",\n\t\t\t\"NEXT_PUBLIC_CDN_DOMAIN=https://your-cdn-domain.com\",\n\t\t\t\"\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"posthog\")) {\n\t\tlines.push(\n\t\t\t\"# PostHog Analytics\",\n\t\t\t\"NEXT_PUBLIC_POSTHOG_KEY=your-posthog-project-api-key\",\n\t\t\t\"NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.com\",\n\t\t\t\"\",\n\t\t);\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n","import type { Feature } from \"../types.js\";\nimport { hasFeature } from \"../utils.js\";\n\nexport function buildNextConfig(features: Feature[]): string {\n\tconst imports = [\n\t\t'import { initOpenNextCloudflareForDev } from \"@opennextjs/cloudflare\";',\n\t\t'import type { NextConfig } from \"next\";',\n\t];\n\n\tconst configParts: string[] = [];\n\n\t// Image loader\n\tif (hasFeature(features, \"imageLoader\")) {\n\t\tconfigParts.push(`\\timages: {\n\\t\\tloader: \"custom\",\n\\t\\tloaderFile: \"./src/lib/image-loader.ts\",\n\\t},`);\n\t}\n\n\t// PostHog rewrites\n\tif (hasFeature(features, \"posthog\")) {\n\t\tconfigParts.push(`\\tasync rewrites() {\n\\t\\treturn [\n\\t\\t\\t{\n\\t\\t\\t\\tsource: \"/ingest/static/:path*\",\n\\t\\t\\t\\tdestination: \"https://us-assets.i.posthog.com/static/:path*\",\n\\t\\t\\t},\n\\t\\t\\t{\n\\t\\t\\t\\tsource: \"/ingest/:path*\",\n\\t\\t\\t\\tdestination: \"https://us.i.posthog.com/:path*\",\n\\t\\t\\t},\n\\t\\t];\n\\t},\n\\tskipTrailingSlashRedirect: true,`);\n\t}\n\n\tconst configBody =\n\t\tconfigParts.length > 0 ? `\\n${configParts.join(\"\\n\")}\\n` : \"\";\n\n\treturn `${imports.join(\"\\n\")}\n\ninitOpenNextCloudflareForDev();\n\nconst nextConfig: NextConfig = {${configBody}};\n\nexport default nextConfig;\n`;\n}\n","import type { Feature } from \"../types.js\";\nimport { hasFeature } from \"../utils.js\";\n\ninterface PackageJson {\n\tname: string;\n\tversion: string;\n\tprivate: boolean;\n\ttype: string;\n\tscripts: Record<string, string>;\n\tdependencies: Record<string, string>;\n\tdevDependencies: Record<string, string>;\n}\n\nexport function buildPackageJson(\n\tprojectName: string,\n\tfeatures: Feature[],\n): string {\n\tconst pkg: PackageJson = {\n\t\tname: projectName,\n\t\tversion: \"0.1.0\",\n\t\tprivate: true,\n\t\ttype: \"module\",\n\t\tscripts: {\n\t\t\tcheck: \"biome check .\",\n\t\t\t\"check:write\": \"biome check --write .\",\n\t\t\tdev: \"next dev --turbopack\",\n\t\t\tbuild: \"next build\",\n\t\t\tstart: \"next start\",\n\t\t\tdeploy: \"opennextjs-cloudflare build && opennextjs-cloudflare deploy\",\n\t\t\tpreview: \"opennextjs-cloudflare build && opennextjs-cloudflare preview\",\n\t\t\t\"cf-typegen\":\n\t\t\t\t\"wrangler types --env-interface CloudflareEnv ./cloudflare-env.d.ts\",\n\t\t},\n\t\tdependencies: {\n\t\t\t\"@opennextjs/cloudflare\": \"^1.3.0\",\n\t\t\t\"@t3-oss/env-nextjs\": \"^0.13.8\",\n\t\t\tnext: \"15.4.6\",\n\t\t\treact: \"19.1.0\",\n\t\t\t\"react-dom\": \"19.1.0\",\n\t\t\tzod: \"^4.1.9\",\n\t\t\tclsx: \"^2.1.1\",\n\t\t\t\"tailwind-merge\": \"^3.5.0\",\n\t\t},\n\t\tdevDependencies: {\n\t\t\t\"@biomejs/biome\": \"^2.3.11\",\n\t\t\t\"@cloudflare/workers-types\": \"^4.20260116.0\",\n\t\t\t\"@tailwindcss/postcss\": \"^4\",\n\t\t\t\"@types/node\": \"^24.5.2\",\n\t\t\t\"@types/react\": \"^19\",\n\t\t\t\"@types/react-dom\": \"^19\",\n\t\t\ttailwindcss: \"^4\",\n\t\t\ttypescript: \"^5\",\n\t\t\twrangler: \"^4.47.0\",\n\t\t},\n\t};\n\n\tif (hasFeature(features, \"trpc\")) {\n\t\tObject.assign(pkg.dependencies, {\n\t\t\t\"@trpc/client\": \"^11.1.2\",\n\t\t\t\"@trpc/react-query\": \"^11.1.2\",\n\t\t\t\"@trpc/server\": \"^11.1.2\",\n\t\t\t\"@tanstack/react-query\": \"^5.89.0\",\n\t\t\tsuperjson: \"^2.2.2\",\n\t\t});\n\t}\n\n\tif (hasFeature(features, \"drizzle\")) {\n\t\tObject.assign(pkg.dependencies, {\n\t\t\t\"drizzle-orm\": \"^0.44.5\",\n\t\t});\n\t\tObject.assign(pkg.devDependencies, {\n\t\t\t\"drizzle-kit\": \"^0.31.4\",\n\t\t});\n\t\tObject.assign(pkg.scripts, {\n\t\t\t\"db:generate\": \"drizzle-kit generate\",\n\t\t\t\"db:migrate\": `wrangler d1 migrations apply ${projectName}`,\n\t\t\t\"db:migrate-remote\": `wrangler d1 migrations apply ${projectName} --remote`,\n\t\t});\n\t}\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tObject.assign(pkg.dependencies, {\n\t\t\t\"better-auth\": \"^1.4.13\",\n\t\t\t\"better-auth-cloudflare\": \"^0.2.9\",\n\t\t});\n\t}\n\n\tif (hasFeature(features, \"posthog\")) {\n\t\tObject.assign(pkg.dependencies, {\n\t\t\t\"posthog-js\": \"^1.352.0\",\n\t\t\t\"posthog-node\": \"^5.24.17\",\n\t\t});\n\t}\n\n\tif (hasFeature(features, \"shadcn\")) {\n\t\tObject.assign(pkg.dependencies, {\n\t\t\t\"class-variance-authority\": \"^0.7.1\",\n\t\t\t\"lucide-react\": \"^0.575.0\",\n\t\t\t\"radix-ui\": \"^1.4.3\",\n\t\t\tsonner: \"^2.0.7\",\n\t\t});\n\t\tObject.assign(pkg.devDependencies, {\n\t\t\t\"tw-animate-css\": \"^1.3.8\",\n\t\t});\n\t}\n\n\treturn JSON.stringify(pkg, null, \"\\t\");\n}\n","import type { Feature } from \"../types.js\";\nimport { hasFeature } from \"../utils.js\";\n\nexport function buildWrangler(\n\tprojectName: string,\n\tfeatures: Feature[],\n): string {\n\tconst lines: string[] = [\n\t\t\"{\",\n\t\t'\\t\"$schema\": \"node_modules/wrangler/config-schema.json\",',\n\t\t`\\t\"name\": \"${projectName}\",`,\n\t\t'\\t\"main\": \"worker.ts\",',\n\t\t'\\t\"compatibility_date\": \"2025-03-01\",',\n\t\t'\\t\"compatibility_flags\": [\"nodejs_compat\"],',\n\t\t'\\t\"assets\": {',\n\t\t'\\t\\t\"binding\": \"ASSETS\",',\n\t\t'\\t\\t\"directory\": \".open-next/assets\"',\n\t\t\"\\t},\",\n\t\t'\\t\"observability\": {',\n\t\t'\\t\\t\"enabled\": true',\n\t\t\"\\t}\",\n\t];\n\n\tif (hasFeature(features, \"drizzle\")) {\n\t\tlines.push(\n\t\t\t\",\",\n\t\t\t'\\t\"d1_databases\": [',\n\t\t\t\"\\t\\t{\",\n\t\t\t'\\t\\t\\t\"binding\": \"DB\",',\n\t\t\t`\\t\\t\\t\"database_name\": \"${projectName}\",`,\n\t\t\t'\\t\\t\\t\"database_id\": \"YOUR_DATABASE_ID\",',\n\t\t\t'\\t\\t\\t\"migrations_dir\": \"migrations\"',\n\t\t\t\"\\t\\t}\",\n\t\t\t\"\\t]\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tlines.push(\n\t\t\t\",\",\n\t\t\t'\\t\"kv_namespaces\": [',\n\t\t\t\"\\t\\t{\",\n\t\t\t'\\t\\t\\t\"binding\": \"KV\",',\n\t\t\t'\\t\\t\\t\"id\": \"YOUR_KV_NAMESPACE_ID\"',\n\t\t\t\"\\t\\t}\",\n\t\t\t\"\\t]\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tlines.push(\n\t\t\t\",\",\n\t\t\t'\\t\"r2_buckets\": [',\n\t\t\t\"\\t\\t{\",\n\t\t\t'\\t\\t\\t\"binding\": \"STORAGE\",',\n\t\t\t`\\t\\t\\t\"bucket_name\": \"${projectName}\"`,\n\t\t\t\"\\t\\t}\",\n\t\t\t\"\\t]\",\n\t\t);\n\t}\n\n\tif (hasFeature(features, \"queues\")) {\n\t\tlines.push(\n\t\t\t\",\",\n\t\t\t'\\t\"queues\": {',\n\t\t\t'\\t\\t\"consumers\": [',\n\t\t\t\"\\t\\t\\t{\",\n\t\t\t`\\t\\t\\t\\t\"queue\": \"${projectName}\",`,\n\t\t\t'\\t\\t\\t\\t\"max_retries\": 3,',\n\t\t\t`\\t\\t\\t\\t\"dead_letter_queue\": \"${projectName}-dlq\"`,\n\t\t\t\"\\t\\t\\t},\",\n\t\t\t\"\\t\\t\\t{\",\n\t\t\t`\\t\\t\\t\\t\"queue\": \"${projectName}-dlq\"`,\n\t\t\t\"\\t\\t\\t}\",\n\t\t\t\"\\t\\t],\",\n\t\t\t'\\t\\t\"producers\": [',\n\t\t\t\"\\t\\t\\t{\",\n\t\t\t'\\t\\t\\t\\t\"binding\": \"QUEUE\",',\n\t\t\t`\\t\\t\\t\\t\"queue\": \"${projectName}\"`,\n\t\t\t\"\\t\\t\\t}\",\n\t\t\t\"\\t\\t]\",\n\t\t\t\"\\t}\",\n\t\t);\n\t}\n\n\t// Vars section\n\tconst vars: Record<string, string> = {\n\t\tSITE_URL: \"http://localhost:3000\",\n\t};\n\n\tif (hasFeature(features, \"auth\")) {\n\t\tvars.BETTER_AUTH_URL = \"http://localhost:3000\";\n\t}\n\n\tif (hasFeature(features, \"r2\")) {\n\t\tvars.NEXT_PUBLIC_R2_DOMAIN = \"https://your-r2-domain.com\";\n\t}\n\n\tif (hasFeature(features, \"imageLoader\")) {\n\t\tvars.NEXT_PUBLIC_CDN_DOMAIN = \"https://your-cdn-domain.com\";\n\t}\n\n\tif (hasFeature(features, \"posthog\")) {\n\t\tvars.NEXT_PUBLIC_POSTHOG_KEY = \"your-posthog-key\";\n\t\tvars.NEXT_PUBLIC_POSTHOG_HOST = \"https://us.i.posthog.com\";\n\t}\n\n\tif (Object.keys(vars).length > 0) {\n\t\tconst varEntries = Object.entries(vars)\n\t\t\t.map(([k, v]) => `\\t\\t\"${k}\": \"${v}\"`)\n\t\t\t.join(\",\\n\");\n\t\tlines.push(\",\", '\\t\"vars\": {', varEntries, \"\\t}\");\n\t}\n\n\tlines.push(\"}\");\n\n\treturn lines.join(\"\\n\");\n}\n","import type { Feature } from \"../types.js\";\nimport { hasFeature } from \"../utils.js\";\n\nexport function buildWorkerTs(features: Feature[]): string {\n\tconst lines: string[] = [\n\t\t\"// @ts-ignore — .open-next is generated by opennextjs-cloudflare build\",\n\t\t'import openNextWorker from \"./.open-next/worker\";',\n\t\t\"\",\n\t\t\"// Durable Objects exported from the package directly\",\n\t\t'export { BucketCachePurge } from \"@opennextjs/cloudflare/durable-objects/bucket-cache-purge\";',\n\t\t'export { DOQueueHandler } from \"@opennextjs/cloudflare/durable-objects/queue\";',\n\t\t'export { DOShardedTagCache } from \"@opennextjs/cloudflare/durable-objects/sharded-tag-cache\";',\n\t];\n\n\tif (hasFeature(features, \"queues\")) {\n\t\tlines.push(\n\t\t\t\"\",\n\t\t\t\"async function handleQueue(\",\n\t\t\t\"\\tbatch: MessageBatch<unknown>,\",\n\t\t\t\"\\tenv: CloudflareEnv,\",\n\t\t\t\") {\",\n\t\t\t\"\\t// Bridge Cloudflare env vars to process.env so modules importing ~/env work.\",\n\t\t\t\"\\tfor (const [key, value] of Object.entries(env)) {\",\n\t\t\t'\\t\\tif (typeof value === \"string\") {',\n\t\t\t\"\\t\\t\\tprocess.env[key] = value;\",\n\t\t\t\"\\t\\t}\",\n\t\t\t\"\\t}\",\n\t\t\t\"\",\n\t\t\t'\\tconst { handleBatch, handleDlqBatch } = await import(\"./src/server/queues/handler\");',\n\t\t\t\"\",\n\t\t\t`\\tif (batch.queue.endsWith(\"-dlq\")) {`,\n\t\t\t\"\\t\\tawait handleDlqBatch(batch, env);\",\n\t\t\t\"\\t\\treturn;\",\n\t\t\t\"\\t}\",\n\t\t\t\"\",\n\t\t\t\"\\tawait handleBatch(batch, env);\",\n\t\t\t\"}\",\n\t\t);\n\t}\n\n\tlines.push(\"\", \"export default {\");\n\tlines.push(\n\t\t\"\\tfetch(request: Request, env: CloudflareEnv, ctx: ExecutionContext) {\",\n\t\t\"\\t\\treturn openNextWorker.fetch(request, env, ctx);\",\n\t\t\"\\t},\",\n\t);\n\n\tif (hasFeature(features, \"queues\")) {\n\t\tlines.push(\n\t\t\t\"\\tasync queue(batch: MessageBatch<unknown>, env: CloudflareEnv) {\",\n\t\t\t\"\\t\\tawait handleQueue(batch, env);\",\n\t\t\t\"\\t},\",\n\t\t);\n\t}\n\n\tlines.push(\"};\");\n\n\treturn lines.join(\"\\n\");\n}\n","import { cli } from \"./cli.js\";\n\ncli().catch(console.error);\n"],"mappings":"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","names":["p","fs","path","pc","fs","path","fs","path","path","fs","pc","spinner"]}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "create-ncf",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a Next.js 15 + Cloudflare Workers project with optional tRPC, Drizzle, auth, and more",
5
+ "type": "module",
6
+ "exports": "./dist/index.js",
7
+ "bin": {
8
+ "create-ncf": "./dist/index.js"
9
+ },
10
+ "files": ["dist", "templates", "README.md", "LICENSE"],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch",
14
+ "typecheck": "tsc --noEmit",
15
+ "start": "node dist/index.js"
16
+ },
17
+ "dependencies": {
18
+ "@clack/prompts": "^0.10.0",
19
+ "fs-extra": "^11.3.0",
20
+ "picocolors": "^1.1.1"
21
+ },
22
+ "devDependencies": {
23
+ "@types/fs-extra": "^11.0.4",
24
+ "@types/node": "^22.0.0",
25
+ "tsup": "^8.4.0",
26
+ "typescript": "^5.7.0"
27
+ }
28
+ }
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useRouter } from "next/navigation";
5
+ import { type FormEvent, useState } from "react";
6
+ import authClient from "~/server/auth/auth-client";
7
+
8
+ export default function SignInPage() {
9
+ const router = useRouter();
10
+ const [email, setEmail] = useState("");
11
+ const [password, setPassword] = useState("");
12
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
13
+ const [isSubmitting, setIsSubmitting] = useState(false);
14
+
15
+ const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
16
+ event.preventDefault();
17
+ setIsSubmitting(true);
18
+ setErrorMessage(null);
19
+
20
+ const result = await authClient.signIn.email({
21
+ email,
22
+ password,
23
+ callbackURL: "/",
24
+ });
25
+
26
+ if (result.error) {
27
+ setErrorMessage(result.error.message ?? "Unable to sign in.");
28
+ setIsSubmitting(false);
29
+ return;
30
+ }
31
+
32
+ router.push("/");
33
+ router.refresh();
34
+ };
35
+
36
+ return (
37
+ <div className="mx-auto flex min-h-screen w-full max-w-md items-center px-6 py-10">
38
+ <div className="w-full space-y-6 rounded-lg border border-border bg-card p-6">
39
+ <div className="space-y-1">
40
+ <h1 className="text-2xl font-semibold tracking-tight">Sign in</h1>
41
+ <p className="text-sm text-muted-foreground">
42
+ Welcome back. Sign in to continue.
43
+ </p>
44
+ </div>
45
+
46
+ <form className="space-y-4" onSubmit={handleSubmit}>
47
+ <div className="space-y-2">
48
+ <label className="text-sm font-medium" htmlFor="email">
49
+ Email
50
+ </label>
51
+ <input
52
+ autoComplete="email"
53
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
54
+ id="email"
55
+ onChange={(event) => setEmail(event.target.value)}
56
+ required
57
+ type="email"
58
+ value={email}
59
+ />
60
+ </div>
61
+
62
+ <div className="space-y-2">
63
+ <label className="text-sm font-medium" htmlFor="password">
64
+ Password
65
+ </label>
66
+ <input
67
+ autoComplete="current-password"
68
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
69
+ id="password"
70
+ onChange={(event) => setPassword(event.target.value)}
71
+ required
72
+ type="password"
73
+ value={password}
74
+ />
75
+ </div>
76
+
77
+ {errorMessage ? (
78
+ <p className="text-sm text-destructive">{errorMessage}</p>
79
+ ) : null}
80
+
81
+ <button
82
+ className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground disabled:cursor-not-allowed disabled:opacity-60"
83
+ disabled={isSubmitting}
84
+ type="submit"
85
+ >
86
+ {isSubmitting ? "Signing in..." : "Sign in"}
87
+ </button>
88
+ </form>
89
+
90
+ <p className="text-sm text-muted-foreground">
91
+ No account yet?{" "}
92
+ <Link className="font-medium text-foreground" href="/sign-up">
93
+ Create one
94
+ </Link>
95
+ </p>
96
+ </div>
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,118 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useRouter } from "next/navigation";
5
+ import { type FormEvent, useState } from "react";
6
+ import authClient from "~/server/auth/auth-client";
7
+
8
+ export default function SignUpPage() {
9
+ const router = useRouter();
10
+ const [name, setName] = useState("");
11
+ const [email, setEmail] = useState("");
12
+ const [password, setPassword] = useState("");
13
+ const [errorMessage, setErrorMessage] = useState<string | null>(null);
14
+ const [isSubmitting, setIsSubmitting] = useState(false);
15
+
16
+ const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
17
+ event.preventDefault();
18
+ setIsSubmitting(true);
19
+ setErrorMessage(null);
20
+
21
+ const result = await authClient.signUp.email({
22
+ name,
23
+ email,
24
+ password,
25
+ callbackURL: "/",
26
+ });
27
+
28
+ if (result.error) {
29
+ setErrorMessage(result.error.message ?? "Unable to sign up.");
30
+ setIsSubmitting(false);
31
+ return;
32
+ }
33
+
34
+ router.push("/");
35
+ router.refresh();
36
+ };
37
+
38
+ return (
39
+ <div className="mx-auto flex min-h-screen w-full max-w-md items-center px-6 py-10">
40
+ <div className="w-full space-y-6 rounded-lg border border-border bg-card p-6">
41
+ <div className="space-y-1">
42
+ <h1 className="text-2xl font-semibold tracking-tight">
43
+ Create account
44
+ </h1>
45
+ <p className="text-sm text-muted-foreground">
46
+ Set up your account to get started.
47
+ </p>
48
+ </div>
49
+
50
+ <form className="space-y-4" onSubmit={handleSubmit}>
51
+ <div className="space-y-2">
52
+ <label className="text-sm font-medium" htmlFor="name">
53
+ Name
54
+ </label>
55
+ <input
56
+ autoComplete="name"
57
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
58
+ id="name"
59
+ onChange={(event) => setName(event.target.value)}
60
+ required
61
+ type="text"
62
+ value={name}
63
+ />
64
+ </div>
65
+
66
+ <div className="space-y-2">
67
+ <label className="text-sm font-medium" htmlFor="email">
68
+ Email
69
+ </label>
70
+ <input
71
+ autoComplete="email"
72
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
73
+ id="email"
74
+ onChange={(event) => setEmail(event.target.value)}
75
+ required
76
+ type="email"
77
+ value={email}
78
+ />
79
+ </div>
80
+
81
+ <div className="space-y-2">
82
+ <label className="text-sm font-medium" htmlFor="password">
83
+ Password
84
+ </label>
85
+ <input
86
+ autoComplete="new-password"
87
+ className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
88
+ id="password"
89
+ onChange={(event) => setPassword(event.target.value)}
90
+ required
91
+ type="password"
92
+ value={password}
93
+ />
94
+ </div>
95
+
96
+ {errorMessage ? (
97
+ <p className="text-sm text-destructive">{errorMessage}</p>
98
+ ) : null}
99
+
100
+ <button
101
+ className="w-full rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground disabled:cursor-not-allowed disabled:opacity-60"
102
+ disabled={isSubmitting}
103
+ type="submit"
104
+ >
105
+ {isSubmitting ? "Creating account..." : "Create account"}
106
+ </button>
107
+ </form>
108
+
109
+ <p className="text-sm text-muted-foreground">
110
+ Already have an account?{" "}
111
+ <Link className="font-medium text-foreground" href="/sign-in">
112
+ Sign in
113
+ </Link>
114
+ </p>
115
+ </div>
116
+ </div>
117
+ );
118
+ }
@@ -0,0 +1,11 @@
1
+ import { initAuth } from "~/server/auth/auth";
2
+
3
+ export async function POST(req: Request) {
4
+ const auth = await initAuth();
5
+ return auth.handler(req);
6
+ }
7
+
8
+ export async function GET(req: Request) {
9
+ const auth = await initAuth();
10
+ return auth.handler(req);
11
+ }
@@ -0,0 +1,74 @@
1
+ import type { NextRequest } from "next/server";
2
+ import { NextResponse } from "next/server";
3
+
4
+ export async function middleware(request: NextRequest) {
5
+ const { pathname } = request.nextUrl;
6
+
7
+ const protectedRoutes = ["/admin", "/dashboard"];
8
+ const authRoutes = ["/sign-in", "/sign-up", "/forgot-password"];
9
+
10
+ const isProtectedRoute = protectedRoutes.some((route) =>
11
+ pathname.startsWith(route),
12
+ );
13
+ const isAuthRoute = authRoutes.includes(pathname);
14
+
15
+ if (isProtectedRoute || isAuthRoute) {
16
+ try {
17
+ const sessionResponse = await fetch(
18
+ new URL("/api/auth/get-session", request.url),
19
+ {
20
+ method: "GET",
21
+ headers: {
22
+ cookie: request.headers.get("cookie") || "",
23
+ },
24
+ },
25
+ );
26
+
27
+ const isAuthenticated = sessionResponse.ok;
28
+ let sessionData: { session?: { userId?: string } } | null = null;
29
+
30
+ if (isAuthenticated) {
31
+ try {
32
+ sessionData = await sessionResponse.json();
33
+ if (!sessionData?.session?.userId) {
34
+ sessionData = null;
35
+ }
36
+ } catch {
37
+ sessionData = null;
38
+ }
39
+ }
40
+
41
+ if (isProtectedRoute && !sessionData) {
42
+ const redirectUrl = request.nextUrl.clone();
43
+ redirectUrl.pathname = "/sign-in";
44
+ return NextResponse.redirect(redirectUrl);
45
+ }
46
+
47
+ if (isAuthRoute && sessionData) {
48
+ const redirectUrl = request.nextUrl.clone();
49
+ redirectUrl.pathname = "/";
50
+ return NextResponse.redirect(redirectUrl);
51
+ }
52
+ } catch (error) {
53
+ console.error("Middleware error:", error);
54
+
55
+ if (isProtectedRoute) {
56
+ const redirectUrl = request.nextUrl.clone();
57
+ redirectUrl.pathname = "/sign-in";
58
+ return NextResponse.redirect(redirectUrl);
59
+ }
60
+ }
61
+ }
62
+
63
+ return NextResponse.next();
64
+ }
65
+
66
+ export const config = {
67
+ matcher: [
68
+ "/admin/:path*",
69
+ "/dashboard/:path*",
70
+ "/sign-in",
71
+ "/sign-up",
72
+ "/forgot-password",
73
+ ],
74
+ };
@@ -0,0 +1,8 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import { cloudflareClient } from "better-auth-cloudflare/client";
3
+
4
+ const client = createAuthClient({
5
+ plugins: [cloudflareClient()],
6
+ });
7
+
8
+ export default client;
@@ -0,0 +1,77 @@
1
+ import type { KVNamespace } from "@cloudflare/workers-types";
2
+ import { getCloudflareContext } from "@opennextjs/cloudflare";
3
+ import { betterAuth } from "better-auth";
4
+ import { drizzleAdapter } from "better-auth/adapters/drizzle";
5
+ import { openAPI } from "better-auth/plugins";
6
+ import { withCloudflare } from "better-auth-cloudflare";
7
+ import { getDB } from "~/server/db";
8
+
9
+ async function authBuilder() {
10
+ const dbInstance = await getDB();
11
+ return betterAuth(
12
+ withCloudflare(
13
+ {
14
+ autoDetectIpAddress: true,
15
+ geolocationTracking: true,
16
+ cf: getCloudflareContext().cf,
17
+ d1: {
18
+ db: dbInstance as ReturnType<typeof drizzleAdapter> extends infer T ? T extends { __brand: infer B } ? never : any : any,
19
+ options: {
20
+ usePlural: true,
21
+ },
22
+ },
23
+ kv: process.env.KV as unknown as KVNamespace<string>,
24
+ },
25
+ {
26
+ emailAndPassword: {
27
+ enabled: true,
28
+ },
29
+ session: {
30
+ cookieCache: {
31
+ enabled: true,
32
+ maxAge: 5 * 60,
33
+ },
34
+ },
35
+ rateLimit: {
36
+ enabled: true,
37
+ window: 60,
38
+ max: 100,
39
+ },
40
+ plugins: [openAPI()],
41
+ },
42
+ ),
43
+ );
44
+ }
45
+
46
+ let authInstance: Awaited<ReturnType<typeof authBuilder>> | null = null;
47
+
48
+ export async function initAuth() {
49
+ if (!authInstance) {
50
+ authInstance = await authBuilder();
51
+ }
52
+ return authInstance;
53
+ }
54
+
55
+ // Schema generation config (for better-auth CLI)
56
+ export const auth = betterAuth({
57
+ ...withCloudflare(
58
+ {
59
+ autoDetectIpAddress: true,
60
+ geolocationTracking: true,
61
+ cf: {},
62
+ },
63
+ {
64
+ session: {
65
+ cookieCache: {
66
+ enabled: true,
67
+ maxAge: 5 * 60,
68
+ },
69
+ },
70
+ plugins: [openAPI()],
71
+ },
72
+ ),
73
+ database: drizzleAdapter(process.env.DATABASE as any, {
74
+ provider: "sqlite",
75
+ usePlural: true,
76
+ }),
77
+ });
@@ -0,0 +1,115 @@
1
+ import { relations, sql } from "drizzle-orm";
2
+ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
3
+
4
+ export const users = sqliteTable("users", {
5
+ id: text("id").primaryKey(),
6
+ name: text("name").notNull(),
7
+ email: text("email").notNull().unique(),
8
+ emailVerified: integer("email_verified", { mode: "boolean" })
9
+ .default(false)
10
+ .notNull(),
11
+ image: text("image"),
12
+ createdAt: integer("created_at", { mode: "timestamp_ms" })
13
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
14
+ .notNull(),
15
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" })
16
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
17
+ .$onUpdate(() => new Date())
18
+ .notNull(),
19
+ });
20
+
21
+ export const sessions = sqliteTable(
22
+ "sessions",
23
+ {
24
+ id: text("id").primaryKey(),
25
+ expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
26
+ token: text("token").notNull().unique(),
27
+ createdAt: integer("created_at", { mode: "timestamp_ms" })
28
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
29
+ .notNull(),
30
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" })
31
+ .$onUpdate(() => new Date())
32
+ .notNull(),
33
+ ipAddress: text("ip_address"),
34
+ userAgent: text("user_agent"),
35
+ userId: text("user_id")
36
+ .notNull()
37
+ .references(() => users.id, { onDelete: "cascade" }),
38
+ timezone: text("timezone"),
39
+ city: text("city"),
40
+ country: text("country"),
41
+ region: text("region"),
42
+ regionCode: text("region_code"),
43
+ colo: text("colo"),
44
+ latitude: text("latitude"),
45
+ longitude: text("longitude"),
46
+ },
47
+ (table) => [index("sessions_userId_idx").on(table.userId)],
48
+ );
49
+
50
+ export const accounts = sqliteTable(
51
+ "accounts",
52
+ {
53
+ id: text("id").primaryKey(),
54
+ accountId: text("account_id").notNull(),
55
+ providerId: text("provider_id").notNull(),
56
+ userId: text("user_id")
57
+ .notNull()
58
+ .references(() => users.id, { onDelete: "cascade" }),
59
+ accessToken: text("access_token"),
60
+ refreshToken: text("refresh_token"),
61
+ idToken: text("id_token"),
62
+ accessTokenExpiresAt: integer("access_token_expires_at", {
63
+ mode: "timestamp_ms",
64
+ }),
65
+ refreshTokenExpiresAt: integer("refresh_token_expires_at", {
66
+ mode: "timestamp_ms",
67
+ }),
68
+ scope: text("scope"),
69
+ password: text("password"),
70
+ createdAt: integer("created_at", { mode: "timestamp_ms" })
71
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
72
+ .notNull(),
73
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" })
74
+ .$onUpdate(() => new Date())
75
+ .notNull(),
76
+ },
77
+ (table) => [index("accounts_userId_idx").on(table.userId)],
78
+ );
79
+
80
+ export const verifications = sqliteTable(
81
+ "verifications",
82
+ {
83
+ id: text("id").primaryKey(),
84
+ identifier: text("identifier").notNull(),
85
+ value: text("value").notNull(),
86
+ expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(),
87
+ createdAt: integer("created_at", { mode: "timestamp_ms" })
88
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
89
+ .notNull(),
90
+ updatedAt: integer("updated_at", { mode: "timestamp_ms" })
91
+ .default(sql`(cast(unixepoch('subsecond') * 1000 as integer))`)
92
+ .$onUpdate(() => new Date())
93
+ .notNull(),
94
+ },
95
+ (table) => [index("verifications_identifier_idx").on(table.identifier)],
96
+ );
97
+
98
+ export const usersRelations = relations(users, ({ many }) => ({
99
+ sessions: many(sessions),
100
+ accounts: many(accounts),
101
+ }));
102
+
103
+ export const sessionsRelations = relations(sessions, ({ one }) => ({
104
+ users: one(users, {
105
+ fields: [sessions.userId],
106
+ references: [users.id],
107
+ }),
108
+ }));
109
+
110
+ export const accountsRelations = relations(accounts, ({ one }) => ({
111
+ users: one(users, {
112
+ fields: [accounts.userId],
113
+ references: [users.id],
114
+ }),
115
+ }));