add-nest-auth 1.0.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 (33) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +368 -0
  3. package/bin/cli.js +11 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +1133 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/generator/templates/decorators/current-user.decorator.ts.hbs +8 -0
  8. package/dist/generator/templates/decorators/public.decorator.ts.hbs +4 -0
  9. package/dist/generator/templates/decorators/roles.decorator.ts.hbs +4 -0
  10. package/dist/generator/templates/dto/auth-response.dto.ts.hbs +13 -0
  11. package/dist/generator/templates/dto/create-user.dto.ts.hbs +17 -0
  12. package/dist/generator/templates/dto/login.dto.ts.hbs +12 -0
  13. package/dist/generator/templates/dto/register.dto.ts.hbs +13 -0
  14. package/dist/generator/templates/entities/refresh-token.entity.typeorm.hbs +24 -0
  15. package/dist/generator/templates/entities/user.entity.typeorm.hbs +30 -0
  16. package/dist/generator/templates/jwt/auth.controller.ts.hbs +34 -0
  17. package/dist/generator/templates/jwt/auth.module.ts.hbs +48 -0
  18. package/dist/generator/templates/jwt/auth.service.ts.hbs +193 -0
  19. package/dist/generator/templates/jwt/jwt-auth.guard.ts.hbs +24 -0
  20. package/dist/generator/templates/jwt/jwt.strategy.ts.hbs +52 -0
  21. package/dist/generator/templates/jwt/local-auth.guard.ts.hbs +5 -0
  22. package/dist/generator/templates/jwt/local.strategy.ts.hbs +22 -0
  23. package/dist/generator/templates/rbac/role.enum.ts.hbs +5 -0
  24. package/dist/generator/templates/rbac/roles.guard.ts.hbs +22 -0
  25. package/dist/generator/templates/shared/README.auth.md.hbs +283 -0
  26. package/dist/generator/templates/shared/env.template.hbs +29 -0
  27. package/dist/generator/templates/users/users.controller.ts.hbs +31 -0
  28. package/dist/generator/templates/users/users.module.ts.hbs +27 -0
  29. package/dist/generator/templates/users/users.service.ts.hbs +93 -0
  30. package/dist/index.d.ts +6 -0
  31. package/dist/index.js +1130 -0
  32. package/dist/index.js.map +1 -0
  33. package/package.json +62 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1133 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+
32
+ // node_modules/tsup/assets/cjs_shims.js
33
+ var init_cjs_shims = __esm({
34
+ "node_modules/tsup/assets/cjs_shims.js"() {
35
+ "use strict";
36
+ }
37
+ });
38
+
39
+ // src/generator/template-engine.ts
40
+ var import_handlebars, path2, fs2, TemplateEngine;
41
+ var init_template_engine = __esm({
42
+ "src/generator/template-engine.ts"() {
43
+ "use strict";
44
+ init_cjs_shims();
45
+ import_handlebars = __toESM(require("handlebars"));
46
+ path2 = __toESM(require("path"));
47
+ fs2 = __toESM(require("fs-extra"));
48
+ TemplateEngine = class {
49
+ handlebars;
50
+ templateCache;
51
+ templatesDir;
52
+ constructor(templatesDir) {
53
+ this.handlebars = import_handlebars.default.create();
54
+ this.templateCache = /* @__PURE__ */ new Map();
55
+ this.templatesDir = templatesDir || path2.join(__dirname, "templates");
56
+ this.registerHelpers();
57
+ }
58
+ /**
59
+ * Register Handlebars helpers
60
+ */
61
+ registerHelpers() {
62
+ this.handlebars.registerHelper("eq", (a, b) => a === b);
63
+ this.handlebars.registerHelper("ne", (a, b) => a !== b);
64
+ this.handlebars.registerHelper("includes", (arr, item) => arr?.includes(item));
65
+ this.handlebars.registerHelper("capitalize", (str) => {
66
+ if (!str) return "";
67
+ return str.charAt(0).toUpperCase() + str.slice(1);
68
+ });
69
+ this.handlebars.registerHelper("lowercase", (str) => {
70
+ if (!str) return "";
71
+ return str.toLowerCase();
72
+ });
73
+ this.handlebars.registerHelper("uppercase", (str) => {
74
+ if (!str) return "";
75
+ return str.toUpperCase();
76
+ });
77
+ this.handlebars.registerHelper("camelCase", (str) => {
78
+ if (!str) return "";
79
+ return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
80
+ });
81
+ this.handlebars.registerHelper("pascalCase", (str) => {
82
+ if (!str) return "";
83
+ const camel = str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
84
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
85
+ });
86
+ }
87
+ /**
88
+ * Render a template with context
89
+ */
90
+ async render(templatePath, context) {
91
+ const template = await this.loadTemplate(templatePath);
92
+ return template(context);
93
+ }
94
+ /**
95
+ * Load a template (with caching)
96
+ */
97
+ async loadTemplate(templatePath) {
98
+ if (this.templateCache.has(templatePath)) {
99
+ return this.templateCache.get(templatePath);
100
+ }
101
+ const fullPath = path2.join(this.templatesDir, templatePath);
102
+ if (!await fs2.pathExists(fullPath)) {
103
+ throw new Error(`Template not found: ${templatePath}`);
104
+ }
105
+ const source = await fs2.readFile(fullPath, "utf-8");
106
+ const compiled = this.handlebars.compile(source);
107
+ this.templateCache.set(templatePath, compiled);
108
+ return compiled;
109
+ }
110
+ /**
111
+ * Clear template cache
112
+ */
113
+ clearCache() {
114
+ this.templateCache.clear();
115
+ }
116
+ };
117
+ }
118
+ });
119
+
120
+ // src/generator/file-writer.ts
121
+ var path3, fs3, FileWriter;
122
+ var init_file_writer = __esm({
123
+ "src/generator/file-writer.ts"() {
124
+ "use strict";
125
+ init_cjs_shims();
126
+ path3 = __toESM(require("path"));
127
+ fs3 = __toESM(require("fs-extra"));
128
+ FileWriter = class {
129
+ writtenFiles = [];
130
+ backups = /* @__PURE__ */ new Map();
131
+ /**
132
+ * Write a file to disk
133
+ */
134
+ async writeFile(filePath, content, options = {}) {
135
+ const { overwrite = false, backup = true } = options;
136
+ const exists = await fs3.pathExists(filePath);
137
+ if (exists && !overwrite) {
138
+ throw new Error(`File already exists: ${filePath}`);
139
+ }
140
+ if (exists && backup) {
141
+ await this.createBackup(filePath);
142
+ }
143
+ await fs3.ensureDir(path3.dirname(filePath));
144
+ await fs3.writeFile(filePath, content, "utf-8");
145
+ this.writtenFiles.push(filePath);
146
+ }
147
+ /**
148
+ * Create a backup of an existing file
149
+ */
150
+ async createBackup(filePath) {
151
+ const backupPath = `${filePath}.backup`;
152
+ await fs3.copy(filePath, backupPath);
153
+ this.backups.set(filePath, backupPath);
154
+ }
155
+ /**
156
+ * Rollback all written files
157
+ */
158
+ async rollback() {
159
+ for (const [originalPath, backupPath] of this.backups) {
160
+ if (await fs3.pathExists(backupPath)) {
161
+ await fs3.copy(backupPath, originalPath, { overwrite: true });
162
+ await fs3.remove(backupPath);
163
+ }
164
+ }
165
+ for (const filePath of this.writtenFiles) {
166
+ if (!this.backups.has(filePath) && await fs3.pathExists(filePath)) {
167
+ await fs3.remove(filePath);
168
+ }
169
+ }
170
+ this.writtenFiles = [];
171
+ this.backups.clear();
172
+ }
173
+ /**
174
+ * Clean up backups
175
+ */
176
+ async cleanupBackups() {
177
+ for (const backupPath of this.backups.values()) {
178
+ if (await fs3.pathExists(backupPath)) {
179
+ await fs3.remove(backupPath);
180
+ }
181
+ }
182
+ this.backups.clear();
183
+ }
184
+ /**
185
+ * Get list of written files
186
+ */
187
+ getWrittenFiles() {
188
+ return [...this.writtenFiles];
189
+ }
190
+ };
191
+ }
192
+ });
193
+
194
+ // src/config/config-builder.ts
195
+ function buildTemplateContext(config) {
196
+ return {
197
+ ...config
198
+ // Add any additional computed properties here
199
+ };
200
+ }
201
+ var init_config_builder = __esm({
202
+ "src/config/config-builder.ts"() {
203
+ "use strict";
204
+ init_cjs_shims();
205
+ }
206
+ });
207
+
208
+ // src/generator/generator.ts
209
+ var path4, Generator;
210
+ var init_generator = __esm({
211
+ "src/generator/generator.ts"() {
212
+ "use strict";
213
+ init_cjs_shims();
214
+ path4 = __toESM(require("path"));
215
+ init_template_engine();
216
+ init_file_writer();
217
+ init_config_builder();
218
+ Generator = class {
219
+ templateEngine;
220
+ fileWriter;
221
+ constructor() {
222
+ this.templateEngine = new TemplateEngine();
223
+ this.fileWriter = new FileWriter();
224
+ }
225
+ /**
226
+ * Generate all authentication files
227
+ */
228
+ async generate(config, projectInfo) {
229
+ try {
230
+ const context = buildTemplateContext(config);
231
+ const plan = this.buildGenerationPlan(config);
232
+ for (const fileSpec of plan) {
233
+ if (fileSpec.condition && !fileSpec.condition(config)) {
234
+ continue;
235
+ }
236
+ const content = await this.templateEngine.render(
237
+ fileSpec.template,
238
+ context
239
+ );
240
+ const outputPath = path4.join(projectInfo.root, fileSpec.output);
241
+ await this.fileWriter.writeFile(outputPath, content, {
242
+ overwrite: false
243
+ });
244
+ }
245
+ const filesCreated = this.fileWriter.getWrittenFiles();
246
+ await this.fileWriter.cleanupBackups();
247
+ return {
248
+ filesCreated,
249
+ success: true
250
+ };
251
+ } catch (error) {
252
+ await this.fileWriter.rollback();
253
+ return {
254
+ filesCreated: [],
255
+ success: false,
256
+ error: error instanceof Error ? error.message : "Unknown error"
257
+ };
258
+ }
259
+ }
260
+ /**
261
+ * Build file generation plan
262
+ */
263
+ buildGenerationPlan(config) {
264
+ const plan = [];
265
+ plan.push(
266
+ { template: "jwt/auth.module.ts.hbs", output: `${config.sourceRoot}/auth/auth.module.ts` },
267
+ { template: "jwt/auth.service.ts.hbs", output: `${config.sourceRoot}/auth/auth.service.ts` },
268
+ { template: "jwt/auth.controller.ts.hbs", output: `${config.sourceRoot}/auth/auth.controller.ts` }
269
+ );
270
+ plan.push(
271
+ { template: "jwt/jwt.strategy.ts.hbs", output: `${config.sourceRoot}/auth/strategies/jwt.strategy.ts` },
272
+ { template: "jwt/local.strategy.ts.hbs", output: `${config.sourceRoot}/auth/strategies/local.strategy.ts` }
273
+ );
274
+ plan.push(
275
+ { template: "jwt/jwt-auth.guard.ts.hbs", output: `${config.sourceRoot}/auth/guards/jwt-auth.guard.ts` },
276
+ { template: "jwt/local-auth.guard.ts.hbs", output: `${config.sourceRoot}/auth/guards/local-auth.guard.ts` }
277
+ );
278
+ if (config.rbac.enabled) {
279
+ plan.push(
280
+ { template: "rbac/roles.guard.ts.hbs", output: `${config.sourceRoot}/auth/guards/roles.guard.ts` },
281
+ { template: "rbac/role.enum.ts.hbs", output: `${config.sourceRoot}/auth/enums/role.enum.ts` },
282
+ { template: "decorators/roles.decorator.ts.hbs", output: `${config.sourceRoot}/auth/decorators/roles.decorator.ts` }
283
+ );
284
+ }
285
+ plan.push(
286
+ { template: "decorators/public.decorator.ts.hbs", output: `${config.sourceRoot}/auth/decorators/public.decorator.ts` },
287
+ { template: "decorators/current-user.decorator.ts.hbs", output: `${config.sourceRoot}/auth/decorators/current-user.decorator.ts` }
288
+ );
289
+ plan.push(
290
+ { template: "dto/login.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/login.dto.ts` },
291
+ { template: "dto/register.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/register.dto.ts` },
292
+ { template: "dto/auth-response.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/auth-response.dto.ts` },
293
+ { template: "dto/create-user.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/create-user.dto.ts` }
294
+ );
295
+ plan.push(
296
+ { template: "users/users.module.ts.hbs", output: `${config.sourceRoot}/users/users.module.ts` },
297
+ { template: "users/users.service.ts.hbs", output: `${config.sourceRoot}/users/users.service.ts` },
298
+ { template: "users/users.controller.ts.hbs", output: `${config.sourceRoot}/users/users.controller.ts` }
299
+ );
300
+ if (config.orm === "typeorm") {
301
+ plan.push(
302
+ { template: "entities/user.entity.typeorm.hbs", output: `${config.sourceRoot}/users/entities/user.entity.ts` }
303
+ );
304
+ if (config.features.refreshTokens) {
305
+ plan.push({
306
+ template: "entities/refresh-token.entity.typeorm.hbs",
307
+ output: `${config.sourceRoot}/users/entities/refresh-token.entity.ts`
308
+ });
309
+ }
310
+ }
311
+ plan.push(
312
+ { template: "shared/env.template.hbs", output: ".env.example" },
313
+ { template: "shared/README.auth.md.hbs", output: `${config.sourceRoot}/auth/README.md` }
314
+ );
315
+ return plan;
316
+ }
317
+ };
318
+ }
319
+ });
320
+
321
+ // src/generator/index.ts
322
+ var generator_exports = {};
323
+ __export(generator_exports, {
324
+ FileWriter: () => FileWriter,
325
+ Generator: () => Generator,
326
+ TemplateEngine: () => TemplateEngine
327
+ });
328
+ var init_generator2 = __esm({
329
+ "src/generator/index.ts"() {
330
+ "use strict";
331
+ init_cjs_shims();
332
+ init_generator();
333
+ init_template_engine();
334
+ init_file_writer();
335
+ }
336
+ });
337
+
338
+ // src/installer/ast-updater.ts
339
+ var import_ts_morph, fs4, AppModuleUpdater;
340
+ var init_ast_updater = __esm({
341
+ "src/installer/ast-updater.ts"() {
342
+ "use strict";
343
+ init_cjs_shims();
344
+ import_ts_morph = require("ts-morph");
345
+ fs4 = __toESM(require("fs-extra"));
346
+ AppModuleUpdater = class {
347
+ constructor(appModulePath) {
348
+ this.appModulePath = appModulePath;
349
+ this.project = new import_ts_morph.Project({
350
+ skipAddingFilesFromTsConfig: true
351
+ });
352
+ }
353
+ project;
354
+ sourceFile;
355
+ backupPath = null;
356
+ /**
357
+ * Update app.module.ts with auth modules
358
+ */
359
+ async update() {
360
+ await this.createBackup();
361
+ try {
362
+ this.sourceFile = this.project.addSourceFileAtPath(this.appModulePath);
363
+ this.addImports();
364
+ this.addModulesToDecorator();
365
+ await this.sourceFile.save();
366
+ } catch (error) {
367
+ await this.restoreBackup();
368
+ throw error;
369
+ }
370
+ }
371
+ /**
372
+ * Add necessary imports
373
+ */
374
+ addImports() {
375
+ if (!this.sourceFile) {
376
+ throw new Error("Source file not loaded");
377
+ }
378
+ this.addImport("@nestjs/config", ["ConfigModule"]);
379
+ this.addImport("./auth/auth.module", ["AuthModule"]);
380
+ this.addImport("./users/users.module", ["UsersModule"]);
381
+ }
382
+ /**
383
+ * Add an import statement if it doesn't exist
384
+ */
385
+ addImport(moduleSpecifier, namedImports) {
386
+ if (!this.sourceFile) return;
387
+ const existingImport = this.sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue() === moduleSpecifier);
388
+ if (existingImport) {
389
+ const existingNames = existingImport.getNamedImports().map((ni) => ni.getName());
390
+ const missingImports = namedImports.filter(
391
+ (name) => !existingNames.includes(name)
392
+ );
393
+ if (missingImports.length > 0) {
394
+ existingImport.addNamedImports(missingImports);
395
+ }
396
+ } else {
397
+ this.sourceFile.addImportDeclaration({
398
+ moduleSpecifier,
399
+ namedImports
400
+ });
401
+ }
402
+ }
403
+ /**
404
+ * Add modules to @Module decorator imports array
405
+ */
406
+ addModulesToDecorator() {
407
+ if (!this.sourceFile) return;
408
+ const appModuleClass = this.sourceFile.getClass("AppModule");
409
+ if (!appModuleClass) {
410
+ throw new Error("AppModule class not found");
411
+ }
412
+ const moduleDecorator = appModuleClass.getDecorator("Module");
413
+ if (!moduleDecorator) {
414
+ throw new Error("@Module decorator not found");
415
+ }
416
+ const decoratorArgs = moduleDecorator.getArguments()[0];
417
+ if (!decoratorArgs || !import_ts_morph.Node.isObjectLiteralExpression(decoratorArgs)) {
418
+ throw new Error("Invalid @Module decorator structure");
419
+ }
420
+ let importsProperty = decoratorArgs.getProperty("imports");
421
+ if (!importsProperty) {
422
+ decoratorArgs.addPropertyAssignment({
423
+ name: "imports",
424
+ initializer: "[]"
425
+ });
426
+ importsProperty = decoratorArgs.getProperty("imports");
427
+ }
428
+ if (!importsProperty || !import_ts_morph.Node.isPropertyAssignment(importsProperty)) {
429
+ throw new Error("Invalid imports property");
430
+ }
431
+ const importsArray = importsProperty.getInitializer();
432
+ if (!import_ts_morph.Node.isArrayLiteralExpression(importsArray)) {
433
+ throw new Error("imports is not an array");
434
+ }
435
+ const existingModules = this.getExistingModuleNames(importsArray);
436
+ if (!existingModules.has("ConfigModule")) {
437
+ importsArray.addElement("ConfigModule.forRoot({ isGlobal: true })");
438
+ }
439
+ if (!existingModules.has("AuthModule")) {
440
+ importsArray.addElement("AuthModule");
441
+ }
442
+ if (!existingModules.has("UsersModule")) {
443
+ importsArray.addElement("UsersModule");
444
+ }
445
+ }
446
+ /**
447
+ * Get existing module names from imports array
448
+ */
449
+ getExistingModuleNames(importsArray) {
450
+ const moduleNames = /* @__PURE__ */ new Set();
451
+ if (!import_ts_morph.Node.isArrayLiteralExpression(importsArray)) {
452
+ return moduleNames;
453
+ }
454
+ for (const element of importsArray.getElements()) {
455
+ const text = element.getText();
456
+ const match = text.match(/^(\w+)/);
457
+ if (match) {
458
+ moduleNames.add(match[1]);
459
+ }
460
+ }
461
+ return moduleNames;
462
+ }
463
+ /**
464
+ * Create backup of app.module.ts
465
+ */
466
+ async createBackup() {
467
+ this.backupPath = `${this.appModulePath}.backup`;
468
+ await fs4.copy(this.appModulePath, this.backupPath);
469
+ }
470
+ /**
471
+ * Restore backup
472
+ */
473
+ async restoreBackup() {
474
+ if (this.backupPath && await fs4.pathExists(this.backupPath)) {
475
+ await fs4.copy(this.backupPath, this.appModulePath, { overwrite: true });
476
+ await fs4.remove(this.backupPath);
477
+ }
478
+ }
479
+ /**
480
+ * Clean up backup
481
+ */
482
+ async cleanupBackup() {
483
+ if (this.backupPath && await fs4.pathExists(this.backupPath)) {
484
+ await fs4.remove(this.backupPath);
485
+ }
486
+ }
487
+ };
488
+ }
489
+ });
490
+
491
+ // src/installer/package-updater.ts
492
+ var fs5, PackageUpdater;
493
+ var init_package_updater = __esm({
494
+ "src/installer/package-updater.ts"() {
495
+ "use strict";
496
+ init_cjs_shims();
497
+ fs5 = __toESM(require("fs-extra"));
498
+ PackageUpdater = class {
499
+ constructor(packageJsonPath) {
500
+ this.packageJsonPath = packageJsonPath;
501
+ }
502
+ backupPath = null;
503
+ /**
504
+ * Update package.json with auth dependencies
505
+ */
506
+ async update(config) {
507
+ await this.createBackup();
508
+ try {
509
+ const packageJson = await fs5.readJSON(this.packageJsonPath);
510
+ const deps = this.getDependencies(config);
511
+ packageJson.dependencies = {
512
+ ...packageJson.dependencies,
513
+ ...deps.dependencies
514
+ };
515
+ packageJson.devDependencies = {
516
+ ...packageJson.devDependencies,
517
+ ...deps.devDependencies
518
+ };
519
+ packageJson.dependencies = this.sortObject(packageJson.dependencies);
520
+ packageJson.devDependencies = this.sortObject(
521
+ packageJson.devDependencies
522
+ );
523
+ await fs5.writeJSON(this.packageJsonPath, packageJson, { spaces: 2 });
524
+ } catch (error) {
525
+ await this.restoreBackup();
526
+ throw error;
527
+ }
528
+ }
529
+ /**
530
+ * Get dependencies based on configuration
531
+ */
532
+ getDependencies(config) {
533
+ const dependencies = {
534
+ "@nestjs/jwt": "^11.0.0",
535
+ "@nestjs/passport": "^11.0.0",
536
+ "@nestjs/config": "^3.0.0",
537
+ passport: "^0.7.0",
538
+ "passport-jwt": "^4.0.1",
539
+ "passport-local": "^1.0.0",
540
+ bcrypt: "^5.1.1",
541
+ "class-validator": "^0.14.0",
542
+ "class-transformer": "^0.5.1"
543
+ };
544
+ const devDependencies = {
545
+ "@types/passport-jwt": "^4.0.0",
546
+ "@types/passport-local": "^1.0.36",
547
+ "@types/bcrypt": "^5.0.2"
548
+ };
549
+ if (config.orm === "typeorm") {
550
+ dependencies["@nestjs/typeorm"] = "^11.0.0";
551
+ dependencies["typeorm"] = "^0.3.20";
552
+ switch (config.database) {
553
+ case "postgres":
554
+ dependencies["pg"] = "^8.11.3";
555
+ break;
556
+ case "mysql":
557
+ dependencies["mysql2"] = "^3.9.1";
558
+ break;
559
+ case "sqlite":
560
+ dependencies["sqlite3"] = "^5.1.7";
561
+ break;
562
+ case "mongodb":
563
+ dependencies["mongodb"] = "^6.3.0";
564
+ break;
565
+ }
566
+ }
567
+ return { dependencies, devDependencies };
568
+ }
569
+ /**
570
+ * Sort object keys alphabetically
571
+ */
572
+ sortObject(obj) {
573
+ return Object.keys(obj).sort().reduce((sorted, key) => {
574
+ sorted[key] = obj[key];
575
+ return sorted;
576
+ }, {});
577
+ }
578
+ /**
579
+ * Create backup
580
+ */
581
+ async createBackup() {
582
+ this.backupPath = `${this.packageJsonPath}.backup`;
583
+ await fs5.copy(this.packageJsonPath, this.backupPath);
584
+ }
585
+ /**
586
+ * Restore backup
587
+ */
588
+ async restoreBackup() {
589
+ if (this.backupPath && await fs5.pathExists(this.backupPath)) {
590
+ await fs5.copy(this.backupPath, this.packageJsonPath, { overwrite: true });
591
+ await fs5.remove(this.backupPath);
592
+ }
593
+ }
594
+ /**
595
+ * Clean up backup
596
+ */
597
+ async cleanupBackup() {
598
+ if (this.backupPath && await fs5.pathExists(this.backupPath)) {
599
+ await fs5.remove(this.backupPath);
600
+ }
601
+ }
602
+ };
603
+ }
604
+ });
605
+
606
+ // src/installer/dependency-installer.ts
607
+ var import_execa, import_detect_package_manager, DependencyInstaller;
608
+ var init_dependency_installer = __esm({
609
+ "src/installer/dependency-installer.ts"() {
610
+ "use strict";
611
+ init_cjs_shims();
612
+ import_execa = require("execa");
613
+ import_detect_package_manager = require("detect-package-manager");
614
+ DependencyInstaller = class {
615
+ /**
616
+ * Install dependencies using the detected package manager
617
+ */
618
+ async install(cwd) {
619
+ const packageManager = await this.detectPackageManager(cwd);
620
+ console.log(`\u{1F4E6} Installing dependencies with ${packageManager}...`);
621
+ try {
622
+ await (0, import_execa.execa)(packageManager, ["install"], {
623
+ cwd,
624
+ stdio: "inherit"
625
+ });
626
+ console.log("\u2705 Dependencies installed successfully");
627
+ } catch (error) {
628
+ throw new Error(
629
+ `Failed to install dependencies with ${packageManager}: ${error instanceof Error ? error.message : "Unknown error"}`
630
+ );
631
+ }
632
+ }
633
+ /**
634
+ * Detect which package manager is being used
635
+ */
636
+ async detectPackageManager(cwd) {
637
+ try {
638
+ return await (0, import_detect_package_manager.detect)({ cwd });
639
+ } catch (error) {
640
+ return "npm";
641
+ }
642
+ }
643
+ };
644
+ }
645
+ });
646
+
647
+ // src/installer/index.ts
648
+ var installer_exports = {};
649
+ __export(installer_exports, {
650
+ AppModuleUpdater: () => AppModuleUpdater,
651
+ DependencyInstaller: () => DependencyInstaller,
652
+ PackageUpdater: () => PackageUpdater
653
+ });
654
+ var init_installer = __esm({
655
+ "src/installer/index.ts"() {
656
+ "use strict";
657
+ init_cjs_shims();
658
+ init_ast_updater();
659
+ init_package_updater();
660
+ init_dependency_installer();
661
+ }
662
+ });
663
+
664
+ // src/cli.ts
665
+ init_cjs_shims();
666
+
667
+ // src/index.ts
668
+ init_cjs_shims();
669
+
670
+ // src/analyzer/index.ts
671
+ init_cjs_shims();
672
+
673
+ // src/analyzer/project-detector.ts
674
+ init_cjs_shims();
675
+ var path = __toESM(require("path"));
676
+ var fs = __toESM(require("fs-extra"));
677
+
678
+ // src/analyzer/orm-detector.ts
679
+ init_cjs_shims();
680
+ async function detectORM(packageJson) {
681
+ const dependencies = {
682
+ ...packageJson.dependencies,
683
+ ...packageJson.devDependencies
684
+ };
685
+ if (dependencies["@nestjs/typeorm"] || dependencies["typeorm"]) {
686
+ return "typeorm";
687
+ }
688
+ if (dependencies["@prisma/client"] || dependencies["prisma"]) {
689
+ return "prisma";
690
+ }
691
+ if (dependencies["@nestjs/mongoose"] || dependencies["mongoose"]) {
692
+ return "mongoose";
693
+ }
694
+ return "none";
695
+ }
696
+
697
+ // src/analyzer/project-detector.ts
698
+ var ProjectDetector = class {
699
+ constructor(cwd) {
700
+ this.cwd = cwd;
701
+ }
702
+ /**
703
+ * Detect and validate a NestJS project
704
+ */
705
+ async detectProject() {
706
+ const errors = [];
707
+ const root = this.cwd;
708
+ const packageJsonPath = path.join(root, "package.json");
709
+ if (!await fs.pathExists(packageJsonPath)) {
710
+ errors.push("package.json not found");
711
+ return this.createInvalidProject(root, errors);
712
+ }
713
+ const packageJson = await this.readPackageJson(packageJsonPath);
714
+ if (!packageJson) {
715
+ errors.push("Failed to read package.json");
716
+ return this.createInvalidProject(root, errors);
717
+ }
718
+ const hasNestCore = packageJson.dependencies?.["@nestjs/core"];
719
+ const hasNestCommon = packageJson.dependencies?.["@nestjs/common"];
720
+ if (!hasNestCore || !hasNestCommon) {
721
+ errors.push("Not a NestJS project (missing @nestjs/core or @nestjs/common)");
722
+ return this.createInvalidProject(root, errors);
723
+ }
724
+ const nestCliConfigPath = path.join(root, "nest-cli.json");
725
+ const nestCliConfig = await this.readNestCliConfig(nestCliConfigPath);
726
+ const sourceRoot = nestCliConfig?.sourceRoot || "src";
727
+ const appModulePath = path.join(root, sourceRoot, "app.module.ts");
728
+ if (!await fs.pathExists(appModulePath)) {
729
+ errors.push(`app.module.ts not found at ${sourceRoot}/app.module.ts`);
730
+ return this.createInvalidProject(root, errors);
731
+ }
732
+ const orm = await detectORM(packageJson);
733
+ const authModulePath = path.join(root, sourceRoot, "auth");
734
+ if (await fs.pathExists(authModulePath)) {
735
+ errors.push("auth/ directory already exists (use --force to overwrite)");
736
+ }
737
+ return {
738
+ root,
739
+ sourceRoot,
740
+ appModulePath,
741
+ packageJsonPath,
742
+ nestCliConfigPath,
743
+ orm,
744
+ nestVersion: packageJson.dependencies?.["@nestjs/core"],
745
+ typescriptVersion: packageJson.devDependencies?.["typescript"],
746
+ isValid: errors.length === 0,
747
+ errors
748
+ };
749
+ }
750
+ /**
751
+ * Read and parse package.json
752
+ */
753
+ async readPackageJson(packageJsonPath) {
754
+ try {
755
+ const content = await fs.readFile(packageJsonPath, "utf-8");
756
+ return JSON.parse(content);
757
+ } catch (error) {
758
+ return null;
759
+ }
760
+ }
761
+ /**
762
+ * Read and parse nest-cli.json
763
+ */
764
+ async readNestCliConfig(nestCliConfigPath) {
765
+ try {
766
+ if (!await fs.pathExists(nestCliConfigPath)) {
767
+ return null;
768
+ }
769
+ const content = await fs.readFile(nestCliConfigPath, "utf-8");
770
+ return JSON.parse(content);
771
+ } catch (error) {
772
+ return null;
773
+ }
774
+ }
775
+ /**
776
+ * Create invalid project info object
777
+ */
778
+ createInvalidProject(root, errors) {
779
+ return {
780
+ root,
781
+ sourceRoot: "src",
782
+ appModulePath: path.join(root, "src", "app.module.ts"),
783
+ packageJsonPath: path.join(root, "package.json"),
784
+ nestCliConfigPath: path.join(root, "nest-cli.json"),
785
+ orm: "none",
786
+ isValid: false,
787
+ errors
788
+ };
789
+ }
790
+ };
791
+ async function detectProject(cwd = process.cwd()) {
792
+ const detector = new ProjectDetector(cwd);
793
+ return detector.detectProject();
794
+ }
795
+
796
+ // src/cli/prompts.ts
797
+ init_cjs_shims();
798
+ var import_inquirer = __toESM(require("inquirer"));
799
+
800
+ // src/config/utils.ts
801
+ init_cjs_shims();
802
+ var import_crypto = require("crypto");
803
+ function generateSecret(length = 32) {
804
+ return (0, import_crypto.randomBytes)(length).toString("base64");
805
+ }
806
+
807
+ // src/cli/prompts.ts
808
+ async function promptConfig(detectedORM) {
809
+ const answers = await import_inquirer.default.prompt([
810
+ {
811
+ type: "list",
812
+ name: "strategy",
813
+ message: "Choose authentication strategy:",
814
+ choices: [
815
+ { name: "JWT Authentication (Recommended)", value: "jwt" },
816
+ { name: "OAuth 2.0 (Google, GitHub) [v1.1]", value: "oauth", disabled: true },
817
+ { name: "Session-based (Traditional) [v1.2]", value: "session", disabled: true }
818
+ ],
819
+ default: "jwt"
820
+ },
821
+ {
822
+ type: "confirm",
823
+ name: "enableRBAC",
824
+ message: "Enable Role-Based Access Control (RBAC)?",
825
+ default: true
826
+ },
827
+ {
828
+ type: "checkbox",
829
+ name: "roles",
830
+ message: "Select default roles:",
831
+ choices: [
832
+ { name: "Admin", value: "Admin", checked: true },
833
+ { name: "User", value: "User", checked: true },
834
+ { name: "Moderator", value: "Moderator", checked: false },
835
+ { name: "Guest", value: "Guest", checked: false }
836
+ ],
837
+ when: (answers2) => answers2.enableRBAC,
838
+ validate: (input) => {
839
+ if (input.length === 0) {
840
+ return "Please select at least one role";
841
+ }
842
+ return true;
843
+ }
844
+ },
845
+ {
846
+ type: "confirm",
847
+ name: "refreshTokens",
848
+ message: "Enable Refresh Token rotation?",
849
+ default: true
850
+ },
851
+ {
852
+ type: "list",
853
+ name: "accessExpiration",
854
+ message: "JWT Access Token expiration:",
855
+ choices: [
856
+ { name: "15 minutes", value: "15m" },
857
+ { name: "30 minutes", value: "30m" },
858
+ { name: "1 hour (Recommended)", value: "1h" },
859
+ { name: "4 hours", value: "4h" },
860
+ { name: "1 day", value: "1d" }
861
+ ],
862
+ default: "1h"
863
+ },
864
+ {
865
+ type: "list",
866
+ name: "refreshExpiration",
867
+ message: "JWT Refresh Token expiration:",
868
+ choices: [
869
+ { name: "7 days (Recommended)", value: "7d" },
870
+ { name: "30 days", value: "30d" },
871
+ { name: "90 days", value: "90d" },
872
+ { name: "1 year", value: "1y" }
873
+ ],
874
+ default: "7d",
875
+ when: (answers2) => answers2.refreshTokens
876
+ },
877
+ {
878
+ type: "confirm",
879
+ name: "useDetectedORM",
880
+ message: `Detected ${detectedORM.toUpperCase()}${detectedORM === "typeorm" ? " with PostgreSQL" : ""}. Use it?`,
881
+ default: true,
882
+ when: () => detectedORM !== "none"
883
+ },
884
+ {
885
+ type: "list",
886
+ name: "database",
887
+ message: "Select database:",
888
+ choices: [
889
+ { name: "PostgreSQL (Recommended)", value: "postgres" },
890
+ { name: "MySQL", value: "mysql" },
891
+ { name: "SQLite (for testing)", value: "sqlite" },
892
+ { name: "MongoDB", value: "mongodb" }
893
+ ],
894
+ default: "postgres",
895
+ when: (answers2) => detectedORM === "none" || !answers2.useDetectedORM
896
+ },
897
+ {
898
+ type: "confirm",
899
+ name: "autoInstall",
900
+ message: "Auto-install dependencies after generation?",
901
+ default: true
902
+ }
903
+ ]);
904
+ return answers;
905
+ }
906
+ function buildConfig(answers, projectName, sourceRoot, detectedORM) {
907
+ const config = {
908
+ projectName,
909
+ sourceRoot,
910
+ strategy: answers.strategy,
911
+ rbac: {
912
+ enabled: answers.enableRBAC,
913
+ roles: answers.roles || []
914
+ },
915
+ orm: answers.useDetectedORM !== false ? detectedORM : "typeorm",
916
+ database: answers.database || "postgres",
917
+ features: {
918
+ refreshTokens: answers.refreshTokens
919
+ },
920
+ jwt: {
921
+ secret: generateSecret(),
922
+ accessExpiration: answers.accessExpiration,
923
+ refreshExpiration: answers.refreshExpiration || "7d"
924
+ },
925
+ autoInstall: answers.autoInstall,
926
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
927
+ generatorVersion: "1.0.0"
928
+ };
929
+ return config;
930
+ }
931
+
932
+ // src/cli/ui.ts
933
+ init_cjs_shims();
934
+ var import_chalk = __toESM(require("chalk"));
935
+ var import_ora = __toESM(require("ora"));
936
+ function showBanner() {
937
+ console.log(import_chalk.default.cyan(`
938
+ ___ _ _ __ __
939
+ / _ \\ | | | | | \\/ |
940
+ / /_\\ \\_ _ | |_| |__ | \\ / | ___
941
+ | _ | | | || __| '_ \\ | |\\/| |/ _ \\
942
+ | | | | |_| || |_| | | | | | | | __/
943
+ \\_| |_/\\__,_| \\__|_| |_| \\_| |_/\\___|
944
+ `));
945
+ console.log(import_chalk.default.bold("\u{1F510} NestJS Authentication Module Generator v1.0.0"));
946
+ console.log();
947
+ }
948
+ function showProjectInfo(info) {
949
+ console.log(import_chalk.default.green("\u2713"), `Detected NestJS ${info.nestVersion || "project"}`);
950
+ if (info.orm !== "none") {
951
+ console.log(import_chalk.default.green("\u2713"), `Found ${info.orm.toUpperCase()}`);
952
+ }
953
+ console.log(import_chalk.default.green("\u2713"), `Source directory: ${info.sourceRoot}/`);
954
+ console.log(import_chalk.default.green("\u2713"), "No existing auth module found");
955
+ console.log();
956
+ }
957
+ function showError(message, errors) {
958
+ console.log();
959
+ console.log(import_chalk.default.red("\u274C Error:"), import_chalk.default.bold(message));
960
+ if (errors && errors.length > 0) {
961
+ console.log();
962
+ errors.forEach((error) => {
963
+ console.log(import_chalk.default.red(" \u2022"), error);
964
+ });
965
+ }
966
+ console.log();
967
+ }
968
+ function showNestJSHelp() {
969
+ console.log(import_chalk.default.yellow("To create a new NestJS project:"));
970
+ console.log();
971
+ console.log(import_chalk.default.cyan(" npm i -g @nestjs/cli"));
972
+ console.log(import_chalk.default.cyan(" nest new my-project"));
973
+ console.log();
974
+ }
975
+ function showSuccess(stats) {
976
+ console.log();
977
+ console.log(import_chalk.default.green.bold("\u{1F389} Success!"), "Authentication module generated.");
978
+ console.log();
979
+ console.log(import_chalk.default.bold("\u{1F4C1} Files created:"));
980
+ console.log(` \u2022 ${stats.filesCreated} new files in src/auth/ and src/users/`);
981
+ console.log(` \u2022 Updated src/app.module.ts`);
982
+ console.log(` \u2022 Updated package.json`);
983
+ console.log();
984
+ console.log(import_chalk.default.bold("\u{1F4E6} Dependencies added:"));
985
+ console.log(` \u2022 @nestjs/jwt, @nestjs/passport, @nestjs/config`);
986
+ console.log(` \u2022 passport, passport-jwt, passport-local`);
987
+ console.log(` \u2022 bcrypt, class-validator, class-transformer`);
988
+ console.log(` \u2022 ${stats.dependenciesAdded} packages total`);
989
+ console.log();
990
+ console.log(import_chalk.default.bold("\u{1F510} JWT Configuration:"));
991
+ console.log(` \u2022 Access token: ${stats.jwt.accessExpiration}`);
992
+ if (stats.jwt.refreshExpiration) {
993
+ console.log(` \u2022 Refresh token: ${stats.jwt.refreshExpiration}`);
994
+ }
995
+ console.log(` \u2022 Secret: Auto-generated (see .env.example)`);
996
+ console.log();
997
+ console.log(import_chalk.default.bold("\u{1F4CB} Next steps:"));
998
+ console.log(import_chalk.default.cyan(" 1. Copy .env.example to .env"));
999
+ console.log(import_chalk.default.gray(" cp .env.example .env"));
1000
+ console.log();
1001
+ console.log(import_chalk.default.cyan(" 2. Update JWT_SECRET in .env (or keep auto-generated)"));
1002
+ console.log();
1003
+ console.log(import_chalk.default.cyan(" 3. Create database migration (if using TypeORM)"));
1004
+ console.log(import_chalk.default.gray(" npm run migration:generate -- src/migrations/CreateUserTable"));
1005
+ console.log(import_chalk.default.gray(" npm run migration:run"));
1006
+ console.log();
1007
+ console.log(import_chalk.default.cyan(" 4. Start your NestJS app"));
1008
+ console.log(import_chalk.default.gray(" npm run start:dev"));
1009
+ console.log();
1010
+ console.log(import_chalk.default.cyan(" 5. Test authentication endpoints"));
1011
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/register"));
1012
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/login"));
1013
+ console.log(import_chalk.default.gray(" GET http://localhost:3000/users/profile (requires JWT)"));
1014
+ console.log();
1015
+ console.log(import_chalk.default.bold("\u{1F4D6} Full documentation:"), "src/auth/README.md");
1016
+ console.log();
1017
+ console.log(import_chalk.default.bold("\u{1F4A1} Tips:"));
1018
+ console.log(" \u2022 Use @Public() decorator for routes that don't require auth");
1019
+ console.log(" \u2022 Use @Roles('Admin') to restrict routes by role");
1020
+ console.log(" \u2022 Access current user with @CurrentUser() decorator");
1021
+ console.log();
1022
+ }
1023
+ function createSpinner(text) {
1024
+ return (0, import_ora.default)({
1025
+ text,
1026
+ color: "cyan"
1027
+ });
1028
+ }
1029
+
1030
+ // src/index.ts
1031
+ async function run(cwd = process.cwd()) {
1032
+ showBanner();
1033
+ const spinner = createSpinner("Analyzing project...").start();
1034
+ const projectInfo = await detectProject(cwd);
1035
+ if (!projectInfo.isValid) {
1036
+ spinner.fail("Project validation failed");
1037
+ showError("Not a valid NestJS project", projectInfo.errors);
1038
+ showNestJSHelp();
1039
+ process.exit(1);
1040
+ }
1041
+ spinner.succeed("Project analyzed");
1042
+ showProjectInfo({
1043
+ nestVersion: projectInfo.nestVersion,
1044
+ orm: projectInfo.orm,
1045
+ sourceRoot: projectInfo.sourceRoot
1046
+ });
1047
+ const answers = await promptConfig(projectInfo.orm);
1048
+ const config = buildConfig(
1049
+ answers,
1050
+ projectInfo.root.split(/[/\\]/).pop() || "project",
1051
+ projectInfo.sourceRoot,
1052
+ projectInfo.orm
1053
+ );
1054
+ console.log();
1055
+ console.log("\u2699\uFE0F Generating authentication module...");
1056
+ console.log();
1057
+ const { Generator: Generator2 } = await Promise.resolve().then(() => (init_generator2(), generator_exports));
1058
+ const generator = new Generator2();
1059
+ const genSpinner = createSpinner("Generating files from templates...").start();
1060
+ const result = await generator.generate(config, projectInfo);
1061
+ if (!result.success) {
1062
+ genSpinner.fail("Generation failed");
1063
+ showError("Failed to generate files", [result.error || "Unknown error"]);
1064
+ process.exit(1);
1065
+ }
1066
+ genSpinner.succeed(`Generated ${result.filesCreated.length} files`);
1067
+ const astSpinner = createSpinner("Updating app.module.ts...").start();
1068
+ try {
1069
+ const { AppModuleUpdater: AppModuleUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1070
+ const astUpdater = new AppModuleUpdater2(projectInfo.appModulePath);
1071
+ await astUpdater.update();
1072
+ await astUpdater.cleanupBackup();
1073
+ astSpinner.succeed("Updated app.module.ts");
1074
+ } catch (error) {
1075
+ astSpinner.fail("Failed to update app.module.ts");
1076
+ showError(
1077
+ "AST modification failed",
1078
+ [error instanceof Error ? error.message : "Unknown error"]
1079
+ );
1080
+ process.exit(1);
1081
+ }
1082
+ const pkgSpinner = createSpinner("Updating package.json...").start();
1083
+ try {
1084
+ const { PackageUpdater: PackageUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1085
+ const pkgUpdater = new PackageUpdater2(projectInfo.packageJsonPath);
1086
+ await pkgUpdater.update(config);
1087
+ await pkgUpdater.cleanupBackup();
1088
+ pkgSpinner.succeed("Updated package.json");
1089
+ } catch (error) {
1090
+ pkgSpinner.fail("Failed to update package.json");
1091
+ showError(
1092
+ "Package update failed",
1093
+ [error instanceof Error ? error.message : "Unknown error"]
1094
+ );
1095
+ process.exit(1);
1096
+ }
1097
+ if (config.autoInstall) {
1098
+ const installSpinner = createSpinner("Installing dependencies...").start();
1099
+ try {
1100
+ const { DependencyInstaller: DependencyInstaller2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1101
+ const installer = new DependencyInstaller2();
1102
+ await installer.install(projectInfo.root);
1103
+ installSpinner.succeed("Dependencies installed");
1104
+ } catch (error) {
1105
+ installSpinner.fail("Failed to install dependencies");
1106
+ console.log(
1107
+ "\n\u26A0\uFE0F Please run npm install manually to install dependencies\n"
1108
+ );
1109
+ }
1110
+ }
1111
+ showSuccess({
1112
+ filesCreated: result.filesCreated.length,
1113
+ dependenciesAdded: 8,
1114
+ jwt: {
1115
+ accessExpiration: config.jwt.accessExpiration,
1116
+ refreshExpiration: config.features.refreshTokens ? config.jwt.refreshExpiration : void 0
1117
+ }
1118
+ });
1119
+ console.log("\u{1F41B} Issues? https://github.com/yourusername/add-nest-auth/issues");
1120
+ console.log("\u2B50 Like it? https://github.com/yourusername/add-nest-auth");
1121
+ console.log();
1122
+ }
1123
+
1124
+ // src/cli.ts
1125
+ process.on("unhandledRejection", (error) => {
1126
+ console.error("Unhandled rejection:", error);
1127
+ process.exit(1);
1128
+ });
1129
+ run().catch((error) => {
1130
+ console.error("Fatal error:", error);
1131
+ process.exit(1);
1132
+ });
1133
+ //# sourceMappingURL=cli.js.map