add-nest-auth 1.1.1 → 1.3.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 (27) hide show
  1. package/dist/cli.js +142 -19
  2. package/dist/cli.js.map +1 -1
  3. package/dist/generator/templates/dto/auth-response.dto.ts.hbs +29 -0
  4. package/dist/generator/templates/dto/change-password.dto.ts.hbs +22 -0
  5. package/dist/generator/templates/dto/create-user.dto.ts.hbs +22 -1
  6. package/dist/generator/templates/dto/forgot-password.dto.ts.hbs +13 -0
  7. package/dist/generator/templates/dto/login.dto.ts.hbs +9 -0
  8. package/dist/generator/templates/dto/register.dto.ts.hbs +20 -0
  9. package/dist/generator/templates/dto/reset-password.dto.ts.hbs +22 -0
  10. package/dist/generator/templates/entities/user.entity.typeorm.hbs +21 -0
  11. package/dist/generator/templates/jwt/auth.controller.ts.hbs +123 -2
  12. package/dist/generator/templates/jwt/auth.module.ts.hbs +8 -0
  13. package/dist/generator/templates/jwt/auth.service.ts.hbs +177 -4
  14. package/dist/generator/templates/prisma/prisma.module.ts.hbs +9 -0
  15. package/dist/generator/templates/prisma/prisma.service.ts.hbs +9 -0
  16. package/dist/generator/templates/prisma/schema.prisma.additions.hbs +40 -0
  17. package/dist/generator/templates/shared/env.hbs +4 -0
  18. package/dist/generator/templates/shared/env.template.hbs +4 -0
  19. package/dist/generator/templates/shared/main.ts.snippet.hbs +18 -0
  20. package/dist/generator/templates/tests/auth.controller.spec.ts.hbs +189 -0
  21. package/dist/generator/templates/tests/auth.service.spec.ts.hbs +330 -0
  22. package/dist/generator/templates/users/users.controller.ts.hbs +24 -0
  23. package/dist/generator/templates/users/users.module.ts.hbs +4 -0
  24. package/dist/generator/templates/users/users.service.ts.hbs +138 -39
  25. package/dist/index.js +142 -19
  26. package/dist/index.js.map +1 -1
  27. package/package.json +5 -2
package/dist/cli.js CHANGED
@@ -292,6 +292,36 @@ async function promptConfig(detectedORM, detectedDB) {
292
292
  message: "Enable rate limiting on auth endpoints? (recommended)",
293
293
  default: true
294
294
  },
295
+ {
296
+ type: "confirm",
297
+ name: "enableSwagger",
298
+ message: "Enable Swagger API documentation? (recommended)",
299
+ default: true
300
+ },
301
+ {
302
+ type: "confirm",
303
+ name: "generateTests",
304
+ message: "Generate unit tests? (recommended)",
305
+ default: true
306
+ },
307
+ {
308
+ type: "confirm",
309
+ name: "useUsername",
310
+ message: "Add username field to user?",
311
+ default: false
312
+ },
313
+ {
314
+ type: "confirm",
315
+ name: "enableEmailVerification",
316
+ message: "Enable email verification?",
317
+ default: false
318
+ },
319
+ {
320
+ type: "confirm",
321
+ name: "enableResetPassword",
322
+ message: "Enable forgot/reset password?",
323
+ default: true
324
+ },
295
325
  {
296
326
  type: "confirm",
297
327
  name: "useDetectedORM",
@@ -330,6 +360,11 @@ function getDefaultAnswers(detectedORM, detectedDB) {
330
360
  accessExpiration: "1h",
331
361
  refreshExpiration: "7d",
332
362
  enableRateLimiting: true,
363
+ enableSwagger: true,
364
+ generateTests: true,
365
+ useUsername: false,
366
+ enableEmailVerification: false,
367
+ enableResetPassword: true,
333
368
  useDetectedORM: true,
334
369
  database: detectedDB || "postgres",
335
370
  autoInstall: true
@@ -344,11 +379,16 @@ function buildConfig(answers, projectName, sourceRoot, detectedORM, detectedDB)
344
379
  enabled: answers.enableRBAC,
345
380
  roles: answers.roles || []
346
381
  },
347
- orm: answers.useDetectedORM !== false ? detectedORM : "typeorm",
382
+ orm: answers.useDetectedORM !== false ? detectedORM : "none",
348
383
  database: answers.database || detectedDB || "postgres",
349
384
  features: {
350
385
  refreshTokens: answers.refreshTokens,
351
- rateLimiting: answers.enableRateLimiting
386
+ rateLimiting: answers.enableRateLimiting,
387
+ swagger: answers.enableSwagger,
388
+ unitTests: answers.generateTests,
389
+ useUsername: answers.useUsername,
390
+ emailVerification: answers.enableEmailVerification,
391
+ resetPassword: answers.enableResetPassword
352
392
  },
353
393
  jwt: {
354
394
  secret: generateSecret(),
@@ -357,7 +397,7 @@ function buildConfig(answers, projectName, sourceRoot, detectedORM, detectedDB)
357
397
  },
358
398
  autoInstall: answers.autoInstall,
359
399
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
360
- generatorVersion: "1.1.0"
400
+ generatorVersion: "1.3.0"
361
401
  };
362
402
  return config;
363
403
  }
@@ -433,7 +473,12 @@ function showSuccess(stats) {
433
473
  console.log(` \u2022 @nestjs/jwt, @nestjs/passport, @nestjs/config`);
434
474
  console.log(` \u2022 passport, passport-jwt, passport-local`);
435
475
  console.log(` \u2022 bcrypt, class-validator, class-transformer`);
436
- console.log(` \u2022 ${stats.dependenciesAdded} packages total`);
476
+ if (stats.orm === "prisma") {
477
+ console.log(` \u2022 @prisma/client, prisma`);
478
+ }
479
+ if (stats.swagger) {
480
+ console.log(` \u2022 @nestjs/swagger`);
481
+ }
437
482
  console.log();
438
483
  console.log(import_chalk.default.bold("\u{1F510} JWT Configuration:"));
439
484
  console.log(` \u2022 Access token: ${stats.jwt.accessExpiration}`);
@@ -446,9 +491,16 @@ function showSuccess(stats) {
446
491
  console.log(import_chalk.default.cyan(" 1. Review .env file (auto-generated with secure secret)"));
447
492
  console.log(import_chalk.default.gray(" # .env.example is also provided as a git-safe reference"));
448
493
  console.log();
449
- console.log(import_chalk.default.cyan(" 2. Create database migration (if using TypeORM)"));
450
- console.log(import_chalk.default.gray(" npm run migration:generate -- src/migrations/CreateUserTable"));
451
- console.log(import_chalk.default.gray(" npm run migration:run"));
494
+ if (stats.orm === "prisma") {
495
+ console.log(import_chalk.default.cyan(" 2. Add Prisma schema models (see prisma-schema-additions.prisma)"));
496
+ console.log(import_chalk.default.gray(" # Copy the models into your prisma/schema.prisma"));
497
+ console.log(import_chalk.default.gray(" npx prisma migrate dev --name add-auth-models"));
498
+ console.log(import_chalk.default.gray(" npx prisma generate"));
499
+ } else {
500
+ console.log(import_chalk.default.cyan(" 2. Create database migration (if using TypeORM)"));
501
+ console.log(import_chalk.default.gray(" npm run migration:generate -- src/migrations/CreateUserTable"));
502
+ console.log(import_chalk.default.gray(" npm run migration:run"));
503
+ }
452
504
  console.log();
453
505
  console.log(import_chalk.default.cyan(" 3. Start your NestJS app"));
454
506
  console.log(import_chalk.default.gray(" npm run start:dev"));
@@ -456,10 +508,24 @@ function showSuccess(stats) {
456
508
  console.log(import_chalk.default.cyan(" 4. Test authentication endpoints"));
457
509
  console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/register"));
458
510
  console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/login"));
511
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/change-password (requires JWT)"));
512
+ if (stats.emailVerification) {
513
+ console.log(import_chalk.default.gray(" GET http://localhost:3000/auth/verify-email?token=..."));
514
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/resend-verification"));
515
+ }
516
+ if (stats.resetPassword) {
517
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/forgot-password"));
518
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/reset-password"));
519
+ }
459
520
  console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/refresh"));
460
521
  console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/logout (requires JWT)"));
461
522
  console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/logout-all (requires JWT)"));
462
523
  console.log(import_chalk.default.gray(" GET http://localhost:3000/users/profile (requires JWT)"));
524
+ if (stats.swagger) {
525
+ console.log();
526
+ console.log(import_chalk.default.cyan(" 5. View Swagger API documentation"));
527
+ console.log(import_chalk.default.gray(" http://localhost:3000/api"));
528
+ }
463
529
  console.log();
464
530
  console.log(import_chalk.default.bold("\u{1F4D6} Full documentation:"), "src/auth/README.md");
465
531
  console.log();
@@ -467,6 +533,9 @@ function showSuccess(stats) {
467
533
  console.log(" \u2022 Use @Public() decorator for routes that don't require auth");
468
534
  console.log(" \u2022 Use @Roles('Admin') to restrict routes by role");
469
535
  console.log(" \u2022 Access current user with @CurrentUser() decorator");
536
+ if (stats.swagger) {
537
+ console.log(" \u2022 Visit /api for interactive Swagger documentation");
538
+ }
470
539
  console.log();
471
540
  }
472
541
  function createSpinner(text) {
@@ -512,6 +581,8 @@ var init_template_engine = __esm({
512
581
  registerHelpers() {
513
582
  this.handlebars.registerHelper("eq", (a, b) => a === b);
514
583
  this.handlebars.registerHelper("ne", (a, b) => a !== b);
584
+ this.handlebars.registerHelper("or", (a, b) => a || b);
585
+ this.handlebars.registerHelper("and", (a, b) => a && b);
515
586
  this.handlebars.registerHelper("includes", (arr, item) => arr?.includes(item));
516
587
  this.handlebars.registerHelper("capitalize", (str) => {
517
588
  if (!str) return "";
@@ -751,9 +822,16 @@ var init_generator = __esm({
751
822
  plan.push(
752
823
  { template: "dto/login.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/login.dto.ts` },
753
824
  { template: "dto/register.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/register.dto.ts` },
825
+ { template: "dto/change-password.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/change-password.dto.ts` },
754
826
  { template: "dto/auth-response.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/auth-response.dto.ts` },
755
827
  { template: "dto/create-user.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/create-user.dto.ts` }
756
828
  );
829
+ if (config.features.resetPassword) {
830
+ plan.push(
831
+ { template: "dto/forgot-password.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/forgot-password.dto.ts` },
832
+ { template: "dto/reset-password.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/reset-password.dto.ts` }
833
+ );
834
+ }
757
835
  plan.push(
758
836
  { template: "users/users.module.ts.hbs", output: `${config.sourceRoot}/users/users.module.ts` },
759
837
  { template: "users/users.service.ts.hbs", output: `${config.sourceRoot}/users/users.service.ts` },
@@ -769,6 +847,18 @@ var init_generator = __esm({
769
847
  output: `${config.sourceRoot}/users/entities/refresh-token.entity.ts`
770
848
  });
771
849
  }
850
+ } else if (config.orm === "prisma") {
851
+ plan.push(
852
+ { template: "prisma/prisma.service.ts.hbs", output: `${config.sourceRoot}/prisma/prisma.service.ts` },
853
+ { template: "prisma/prisma.module.ts.hbs", output: `${config.sourceRoot}/prisma/prisma.module.ts` },
854
+ { template: "prisma/schema.prisma.additions.hbs", output: "prisma-schema-additions.prisma" }
855
+ );
856
+ }
857
+ if (config.features.unitTests) {
858
+ plan.push(
859
+ { template: "tests/auth.service.spec.ts.hbs", output: `${config.sourceRoot}/auth/auth.service.spec.ts` },
860
+ { template: "tests/auth.controller.spec.ts.hbs", output: `${config.sourceRoot}/auth/auth.controller.spec.ts` }
861
+ );
772
862
  }
773
863
  plan.push(
774
864
  { template: "shared/env.template.hbs", output: ".env.example" },
@@ -850,6 +940,8 @@ var init_ast_updater = __esm({
850
940
  if (config.features.refreshTokens) {
851
941
  this.addImport("./users/entities/refresh-token.entity", ["RefreshToken"]);
852
942
  }
943
+ } else if (config && config.orm === "prisma") {
944
+ this.addImport("./prisma/prisma.module", ["PrismaModule"]);
853
945
  }
854
946
  this.addImport("./auth/auth.module", ["AuthModule"]);
855
947
  this.addImport("./users/users.module", ["UsersModule"]);
@@ -916,6 +1008,8 @@ var init_ast_updater = __esm({
916
1008
  if (config && config.orm === "typeorm" && !existingModules.has("TypeOrmModule")) {
917
1009
  const entities = config.features.refreshTokens ? "[User, RefreshToken]" : "[User]";
918
1010
  allElements.push(this.buildTypeOrmConfig(config.database, entities));
1011
+ } else if (config && config.orm === "prisma" && !existingModules.has("PrismaModule")) {
1012
+ allElements.push("PrismaModule");
919
1013
  }
920
1014
  if (!existingModules.has("AuthModule")) {
921
1015
  allElements.push("AuthModule");
@@ -1034,17 +1128,17 @@ var init_main_ts_updater = __esm({
1034
1128
  sourceFile;
1035
1129
  backupPath = null;
1036
1130
  /**
1037
- * Update main.ts with global guards and validation pipe
1131
+ * Update main.ts with global guards, validation pipe, and optionally Swagger
1038
1132
  */
1039
- async update() {
1133
+ async update(config) {
1040
1134
  if (!await fs5.pathExists(this.mainTsPath)) {
1041
1135
  throw new Error(`main.ts not found at ${this.mainTsPath}`);
1042
1136
  }
1043
1137
  await this.createBackup();
1044
1138
  try {
1045
1139
  this.sourceFile = this.project.addSourceFileAtPath(this.mainTsPath);
1046
- this.addImports();
1047
- this.addGlobalGuardsAndPipes();
1140
+ this.addImports(config);
1141
+ this.addGlobalGuardsAndPipes(config);
1048
1142
  this.sourceFile.formatText();
1049
1143
  await this.sourceFile.save();
1050
1144
  } catch (error) {
@@ -1055,13 +1149,16 @@ var init_main_ts_updater = __esm({
1055
1149
  /**
1056
1150
  * Add necessary imports
1057
1151
  */
1058
- addImports() {
1152
+ addImports(config) {
1059
1153
  if (!this.sourceFile) {
1060
1154
  throw new Error("Source file not loaded");
1061
1155
  }
1062
1156
  this.addImport("@nestjs/core", ["Reflector"]);
1063
1157
  this.addImport("@nestjs/common", ["ValidationPipe"]);
1064
1158
  this.addImport("./auth/guards/jwt-auth.guard", ["JwtAuthGuard"]);
1159
+ if (config?.features?.swagger) {
1160
+ this.addImport("@nestjs/swagger", ["SwaggerModule", "DocumentBuilder"]);
1161
+ }
1065
1162
  }
1066
1163
  /**
1067
1164
  * Add an import statement if it doesn't exist
@@ -1087,7 +1184,7 @@ var init_main_ts_updater = __esm({
1087
1184
  /**
1088
1185
  * Add global guards and validation pipe to bootstrap function
1089
1186
  */
1090
- addGlobalGuardsAndPipes() {
1187
+ addGlobalGuardsAndPipes(config) {
1091
1188
  if (!this.sourceFile) return;
1092
1189
  const bootstrapFunc = this.sourceFile.getFunction("bootstrap");
1093
1190
  if (!bootstrapFunc) {
@@ -1113,7 +1210,7 @@ var init_main_ts_updater = __esm({
1113
1210
  if (listenIndex === -1) {
1114
1211
  listenIndex = statements.length;
1115
1212
  }
1116
- const codeToInsert = [
1213
+ const codeLines = [
1117
1214
  "",
1118
1215
  "// Enable global validation pipe",
1119
1216
  "app.useGlobalPipes(",
@@ -1127,9 +1224,24 @@ var init_main_ts_updater = __esm({
1127
1224
  "// Enable global JWT guard (all routes protected by default)",
1128
1225
  "// Use @Public() decorator on routes that should be accessible without auth",
1129
1226
  "const reflector = app.get(Reflector);",
1130
- "app.useGlobalGuards(new JwtAuthGuard(reflector));",
1131
- ""
1132
- ].join("\n");
1227
+ "app.useGlobalGuards(new JwtAuthGuard(reflector));"
1228
+ ];
1229
+ if (config?.features?.swagger) {
1230
+ codeLines.push(
1231
+ "",
1232
+ "// Swagger API documentation",
1233
+ "const swaggerConfig = new DocumentBuilder()",
1234
+ " .setTitle('API Documentation')",
1235
+ " .setDescription('JWT Authentication API')",
1236
+ " .setVersion('1.0')",
1237
+ " .addBearerAuth()",
1238
+ " .build();",
1239
+ "const document = SwaggerModule.createDocument(app, swaggerConfig);",
1240
+ "SwaggerModule.setup('api', app, document);"
1241
+ );
1242
+ }
1243
+ codeLines.push("");
1244
+ const codeToInsert = codeLines.join("\n");
1133
1245
  body.insertStatements(listenIndex, codeToInsert);
1134
1246
  }
1135
1247
  /**
@@ -1236,9 +1348,16 @@ var init_package_updater = __esm({
1236
1348
  break;
1237
1349
  }
1238
1350
  }
1351
+ if (config.orm === "prisma") {
1352
+ dependencies["@prisma/client"] = "^6.0.0";
1353
+ devDependencies["prisma"] = "^6.0.0";
1354
+ }
1239
1355
  if (config.features.rateLimiting) {
1240
1356
  dependencies["@nestjs/throttler"] = "^6.0.0";
1241
1357
  }
1358
+ if (config.features.swagger) {
1359
+ dependencies["@nestjs/swagger"] = "^11.0.0";
1360
+ }
1242
1361
  return { dependencies, devDependencies };
1243
1362
  }
1244
1363
  /**
@@ -1419,7 +1538,7 @@ async function run(cwd = process.cwd(), options = {}) {
1419
1538
  try {
1420
1539
  const { MainTsUpdater: MainTsUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1421
1540
  const mainUpdater = new MainTsUpdater2(projectInfo.mainTsPath);
1422
- await mainUpdater.update();
1541
+ await mainUpdater.update(config);
1423
1542
  await mainUpdater.cleanupBackup();
1424
1543
  mainSpinner.succeed("Updated main.ts with global JWT guard");
1425
1544
  } catch (error) {
@@ -1460,7 +1579,11 @@ async function run(cwd = process.cwd(), options = {}) {
1460
1579
  jwt: {
1461
1580
  accessExpiration: config.jwt.accessExpiration,
1462
1581
  refreshExpiration: config.features.refreshTokens ? config.jwt.refreshExpiration : void 0
1463
- }
1582
+ },
1583
+ orm: config.orm,
1584
+ swagger: config.features.swagger,
1585
+ emailVerification: config.features.emailVerification,
1586
+ resetPassword: config.features.resetPassword
1464
1587
  });
1465
1588
  console.log("\u{1F41B} Issues? https://github.com/Islamawad132/add-nest-auth/issues");
1466
1589
  console.log("\u2B50 Like it? https://github.com/Islamawad132/add-nest-auth");