create-stackkit-app 0.4.4 → 0.4.6

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 (72) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +61 -4
  3. package/dist/lib/code-generator.d.ts +80 -0
  4. package/dist/lib/code-generator.js +541 -0
  5. package/dist/lib/create-project.d.ts +20 -1
  6. package/dist/lib/create-project.js +154 -96
  7. package/dist/lib/framework-utils.d.ts +22 -0
  8. package/dist/lib/framework-utils.js +74 -0
  9. package/dist/lib/utils/logger.d.ts +16 -0
  10. package/dist/lib/utils/logger.js +59 -0
  11. package/dist/lib/utils/module-discovery.d.ts +62 -0
  12. package/dist/lib/utils/module-discovery.js +180 -0
  13. package/dist/lib/utils/package-utils.js +2 -2
  14. package/modules/auth/authjs/files/api/auth/[...nextauth]/route.ts +6 -0
  15. package/modules/auth/authjs/files/lib/auth-client.ts +11 -0
  16. package/modules/auth/authjs/files/lib/auth.ts +36 -0
  17. package/modules/auth/authjs/files/schemas/prisma-schema.prisma +45 -0
  18. package/modules/auth/authjs/module.json +22 -0
  19. package/modules/auth/better-auth/files/lib/auth-client.ts +7 -0
  20. package/modules/auth/better-auth/files/lib/auth.ts +77 -1
  21. package/modules/auth/better-auth/files/lib/email-service.ts +34 -0
  22. package/modules/auth/better-auth/files/lib/email-templates.ts +89 -0
  23. package/modules/auth/better-auth/files/schemas/prisma-schema.prisma +7 -7
  24. package/modules/auth/better-auth/generator.json +83 -0
  25. package/modules/auth/better-auth/module.json +17 -34
  26. package/modules/database/mongoose/files/lib/db.ts +63 -0
  27. package/modules/database/{mongoose-mongodb → mongoose}/files/models/User.ts +0 -5
  28. package/modules/database/mongoose/generator.json +33 -0
  29. package/modules/database/mongoose/module.json +15 -0
  30. package/modules/database/prisma/files/lib/prisma.ts +7 -4
  31. package/modules/database/prisma/files/prisma/schema.prisma +1 -1
  32. package/modules/database/prisma/files/prisma.config.ts +2 -2
  33. package/modules/database/prisma/generator.json +46 -0
  34. package/modules/database/prisma/module.json +6 -132
  35. package/package.json +1 -1
  36. package/templates/express/.env.example +0 -1
  37. package/templates/express/package.json +4 -4
  38. package/templates/express/src/app.ts +2 -2
  39. package/templates/express/src/features/health/health.controller.ts +18 -0
  40. package/templates/express/src/features/health/health.route.ts +9 -0
  41. package/templates/express/src/features/health/health.service.ts +6 -0
  42. package/templates/express/template.json +6 -19
  43. package/templates/nextjs/lib/env.ts +8 -0
  44. package/templates/nextjs/package.json +7 -7
  45. package/templates/nextjs/template.json +5 -1
  46. package/templates/react-vite/.env.example +1 -2
  47. package/templates/react-vite/.prettierignore +4 -0
  48. package/templates/react-vite/.prettierrc +9 -0
  49. package/templates/react-vite/README.md +22 -0
  50. package/templates/react-vite/package.json +16 -16
  51. package/templates/react-vite/src/router.tsx +0 -12
  52. package/templates/react-vite/template.json +6 -14
  53. package/templates/react-vite/vite.config.ts +0 -6
  54. package/dist/lib/utils/config-utils.d.ts +0 -2
  55. package/dist/lib/utils/config-utils.js +0 -33
  56. package/dist/lib/utils/file-utils.d.ts +0 -8
  57. package/dist/lib/utils/file-utils.js +0 -75
  58. package/dist/lib/utils/module-utils.d.ts +0 -2
  59. package/dist/lib/utils/module-utils.js +0 -311
  60. package/modules/auth/clerk/files/express/auth.ts +0 -7
  61. package/modules/auth/clerk/files/nextjs/auth-provider.tsx +0 -5
  62. package/modules/auth/clerk/files/nextjs/middleware.ts +0 -9
  63. package/modules/auth/clerk/files/react/auth-provider.tsx +0 -15
  64. package/modules/auth/clerk/module.json +0 -115
  65. package/modules/database/mongoose-mongodb/files/lib/db.ts +0 -78
  66. package/modules/database/mongoose-mongodb/module.json +0 -70
  67. package/templates/express/src/features/auth/auth.controller.ts +0 -48
  68. package/templates/express/src/features/auth/auth.route.ts +0 -10
  69. package/templates/express/src/features/auth/auth.service.ts +0 -21
  70. package/templates/react-vite/src/api/services/user.service.ts +0 -18
  71. package/templates/react-vite/src/pages/UserProfile.tsx +0 -40
  72. package/templates/react-vite/src/types/user.d.ts +0 -6
@@ -0,0 +1,180 @@
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.discoverModules = discoverModules;
7
+ exports.getValidDatabaseOptions = getValidDatabaseOptions;
8
+ exports.getValidAuthOptions = getValidAuthOptions;
9
+ exports.parseDatabaseOption = parseDatabaseOption;
10
+ exports.getCompatibleAuthOptions = getCompatibleAuthOptions;
11
+ exports.getDatabaseChoices = getDatabaseChoices;
12
+ const fs_extra_1 = __importDefault(require("fs-extra"));
13
+ const path_1 = __importDefault(require("path"));
14
+ /**
15
+ * Discover all available modules from the modules directory
16
+ */
17
+ async function discoverModules(modulesDir) {
18
+ const discovered = {
19
+ frameworks: [],
20
+ databases: [],
21
+ auth: [],
22
+ };
23
+ if (!(await fs_extra_1.default.pathExists(modulesDir))) {
24
+ return discovered;
25
+ }
26
+ // Discover frameworks from templates directory
27
+ const templatesDir = path_1.default.join(modulesDir, '..', 'templates');
28
+ if (await fs_extra_1.default.pathExists(templatesDir)) {
29
+ const frameworkDirs = await fs_extra_1.default.readdir(templatesDir);
30
+ for (const frameworkName of frameworkDirs) {
31
+ const templateJsonPath = path_1.default.join(templatesDir, frameworkName, 'template.json');
32
+ if (await fs_extra_1.default.pathExists(templateJsonPath)) {
33
+ try {
34
+ const templateConfig = await fs_extra_1.default.readJson(templateJsonPath);
35
+ discovered.frameworks.push({
36
+ ...templateConfig,
37
+ name: frameworkName,
38
+ category: 'framework',
39
+ });
40
+ }
41
+ catch {
42
+ // Silently skip invalid templates
43
+ }
44
+ }
45
+ }
46
+ }
47
+ // Discover database modules
48
+ const databaseDir = path_1.default.join(modulesDir, 'database');
49
+ if (await fs_extra_1.default.pathExists(databaseDir)) {
50
+ const dbModules = await fs_extra_1.default.readdir(databaseDir);
51
+ for (const moduleName of dbModules) {
52
+ const modulePath = path_1.default.join(databaseDir, moduleName);
53
+ const moduleJsonPath = path_1.default.join(modulePath, 'module.json');
54
+ if (await fs_extra_1.default.pathExists(moduleJsonPath)) {
55
+ try {
56
+ const metadata = await fs_extra_1.default.readJson(moduleJsonPath);
57
+ discovered.databases.push(metadata);
58
+ }
59
+ catch {
60
+ // Silently skip invalid modules
61
+ }
62
+ }
63
+ }
64
+ }
65
+ // Discover auth modules
66
+ const authDir = path_1.default.join(modulesDir, 'auth');
67
+ if (await fs_extra_1.default.pathExists(authDir)) {
68
+ const authModules = await fs_extra_1.default.readdir(authDir);
69
+ for (const moduleName of authModules) {
70
+ const modulePath = path_1.default.join(authDir, moduleName);
71
+ const moduleJsonPath = path_1.default.join(modulePath, 'module.json');
72
+ if (await fs_extra_1.default.pathExists(moduleJsonPath)) {
73
+ try {
74
+ const metadata = await fs_extra_1.default.readJson(moduleJsonPath);
75
+ discovered.auth.push(metadata);
76
+ }
77
+ catch {
78
+ // Silently skip invalid modules
79
+ }
80
+ }
81
+ }
82
+ }
83
+ return discovered;
84
+ }
85
+ /**
86
+ * Get valid database options for CLI
87
+ */
88
+ function getValidDatabaseOptions(databases) {
89
+ const options = ['none'];
90
+ for (const db of databases) {
91
+ if (db.name === 'prisma') {
92
+ // For Prisma, add provider-specific options
93
+ options.push('prisma-postgresql', 'prisma-mongodb', 'prisma-mysql', 'prisma-sqlite');
94
+ }
95
+ else if (db.name === 'mongoose') {
96
+ options.push('mongoose-mongodb');
97
+ }
98
+ else {
99
+ // For other databases, add the name directly
100
+ options.push(db.name);
101
+ }
102
+ }
103
+ return options;
104
+ }
105
+ /**
106
+ * Get valid auth options for CLI
107
+ */
108
+ function getValidAuthOptions(authModules) {
109
+ const options = ['none'];
110
+ for (const auth of authModules) {
111
+ options.push(auth.name);
112
+ }
113
+ return options;
114
+ }
115
+ /**
116
+ * Parse database option into database name and provider
117
+ */
118
+ function parseDatabaseOption(dbOption) {
119
+ if (dbOption === 'none') {
120
+ return { database: 'none' };
121
+ }
122
+ if (dbOption.startsWith('prisma-')) {
123
+ const provider = dbOption.split('-')[1];
124
+ return { database: 'prisma', provider };
125
+ }
126
+ if (dbOption === 'mongoose-mongodb') {
127
+ return { database: 'mongoose' };
128
+ }
129
+ return { database: dbOption };
130
+ }
131
+ /**
132
+ * Get compatible auth options for given framework and database
133
+ */
134
+ function getCompatibleAuthOptions(authModules, framework, database) {
135
+ const compatible = [];
136
+ for (const auth of authModules) {
137
+ // Check if auth supports the framework
138
+ if (auth.supportedFrameworks && !auth.supportedFrameworks.includes(framework)) {
139
+ continue;
140
+ }
141
+ // Special compatibility rules
142
+ if (auth.name === 'authjs' && (database !== 'prisma' || framework !== 'nextjs')) {
143
+ continue;
144
+ }
145
+ if (auth.name === 'better-auth' && database === 'none' && framework !== 'react-vite') {
146
+ continue;
147
+ }
148
+ compatible.push({
149
+ name: auth.displayName,
150
+ value: auth.name
151
+ });
152
+ }
153
+ // Add "None" at the end
154
+ compatible.push({ name: 'None', value: 'none' });
155
+ return compatible;
156
+ }
157
+ /**
158
+ * Get database choices for inquirer prompts
159
+ */
160
+ function getDatabaseChoices(databases, framework) {
161
+ const choices = [];
162
+ for (const db of databases) {
163
+ // Check framework compatibility
164
+ if (db.supportedFrameworks && !db.supportedFrameworks.includes(framework)) {
165
+ continue;
166
+ }
167
+ if (db.name === 'prisma') {
168
+ choices.push({ name: 'Prisma (PostgreSQL)', value: 'prisma-postgresql' }, { name: 'Prisma (MongoDB)', value: 'prisma-mongodb' }, { name: 'Prisma (MySQL)', value: 'prisma-mysql' }, { name: 'Prisma (SQLite)', value: 'prisma-sqlite' });
169
+ }
170
+ else if (db.name === 'mongoose') {
171
+ choices.push({ name: 'Mongoose (MongoDB)', value: 'mongoose-mongodb' });
172
+ }
173
+ else {
174
+ choices.push({ name: db.displayName, value: db.name });
175
+ }
176
+ }
177
+ // Add "None" at the end
178
+ choices.push({ name: 'None', value: 'none' });
179
+ return choices;
180
+ }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.installDependencies = installDependencies;
4
4
  const child_process_1 = require("child_process");
5
+ const logger_1 = require("./logger");
5
6
  async function installDependencies(cwd, packageManager) {
6
7
  const commands = {
7
8
  npm: "npm install",
@@ -23,8 +24,7 @@ async function installDependencies(cwd, packageManager) {
23
24
  const fallbacks = ["pnpm", "npm", "yarn", "bun"];
24
25
  const found = fallbacks.find((p) => isAvailable(p));
25
26
  if (found) {
26
- // eslint-disable-next-line no-console
27
- console.warn(`Selected package manager '${chosen}' was not found. Falling back to '${found}'.`);
27
+ logger_1.logger.warn(`Selected package manager '${chosen}' was not found. Falling back to '${found}'.`);
28
28
  chosen = found;
29
29
  }
30
30
  else {
@@ -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,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;
@@ -1,13 +1,89 @@
1
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}}
2
12
 
3
- {{databaseAdapter}}
13
+ export const auth = betterAuth({
14
+ {{#if database=='prisma'}}
15
+ database: prismaAdapter(prisma),
16
+ {{/if}}
17
+ {{#if database=='mongoose'}}
18
+ database: mongodbAdapter(db),
19
+ {{/if}}
20
+ user: {
21
+ additionalFields: {
22
+ role: {
23
+ type: "string",
24
+ defaultValue: "USER",
25
+ required: true,
26
+ },
27
+ },
28
+ },
4
29
  emailAndPassword: {
5
30
  enabled: true,
31
+ requireEmailVerification: {{feature:emailVerification}},
6
32
  },
33
+ {{#if features.includes('socialAuth')}}
7
34
  socialProviders: {
8
35
  google: {
9
36
  clientId: process.env.GOOGLE_CLIENT_ID!,
10
37
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
11
38
  },
12
39
  },
40
+ {{/if}}
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
+ // Additional production-ready options
67
+ rateLimit: {
68
+ window: 10, // 10 seconds
69
+ max: 100, // max requests per window
70
+ },
71
+ account: {
72
+ accountLinking: {
73
+ enabled: true,
74
+ trustedProviders: ["google"],
75
+ },
76
+ },
77
+ session: {
78
+ cookieCache: {
79
+ enabled: true,
80
+ },
81
+ expiresIn: 60 * 60 * 24 * 7, // 7 days
82
+ updateAge: 60 * 60 * 24, // 1 day
83
+ },
84
+ user: {
85
+ changeEmail: {
86
+ enabled: true,
87
+ },
88
+ },
13
89
  });
@@ -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
+ };
@@ -0,0 +1,89 @@
1
+ export const getVerificationEmailTemplate = (user: { name?: string; email: string }, url: string) => {
2
+ const html = `
3
+ <!DOCTYPE html>
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="UTF-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
8
+ <title>Verify Your Email</title>
9
+ <style>
10
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #000; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fff; }
11
+ .header { padding: 20px; text-align: center; border-bottom: 1px solid #000; }
12
+ .content { padding: 20px; }
13
+ .button { display: inline-block; background-color: #fff; color: #000; padding: 10px 20px; text-decoration: none; border: 1px solid #000; border-radius: 5px; margin: 20px 0; }
14
+ .footer { font-size: 12px; color: #000; text-align: center; margin-top: 20px; border-top: 1px solid #000; padding-top: 20px; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <div class="header">
19
+ <h1>Verify Your Email Address</h1>
20
+ </div>
21
+ <div class="content">
22
+ <p>Hi ${user.name || user.email},</p>
23
+ <p>Thank you for signing up. Please verify your email address to complete your registration.</p>
24
+ <a href="${url}" class="button">Verify Email</a>
25
+ <p>If the button doesn't work, copy and paste this link: <a href="${url}">${url}</a></p>
26
+ <p>This link expires in 24 hours.</p>
27
+ </div>
28
+ <div class="footer">
29
+ <p>If you didn't create an account, ignore this email.</p>
30
+ </div>
31
+ </body>
32
+ </html>
33
+ `;
34
+
35
+ const text = `Hi ${user.name || user.email},
36
+
37
+ Thank you for signing up. Please verify your email address by clicking this link: ${url}
38
+
39
+ This link expires in 24 hours.
40
+
41
+ If you didn't create an account, ignore this email.`;
42
+
43
+ return { html, text };
44
+ };
45
+
46
+ export const getPasswordResetEmailTemplate = (user: { name?: string; email: string }, url: string) => {
47
+ const html = `
48
+ <!DOCTYPE html>
49
+ <html lang="en">
50
+ <head>
51
+ <meta charset="UTF-8">
52
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
53
+ <title>Reset Your Password</title>
54
+ <style>
55
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #000; max-width: 600px; margin: 0 auto; padding: 20px; background-color: #fff; }
56
+ .header { padding: 20px; text-align: center; border-bottom: 1px solid #000; }
57
+ .content { padding: 20px; }
58
+ .button { display: inline-block; background-color: #fff; color: #000; padding: 10px 20px; text-decoration: none; border: 1px solid #000; border-radius: 5px; margin: 20px 0; }
59
+ .footer { font-size: 12px; color: #000; text-align: center; margin-top: 20px; border-top: 1px solid #000; padding-top: 20px; }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <div class="header">
64
+ <h1>Reset Your Password</h1>
65
+ </div>
66
+ <div class="content">
67
+ <p>Hi ${user.name || user.email},</p>
68
+ <p>You requested a password reset. Click the link below to reset your password.</p>
69
+ <a href="${url}" class="button">Reset Password</a>
70
+ <p>If the button doesn't work, copy and paste this link: <a href="${url}">${url}</a></p>
71
+ <p>This link expires in 1 hour.</p>
72
+ </div>
73
+ <div class="footer">
74
+ <p>If you didn't request this, ignore this email.</p>
75
+ </div>
76
+ </body>
77
+ </html>
78
+ `;
79
+
80
+ const text = `Hi ${user.name || user.email},
81
+
82
+ You requested a password reset. Click this link to reset your password: ${url}
83
+
84
+ This link expires in 1 hour.
85
+
86
+ If you didn't request this, ignore this email.`;
87
+
88
+ return { html, text };
89
+ };
@@ -1,6 +1,6 @@
1
- // Better Auth models
1
+
2
2
  model User {
3
- id String @id {{idDefault}}
3
+ id String @id {{#if prismaProvider == postgresql}}@default(cuid()){{/if}}{{#if prismaProvider == mysql}}@default(uuid()){{/if}}{{#if prismaProvider == sqlite}}@default(uuid()){{/if}}
4
4
  name String
5
5
  email String
6
6
  emailVerified Boolean @default(false)
@@ -16,14 +16,14 @@ model User {
16
16
  }
17
17
 
18
18
  model Session {
19
- id String @id {{idDefault}}
19
+ id String @id {{#if prismaProvider == postgresql}}@default(cuid()){{/if}}{{#if prismaProvider == mysql}}@default(uuid()){{/if}}{{#if prismaProvider == sqlite}}@default(uuid()){{/if}}
20
20
  expiresAt DateTime
21
21
  token String @unique
22
22
  createdAt DateTime @default(now())
23
23
  updatedAt DateTime @updatedAt
24
24
  ipAddress String?
25
25
  userAgent String?
26
- userId String {{userIdType}}
26
+ userId String {{#if prismaProvider == postgresql}}{{/if}}{{#if prismaProvider == mysql}}{{/if}}{{#if prismaProvider == sqlite}}{{/if}}
27
27
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
28
28
 
29
29
  @@index([userId])
@@ -31,10 +31,10 @@ model Session {
31
31
  }
32
32
 
33
33
  model Account {
34
- id String @id {{idDefault}}
34
+ id String @id {{#if prismaProvider == postgresql}}@default(cuid()){{/if}}{{#if prismaProvider == mysql}}@default(uuid()){{/if}}{{#if prismaProvider == sqlite}}@default(uuid()){{/if}}
35
35
  accountId String
36
36
  providerId String
37
- userId String {{userIdType}}
37
+ userId String {{#if prismaProvider == postgresql}}{{/if}}{{#if prismaProvider == mysql}}{{/if}}{{#if prismaProvider == sqlite}}{{/if}}
38
38
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
39
39
  accessToken String?
40
40
  refreshToken String?
@@ -51,7 +51,7 @@ model Account {
51
51
  }
52
52
 
53
53
  model Verification {
54
- id String @id {{idDefault}}
54
+ id String @id {{#if prismaProvider == postgresql}}@default(cuid()){{/if}}{{#if prismaProvider == mysql}}@default(uuid()){{/if}}{{#if prismaProvider == sqlite}}@default(uuid()){{/if}}
55
55
  identifier String
56
56
  value String
57
57
  expiresAt DateTime
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "better-auth",
3
+ "type": "auth",
4
+ "priority": 10,
5
+ "operations": [
6
+ {
7
+ "type": "create-file",
8
+ "description": "Create Better Auth configuration file",
9
+ "source": "lib/auth.ts",
10
+ "destination": "{{lib}}/auth.ts"
11
+ },
12
+ {
13
+ "type": "create-file",
14
+ "description": "Create email service for sending emails",
15
+ "source": "lib/email-service.ts",
16
+ "destination": "{{lib}}/email/email-service.ts"
17
+ },
18
+ {
19
+ "type": "create-file",
20
+ "description": "Create email templates",
21
+ "source": "lib/email-templates.ts",
22
+ "destination": "{{lib}}/email/email-templates.ts"
23
+ },
24
+ {
25
+ "type": "create-file",
26
+ "description": "Create Better Auth API routes for Next.js",
27
+ "source": "api/auth/[...all]/route.ts",
28
+ "destination": "app/api/auth/[...all]/route.ts",
29
+ "condition": { "framework": "nextjs" }
30
+ },
31
+ {
32
+ "type": "create-file",
33
+ "description": "Create Better Auth client for React",
34
+ "source": "lib/auth-client.ts",
35
+ "destination": "{{lib}}/auth-client.ts",
36
+ "condition": { "framework": "nextjs" }
37
+ },
38
+ {
39
+ "type": "create-file",
40
+ "description": "Create Better Auth middleware for Next.js",
41
+ "destination": "middleware.ts",
42
+ "condition": { "framework": "nextjs" },
43
+ "content": "import { auth } from \"@/lib/auth\";\n\nexport default auth((req) => {\n console.log('middleware', req.auth)\n})\n\nexport const config = {\n matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],\n}"
44
+ },
45
+ {
46
+ "type": "patch-file",
47
+ "description": "Add Better Auth routes to Express app",
48
+ "file": "src/app.ts",
49
+ "condition": { "framework": "express" },
50
+ "operations": [
51
+ {
52
+ "type": "add-import",
53
+ "imports": [
54
+ "import { auth } from \"@/lib/auth\";",
55
+ "import { toNodeHandler } from \"better-auth/node\";"
56
+ ]
57
+ },
58
+ {
59
+ "type": "add-code",
60
+ "after": "// routes",
61
+ "code": "\napp.all(\"/api/auth/*splat\", toNodeHandler(auth));"
62
+ }
63
+ ]
64
+ }
65
+ ],
66
+ "dependencies": {
67
+ "better-auth": "^1.4.12",
68
+ "nodemailer": "^7.0.12"
69
+ },
70
+ "devDependencies": {
71
+ "@types/nodemailer": "^6.4.17"
72
+ },
73
+ "scripts": {},
74
+ "envVars": {
75
+ "BETTER_AUTH_SECRET": "",
76
+ "BETTER_AUTH_URL": "http://localhost:3000",
77
+ "EMAIL_HOST": "smtp.gmail.com",
78
+ "EMAIL_PORT": "587",
79
+ "EMAIL_USER": "",
80
+ "EMAIL_PASS": "",
81
+ "EMAIL_FROM": "noreply@yourapp.com"
82
+ }
83
+ }