add-nest-auth 1.0.5 → 1.0.7

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.
package/README.md CHANGED
@@ -359,9 +359,9 @@ If this tool helped you, please consider:
359
359
 
360
360
  ## 🔗 Links
361
361
 
362
- - **GitHub**: https://github.com/yourusername/add-nest-auth
362
+ - **GitHub**: https://github.com/Islamawad132/add-nest-auth
363
363
  - **npm**: https://www.npmjs.com/package/add-nest-auth
364
- - **Issues**: https://github.com/yourusername/add-nest-auth/issues
364
+ - **Issues**: https://github.com/Islamawad132/add-nest-auth/issues
365
365
 
366
366
  ---
367
367
 
package/dist/cli.js CHANGED
@@ -127,6 +127,7 @@ var init_file_writer = __esm({
127
127
  fs3 = __toESM(require("fs-extra"));
128
128
  FileWriter = class {
129
129
  writtenFiles = [];
130
+ skippedFiles = [];
130
131
  backups = /* @__PURE__ */ new Map();
131
132
  /**
132
133
  * Write a file to disk
@@ -135,7 +136,8 @@ var init_file_writer = __esm({
135
136
  const { overwrite = false, backup = true } = options;
136
137
  const exists = await fs3.pathExists(filePath);
137
138
  if (exists && !overwrite) {
138
- throw new Error(`File already exists: ${filePath}`);
139
+ this.skippedFiles.push(filePath);
140
+ return;
139
141
  }
140
142
  if (exists && backup) {
141
143
  await this.createBackup(filePath);
@@ -187,6 +189,12 @@ var init_file_writer = __esm({
187
189
  getWrittenFiles() {
188
190
  return [...this.writtenFiles];
189
191
  }
192
+ /**
193
+ * Get list of skipped files (already existed)
194
+ */
195
+ getSkippedFiles() {
196
+ return [...this.skippedFiles];
197
+ }
190
198
  };
191
199
  }
192
200
  });
@@ -243,15 +251,18 @@ var init_generator = __esm({
243
251
  });
244
252
  }
245
253
  const filesCreated = this.fileWriter.getWrittenFiles();
254
+ const filesSkipped = this.fileWriter.getSkippedFiles();
246
255
  await this.fileWriter.cleanupBackups();
247
256
  return {
248
257
  filesCreated,
258
+ filesSkipped,
249
259
  success: true
250
260
  };
251
261
  } catch (error) {
252
262
  await this.fileWriter.rollback();
253
263
  return {
254
264
  filesCreated: [],
265
+ filesSkipped: [],
255
266
  success: false,
256
267
  error: error instanceof Error ? error.message : "Unknown error"
257
268
  };
@@ -357,12 +368,12 @@ var init_ast_updater = __esm({
357
368
  /**
358
369
  * Update app.module.ts with auth modules
359
370
  */
360
- async update() {
371
+ async update(config) {
361
372
  await this.createBackup();
362
373
  try {
363
374
  this.sourceFile = this.project.addSourceFileAtPath(this.appModulePath);
364
- this.addImports();
365
- this.addModulesToDecorator();
375
+ this.addImports(config);
376
+ this.addModulesToDecorator(config);
366
377
  await this.sourceFile.save();
367
378
  } catch (error) {
368
379
  await this.restoreBackup();
@@ -372,11 +383,18 @@ var init_ast_updater = __esm({
372
383
  /**
373
384
  * Add necessary imports
374
385
  */
375
- addImports() {
386
+ addImports(config) {
376
387
  if (!this.sourceFile) {
377
388
  throw new Error("Source file not loaded");
378
389
  }
379
390
  this.addImport("@nestjs/config", ["ConfigModule"]);
391
+ if (config && config.orm === "typeorm") {
392
+ this.addImport("@nestjs/typeorm", ["TypeOrmModule"]);
393
+ this.addImport("./users/entities/user.entity", ["User"]);
394
+ if (config.features.refreshTokens) {
395
+ this.addImport("./users/entities/refresh-token.entity", ["RefreshToken"]);
396
+ }
397
+ }
380
398
  this.addImport("./auth/auth.module", ["AuthModule"]);
381
399
  this.addImport("./users/users.module", ["UsersModule"]);
382
400
  }
@@ -404,7 +422,7 @@ var init_ast_updater = __esm({
404
422
  /**
405
423
  * Add modules to @Module decorator imports array
406
424
  */
407
- addModulesToDecorator() {
425
+ addModulesToDecorator(config) {
408
426
  if (!this.sourceFile) return;
409
427
  const appModuleClass = this.sourceFile.getClass("AppModule");
410
428
  if (!appModuleClass) {
@@ -437,6 +455,11 @@ var init_ast_updater = __esm({
437
455
  if (!existingModules.has("ConfigModule")) {
438
456
  importsArray.addElement("ConfigModule.forRoot({ isGlobal: true })");
439
457
  }
458
+ if (config && config.orm === "typeorm" && !existingModules.has("TypeOrmModule")) {
459
+ const entities = config.features.refreshTokens ? "[User, RefreshToken]" : "[User]";
460
+ const typeOrmConfig = this.buildTypeOrmConfig(config.database, entities);
461
+ importsArray.addElement(typeOrmConfig);
462
+ }
440
463
  if (!existingModules.has("AuthModule")) {
441
464
  importsArray.addElement("AuthModule");
442
465
  }
@@ -444,6 +467,43 @@ var init_ast_updater = __esm({
444
467
  importsArray.addElement("UsersModule");
445
468
  }
446
469
  }
470
+ /**
471
+ * Build TypeORM.forRoot() configuration string based on database type
472
+ */
473
+ buildTypeOrmConfig(database, entities) {
474
+ switch (database) {
475
+ case "sqlite":
476
+ return `TypeOrmModule.forRoot({
477
+ type: 'sqlite',
478
+ database: 'database.sqlite',
479
+ entities: ${entities},
480
+ synchronize: true, // WARNING: disable in production!
481
+ })`;
482
+ case "mysql":
483
+ return `TypeOrmModule.forRoot({
484
+ type: 'mysql',
485
+ host: process.env.DATABASE_HOST || 'localhost',
486
+ port: parseInt(process.env.DATABASE_PORT || '3306'),
487
+ username: process.env.DATABASE_USER || 'root',
488
+ password: process.env.DATABASE_PASSWORD || '',
489
+ database: process.env.DATABASE_NAME || 'auth_db',
490
+ entities: ${entities},
491
+ synchronize: true, // WARNING: disable in production!
492
+ })`;
493
+ case "postgres":
494
+ default:
495
+ return `TypeOrmModule.forRoot({
496
+ type: 'postgres',
497
+ host: process.env.DATABASE_HOST || 'localhost',
498
+ port: parseInt(process.env.DATABASE_PORT || '5432'),
499
+ username: process.env.DATABASE_USER || 'postgres',
500
+ password: process.env.DATABASE_PASSWORD || 'postgres',
501
+ database: process.env.DATABASE_NAME || 'auth_db',
502
+ entities: ${entities},
503
+ synchronize: true, // WARNING: disable in production!
504
+ })`;
505
+ }
506
+ }
447
507
  /**
448
508
  * Get existing module names from imports array
449
509
  */
@@ -489,13 +549,157 @@ var init_ast_updater = __esm({
489
549
  }
490
550
  });
491
551
 
552
+ // src/installer/main-ts-updater.ts
553
+ var import_ts_morph2, fs5, MainTsUpdater;
554
+ var init_main_ts_updater = __esm({
555
+ "src/installer/main-ts-updater.ts"() {
556
+ "use strict";
557
+ init_cjs_shims();
558
+ import_ts_morph2 = require("ts-morph");
559
+ fs5 = __toESM(require("fs-extra"));
560
+ MainTsUpdater = class {
561
+ constructor(mainTsPath) {
562
+ this.mainTsPath = mainTsPath;
563
+ this.project = new import_ts_morph2.Project({
564
+ skipAddingFilesFromTsConfig: true
565
+ });
566
+ }
567
+ project;
568
+ sourceFile;
569
+ backupPath = null;
570
+ /**
571
+ * Update main.ts with global guards and validation pipe
572
+ */
573
+ async update() {
574
+ if (!await fs5.pathExists(this.mainTsPath)) {
575
+ throw new Error(`main.ts not found at ${this.mainTsPath}`);
576
+ }
577
+ await this.createBackup();
578
+ try {
579
+ this.sourceFile = this.project.addSourceFileAtPath(this.mainTsPath);
580
+ this.addImports();
581
+ this.addGlobalGuardsAndPipes();
582
+ await this.sourceFile.save();
583
+ } catch (error) {
584
+ await this.restoreBackup();
585
+ throw error;
586
+ }
587
+ }
588
+ /**
589
+ * Add necessary imports
590
+ */
591
+ addImports() {
592
+ if (!this.sourceFile) {
593
+ throw new Error("Source file not loaded");
594
+ }
595
+ this.addImport("@nestjs/core", ["Reflector"]);
596
+ this.addImport("@nestjs/common", ["ValidationPipe"]);
597
+ this.addImport("./auth/guards/jwt-auth.guard", ["JwtAuthGuard"]);
598
+ }
599
+ /**
600
+ * Add an import statement if it doesn't exist
601
+ */
602
+ addImport(moduleSpecifier, namedImports) {
603
+ if (!this.sourceFile) return;
604
+ const existingImport = this.sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue() === moduleSpecifier);
605
+ if (existingImport) {
606
+ const existingNames = existingImport.getNamedImports().map((ni) => ni.getName());
607
+ const missingImports = namedImports.filter(
608
+ (name) => !existingNames.includes(name)
609
+ );
610
+ if (missingImports.length > 0) {
611
+ existingImport.addNamedImports(missingImports);
612
+ }
613
+ } else {
614
+ this.sourceFile.addImportDeclaration({
615
+ moduleSpecifier,
616
+ namedImports
617
+ });
618
+ }
619
+ }
620
+ /**
621
+ * Add global guards and validation pipe to bootstrap function
622
+ */
623
+ addGlobalGuardsAndPipes() {
624
+ if (!this.sourceFile) return;
625
+ const bootstrapFunc = this.sourceFile.getFunction("bootstrap");
626
+ if (!bootstrapFunc) {
627
+ throw new Error("bootstrap function not found in main.ts");
628
+ }
629
+ const body = bootstrapFunc.getBody();
630
+ if (!body || !import_ts_morph2.Node.isBlock(body)) {
631
+ throw new Error("bootstrap function has no body");
632
+ }
633
+ const bodyText = body.getText();
634
+ if (bodyText.includes("useGlobalGuards") || bodyText.includes("JwtAuthGuard")) {
635
+ return;
636
+ }
637
+ const statements = body.getStatements();
638
+ let listenIndex = -1;
639
+ for (let i = 0; i < statements.length; i++) {
640
+ const text = statements[i].getText();
641
+ if (text.includes(".listen(") || text.includes(".listen (")) {
642
+ listenIndex = i;
643
+ break;
644
+ }
645
+ }
646
+ if (listenIndex === -1) {
647
+ listenIndex = statements.length;
648
+ }
649
+ const codeToInsert = [
650
+ "",
651
+ "// Enable global validation pipe",
652
+ "app.useGlobalPipes(",
653
+ " new ValidationPipe({",
654
+ " whitelist: true,",
655
+ " forbidNonWhitelisted: true,",
656
+ " transform: true,",
657
+ " }),",
658
+ ");",
659
+ "",
660
+ "// Enable global JWT guard (all routes protected by default)",
661
+ "// Use @Public() decorator on routes that should be accessible without auth",
662
+ "const reflector = app.get(Reflector);",
663
+ "app.useGlobalGuards(new JwtAuthGuard(reflector));",
664
+ ""
665
+ ].join("\n");
666
+ body.insertStatements(listenIndex, codeToInsert);
667
+ }
668
+ /**
669
+ * Create backup of main.ts
670
+ */
671
+ async createBackup() {
672
+ this.backupPath = `${this.mainTsPath}.backup`;
673
+ await fs5.copy(this.mainTsPath, this.backupPath);
674
+ }
675
+ /**
676
+ * Restore backup
677
+ */
678
+ async restoreBackup() {
679
+ if (this.backupPath && await fs5.pathExists(this.backupPath)) {
680
+ await fs5.copy(this.backupPath, this.mainTsPath, { overwrite: true });
681
+ await fs5.remove(this.backupPath);
682
+ }
683
+ }
684
+ /**
685
+ * Clean up backup
686
+ */
687
+ async cleanupBackup() {
688
+ if (this.backupPath && await fs5.pathExists(this.backupPath)) {
689
+ await fs5.remove(this.backupPath);
690
+ }
691
+ }
692
+ };
693
+ }
694
+ });
695
+
492
696
  // src/installer/package-updater.ts
493
- var fs5, PackageUpdater;
697
+ var fs6, PackageUpdater;
494
698
  var init_package_updater = __esm({
495
699
  "src/installer/package-updater.ts"() {
496
700
  "use strict";
497
701
  init_cjs_shims();
498
- fs5 = __toESM(require("fs-extra"));
702
+ fs6 = __toESM(require("fs-extra"));
499
703
  PackageUpdater = class {
500
704
  constructor(packageJsonPath) {
501
705
  this.packageJsonPath = packageJsonPath;
@@ -507,7 +711,7 @@ var init_package_updater = __esm({
507
711
  async update(config) {
508
712
  await this.createBackup();
509
713
  try {
510
- const packageJson = await fs5.readJSON(this.packageJsonPath);
714
+ const packageJson = await fs6.readJSON(this.packageJsonPath);
511
715
  const deps = this.getDependencies(config);
512
716
  packageJson.dependencies = {
513
717
  ...packageJson.dependencies,
@@ -521,7 +725,7 @@ var init_package_updater = __esm({
521
725
  packageJson.devDependencies = this.sortObject(
522
726
  packageJson.devDependencies
523
727
  );
524
- await fs5.writeJSON(this.packageJsonPath, packageJson, { spaces: 2 });
728
+ await fs6.writeJSON(this.packageJsonPath, packageJson, { spaces: 2 });
525
729
  } catch (error) {
526
730
  await this.restoreBackup();
527
731
  throw error;
@@ -581,23 +785,23 @@ var init_package_updater = __esm({
581
785
  */
582
786
  async createBackup() {
583
787
  this.backupPath = `${this.packageJsonPath}.backup`;
584
- await fs5.copy(this.packageJsonPath, this.backupPath);
788
+ await fs6.copy(this.packageJsonPath, this.backupPath);
585
789
  }
586
790
  /**
587
791
  * Restore backup
588
792
  */
589
793
  async restoreBackup() {
590
- if (this.backupPath && await fs5.pathExists(this.backupPath)) {
591
- await fs5.copy(this.backupPath, this.packageJsonPath, { overwrite: true });
592
- await fs5.remove(this.backupPath);
794
+ if (this.backupPath && await fs6.pathExists(this.backupPath)) {
795
+ await fs6.copy(this.backupPath, this.packageJsonPath, { overwrite: true });
796
+ await fs6.remove(this.backupPath);
593
797
  }
594
798
  }
595
799
  /**
596
800
  * Clean up backup
597
801
  */
598
802
  async cleanupBackup() {
599
- if (this.backupPath && await fs5.pathExists(this.backupPath)) {
600
- await fs5.remove(this.backupPath);
803
+ if (this.backupPath && await fs6.pathExists(this.backupPath)) {
804
+ await fs6.remove(this.backupPath);
601
805
  }
602
806
  }
603
807
  };
@@ -650,6 +854,7 @@ var installer_exports = {};
650
854
  __export(installer_exports, {
651
855
  AppModuleUpdater: () => AppModuleUpdater,
652
856
  DependencyInstaller: () => DependencyInstaller,
857
+ MainTsUpdater: () => MainTsUpdater,
653
858
  PackageUpdater: () => PackageUpdater
654
859
  });
655
860
  var init_installer = __esm({
@@ -657,6 +862,7 @@ var init_installer = __esm({
657
862
  "use strict";
658
863
  init_cjs_shims();
659
864
  init_ast_updater();
865
+ init_main_ts_updater();
660
866
  init_package_updater();
661
867
  init_dependency_installer();
662
868
  }
@@ -694,6 +900,25 @@ async function detectORM(packageJson) {
694
900
  }
695
901
  return "none";
696
902
  }
903
+ function detectDatabase(packageJson, orm) {
904
+ const dependencies = {
905
+ ...packageJson.dependencies,
906
+ ...packageJson.devDependencies
907
+ };
908
+ if (orm === "typeorm") {
909
+ if (dependencies["pg"]) return "postgres";
910
+ if (dependencies["mysql2"] || dependencies["mysql"]) return "mysql";
911
+ if (dependencies["sqlite3"]) return "sqlite";
912
+ if (dependencies["mongodb"]) return "mongodb";
913
+ }
914
+ if (orm === "prisma") {
915
+ return void 0;
916
+ }
917
+ if (orm === "mongoose") {
918
+ return "mongodb";
919
+ }
920
+ return void 0;
921
+ }
697
922
 
698
923
  // src/analyzer/project-detector.ts
699
924
  var ProjectDetector = class {
@@ -730,7 +955,9 @@ var ProjectDetector = class {
730
955
  errors.push(`app.module.ts not found at ${sourceRoot}/app.module.ts`);
731
956
  return this.createInvalidProject(root, errors);
732
957
  }
958
+ const mainTsPath = path.join(root, sourceRoot, "main.ts");
733
959
  const orm = await detectORM(packageJson);
960
+ const database = detectDatabase(packageJson, orm);
734
961
  const authModulePath = path.join(root, sourceRoot, "auth");
735
962
  if (await fs.pathExists(authModulePath)) {
736
963
  errors.push("auth/ directory already exists (use --force to overwrite)");
@@ -739,9 +966,11 @@ var ProjectDetector = class {
739
966
  root,
740
967
  sourceRoot,
741
968
  appModulePath,
969
+ mainTsPath,
742
970
  packageJsonPath,
743
971
  nestCliConfigPath,
744
972
  orm,
973
+ database,
745
974
  nestVersion: packageJson.dependencies?.["@nestjs/core"],
746
975
  typescriptVersion: packageJson.devDependencies?.["typescript"],
747
976
  isValid: errors.length === 0,
@@ -781,6 +1010,7 @@ var ProjectDetector = class {
781
1010
  root,
782
1011
  sourceRoot: "src",
783
1012
  appModulePath: path.join(root, "src", "app.module.ts"),
1013
+ mainTsPath: path.join(root, "src", "main.ts"),
784
1014
  packageJsonPath: path.join(root, "package.json"),
785
1015
  nestCliConfigPath: path.join(root, "nest-cli.json"),
786
1016
  orm: "none",
@@ -806,7 +1036,8 @@ function generateSecret(length = 32) {
806
1036
  }
807
1037
 
808
1038
  // src/cli/prompts.ts
809
- async function promptConfig(detectedORM) {
1039
+ async function promptConfig(detectedORM, detectedDB) {
1040
+ const dbLabel = detectedDB ? ` with ${detectedDB.charAt(0).toUpperCase() + detectedDB.slice(1)}` : "";
810
1041
  const answers = await import_inquirer.default.prompt([
811
1042
  {
812
1043
  type: "list",
@@ -878,7 +1109,7 @@ async function promptConfig(detectedORM) {
878
1109
  {
879
1110
  type: "confirm",
880
1111
  name: "useDetectedORM",
881
- message: `Detected ${detectedORM.toUpperCase()}${detectedORM === "typeorm" ? " with PostgreSQL" : ""}. Use it?`,
1112
+ message: `Detected ${detectedORM.toUpperCase()}${dbLabel}. Use it?`,
882
1113
  default: true,
883
1114
  when: () => detectedORM !== "none"
884
1115
  },
@@ -904,7 +1135,7 @@ async function promptConfig(detectedORM) {
904
1135
  ]);
905
1136
  return answers;
906
1137
  }
907
- function buildConfig(answers, projectName, sourceRoot, detectedORM) {
1138
+ function buildConfig(answers, projectName, sourceRoot, detectedORM, detectedDB) {
908
1139
  const config = {
909
1140
  projectName,
910
1141
  sourceRoot,
@@ -914,7 +1145,7 @@ function buildConfig(answers, projectName, sourceRoot, detectedORM) {
914
1145
  roles: answers.roles || []
915
1146
  },
916
1147
  orm: answers.useDetectedORM !== false ? detectedORM : "typeorm",
917
- database: answers.database || "postgres",
1148
+ database: answers.database || detectedDB || "postgres",
918
1149
  features: {
919
1150
  refreshTokens: answers.refreshTokens
920
1151
  },
@@ -1056,12 +1287,13 @@ async function run(cwd = process.cwd()) {
1056
1287
  orm: projectInfo.orm,
1057
1288
  sourceRoot: projectInfo.sourceRoot
1058
1289
  });
1059
- const answers = await promptConfig(projectInfo.orm);
1290
+ const answers = await promptConfig(projectInfo.orm, projectInfo.database);
1060
1291
  const config = buildConfig(
1061
1292
  answers,
1062
1293
  projectInfo.root.split(/[/\\]/).pop() || "project",
1063
1294
  projectInfo.sourceRoot,
1064
- projectInfo.orm
1295
+ projectInfo.orm,
1296
+ projectInfo.database
1065
1297
  );
1066
1298
  console.log();
1067
1299
  console.log("\u2699\uFE0F Generating authentication module...");
@@ -1076,11 +1308,14 @@ async function run(cwd = process.cwd()) {
1076
1308
  process.exit(1);
1077
1309
  }
1078
1310
  genSpinner.succeed(`Generated ${result.filesCreated.length} files`);
1311
+ if (result.filesSkipped.length > 0) {
1312
+ console.log(` \u26A0\uFE0F Skipped ${result.filesSkipped.length} existing file(s)`);
1313
+ }
1079
1314
  const astSpinner = createSpinner("Updating app.module.ts...").start();
1080
1315
  try {
1081
1316
  const { AppModuleUpdater: AppModuleUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1082
1317
  const astUpdater = new AppModuleUpdater2(projectInfo.appModulePath);
1083
- await astUpdater.update();
1318
+ await astUpdater.update(config);
1084
1319
  await astUpdater.cleanupBackup();
1085
1320
  astSpinner.succeed("Updated app.module.ts");
1086
1321
  } catch (error) {
@@ -1091,6 +1326,16 @@ async function run(cwd = process.cwd()) {
1091
1326
  );
1092
1327
  process.exit(1);
1093
1328
  }
1329
+ const mainSpinner = createSpinner("Updating main.ts with global guards...").start();
1330
+ try {
1331
+ const { MainTsUpdater: MainTsUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1332
+ const mainUpdater = new MainTsUpdater2(projectInfo.mainTsPath);
1333
+ await mainUpdater.update();
1334
+ await mainUpdater.cleanupBackup();
1335
+ mainSpinner.succeed("Updated main.ts with global JWT guard");
1336
+ } catch (error) {
1337
+ mainSpinner.warn("Could not auto-update main.ts (see main.ts.example for manual setup)");
1338
+ }
1094
1339
  const pkgSpinner = createSpinner("Updating package.json...").start();
1095
1340
  try {
1096
1341
  const { PackageUpdater: PackageUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
@@ -1128,8 +1373,8 @@ async function run(cwd = process.cwd()) {
1128
1373
  refreshExpiration: config.features.refreshTokens ? config.jwt.refreshExpiration : void 0
1129
1374
  }
1130
1375
  });
1131
- console.log("\u{1F41B} Issues? https://github.com/yourusername/add-nest-auth/issues");
1132
- console.log("\u2B50 Like it? https://github.com/yourusername/add-nest-auth");
1376
+ console.log("\u{1F41B} Issues? https://github.com/Islamawad132/add-nest-auth/issues");
1377
+ console.log("\u2B50 Like it? https://github.com/Islamawad132/add-nest-auth");
1133
1378
  console.log();
1134
1379
  }
1135
1380