create-coreback 1.0.1 → 1.0.3

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.
@@ -91,7 +91,14 @@ const envSchema = z.object({
91
91
  PORT: z.coerce.number().default(3000),
92
92
  DATABASE_URL: z.string(),
93
93
  ${config.includeAuth ? ` JWT_SECRET: z.string(),
94
- JWT_EXPIRES_IN: z.string().default('7d'),` : ''}
94
+ JWT_EXPIRES_IN: z.string().default('7d'),
95
+ EMAIL_HOST: z.string().optional(),
96
+ EMAIL_PORT: z.coerce.number().optional(),
97
+ EMAIL_SECURE: z.coerce.boolean().default(false),
98
+ EMAIL_USER: z.string().optional(),
99
+ EMAIL_PASSWORD: z.string().optional(),
100
+ EMAIL_FROM: z.string().default('noreply@coreback.app'),
101
+ APP_URL: z.string().default('http://localhost:3000'),` : ''}
95
102
  RATE_LIMIT_WINDOW_MS: z.coerce.number().default(900000),
96
103
  RATE_LIMIT_MAX: z.coerce.number().default(100),
97
104
  });
@@ -425,7 +432,7 @@ export { router as healthRoutes };
425
432
  const authRoutesContent = `import { Router } from 'express';
426
433
  import { authController } from '../controllers/auth.controller.js';
427
434
  import { validate } from '../middlewares/validator.js';
428
- import { registerSchema, loginSchema } from '../validators/auth.validator.js';
435
+ import { registerSchema, loginSchema, emailSchema, resetPasswordSchema } from '../validators/auth.validator.js';
429
436
  import { authenticate } from '../middlewares/auth.js';
430
437
 
431
438
  /**
@@ -514,6 +521,108 @@ router.post('/login', validate(loginSchema), authController.login);
514
521
  */
515
522
  router.get('/me', authenticate, authController.me);
516
523
 
524
+ /**
525
+ * @swagger
526
+ * /api/auth/verify-email:
527
+ * get:
528
+ * summary: Verify email address
529
+ * tags: [Auth]
530
+ * parameters:
531
+ * - in: query
532
+ * name: token
533
+ * required: true
534
+ * schema:
535
+ * type: string
536
+ * responses:
537
+ * 200:
538
+ * description: Email verified successfully
539
+ * 400:
540
+ * description: Invalid or expired token
541
+ */
542
+ router.get('/verify-email', authController.verifyEmail);
543
+
544
+ /**
545
+ * @swagger
546
+ * /api/auth/resend-verification:
547
+ * post:
548
+ * summary: Resend verification email
549
+ * tags: [Auth]
550
+ * requestBody:
551
+ * required: true
552
+ * content:
553
+ * application/json:
554
+ * schema:
555
+ * type: object
556
+ * required:
557
+ * - email
558
+ * properties:
559
+ * email:
560
+ * type: string
561
+ * format: email
562
+ * responses:
563
+ * 200:
564
+ * description: Verification email sent
565
+ * 400:
566
+ * description: Email already verified
567
+ */
568
+ router.post('/resend-verification', validate(emailSchema), authController.resendVerification);
569
+
570
+ /**
571
+ * @swagger
572
+ * /api/auth/forgot-password:
573
+ * post:
574
+ * summary: Request password reset
575
+ * tags: [Auth]
576
+ * requestBody:
577
+ * required: true
578
+ * content:
579
+ * application/json:
580
+ * schema:
581
+ * type: object
582
+ * required:
583
+ * - email
584
+ * properties:
585
+ * email:
586
+ * type: string
587
+ * format: email
588
+ * responses:
589
+ * 200:
590
+ * description: Password reset email sent
591
+ */
592
+ router.post('/forgot-password', validate(emailSchema), authController.forgotPassword);
593
+
594
+ /**
595
+ * @swagger
596
+ * /api/auth/reset-password:
597
+ * post:
598
+ * summary: Reset password
599
+ * tags: [Auth]
600
+ * parameters:
601
+ * - in: query
602
+ * name: token
603
+ * required: true
604
+ * schema:
605
+ * type: string
606
+ * requestBody:
607
+ * required: true
608
+ * content:
609
+ * application/json:
610
+ * schema:
611
+ * type: object
612
+ * required:
613
+ * - password
614
+ * properties:
615
+ * password:
616
+ * type: string
617
+ * minLength: 8
618
+ * responses:
619
+ * 200:
620
+ * description: Password reset successfully
621
+ * 400:
622
+ * description: Invalid or expired token
623
+ */
624
+ router.post('/reset-password', validate(resetPasswordSchema), authController.resetPassword);
625
+
517
626
  export { router as authRoutes };
518
627
  `;
519
628
 
@@ -551,6 +660,18 @@ export const loginSchema = z.object({
551
660
  password: z.string().min(1, 'Password is required'),
552
661
  }),
553
662
  });
663
+
664
+ export const emailSchema = z.object({
665
+ body: z.object({
666
+ email: z.string().email('Invalid email format'),
667
+ }),
668
+ });
669
+
670
+ export const resetPasswordSchema = z.object({
671
+ body: z.object({
672
+ password: z.string().min(8, 'Password must be at least 8 characters'),
673
+ }),
674
+ });
554
675
  `;
555
676
 
556
677
  await fs.writeFile(path.join(validatorsDir, 'auth.validator.ts'), authValidatorContent);
@@ -630,6 +751,77 @@ export const authController = {
630
751
  throw new AppError(500, 'Failed to get user');
631
752
  }
632
753
  },
754
+
755
+ verifyEmail: async (req: AuthRequest, res: Response) => {
756
+ try {
757
+ const { token } = req.query;
758
+ if (!token || typeof token !== 'string') {
759
+ throw new AppError(400, 'Verification token is required');
760
+ }
761
+ const result = await authService.verifyEmail(token);
762
+ res.json({
763
+ status: 'success',
764
+ data: result,
765
+ });
766
+ } catch (error) {
767
+ if (error instanceof AppError) {
768
+ throw error;
769
+ }
770
+ throw new AppError(500, 'Failed to verify email');
771
+ }
772
+ },
773
+
774
+ resendVerification: async (req: AuthRequest, res: Response) => {
775
+ try {
776
+ const { email } = req.body;
777
+ const result = await authService.resendVerificationEmail(email);
778
+ res.json({
779
+ status: 'success',
780
+ data: result,
781
+ });
782
+ } catch (error) {
783
+ if (error instanceof AppError) {
784
+ throw error;
785
+ }
786
+ throw new AppError(500, 'Failed to resend verification email');
787
+ }
788
+ },
789
+
790
+ forgotPassword: async (req: AuthRequest, res: Response) => {
791
+ try {
792
+ const { email } = req.body;
793
+ const result = await authService.requestPasswordReset(email);
794
+ res.json({
795
+ status: 'success',
796
+ data: result,
797
+ });
798
+ } catch (error) {
799
+ if (error instanceof AppError) {
800
+ throw error;
801
+ }
802
+ throw new AppError(500, 'Failed to send password reset email');
803
+ }
804
+ },
805
+
806
+ resetPassword: async (req: AuthRequest, res: Response) => {
807
+ try {
808
+ const { token } = req.query;
809
+ const { password } = req.body;
810
+ if (!token || typeof token !== 'string') {
811
+ throw new AppError(400, 'Reset token is required');
812
+ }
813
+ const result = await authService.resetPassword(token, password);
814
+ res.json({
815
+ status: 'success',
816
+ data: result,
817
+ });
818
+ } catch (error) {
819
+ if (error instanceof AppError) {
820
+ throw error;
821
+ }
822
+ throw new AppError(500, 'Failed to reset password');
823
+ }
824
+ },
633
825
  };
634
826
  `;
635
827
 
@@ -644,10 +836,15 @@ async function generateServices(servicesDir: string, config: ProjectConfig): Pro
644
836
  if (config.includeAuth) {
645
837
  const authServiceContent = `import bcrypt from 'bcrypt';
646
838
  import jwt from 'jsonwebtoken';
839
+ import crypto from 'crypto';
647
840
  import { config } from '../config/env.js';
648
841
  import { userRepository } from '../repositories/user.repository.js';
842
+ import { emailService } from './email.service.js';
649
843
  import { AppError } from '../middlewares/errorHandler.js';
650
844
 
845
+ const generateVerificationToken = () => crypto.randomBytes(32).toString('hex');
846
+ const generateResetToken = () => crypto.randomBytes(32).toString('hex');
847
+
651
848
  export const authService = {
652
849
  async register(email: string, password: string, name?: string) {
653
850
  const existingUser = await userRepository.findByEmail(email);
@@ -657,25 +854,34 @@ export const authService = {
657
854
  }
658
855
 
659
856
  const hashedPassword = await bcrypt.hash(password, 10);
857
+ const verificationToken = generateVerificationToken();
858
+ const verificationExpires = new Date();
859
+ verificationExpires.setHours(verificationExpires.getHours() + 24); // 24 hours
860
+
660
861
  const user = await userRepository.create({
661
862
  email,
662
863
  password: hashedPassword,
663
864
  name,
865
+ emailVerificationToken: verificationToken,
866
+ emailVerificationExpires: verificationExpires,
664
867
  });
665
868
 
666
- const token = jwt.sign(
667
- { id: user.id, email: user.email },
668
- config.JWT_SECRET,
669
- { expiresIn: config.JWT_EXPIRES_IN }
670
- );
869
+ // Send verification email
870
+ try {
871
+ await emailService.sendVerificationEmail(email, verificationToken, name);
872
+ } catch (error) {
873
+ // Log error but don't fail registration
874
+ console.error('Failed to send verification email:', error);
875
+ }
671
876
 
672
877
  return {
673
878
  user: {
674
879
  id: user.id,
675
880
  email: user.email,
676
881
  name: user.name,
882
+ emailVerified: user.emailVerified,
677
883
  },
678
- token,
884
+ message: 'Registration successful. Please check your email to verify your account.',
679
885
  };
680
886
  },
681
887
 
@@ -692,6 +898,10 @@ export const authService = {
692
898
  throw new AppError(401, 'Invalid credentials');
693
899
  }
694
900
 
901
+ if (!user.emailVerified) {
902
+ throw new AppError(403, 'Please verify your email before logging in');
903
+ }
904
+
695
905
  const token = jwt.sign(
696
906
  { id: user.id, email: user.email },
697
907
  config.JWT_SECRET,
@@ -703,11 +913,101 @@ export const authService = {
703
913
  id: user.id,
704
914
  email: user.email,
705
915
  name: user.name,
916
+ emailVerified: user.emailVerified,
706
917
  },
707
918
  token,
708
919
  };
709
920
  },
710
921
 
922
+ async verifyEmail(token: string) {
923
+ const user = await userRepository.findByVerificationToken(token);
924
+
925
+ if (!user) {
926
+ throw new AppError(400, 'Invalid or expired verification token');
927
+ }
928
+
929
+ await userRepository.updateEmailVerification(user.id, true);
930
+
931
+ return {
932
+ message: 'Email verified successfully',
933
+ };
934
+ },
935
+
936
+ async resendVerificationEmail(email: string) {
937
+ const user = await userRepository.findByEmail(email);
938
+
939
+ if (!user) {
940
+ // Don't reveal if user exists
941
+ return {
942
+ message: 'If an account exists with this email, a verification link has been sent.',
943
+ };
944
+ }
945
+
946
+ if (user.emailVerified) {
947
+ throw new AppError(400, 'Email is already verified');
948
+ }
949
+
950
+ const verificationToken = generateVerificationToken();
951
+ const verificationExpires = new Date();
952
+ verificationExpires.setHours(verificationExpires.getHours() + 24);
953
+
954
+ await userRepository.updateVerificationToken(user.id, verificationToken, verificationExpires);
955
+
956
+ try {
957
+ await emailService.sendVerificationEmail(email, verificationToken, user.name || undefined);
958
+ } catch (error) {
959
+ console.error('Failed to send verification email:', error);
960
+ throw new AppError(500, 'Failed to send verification email');
961
+ }
962
+
963
+ return {
964
+ message: 'Verification email sent',
965
+ };
966
+ },
967
+
968
+ async requestPasswordReset(email: string) {
969
+ const user = await userRepository.findByEmail(email);
970
+
971
+ if (!user) {
972
+ // Don't reveal if user exists
973
+ return {
974
+ message: 'If an account exists with this email, a password reset link has been sent.',
975
+ };
976
+ }
977
+
978
+ const resetToken = generateResetToken();
979
+ const resetExpires = new Date();
980
+ resetExpires.setHours(resetExpires.getHours() + 1); // 1 hour
981
+
982
+ await userRepository.updatePasswordResetToken(user.id, resetToken, resetExpires);
983
+
984
+ try {
985
+ await emailService.sendPasswordResetEmail(email, resetToken, user.name || undefined);
986
+ } catch (error) {
987
+ console.error('Failed to send password reset email:', error);
988
+ throw new AppError(500, 'Failed to send password reset email');
989
+ }
990
+
991
+ return {
992
+ message: 'Password reset email sent',
993
+ };
994
+ },
995
+
996
+ async resetPassword(token: string, newPassword: string) {
997
+ const user = await userRepository.findByPasswordResetToken(token);
998
+
999
+ if (!user) {
1000
+ throw new AppError(400, 'Invalid or expired reset token');
1001
+ }
1002
+
1003
+ const hashedPassword = await bcrypt.hash(newPassword, 10);
1004
+ await userRepository.updatePassword(user.id, hashedPassword);
1005
+
1006
+ return {
1007
+ message: 'Password reset successfully',
1008
+ };
1009
+ },
1010
+
711
1011
  async getUserById(id: string) {
712
1012
  const user = await userRepository.findById(id);
713
1013
 
@@ -730,6 +1030,118 @@ export const authService = {
730
1030
  path.join(servicesDir, 'auth.service.ts'),
731
1031
  authServiceContent
732
1032
  );
1033
+
1034
+ // email.service.ts
1035
+ const emailServiceContent = `import nodemailer from 'nodemailer';
1036
+ import { config } from '../config/env.js';
1037
+
1038
+ const createTransporter = () => {
1039
+ // If email is not configured, use a test account (for development)
1040
+ if (!config.EMAIL_HOST) {
1041
+ return nodemailer.createTransporter({
1042
+ host: 'smtp.ethereal.email',
1043
+ port: 587,
1044
+ secure: false,
1045
+ auth: {
1046
+ user: 'test@ethereal.email',
1047
+ pass: 'test',
1048
+ },
1049
+ });
1050
+ }
1051
+
1052
+ return nodemailer.createTransporter({
1053
+ host: config.EMAIL_HOST,
1054
+ port: config.EMAIL_PORT,
1055
+ secure: config.EMAIL_SECURE,
1056
+ auth: config.EMAIL_USER && config.EMAIL_PASSWORD ? {
1057
+ user: config.EMAIL_USER,
1058
+ pass: config.EMAIL_PASSWORD,
1059
+ } : undefined,
1060
+ });
1061
+ };
1062
+
1063
+ const transporter = createTransporter();
1064
+
1065
+ export const emailService = {
1066
+ async sendVerificationEmail(email: string, token: string, name?: string) {
1067
+ const verificationUrl = \`\${config.APP_URL}/api/auth/verify-email?token=\${token}\`;
1068
+
1069
+ const mailOptions = {
1070
+ from: config.EMAIL_FROM,
1071
+ to: email,
1072
+ subject: 'Verify your email address',
1073
+ html: \`
1074
+ <!DOCTYPE html>
1075
+ <html>
1076
+ <head>
1077
+ <meta charset="utf-8">
1078
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1079
+ </head>
1080
+ <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
1081
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
1082
+ <h1 style="color: white; margin: 0;">Welcome to CoreBack!</h1>
1083
+ </div>
1084
+ <div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px;">
1085
+ <h2 style="color: #333; margin-top: 0;">Hello \${name || 'there'}!</h2>
1086
+ <p>Thank you for registering. Please verify your email address by clicking the button below:</p>
1087
+ <div style="text-align: center; margin: 30px 0;">
1088
+ <a href="\${verificationUrl}" style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">Verify Email</a>
1089
+ </div>
1090
+ <p style="color: #666; font-size: 14px;">Or copy and paste this link into your browser:</p>
1091
+ <p style="color: #667eea; font-size: 12px; word-break: break-all;">\${verificationUrl}</p>
1092
+ <p style="color: #666; font-size: 12px; margin-top: 30px;">This link will expire in 24 hours.</p>
1093
+ </div>
1094
+ </body>
1095
+ </html>
1096
+ \`,
1097
+ };
1098
+
1099
+ await transporter.sendMail(mailOptions);
1100
+ },
1101
+
1102
+ async sendPasswordResetEmail(email: string, token: string, name?: string) {
1103
+ const resetUrl = \`\${config.APP_URL}/api/auth/reset-password?token=\${token}\`;
1104
+
1105
+ const mailOptions = {
1106
+ from: config.EMAIL_FROM,
1107
+ to: email,
1108
+ subject: 'Reset your password',
1109
+ html: \`
1110
+ <!DOCTYPE html>
1111
+ <html>
1112
+ <head>
1113
+ <meta charset="utf-8">
1114
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1115
+ </head>
1116
+ <body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px;">
1117
+ <div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; text-align: center; border-radius: 10px 10px 0 0;">
1118
+ <h1 style="color: white; margin: 0;">Password Reset</h1>
1119
+ </div>
1120
+ <div style="background: #f9f9f9; padding: 30px; border-radius: 0 0 10px 10px;">
1121
+ <h2 style="color: #333; margin-top: 0;">Hello \${name || 'there'}!</h2>
1122
+ <p>You requested to reset your password. Click the button below to reset it:</p>
1123
+ <div style="text-align: center; margin: 30px 0;">
1124
+ <a href="\${resetUrl}" style="background: #667eea; color: white; padding: 12px 30px; text-decoration: none; border-radius: 5px; display: inline-block;">Reset Password</a>
1125
+ </div>
1126
+ <p style="color: #666; font-size: 14px;">Or copy and paste this link into your browser:</p>
1127
+ <p style="color: #667eea; font-size: 12px; word-break: break-all;">\${resetUrl}</p>
1128
+ <p style="color: #666; font-size: 12px; margin-top: 30px;">This link will expire in 1 hour.</p>
1129
+ <p style="color: #999; font-size: 12px; margin-top: 30px;">If you didn't request this, please ignore this email.</p>
1130
+ </div>
1131
+ </body>
1132
+ </html>
1133
+ \`,
1134
+ };
1135
+
1136
+ await transporter.sendMail(mailOptions);
1137
+ },
1138
+ };
1139
+ `;
1140
+
1141
+ await fs.writeFile(
1142
+ path.join(servicesDir, 'email.service.ts'),
1143
+ emailServiceContent
1144
+ );
733
1145
  }
734
1146
  }
735
1147
 
@@ -757,18 +1169,83 @@ export const userRepository = {
757
1169
  });
758
1170
  },
759
1171
 
760
- async create(data: { email: string; password: string; name?: string }) {
1172
+ async create(data: { email: string; password: string; name?: string; emailVerificationToken?: string; emailVerificationExpires?: Date }) {
761
1173
  return prisma.user.create({
762
1174
  data,
763
1175
  select: {
764
1176
  id: true,
765
1177
  email: true,
766
1178
  name: true,
1179
+ emailVerified: true,
767
1180
  createdAt: true,
768
1181
  updatedAt: true,
769
1182
  },
770
1183
  });
771
1184
  },
1185
+
1186
+ async updateEmailVerification(id: string, verified: boolean) {
1187
+ return prisma.user.update({
1188
+ where: { id },
1189
+ data: {
1190
+ emailVerified: verified,
1191
+ emailVerificationToken: null,
1192
+ emailVerificationExpires: null,
1193
+ },
1194
+ });
1195
+ },
1196
+
1197
+ async findByVerificationToken(token: string) {
1198
+ return prisma.user.findFirst({
1199
+ where: {
1200
+ emailVerificationToken: token,
1201
+ emailVerificationExpires: {
1202
+ gt: new Date(),
1203
+ },
1204
+ },
1205
+ });
1206
+ },
1207
+
1208
+ async updateVerificationToken(id: string, token: string, expires: Date) {
1209
+ return prisma.user.update({
1210
+ where: { id },
1211
+ data: {
1212
+ emailVerificationToken: token,
1213
+ emailVerificationExpires: expires,
1214
+ },
1215
+ });
1216
+ },
1217
+
1218
+ async updatePasswordResetToken(id: string, token: string, expires: Date) {
1219
+ return prisma.user.update({
1220
+ where: { id },
1221
+ data: {
1222
+ passwordResetToken: token,
1223
+ passwordResetExpires: expires,
1224
+ },
1225
+ });
1226
+ },
1227
+
1228
+ async findByPasswordResetToken(token: string) {
1229
+ return prisma.user.findFirst({
1230
+ where: {
1231
+ passwordResetToken: token,
1232
+ passwordResetExpires: {
1233
+ gt: new Date(),
1234
+ },
1235
+ },
1236
+ });
1237
+ },
1238
+
1239
+ async updatePassword(id: string, password: string) {
1240
+ return prisma.user.update({
1241
+ where: { id },
1242
+ data: {
1243
+ password,
1244
+ passwordResetToken: null,
1245
+ passwordResetExpires: null,
1246
+ },
1247
+ });
1248
+ },
772
1249
  };
773
1250
  `;
774
1251
 
package/src/index.ts CHANGED
@@ -7,20 +7,20 @@ import chalk from 'chalk';
7
7
  const banner = `
8
8
  ${chalk.cyan.bold('╔═══════════════════════════════════════════════════════════════╗')}
9
9
  ${chalk.cyan.bold('║')} ${chalk.cyan.bold('║')}
10
- ${chalk.cyan.bold('║')} ${chalk.white.bold(' ██████╗ ██████╗ ██████╗ ███████╗')} ${chalk.cyan.bold('║')}
11
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██╔════╝██╔═══██╗██╔══██╗██╔════╝')} ${chalk.cyan.bold('║')}
12
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██║ ██║ ██║██████╔╝█████╗ ')} ${chalk.cyan.bold('║')}
13
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██║ ██║ ██║██╔══██╗██╔══╝ ')} ${chalk.cyan.bold('║')}
14
- ${chalk.cyan.bold('║')} ${chalk.white.bold('╚██████╗╚██████╝ ██║ ██║███████╗')} ${chalk.cyan.bold('║')}
10
+ ${chalk.cyan.bold('║')} ${chalk.white.bold(' ██████╗ ██████╗ ██████╗ ███████╗')} ${chalk.cyan.bold('║')}
11
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██╔════╝██╔═══██╗██╔══██╗██╔════╝')} ${chalk.cyan.bold('║')}
12
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██║ ██║ ██║██████╔╝█████╗ ')} ${chalk.cyan.bold('║')}
13
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██║ ██║ ██║██╔══██╗██╔══╝ ')} ${chalk.cyan.bold('║')}
14
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('╚██████╗╚██████╝ ██║ ██║███████╗')} ${chalk.cyan.bold('║')}
15
+ ${chalk.cyan.bold('║')} ${chalk.cyan.bold('║')}
16
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██████╗ █████╗ ██████╗██╗ ██╗')} ${chalk.cyan.bold('║')}
17
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██╔══██╗██╔══██╗██╔════╝██║ ██╔╝')} ${chalk.cyan.bold('║')}
18
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██████╔╝███████║██║ █████╔╝ ')} ${chalk.cyan.bold('║')}
19
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██╔══██╗██╔══██║██║ ██╔═██╗ ')} ${chalk.cyan.bold('║')}
20
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('██████╔╝██║ ██║╚██████╗██║ ██╗')} ${chalk.cyan.bold('║')}
21
+ ${chalk.cyan.bold('║')} ${chalk.white.bold('╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝')} ${chalk.cyan.bold('║')}
15
22
  ${chalk.cyan.bold('║')} ${chalk.cyan.bold('║')}
16
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██████╗ █████╗ ██████╗██╗ ██╗')} ${chalk.cyan.bold('║')}
17
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██╔══██╗██╔══██╗██╔════╝██║ ██╔╝')} ${chalk.cyan.bold('║')}
18
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██████╔╝███████║██║ █████╔╝ ')} ${chalk.cyan.bold('║')}
19
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██╔══██╗██╔══██║██║ ██╔═██╗ ')} ${chalk.cyan.bold('║')}
20
- ${chalk.cyan.bold('║')} ${chalk.white.bold('██████╔╝██║ ██║╚██████╗██║ ██╗')} ${chalk.cyan.bold('║')}
21
- ${chalk.cyan.bold('║')} ${chalk.white.bold('╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝')} ${chalk.cyan.bold('║')}
22
23
  ${chalk.cyan.bold('║')} ${chalk.cyan.bold('║')}
23
- ${chalk.cyan.bold('║')} ${chalk.gray(' Production-Ready Backend Generator')} ${chalk.cyan.bold('║')}
24
24
  ${chalk.cyan.bold('║')} ${chalk.cyan.bold('║')}
25
25
  ${chalk.cyan.bold('╚═══════════════════════════════════════════════════════════════╝')}
26
26
  `;
package/src/prompts.ts CHANGED
@@ -1,16 +1,28 @@
1
1
  import inquirer from 'inquirer';
2
- import chalk from 'chalk';
2
+
3
3
  import { ProjectConfig, DatabaseType, PackageManager } from './types.js';
4
4
 
5
5
  export async function promptProjectConfig(
6
6
  defaultName?: string
7
7
  ): Promise<ProjectConfig> {
8
+ // If project name is provided as argument, use it directly without prompting
9
+ const projectName = defaultName
10
+ ? defaultName.toLowerCase().trim()
11
+ : undefined;
12
+
13
+ // Validate the provided name if it exists
14
+ if (projectName) {
15
+ if (!/^[a-z0-9-]+$/.test(projectName)) {
16
+ throw new Error('Project name must be lowercase, alphanumeric, and can contain hyphens');
17
+ }
18
+ }
19
+
8
20
  const answers = await inquirer.prompt([
9
- {
21
+ ...(projectName ? [] : [{
10
22
  type: 'input',
11
23
  name: 'projectName',
12
24
  message: 'Project name:',
13
- default: defaultName || 'my-backend',
25
+ default: 'my-project',
14
26
  validate: (input: string) => {
15
27
  if (!input.trim()) {
16
28
  return 'Project name cannot be empty';
@@ -21,7 +33,7 @@ export async function promptProjectConfig(
21
33
  return true;
22
34
  },
23
35
  filter: (input: string) => input.toLowerCase().trim(),
24
- },
36
+ }]),
25
37
  {
26
38
  type: 'list',
27
39
  name: 'database',
@@ -57,6 +69,9 @@ export async function promptProjectConfig(
57
69
  },
58
70
  ]);
59
71
 
60
- return answers as ProjectConfig;
72
+ return {
73
+ ...answers,
74
+ projectName: projectName || answers.projectName,
75
+ } as ProjectConfig;
61
76
  }
62
77