stackkit 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 (121) hide show
  1. package/README.md +41 -0
  2. package/bin/stackkit.js +4 -0
  3. package/dist/cli/add.d.ts +8 -0
  4. package/dist/cli/add.js +313 -0
  5. package/dist/cli/create.d.ts +22 -0
  6. package/dist/cli/create.js +336 -0
  7. package/dist/cli/doctor.d.ts +7 -0
  8. package/dist/cli/doctor.js +569 -0
  9. package/dist/cli/list.d.ts +6 -0
  10. package/dist/cli/list.js +123 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +91 -0
  13. package/dist/lib/conversion/js-conversion.d.ts +1 -0
  14. package/dist/lib/conversion/js-conversion.js +244 -0
  15. package/dist/lib/database/database-config.d.ts +6 -0
  16. package/dist/lib/database/database-config.js +9 -0
  17. package/dist/lib/discovery/module-discovery.d.ts +62 -0
  18. package/dist/lib/discovery/module-discovery.js +188 -0
  19. package/dist/lib/env/env-editor.d.ts +9 -0
  20. package/dist/lib/env/env-editor.js +116 -0
  21. package/dist/lib/framework/framework-utils.d.ts +22 -0
  22. package/dist/lib/framework/framework-utils.js +74 -0
  23. package/dist/lib/fs/files.d.ts +14 -0
  24. package/dist/lib/fs/files.js +101 -0
  25. package/dist/lib/generation/code-generator.d.ts +83 -0
  26. package/dist/lib/generation/code-generator.js +681 -0
  27. package/dist/lib/git-utils.d.ts +1 -0
  28. package/dist/lib/git-utils.js +9 -0
  29. package/dist/lib/pm/package-manager.d.ts +5 -0
  30. package/dist/lib/pm/package-manager.js +69 -0
  31. package/dist/lib/project/detect.d.ts +4 -0
  32. package/dist/lib/project/detect.js +121 -0
  33. package/dist/lib/ui/logger.d.ts +16 -0
  34. package/dist/lib/ui/logger.js +59 -0
  35. package/dist/types/index.d.ts +92 -0
  36. package/dist/types/index.js +2 -0
  37. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
  38. package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
  39. package/modules/auth/authjs/files/lib/auth.ts +36 -0
  40. package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
  41. package/modules/auth/authjs/module.json +22 -0
  42. package/modules/auth/better-auth/files/api/auth/[...all]/route.ts +4 -0
  43. package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
  44. package/modules/auth/better-auth/files/lib/auth.ts +83 -0
  45. package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
  46. package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
  47. package/modules/auth/better-auth/files/prisma/schema.prisma +63 -0
  48. package/modules/auth/better-auth/generator.json +78 -0
  49. package/modules/auth/better-auth/module.json +37 -0
  50. package/modules/database/mongoose/files/lib/db.ts +63 -0
  51. package/modules/database/mongoose/files/models/User.ts +34 -0
  52. package/modules/database/mongoose/generator.json +24 -0
  53. package/modules/database/mongoose/module.json +15 -0
  54. package/modules/database/prisma/files/lib/prisma.ts +45 -0
  55. package/modules/database/prisma/files/prisma/schema.prisma +8 -0
  56. package/modules/database/prisma/files/prisma.config.ts +12 -0
  57. package/modules/database/prisma/generator.json +43 -0
  58. package/modules/database/prisma/module.json +17 -0
  59. package/package.json +83 -0
  60. package/templates/express/.env.example +2 -0
  61. package/templates/express/eslint.config.cjs +42 -0
  62. package/templates/express/package.json +33 -0
  63. package/templates/express/src/app.ts +51 -0
  64. package/templates/express/src/config/env.ts +12 -0
  65. package/templates/express/src/features/health/health.controller.ts +18 -0
  66. package/templates/express/src/features/health/health.route.ts +9 -0
  67. package/templates/express/src/features/health/health.service.ts +6 -0
  68. package/templates/express/src/middlewares/error.middleware.ts +18 -0
  69. package/templates/express/src/server.ts +8 -0
  70. package/templates/express/template.json +27 -0
  71. package/templates/express/tsconfig.json +30 -0
  72. package/templates/nextjs/README.md +52 -0
  73. package/templates/nextjs/app/favicon.ico +0 -0
  74. package/templates/nextjs/app/globals.css +26 -0
  75. package/templates/nextjs/app/layout.tsx +30 -0
  76. package/templates/nextjs/app/page.tsx +57 -0
  77. package/templates/nextjs/eslint.config.mjs +18 -0
  78. package/templates/nextjs/lib/env.ts +8 -0
  79. package/templates/nextjs/next.config.ts +7 -0
  80. package/templates/nextjs/package.json +27 -0
  81. package/templates/nextjs/postcss.config.mjs +7 -0
  82. package/templates/nextjs/public/file.svg +1 -0
  83. package/templates/nextjs/public/globe.svg +1 -0
  84. package/templates/nextjs/public/next.svg +1 -0
  85. package/templates/nextjs/public/vercel.svg +1 -0
  86. package/templates/nextjs/public/window.svg +1 -0
  87. package/templates/nextjs/template.json +34 -0
  88. package/templates/nextjs/tsconfig.json +34 -0
  89. package/templates/react/.env.example +1 -0
  90. package/templates/react/.prettierignore +4 -0
  91. package/templates/react/.prettierrc +9 -0
  92. package/templates/react/README.md +56 -0
  93. package/templates/react/eslint.config.js +23 -0
  94. package/templates/react/index.html +14 -0
  95. package/templates/react/package.json +44 -0
  96. package/templates/react/public/vite.svg +1 -0
  97. package/templates/react/src/api/client.ts +47 -0
  98. package/templates/react/src/assets/react.svg +1 -0
  99. package/templates/react/src/components/ErrorBoundary.tsx +51 -0
  100. package/templates/react/src/components/Layout.tsx +13 -0
  101. package/templates/react/src/components/Loading.tsx +8 -0
  102. package/templates/react/src/components/SEO.tsx +49 -0
  103. package/templates/react/src/config/constants.ts +5 -0
  104. package/templates/react/src/hooks/index.ts +64 -0
  105. package/templates/react/src/index.css +1 -0
  106. package/templates/react/src/lib/queryClient.ts +12 -0
  107. package/templates/react/src/main.tsx +22 -0
  108. package/templates/react/src/pages/About.tsx +78 -0
  109. package/templates/react/src/pages/Home.tsx +49 -0
  110. package/templates/react/src/pages/NotFound.tsx +24 -0
  111. package/templates/react/src/router.tsx +21 -0
  112. package/templates/react/src/types/api.d.ts +20 -0
  113. package/templates/react/src/utils/helpers.ts +51 -0
  114. package/templates/react/src/utils/storage.ts +35 -0
  115. package/templates/react/src/vite-env.d.ts +11 -0
  116. package/templates/react/template.json +38 -0
  117. package/templates/react/tsconfig.app.json +28 -0
  118. package/templates/react/tsconfig.json +4 -0
  119. package/templates/react/tsconfig.node.json +26 -0
  120. package/templates/react/vite.config.ts +7 -0
  121. package/templates/react-vite/README.md +56 -0
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initGit = initGit;
4
+ const child_process_1 = require("child_process");
5
+ async function initGit(cwd) {
6
+ (0, child_process_1.execSync)("git init", { cwd, stdio: "pipe" });
7
+ (0, child_process_1.execSync)("git add .", { cwd, stdio: "pipe" });
8
+ (0, child_process_1.execSync)('git commit -m "Initial commit"', { cwd, stdio: "pipe" });
9
+ }
@@ -0,0 +1,5 @@
1
+ export type PackageManager = "npm" | "yarn" | "pnpm" | "bun";
2
+ export declare function detectPackageManager(cwd: string): Promise<PackageManager>;
3
+ export declare function installDependencies(cwd: string, pm: PackageManager): Promise<void>;
4
+ export declare function addDependencies(cwd: string, pm: PackageManager, packages: string[], dev?: boolean): Promise<void>;
5
+ export declare function initGit(cwd: string): Promise<void>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectPackageManager = detectPackageManager;
7
+ exports.installDependencies = installDependencies;
8
+ exports.addDependencies = addDependencies;
9
+ exports.initGit = initGit;
10
+ const detect_package_manager_1 = require("detect-package-manager");
11
+ const execa_1 = __importDefault(require("execa"));
12
+ const logger_1 = require("../ui/logger");
13
+ async function detectPackageManager(cwd) {
14
+ try {
15
+ const pm = await (0, detect_package_manager_1.detect)({ cwd });
16
+ return pm;
17
+ }
18
+ catch {
19
+ return "npm";
20
+ }
21
+ }
22
+ async function installDependencies(cwd, pm) {
23
+ const args = [];
24
+ if (pm === "npm") {
25
+ args.push("install");
26
+ }
27
+ else if (pm === "yarn") {
28
+ args.push("install");
29
+ }
30
+ else if (pm === "pnpm") {
31
+ args.push("install");
32
+ }
33
+ else if (pm === "bun") {
34
+ args.push("install");
35
+ }
36
+ await (0, execa_1.default)(pm, args, { cwd, stdio: "pipe" });
37
+ }
38
+ async function addDependencies(cwd, pm, packages, dev = false) {
39
+ if (packages.length === 0)
40
+ return;
41
+ const spinner = logger_1.logger.startSpinner(`Adding ${dev ? "dev " : ""}dependencies: ${packages.join(", ")}...`);
42
+ try {
43
+ const args = [];
44
+ if (pm === "npm") {
45
+ args.push("install", dev ? "--save-dev" : "--save", ...packages);
46
+ }
47
+ else if (pm === "yarn") {
48
+ args.push("add", dev ? "--dev" : "", ...packages);
49
+ }
50
+ else if (pm === "pnpm") {
51
+ args.push("add", dev ? "-D" : "", ...packages);
52
+ }
53
+ else if (pm === "bun") {
54
+ // bun uses `bun add` and `-d` for dev dependencies
55
+ args.push("add", ...(dev ? ["-d"] : []), ...packages);
56
+ }
57
+ await (0, execa_1.default)(pm, args.filter(Boolean), { cwd, stdio: "pipe" });
58
+ spinner.succeed(`Dependencies added successfully`);
59
+ }
60
+ catch (error) {
61
+ spinner.fail(`Failed to add dependencies`);
62
+ throw new Error(`Failed to add dependencies: ${error instanceof Error ? error.message : String(error)}`);
63
+ }
64
+ }
65
+ async function initGit(cwd) {
66
+ await (0, execa_1.default)("git", ["init"], { cwd });
67
+ await (0, execa_1.default)("git", ["add", "."], { cwd });
68
+ await (0, execa_1.default)("git", ["commit", "-m", "Initial commit from StackKit"], { cwd });
69
+ }
@@ -0,0 +1,4 @@
1
+ import { ProjectInfo } from "../../types";
2
+ export declare function detectProjectInfo(targetDir: string): Promise<ProjectInfo>;
3
+ export declare function getRouterBasePath(projectInfo: ProjectInfo): string;
4
+ export declare function getLibPath(projectInfo: ProjectInfo): string;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.detectProjectInfo = detectProjectInfo;
7
+ exports.getRouterBasePath = getRouterBasePath;
8
+ exports.getLibPath = getLibPath;
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const path_1 = __importDefault(require("path"));
11
+ async function detectProjectInfo(targetDir) {
12
+ const packageJsonPath = path_1.default.join(targetDir, "package.json");
13
+ if (!(await fs_extra_1.default.pathExists(packageJsonPath))) {
14
+ throw new Error("No package.json found. This does not appear to be a Node.js project.");
15
+ }
16
+ const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
17
+ // Detect framework
18
+ const isNextJs = packageJson.dependencies?.next || packageJson.devDependencies?.next;
19
+ const isExpress = packageJson.dependencies?.express || packageJson.devDependencies?.express;
20
+ const isReact = packageJson.dependencies?.react || packageJson.devDependencies?.react;
21
+ const isVite = packageJson.dependencies?.vite || packageJson.devDependencies?.vite;
22
+ let framework;
23
+ if (isNextJs) {
24
+ framework = "nextjs";
25
+ }
26
+ else if (isExpress) {
27
+ framework = "express";
28
+ }
29
+ else if (isReact && isVite) {
30
+ framework = "react";
31
+ }
32
+ else if (isReact) {
33
+ framework = "react";
34
+ }
35
+ else {
36
+ framework = "unknown";
37
+ }
38
+ if (framework === "unknown") {
39
+ throw new Error("Only Next.js, Express, and React projects are currently supported.");
40
+ }
41
+ // Detect router type (only for Next.js)
42
+ let router = "unknown";
43
+ if (framework === "nextjs") {
44
+ const appDirExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "app"));
45
+ const pagesDirExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "pages"));
46
+ const srcAppDirExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "src", "app"));
47
+ const srcPagesDirExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "src", "pages"));
48
+ if (appDirExists || srcAppDirExists) {
49
+ router = "app";
50
+ }
51
+ else if (pagesDirExists || srcPagesDirExists) {
52
+ router = "pages";
53
+ }
54
+ }
55
+ // Detect TypeScript vs JavaScript
56
+ const tsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "tsconfig.json"));
57
+ const jsconfigExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "jsconfig.json"));
58
+ let language;
59
+ if (tsconfigExists) {
60
+ language = "ts";
61
+ }
62
+ else if (jsconfigExists) {
63
+ language = "js";
64
+ }
65
+ else {
66
+ // Default to TypeScript if neither exists
67
+ language = "ts";
68
+ }
69
+ // Detect package manager
70
+ const yarnLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "yarn.lock"));
71
+ const pnpmLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "pnpm-lock.yaml"));
72
+ const bunLockExists = await fs_extra_1.default.pathExists(path_1.default.join(targetDir, "bun.lockb"));
73
+ let packageManager = "npm";
74
+ if (pnpmLockExists) {
75
+ packageManager = "pnpm";
76
+ }
77
+ else if (yarnLockExists) {
78
+ packageManager = "yarn";
79
+ }
80
+ else if (bunLockExists) {
81
+ packageManager = "bun";
82
+ }
83
+ // Check for existing integrations
84
+ const hasAuth = !!(packageJson.dependencies?.["next-auth"] ||
85
+ packageJson.dependencies?.["better-auth"] ||
86
+ packageJson.dependencies?.["@auth/core"] ||
87
+ packageJson.dependencies?.["@kinde-oss/kinde-auth-nextjs"] ||
88
+ packageJson.dependencies?.["passport"]);
89
+ const hasPrisma = !!(packageJson.dependencies?.["@prisma/client"] || packageJson.devDependencies?.["prisma"]);
90
+ const hasDatabase = hasPrisma ||
91
+ !!(packageJson.dependencies?.["mongoose"] ||
92
+ packageJson.dependencies?.["typeorm"] ||
93
+ packageJson.dependencies?.["drizzle-orm"]);
94
+ return {
95
+ framework,
96
+ router,
97
+ language,
98
+ packageManager,
99
+ hasAuth,
100
+ hasPrisma,
101
+ hasDatabase,
102
+ rootDir: targetDir,
103
+ };
104
+ }
105
+ function getRouterBasePath(projectInfo) {
106
+ const srcExists = fs_extra_1.default.existsSync(path_1.default.join(projectInfo.rootDir, "src"));
107
+ if (projectInfo.router === "app") {
108
+ return srcExists ? "src/app" : "app";
109
+ }
110
+ else if (projectInfo.router === "pages") {
111
+ return srcExists ? "src/pages" : "pages";
112
+ }
113
+ throw new Error("Unknown router type");
114
+ }
115
+ function getLibPath(projectInfo) {
116
+ if (projectInfo.framework === "express") {
117
+ return "src/lib";
118
+ }
119
+ const srcExists = fs_extra_1.default.existsSync(path_1.default.join(projectInfo.rootDir, "src"));
120
+ return srcExists ? "src/lib" : "lib";
121
+ }
@@ -0,0 +1,16 @@
1
+ import { Ora } from "ora";
2
+ export declare class Logger {
3
+ private spinner;
4
+ info(message: string): void;
5
+ success(message: string): void;
6
+ error(message: string): void;
7
+ warn(message: string): void;
8
+ log(message: string): void;
9
+ newLine(): void;
10
+ startSpinner(text: string): Ora;
11
+ stopSpinner(success?: boolean, text?: string): void;
12
+ updateSpinner(text: string): void;
13
+ header(text: string): void;
14
+ footer(): void;
15
+ }
16
+ export declare const logger: Logger;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.logger = exports.Logger = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ class Logger {
10
+ constructor() {
11
+ this.spinner = null;
12
+ }
13
+ info(message) {
14
+ process.stdout.write(chalk_1.default.blue("ℹ") + " " + message + "\n");
15
+ }
16
+ success(message) {
17
+ process.stdout.write(chalk_1.default.green("✔") + " " + message + "\n");
18
+ }
19
+ error(message) {
20
+ process.stderr.write(chalk_1.default.red("✖") + " " + message + "\n");
21
+ }
22
+ warn(message) {
23
+ process.stdout.write(chalk_1.default.yellow("⚠") + " " + message + "\n");
24
+ }
25
+ log(message) {
26
+ process.stdout.write(message + "\n");
27
+ }
28
+ newLine() {
29
+ process.stdout.write("\n");
30
+ }
31
+ startSpinner(text) {
32
+ this.spinner = (0, ora_1.default)(text).start();
33
+ return this.spinner;
34
+ }
35
+ stopSpinner(success = true, text) {
36
+ if (this.spinner) {
37
+ if (success) {
38
+ this.spinner.succeed(text);
39
+ }
40
+ else {
41
+ this.spinner.fail(text);
42
+ }
43
+ this.spinner = null;
44
+ }
45
+ }
46
+ updateSpinner(text) {
47
+ if (this.spinner) {
48
+ this.spinner.text = text;
49
+ }
50
+ }
51
+ header(text) {
52
+ process.stdout.write(chalk_1.default.bold.cyan(text) + "\n");
53
+ }
54
+ footer() {
55
+ process.stdout.write("\n");
56
+ }
57
+ }
58
+ exports.Logger = Logger;
59
+ exports.logger = new Logger();
@@ -0,0 +1,92 @@
1
+ export interface TemplateMetadata {
2
+ name: string;
3
+ displayName: string;
4
+ description: string;
5
+ tags: string[];
6
+ defaultPackageManager: "pnpm" | "npm" | "yarn" | "bun";
7
+ features: string[];
8
+ }
9
+ export interface ModuleMetadata {
10
+ name: string;
11
+ displayName: string;
12
+ description: string;
13
+ category: "auth" | "database" | "ui" | "other";
14
+ supportedFrameworks: string[];
15
+ dependencies: {
16
+ common?: Record<string, string>;
17
+ providers?: Record<string, Record<string, string>>;
18
+ } | Record<string, string>;
19
+ devDependencies?: {
20
+ common?: Record<string, string>;
21
+ providers?: Record<string, Record<string, string>>;
22
+ } | Record<string, string>;
23
+ envVars: EnvVar[];
24
+ patches: ModulePatch[];
25
+ frameworkPatches?: Record<string, {
26
+ [file: string]: {
27
+ merge?: Record<string, unknown>;
28
+ };
29
+ }>;
30
+ postInstall?: string[];
31
+ databaseAdapters?: {
32
+ common?: {
33
+ dependencies?: Record<string, string>;
34
+ devDependencies?: Record<string, string>;
35
+ };
36
+ providers?: Record<string, Record<string, {
37
+ dependencies?: Record<string, string>;
38
+ devDependencies?: Record<string, string>;
39
+ }>>;
40
+ };
41
+ frameworkConfigs?: Record<string, {
42
+ dependencies?: Record<string, string>;
43
+ devDependencies?: Record<string, string>;
44
+ patches?: ModulePatch[];
45
+ }>;
46
+ }
47
+ export interface EnvVar {
48
+ key: string;
49
+ value?: string;
50
+ description: string;
51
+ required: boolean;
52
+ }
53
+ export interface ModulePatch {
54
+ type: "create-file" | "modify-json" | "append-env" | "inject-code";
55
+ description: string;
56
+ [key: string]: unknown;
57
+ }
58
+ export interface CreateFilePatch extends ModulePatch {
59
+ type: "create-file";
60
+ source: string;
61
+ destination: string;
62
+ condition?: {
63
+ router?: "app" | "pages";
64
+ language?: "ts" | "js";
65
+ };
66
+ }
67
+ export interface ModifyJsonPatch extends ModulePatch {
68
+ type: "modify-json";
69
+ file: string;
70
+ operations: {
71
+ path: string;
72
+ value: unknown;
73
+ merge?: boolean;
74
+ }[];
75
+ }
76
+ export interface ProjectInfo {
77
+ framework: "nextjs" | "express" | "react" | "unknown";
78
+ router: "app" | "pages" | "unknown";
79
+ language: "ts" | "js";
80
+ packageManager: "npm" | "yarn" | "pnpm" | "bun";
81
+ hasAuth: boolean;
82
+ hasPrisma: boolean;
83
+ hasDatabase: boolean;
84
+ rootDir: string;
85
+ }
86
+ export interface CLIOptions {
87
+ force?: boolean;
88
+ dryRun?: boolean;
89
+ yes?: boolean;
90
+ noInstall?: boolean;
91
+ pm?: "npm" | "yarn" | "pnpm" | "bun";
92
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
1
+ import NextAuth from "next-auth";
2
+ import { authOptions } from "@/lib/auth";
3
+
4
+ const handler = NextAuth(authOptions);
5
+
6
+ export { handler as GET, handler as POST };
@@ -0,0 +1,11 @@
1
+ import { getServerSession } from "next-auth/next";
2
+ import { authOptions } from "@/lib/auth";
3
+
4
+ export async function getSession() {
5
+ return await getServerSession(authOptions);
6
+ }
7
+
8
+ export async function getCurrentUser() {
9
+ const session = await getSession();
10
+ return session?.user;
11
+ }
@@ -0,0 +1,36 @@
1
+ import { NextAuthOptions } from "next-auth";
2
+ import { PrismaAdapter } from "@auth/prisma-adapter";
3
+ import { prisma } from "@/lib/prisma";
4
+ import GoogleProvider from "next-auth/providers/google";
5
+
6
+ export const authOptions: NextAuthOptions = {
7
+ adapter: PrismaAdapter(prisma),
8
+ providers: [
9
+ GoogleProvider({
10
+ clientId: process.env.GOOGLE_CLIENT_ID!,
11
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
12
+ }),
13
+ ],
14
+ session: {
15
+ strategy: "jwt",
16
+ },
17
+ callbacks: {
18
+ async jwt({ token, user }) {
19
+ if (user) {
20
+ token.id = user.id;
21
+ }
22
+ return token;
23
+ },
24
+ async session({ session, token }) {
25
+ if (token) {
26
+ session.user.id = token.id as string;
27
+ }
28
+ return session;
29
+ },
30
+ },
31
+ pages: {
32
+ signIn: "/auth/signin",
33
+ signOut: "/auth/signout",
34
+ error: "/auth/error",
35
+ },
36
+ };
@@ -0,0 +1,45 @@
1
+
2
+ model Account {
3
+ id String @id {{idDefault}}
4
+ userId String {{userIdType}}
5
+ type String
6
+ provider String
7
+ providerAccountId String
8
+ refresh_token String? @db.Text
9
+ access_token String? @db.Text
10
+ expires_at Int?
11
+ token_type String?
12
+ scope String?
13
+ id_token String? @db.Text
14
+ session_state String?
15
+
16
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
17
+
18
+ @@unique([provider, providerAccountId])
19
+ }
20
+
21
+ model Session {
22
+ id String @id {{idDefault}}
23
+ sessionToken String @unique
24
+ userId String {{userIdType}}
25
+ expires DateTime
26
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
27
+ }
28
+
29
+ model User {
30
+ id String @id {{idDefault}}
31
+ name String?
32
+ email String @unique
33
+ emailVerified DateTime?
34
+ image String?
35
+ accounts Account[]
36
+ sessions Session[]
37
+ }
38
+
39
+ model VerificationToken {
40
+ identifier String
41
+ token String @unique
42
+ expires DateTime
43
+
44
+ @@unique([identifier, token])
45
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "authjs",
3
+ "displayName": "Auth.js",
4
+ "description": "Authentication with Auth.js (NextAuth.js v5)",
5
+ "category": "auth",
6
+ "provider": "authjs",
7
+ "supportedFrameworks": ["nextjs"],
8
+ "compatibility": {
9
+ "frameworks": ["nextjs"],
10
+ "databases": ["prisma"],
11
+ "languages": ["typescript", "javascript"],
12
+ "packageManagers": ["npm", "yarn", "pnpm", "bun"]
13
+ },
14
+ "databaseAdapters": {
15
+ "common": {
16
+ "dependencies": {
17
+ "@auth/prisma-adapter": "^2.7.1"
18
+ },
19
+ "devDependencies": {}
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,4 @@
1
+ import { auth } from "@/lib/auth";
2
+ import { toNextJsHandler } from "better-auth/next-js";
3
+
4
+ export const { GET, POST } = toNextJsHandler(auth);
@@ -0,0 +1,7 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+
3
+ export const authClient = createAuthClient({
4
+ baseURL: "http://localhost:3000",
5
+ });
6
+
7
+ export const { signIn, signUp, signOut, useSession } = authClient;
@@ -0,0 +1,83 @@
1
+ import { betterAuth } from "better-auth";
2
+ import { sendEmail } from "./email/email-service";
3
+ import { getVerificationEmailTemplate, getPasswordResetEmailTemplate } from "./email/email-templates";
4
+ {{#if database=='prisma'}}
5
+ import { prisma } from "{{framework=='nextjs' ? '@/lib' : '.'}}/prisma";
6
+ import { prismaAdapter } from "better-auth/adapters/prisma";
7
+ {{/if}}
8
+ {{#if database=='mongoose'}}
9
+ import { mongoClient, db } from "{{framework=='nextjs' ? '@/lib' : '.'}}/db";
10
+ import { mongodbAdapter } from "better-auth/adapters/mongodb";
11
+ {{/if}}
12
+
13
+ export const auth = betterAuth({
14
+ {{#if database=='prisma'}}
15
+ database: prismaAdapter(prisma, {
16
+ provider: {{prismaProvider}},
17
+ }),
18
+ {{/if}}
19
+ {{#if database=='mongoose'}}
20
+ database: mongodbAdapter(db),
21
+ {{/if}}
22
+ user: {
23
+ additionalFields: {
24
+ role: {
25
+ type: "string",
26
+ defaultValue: "USER",
27
+ required: true,
28
+ },
29
+ },
30
+ },
31
+ emailAndPassword: {
32
+ enabled: true,
33
+ requireEmailVerification: true,
34
+ },
35
+ socialProviders: {
36
+ google: {
37
+ clientId: process.env.GOOGLE_CLIENT_ID!,
38
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
39
+ },
40
+ },
41
+ emailVerification: {
42
+ sendVerificationEmail: async ({ user, url }) => {
43
+ const { html, text } = getVerificationEmailTemplate(user, url);
44
+ await sendEmail({
45
+ to: user.email,
46
+ subject: "Verify Your Email Address",
47
+ text,
48
+ html,
49
+ });
50
+ },
51
+ sendOnSignIn: true,
52
+ },
53
+ password: {
54
+ reset: {
55
+ sendResetEmail: async ({ user, url }) => {
56
+ const { html, text } = getPasswordResetEmailTemplate(user, url);
57
+ await sendEmail({
58
+ to: user.email,
59
+ subject: "Reset Your Password",
60
+ text,
61
+ html,
62
+ });
63
+ },
64
+ },
65
+ },
66
+ rateLimit: {
67
+ window: 10, // 10 seconds
68
+ max: 100, // max requests per window
69
+ },
70
+ account: {
71
+ accountLinking: {
72
+ enabled: true,
73
+ trustedProviders: ["google"],
74
+ },
75
+ },
76
+ session: {
77
+ cookieCache: {
78
+ enabled: true,
79
+ },
80
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
81
+ updateAge: 60 * 60 * 24, // 1 day
82
+ }
83
+ });
@@ -0,0 +1,34 @@
1
+ import nodemailer from "nodemailer";
2
+
3
+ // Create email transporter
4
+ const transporter = nodemailer.createTransporter({
5
+ host: process.env.EMAIL_HOST,
6
+ port: parseInt(process.env.EMAIL_PORT || "587"),
7
+ secure: process.env.EMAIL_PORT === "465",
8
+ auth: {
9
+ user: process.env.EMAIL_USER,
10
+ pass: process.env.EMAIL_PASS,
11
+ },
12
+ });
13
+
14
+ // Send email function
15
+ export const sendEmail = async ({ to, subject, text, html }: {
16
+ to: string;
17
+ subject: string;
18
+ text?: string;
19
+ html?: string;
20
+ }) => {
21
+ try {
22
+ await transporter.sendMail({
23
+ from: process.env.EMAIL_FROM,
24
+ to,
25
+ subject,
26
+ text,
27
+ html,
28
+ });
29
+ } catch (error) {
30
+ // eslint-disable-next-line no-console
31
+ console.error("Email sending failed:", error);
32
+ throw error;
33
+ }
34
+ };