nest-authme 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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +305 -0
  3. package/bin/cli.js +11 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +1619 -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 +42 -0
  11. package/dist/generator/templates/dto/change-password.dto.ts.hbs +22 -0
  12. package/dist/generator/templates/dto/create-user.dto.ts.hbs +38 -0
  13. package/dist/generator/templates/dto/forgot-password.dto.ts.hbs +13 -0
  14. package/dist/generator/templates/dto/login.dto.ts.hbs +21 -0
  15. package/dist/generator/templates/dto/register.dto.ts.hbs +33 -0
  16. package/dist/generator/templates/dto/reset-password.dto.ts.hbs +22 -0
  17. package/dist/generator/templates/entities/refresh-token.entity.typeorm.hbs +24 -0
  18. package/dist/generator/templates/entities/user.entity.typeorm.hbs +51 -0
  19. package/dist/generator/templates/jwt/auth.controller.ts.hbs +177 -0
  20. package/dist/generator/templates/jwt/auth.module.ts.hbs +81 -0
  21. package/dist/generator/templates/jwt/auth.service.ts.hbs +416 -0
  22. package/dist/generator/templates/jwt/jwt-auth.guard.ts.hbs +24 -0
  23. package/dist/generator/templates/jwt/jwt.strategy.ts.hbs +61 -0
  24. package/dist/generator/templates/jwt/local-auth.guard.ts.hbs +5 -0
  25. package/dist/generator/templates/jwt/local.strategy.ts.hbs +22 -0
  26. package/dist/generator/templates/prisma/prisma.module.ts.hbs +9 -0
  27. package/dist/generator/templates/prisma/prisma.service.ts.hbs +9 -0
  28. package/dist/generator/templates/prisma/schema.prisma.additions.hbs +40 -0
  29. package/dist/generator/templates/rbac/role.enum.ts.hbs +5 -0
  30. package/dist/generator/templates/rbac/roles.guard.ts.hbs +22 -0
  31. package/dist/generator/templates/shared/README.auth.md.hbs +306 -0
  32. package/dist/generator/templates/shared/env.hbs +36 -0
  33. package/dist/generator/templates/shared/env.template.hbs +36 -0
  34. package/dist/generator/templates/shared/main.ts.snippet.hbs +49 -0
  35. package/dist/generator/templates/tests/auth.controller.spec.ts.hbs +189 -0
  36. package/dist/generator/templates/tests/auth.service.spec.ts.hbs +334 -0
  37. package/dist/generator/templates/users/users.controller.ts.hbs +55 -0
  38. package/dist/generator/templates/users/users.module.ts.hbs +31 -0
  39. package/dist/generator/templates/users/users.service.ts.hbs +192 -0
  40. package/dist/index.d.ts +9 -0
  41. package/dist/index.js +1566 -0
  42. package/dist/index.js.map +1 -0
  43. package/package.json +65 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1619 @@
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/analyzer/orm-detector.ts
40
+ async function detectORM(packageJson) {
41
+ const dependencies = {
42
+ ...packageJson.dependencies,
43
+ ...packageJson.devDependencies
44
+ };
45
+ if (dependencies["@nestjs/typeorm"] || dependencies["typeorm"]) {
46
+ return "typeorm";
47
+ }
48
+ if (dependencies["@prisma/client"] || dependencies["prisma"]) {
49
+ return "prisma";
50
+ }
51
+ if (dependencies["@nestjs/mongoose"] || dependencies["mongoose"]) {
52
+ return "mongoose";
53
+ }
54
+ return "none";
55
+ }
56
+ function detectDatabase(packageJson, orm) {
57
+ const dependencies = {
58
+ ...packageJson.dependencies,
59
+ ...packageJson.devDependencies
60
+ };
61
+ if (orm === "typeorm") {
62
+ if (dependencies["pg"]) return "postgres";
63
+ if (dependencies["mysql2"] || dependencies["mysql"]) return "mysql";
64
+ if (dependencies["sqlite3"]) return "sqlite";
65
+ if (dependencies["mongodb"]) return "mongodb";
66
+ }
67
+ if (orm === "prisma") {
68
+ return void 0;
69
+ }
70
+ if (orm === "mongoose") {
71
+ return "mongodb";
72
+ }
73
+ return void 0;
74
+ }
75
+ var init_orm_detector = __esm({
76
+ "src/analyzer/orm-detector.ts"() {
77
+ "use strict";
78
+ init_cjs_shims();
79
+ }
80
+ });
81
+
82
+ // src/analyzer/project-detector.ts
83
+ async function detectProject(cwd = process.cwd()) {
84
+ const detector = new ProjectDetector(cwd);
85
+ return detector.detectProject();
86
+ }
87
+ var path, fs, ProjectDetector;
88
+ var init_project_detector = __esm({
89
+ "src/analyzer/project-detector.ts"() {
90
+ "use strict";
91
+ init_cjs_shims();
92
+ path = __toESM(require("path"));
93
+ fs = __toESM(require("fs-extra"));
94
+ init_orm_detector();
95
+ ProjectDetector = class {
96
+ constructor(cwd) {
97
+ this.cwd = cwd;
98
+ }
99
+ /**
100
+ * Detect and validate a NestJS project
101
+ */
102
+ async detectProject() {
103
+ const errors = [];
104
+ const root = this.cwd;
105
+ const packageJsonPath = path.join(root, "package.json");
106
+ if (!await fs.pathExists(packageJsonPath)) {
107
+ errors.push("package.json not found");
108
+ return this.createInvalidProject(root, errors);
109
+ }
110
+ const packageJson = await this.readPackageJson(packageJsonPath);
111
+ if (!packageJson) {
112
+ errors.push("Failed to read package.json");
113
+ return this.createInvalidProject(root, errors);
114
+ }
115
+ const hasNestCore = packageJson.dependencies?.["@nestjs/core"];
116
+ const hasNestCommon = packageJson.dependencies?.["@nestjs/common"];
117
+ if (!hasNestCore || !hasNestCommon) {
118
+ errors.push("Not a NestJS project (missing @nestjs/core or @nestjs/common)");
119
+ return this.createInvalidProject(root, errors);
120
+ }
121
+ const nestCliConfigPath = path.join(root, "nest-cli.json");
122
+ const nestCliConfig = await this.readNestCliConfig(nestCliConfigPath);
123
+ const sourceRoot = nestCliConfig?.sourceRoot || "src";
124
+ const appModulePath = path.join(root, sourceRoot, "app.module.ts");
125
+ if (!await fs.pathExists(appModulePath)) {
126
+ errors.push(`app.module.ts not found at ${sourceRoot}/app.module.ts`);
127
+ return this.createInvalidProject(root, errors);
128
+ }
129
+ const mainTsPath = path.join(root, sourceRoot, "main.ts");
130
+ const orm = await detectORM(packageJson);
131
+ const database = detectDatabase(packageJson, orm);
132
+ const authModulePath = path.join(root, sourceRoot, "auth");
133
+ const authExists = await fs.pathExists(authModulePath);
134
+ return {
135
+ authExists,
136
+ root,
137
+ sourceRoot,
138
+ appModulePath,
139
+ mainTsPath,
140
+ packageJsonPath,
141
+ nestCliConfigPath,
142
+ orm,
143
+ database,
144
+ nestVersion: packageJson.dependencies?.["@nestjs/core"],
145
+ typescriptVersion: packageJson.devDependencies?.["typescript"],
146
+ isValid: errors.length === 0,
147
+ errors
148
+ };
149
+ }
150
+ /**
151
+ * Read and parse package.json
152
+ */
153
+ async readPackageJson(packageJsonPath) {
154
+ try {
155
+ const content = await fs.readFile(packageJsonPath, "utf-8");
156
+ return JSON.parse(content);
157
+ } catch (error) {
158
+ return null;
159
+ }
160
+ }
161
+ /**
162
+ * Read and parse nest-cli.json
163
+ */
164
+ async readNestCliConfig(nestCliConfigPath) {
165
+ try {
166
+ if (!await fs.pathExists(nestCliConfigPath)) {
167
+ return null;
168
+ }
169
+ const content = await fs.readFile(nestCliConfigPath, "utf-8");
170
+ return JSON.parse(content);
171
+ } catch (error) {
172
+ return null;
173
+ }
174
+ }
175
+ /**
176
+ * Create invalid project info object
177
+ */
178
+ createInvalidProject(root, errors) {
179
+ return {
180
+ root,
181
+ sourceRoot: "src",
182
+ appModulePath: path.join(root, "src", "app.module.ts"),
183
+ mainTsPath: path.join(root, "src", "main.ts"),
184
+ packageJsonPath: path.join(root, "package.json"),
185
+ nestCliConfigPath: path.join(root, "nest-cli.json"),
186
+ orm: "none",
187
+ isValid: false,
188
+ errors
189
+ };
190
+ }
191
+ };
192
+ }
193
+ });
194
+
195
+ // src/analyzer/index.ts
196
+ var init_analyzer = __esm({
197
+ "src/analyzer/index.ts"() {
198
+ "use strict";
199
+ init_cjs_shims();
200
+ init_project_detector();
201
+ init_orm_detector();
202
+ }
203
+ });
204
+
205
+ // src/config/utils.ts
206
+ function generateSecret(length = 32) {
207
+ return (0, import_crypto.randomBytes)(length).toString("base64");
208
+ }
209
+ var import_crypto;
210
+ var init_utils = __esm({
211
+ "src/config/utils.ts"() {
212
+ "use strict";
213
+ init_cjs_shims();
214
+ import_crypto = require("crypto");
215
+ }
216
+ });
217
+
218
+ // src/cli/prompts.ts
219
+ async function promptConfig(detectedORM, detectedDB) {
220
+ const dbLabel = detectedDB ? ` with ${detectedDB.charAt(0).toUpperCase() + detectedDB.slice(1)}` : "";
221
+ const answers = await import_inquirer.default.prompt([
222
+ {
223
+ type: "list",
224
+ name: "strategy",
225
+ message: "Choose authentication strategy:",
226
+ choices: [
227
+ { name: "JWT Authentication (Recommended)", value: "jwt" },
228
+ { name: "OAuth 2.0 (Google, GitHub) [Coming soon]", value: "oauth", disabled: true },
229
+ { name: "Session-based (Traditional) [Coming soon]", value: "session", disabled: true }
230
+ ],
231
+ default: "jwt"
232
+ },
233
+ {
234
+ type: "confirm",
235
+ name: "enableRBAC",
236
+ message: "Enable Role-Based Access Control (RBAC)?",
237
+ default: true
238
+ },
239
+ {
240
+ type: "checkbox",
241
+ name: "roles",
242
+ message: "Select default roles:",
243
+ choices: [
244
+ { name: "Admin", value: "Admin", checked: true },
245
+ { name: "User", value: "User", checked: true },
246
+ { name: "Moderator", value: "Moderator", checked: false },
247
+ { name: "Guest", value: "Guest", checked: false }
248
+ ],
249
+ when: (answers2) => answers2.enableRBAC,
250
+ validate: (input) => {
251
+ if (input.length === 0) {
252
+ return "Please select at least one role";
253
+ }
254
+ return true;
255
+ }
256
+ },
257
+ {
258
+ type: "confirm",
259
+ name: "refreshTokens",
260
+ message: "Enable Refresh Token rotation?",
261
+ default: true
262
+ },
263
+ {
264
+ type: "list",
265
+ name: "accessExpiration",
266
+ message: "JWT Access Token expiration:",
267
+ choices: [
268
+ { name: "15 minutes", value: "15m" },
269
+ { name: "30 minutes", value: "30m" },
270
+ { name: "1 hour (Recommended)", value: "1h" },
271
+ { name: "4 hours", value: "4h" },
272
+ { name: "1 day", value: "1d" }
273
+ ],
274
+ default: "1h"
275
+ },
276
+ {
277
+ type: "list",
278
+ name: "refreshExpiration",
279
+ message: "JWT Refresh Token expiration:",
280
+ choices: [
281
+ { name: "7 days (Recommended)", value: "7d" },
282
+ { name: "30 days", value: "30d" },
283
+ { name: "90 days", value: "90d" },
284
+ { name: "1 year", value: "1y" }
285
+ ],
286
+ default: "7d",
287
+ when: (answers2) => answers2.refreshTokens
288
+ },
289
+ {
290
+ type: "confirm",
291
+ name: "enableRateLimiting",
292
+ message: "Enable rate limiting on auth endpoints? (recommended)",
293
+ default: true
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
+ },
325
+ {
326
+ type: "confirm",
327
+ name: "useDetectedORM",
328
+ message: `Detected ${detectedORM.toUpperCase()}${dbLabel}. Use it?`,
329
+ default: true,
330
+ when: () => detectedORM !== "none"
331
+ },
332
+ {
333
+ type: "list",
334
+ name: "database",
335
+ message: "Select database:",
336
+ choices: [
337
+ { name: "PostgreSQL (Recommended)", value: "postgres" },
338
+ { name: "MySQL", value: "mysql" },
339
+ { name: "SQLite (for testing)", value: "sqlite" },
340
+ { name: "MongoDB", value: "mongodb" }
341
+ ],
342
+ default: "postgres",
343
+ when: (answers2) => detectedORM === "none" || !answers2.useDetectedORM
344
+ },
345
+ {
346
+ type: "confirm",
347
+ name: "autoInstall",
348
+ message: "Auto-install dependencies after generation?",
349
+ default: true
350
+ }
351
+ ]);
352
+ return answers;
353
+ }
354
+ function getDefaultAnswers(detectedORM, detectedDB) {
355
+ return {
356
+ strategy: "jwt",
357
+ enableRBAC: true,
358
+ roles: ["Admin", "User"],
359
+ refreshTokens: true,
360
+ accessExpiration: "1h",
361
+ refreshExpiration: "7d",
362
+ enableRateLimiting: true,
363
+ enableSwagger: true,
364
+ generateTests: true,
365
+ useUsername: false,
366
+ enableEmailVerification: false,
367
+ enableResetPassword: true,
368
+ useDetectedORM: true,
369
+ database: detectedDB || "postgres",
370
+ autoInstall: true
371
+ };
372
+ }
373
+ function buildConfig(answers, projectName, sourceRoot, detectedORM, detectedDB) {
374
+ const config = {
375
+ projectName,
376
+ sourceRoot,
377
+ strategy: answers.strategy,
378
+ rbac: {
379
+ enabled: answers.enableRBAC,
380
+ roles: answers.roles || []
381
+ },
382
+ orm: answers.useDetectedORM !== false ? detectedORM : "none",
383
+ database: answers.database || detectedDB || "postgres",
384
+ features: {
385
+ refreshTokens: answers.refreshTokens,
386
+ rateLimiting: answers.enableRateLimiting,
387
+ swagger: answers.enableSwagger,
388
+ unitTests: answers.generateTests,
389
+ useUsername: answers.useUsername,
390
+ emailVerification: answers.enableEmailVerification,
391
+ resetPassword: answers.enableResetPassword
392
+ },
393
+ jwt: {
394
+ secret: generateSecret(),
395
+ accessExpiration: answers.accessExpiration,
396
+ refreshExpiration: answers.refreshExpiration || "7d"
397
+ },
398
+ autoInstall: answers.autoInstall,
399
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
400
+ generatorVersion: "1.3.0"
401
+ };
402
+ return config;
403
+ }
404
+ var import_inquirer;
405
+ var init_prompts = __esm({
406
+ "src/cli/prompts.ts"() {
407
+ "use strict";
408
+ init_cjs_shims();
409
+ import_inquirer = __toESM(require("inquirer"));
410
+ init_utils();
411
+ }
412
+ });
413
+
414
+ // src/cli/ui.ts
415
+ function getVersion() {
416
+ try {
417
+ const packageJsonPath = (0, import_path.join)(__dirname, "../package.json");
418
+ const packageJson = JSON.parse((0, import_fs.readFileSync)(packageJsonPath, "utf-8"));
419
+ return packageJson.version;
420
+ } catch (error) {
421
+ return "1.0.0";
422
+ }
423
+ }
424
+ function showBanner() {
425
+ console.log(import_chalk.default.cyan(`
426
+ _ _ _ __ __
427
+ / \\ _ _| |_| |__ | \\/ | ___
428
+ / _ \\| | | | __| '_ \\| |\\/| |/ _ \\
429
+ / ___ \\ |_| | |_| | | | | | | __/
430
+ /_/ \\_\\__,_|\\__|_| |_|_| |_|\\___|
431
+ `));
432
+ console.log(import_chalk.default.bold(`\u{1F510} AuthMe - NestJS Auth Generator v${getVersion()}`));
433
+ console.log();
434
+ }
435
+ function showProjectInfo(info) {
436
+ console.log(import_chalk.default.green("\u2713"), `Detected NestJS ${info.nestVersion || "project"}`);
437
+ if (info.orm !== "none") {
438
+ console.log(import_chalk.default.green("\u2713"), `Found ${info.orm.toUpperCase()}`);
439
+ }
440
+ console.log(import_chalk.default.green("\u2713"), `Source directory: ${info.sourceRoot}/`);
441
+ console.log(import_chalk.default.green("\u2713"), "No existing auth module found");
442
+ console.log();
443
+ }
444
+ function showError(message, errors) {
445
+ console.log();
446
+ console.log(import_chalk.default.red("\u274C Error:"), import_chalk.default.bold(message));
447
+ if (errors && errors.length > 0) {
448
+ console.log();
449
+ errors.forEach((error) => {
450
+ console.log(import_chalk.default.red(" \u2022"), error);
451
+ });
452
+ }
453
+ console.log();
454
+ }
455
+ function showNestJSHelp() {
456
+ console.log(import_chalk.default.yellow("To create a new NestJS project:"));
457
+ console.log();
458
+ console.log(import_chalk.default.cyan(" npm i -g @nestjs/cli"));
459
+ console.log(import_chalk.default.cyan(" nest new my-project"));
460
+ console.log();
461
+ }
462
+ function showSuccess(stats) {
463
+ console.log();
464
+ console.log(import_chalk.default.green.bold("\u{1F389} Success!"), "Authentication module generated.");
465
+ console.log();
466
+ console.log(import_chalk.default.bold("\u{1F4C1} Files created:"));
467
+ console.log(` \u2022 ${stats.filesCreated} new files in src/auth/ and src/users/`);
468
+ console.log(` \u2022 Updated src/app.module.ts`);
469
+ console.log(` \u2022 Updated package.json`);
470
+ console.log();
471
+ console.log(import_chalk.default.bold("\u{1F4E6} Dependencies added:"));
472
+ console.log(` \u2022 @nestjs/jwt, @nestjs/passport, @nestjs/config`);
473
+ console.log(` \u2022 passport, passport-jwt, passport-local`);
474
+ console.log(` \u2022 bcrypt, class-validator, class-transformer`);
475
+ if (stats.orm === "prisma") {
476
+ console.log(` \u2022 @prisma/client, prisma`);
477
+ }
478
+ if (stats.swagger) {
479
+ console.log(` \u2022 @nestjs/swagger`);
480
+ }
481
+ console.log();
482
+ console.log(import_chalk.default.bold("\u{1F510} JWT Configuration:"));
483
+ console.log(` \u2022 Access token: ${stats.jwt.accessExpiration}`);
484
+ if (stats.jwt.refreshExpiration) {
485
+ console.log(` \u2022 Refresh token: ${stats.jwt.refreshExpiration}`);
486
+ }
487
+ console.log(` \u2022 Secret: Auto-generated (see .env)`);
488
+ console.log();
489
+ console.log(import_chalk.default.bold("\u{1F4CB} Next steps:"));
490
+ console.log(import_chalk.default.cyan(" 1. Review .env file (auto-generated with secure secret)"));
491
+ console.log(import_chalk.default.gray(" # .env.example is also provided as a git-safe reference"));
492
+ console.log();
493
+ if (stats.orm === "prisma") {
494
+ console.log(import_chalk.default.cyan(" 2. Add Prisma schema models (see prisma-schema-additions.prisma)"));
495
+ console.log(import_chalk.default.gray(" # Copy the models into your prisma/schema.prisma"));
496
+ console.log(import_chalk.default.gray(" npx prisma migrate dev --name add-auth-models"));
497
+ console.log(import_chalk.default.gray(" npx prisma generate"));
498
+ } else {
499
+ console.log(import_chalk.default.cyan(" 2. Create database migration (if using TypeORM)"));
500
+ console.log(import_chalk.default.gray(" npm run migration:generate -- src/migrations/CreateUserTable"));
501
+ console.log(import_chalk.default.gray(" npm run migration:run"));
502
+ }
503
+ console.log();
504
+ console.log(import_chalk.default.cyan(" 3. Start your NestJS app"));
505
+ console.log(import_chalk.default.gray(" npm run start:dev"));
506
+ console.log();
507
+ console.log(import_chalk.default.cyan(" 4. Test authentication endpoints"));
508
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/register"));
509
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/login"));
510
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/change-password (requires JWT)"));
511
+ if (stats.emailVerification) {
512
+ console.log(import_chalk.default.gray(" GET http://localhost:3000/auth/verify-email?token=..."));
513
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/resend-verification"));
514
+ }
515
+ if (stats.resetPassword) {
516
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/forgot-password"));
517
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/reset-password"));
518
+ }
519
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/refresh"));
520
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/logout (requires JWT)"));
521
+ console.log(import_chalk.default.gray(" POST http://localhost:3000/auth/logout-all (requires JWT)"));
522
+ console.log(import_chalk.default.gray(" GET http://localhost:3000/users/profile (requires JWT)"));
523
+ if (stats.swagger) {
524
+ console.log();
525
+ console.log(import_chalk.default.cyan(" 5. View Swagger API documentation"));
526
+ console.log(import_chalk.default.gray(" http://localhost:3000/api"));
527
+ }
528
+ console.log();
529
+ console.log(import_chalk.default.bold("\u{1F4D6} Full documentation:"), "src/auth/README.md");
530
+ console.log();
531
+ console.log(import_chalk.default.bold("\u{1F4A1} Tips:"));
532
+ console.log(" \u2022 Use @Public() decorator for routes that don't require auth");
533
+ console.log(" \u2022 Use @Roles('Admin') to restrict routes by role");
534
+ console.log(" \u2022 Access current user with @CurrentUser() decorator");
535
+ if (stats.swagger) {
536
+ console.log(" \u2022 Visit /api for interactive Swagger documentation");
537
+ }
538
+ console.log();
539
+ }
540
+ function createSpinner(text) {
541
+ return (0, import_ora.default)({
542
+ text,
543
+ color: "cyan"
544
+ });
545
+ }
546
+ var import_chalk, import_ora, import_fs, import_path;
547
+ var init_ui = __esm({
548
+ "src/cli/ui.ts"() {
549
+ "use strict";
550
+ init_cjs_shims();
551
+ import_chalk = __toESM(require("chalk"));
552
+ import_ora = __toESM(require("ora"));
553
+ import_fs = require("fs");
554
+ import_path = require("path");
555
+ }
556
+ });
557
+
558
+ // src/generator/template-engine.ts
559
+ var import_handlebars, path2, fs2, TemplateEngine;
560
+ var init_template_engine = __esm({
561
+ "src/generator/template-engine.ts"() {
562
+ "use strict";
563
+ init_cjs_shims();
564
+ import_handlebars = __toESM(require("handlebars"));
565
+ path2 = __toESM(require("path"));
566
+ fs2 = __toESM(require("fs-extra"));
567
+ TemplateEngine = class {
568
+ handlebars;
569
+ templateCache;
570
+ templatesDir;
571
+ constructor(templatesDir) {
572
+ this.handlebars = import_handlebars.default.create();
573
+ this.templateCache = /* @__PURE__ */ new Map();
574
+ this.templatesDir = templatesDir || path2.join(__dirname, "generator", "templates");
575
+ this.registerHelpers();
576
+ }
577
+ /**
578
+ * Register Handlebars helpers
579
+ */
580
+ registerHelpers() {
581
+ this.handlebars.registerHelper("eq", (a, b) => a === b);
582
+ this.handlebars.registerHelper("ne", (a, b) => a !== b);
583
+ this.handlebars.registerHelper("or", (a, b) => a || b);
584
+ this.handlebars.registerHelper("and", (a, b) => a && b);
585
+ this.handlebars.registerHelper("includes", (arr, item) => arr?.includes(item));
586
+ this.handlebars.registerHelper("capitalize", (str) => {
587
+ if (!str) return "";
588
+ return str.charAt(0).toUpperCase() + str.slice(1);
589
+ });
590
+ this.handlebars.registerHelper("lowercase", (str) => {
591
+ if (!str) return "";
592
+ return str.toLowerCase();
593
+ });
594
+ this.handlebars.registerHelper("uppercase", (str) => {
595
+ if (!str) return "";
596
+ return str.toUpperCase();
597
+ });
598
+ this.handlebars.registerHelper("camelCase", (str) => {
599
+ if (!str) return "";
600
+ return str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
601
+ });
602
+ this.handlebars.registerHelper("pascalCase", (str) => {
603
+ if (!str) return "";
604
+ const camel = str.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
605
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
606
+ });
607
+ }
608
+ /**
609
+ * Render a template with context
610
+ */
611
+ async render(templatePath, context) {
612
+ const template = await this.loadTemplate(templatePath);
613
+ return template(context);
614
+ }
615
+ /**
616
+ * Load a template (with caching)
617
+ */
618
+ async loadTemplate(templatePath) {
619
+ if (this.templateCache.has(templatePath)) {
620
+ return this.templateCache.get(templatePath);
621
+ }
622
+ const fullPath = path2.join(this.templatesDir, templatePath);
623
+ if (!await fs2.pathExists(fullPath)) {
624
+ throw new Error(`Template not found: ${templatePath}`);
625
+ }
626
+ const source = await fs2.readFile(fullPath, "utf-8");
627
+ const compiled = this.handlebars.compile(source);
628
+ this.templateCache.set(templatePath, compiled);
629
+ return compiled;
630
+ }
631
+ /**
632
+ * Clear template cache
633
+ */
634
+ clearCache() {
635
+ this.templateCache.clear();
636
+ }
637
+ };
638
+ }
639
+ });
640
+
641
+ // src/generator/file-writer.ts
642
+ var path3, fs3, FileWriter;
643
+ var init_file_writer = __esm({
644
+ "src/generator/file-writer.ts"() {
645
+ "use strict";
646
+ init_cjs_shims();
647
+ path3 = __toESM(require("path"));
648
+ fs3 = __toESM(require("fs-extra"));
649
+ FileWriter = class {
650
+ writtenFiles = [];
651
+ skippedFiles = [];
652
+ backups = /* @__PURE__ */ new Map();
653
+ /**
654
+ * Write a file to disk
655
+ */
656
+ async writeFile(filePath, content, options = {}) {
657
+ const { overwrite = false, backup = true } = options;
658
+ const exists = await fs3.pathExists(filePath);
659
+ if (exists && !overwrite) {
660
+ this.skippedFiles.push(filePath);
661
+ return;
662
+ }
663
+ if (exists && backup) {
664
+ await this.createBackup(filePath);
665
+ }
666
+ await fs3.ensureDir(path3.dirname(filePath));
667
+ await fs3.writeFile(filePath, content, "utf-8");
668
+ this.writtenFiles.push(filePath);
669
+ }
670
+ /**
671
+ * Create a backup of an existing file
672
+ */
673
+ async createBackup(filePath) {
674
+ const backupPath = `${filePath}.backup`;
675
+ await fs3.copy(filePath, backupPath);
676
+ this.backups.set(filePath, backupPath);
677
+ }
678
+ /**
679
+ * Rollback all written files
680
+ */
681
+ async rollback() {
682
+ for (const [originalPath, backupPath] of this.backups) {
683
+ if (await fs3.pathExists(backupPath)) {
684
+ await fs3.copy(backupPath, originalPath, { overwrite: true });
685
+ await fs3.remove(backupPath);
686
+ }
687
+ }
688
+ for (const filePath of this.writtenFiles) {
689
+ if (!this.backups.has(filePath) && await fs3.pathExists(filePath)) {
690
+ await fs3.remove(filePath);
691
+ }
692
+ }
693
+ this.writtenFiles = [];
694
+ this.backups.clear();
695
+ }
696
+ /**
697
+ * Clean up backups
698
+ */
699
+ async cleanupBackups() {
700
+ for (const backupPath of this.backups.values()) {
701
+ if (await fs3.pathExists(backupPath)) {
702
+ await fs3.remove(backupPath);
703
+ }
704
+ }
705
+ this.backups.clear();
706
+ }
707
+ /**
708
+ * Get list of written files
709
+ */
710
+ getWrittenFiles() {
711
+ return [...this.writtenFiles];
712
+ }
713
+ /**
714
+ * Get list of skipped files (already existed)
715
+ */
716
+ getSkippedFiles() {
717
+ return [...this.skippedFiles];
718
+ }
719
+ };
720
+ }
721
+ });
722
+
723
+ // src/config/config-builder.ts
724
+ function buildTemplateContext(config) {
725
+ return {
726
+ ...config
727
+ // Add any additional computed properties here
728
+ };
729
+ }
730
+ var init_config_builder = __esm({
731
+ "src/config/config-builder.ts"() {
732
+ "use strict";
733
+ init_cjs_shims();
734
+ }
735
+ });
736
+
737
+ // src/generator/generator.ts
738
+ var path4, Generator;
739
+ var init_generator = __esm({
740
+ "src/generator/generator.ts"() {
741
+ "use strict";
742
+ init_cjs_shims();
743
+ path4 = __toESM(require("path"));
744
+ init_template_engine();
745
+ init_file_writer();
746
+ init_config_builder();
747
+ Generator = class {
748
+ templateEngine;
749
+ fileWriter;
750
+ constructor() {
751
+ this.templateEngine = new TemplateEngine();
752
+ this.fileWriter = new FileWriter();
753
+ }
754
+ /**
755
+ * Generate all authentication files
756
+ */
757
+ async generate(config, projectInfo, overwrite = false) {
758
+ try {
759
+ const context = buildTemplateContext(config);
760
+ const plan = this.buildGenerationPlan(config);
761
+ for (const fileSpec of plan) {
762
+ if (fileSpec.condition && !fileSpec.condition(config)) {
763
+ continue;
764
+ }
765
+ const content = await this.templateEngine.render(
766
+ fileSpec.template,
767
+ context
768
+ );
769
+ const outputPath = path4.join(projectInfo.root, fileSpec.output);
770
+ await this.fileWriter.writeFile(outputPath, content, {
771
+ overwrite
772
+ });
773
+ }
774
+ const filesCreated = this.fileWriter.getWrittenFiles();
775
+ const filesSkipped = this.fileWriter.getSkippedFiles();
776
+ await this.fileWriter.cleanupBackups();
777
+ return {
778
+ filesCreated,
779
+ filesSkipped,
780
+ success: true
781
+ };
782
+ } catch (error) {
783
+ await this.fileWriter.rollback();
784
+ return {
785
+ filesCreated: [],
786
+ filesSkipped: [],
787
+ success: false,
788
+ error: error instanceof Error ? error.message : "Unknown error"
789
+ };
790
+ }
791
+ }
792
+ /**
793
+ * Build file generation plan
794
+ */
795
+ buildGenerationPlan(config) {
796
+ const plan = [];
797
+ plan.push(
798
+ { template: "jwt/auth.module.ts.hbs", output: `${config.sourceRoot}/auth/auth.module.ts` },
799
+ { template: "jwt/auth.service.ts.hbs", output: `${config.sourceRoot}/auth/auth.service.ts` },
800
+ { template: "jwt/auth.controller.ts.hbs", output: `${config.sourceRoot}/auth/auth.controller.ts` }
801
+ );
802
+ plan.push(
803
+ { template: "jwt/jwt.strategy.ts.hbs", output: `${config.sourceRoot}/auth/strategies/jwt.strategy.ts` },
804
+ { template: "jwt/local.strategy.ts.hbs", output: `${config.sourceRoot}/auth/strategies/local.strategy.ts` }
805
+ );
806
+ plan.push(
807
+ { template: "jwt/jwt-auth.guard.ts.hbs", output: `${config.sourceRoot}/auth/guards/jwt-auth.guard.ts` },
808
+ { template: "jwt/local-auth.guard.ts.hbs", output: `${config.sourceRoot}/auth/guards/local-auth.guard.ts` }
809
+ );
810
+ if (config.rbac.enabled) {
811
+ plan.push(
812
+ { template: "rbac/roles.guard.ts.hbs", output: `${config.sourceRoot}/auth/guards/roles.guard.ts` },
813
+ { template: "rbac/role.enum.ts.hbs", output: `${config.sourceRoot}/auth/enums/role.enum.ts` },
814
+ { template: "decorators/roles.decorator.ts.hbs", output: `${config.sourceRoot}/auth/decorators/roles.decorator.ts` }
815
+ );
816
+ }
817
+ plan.push(
818
+ { template: "decorators/public.decorator.ts.hbs", output: `${config.sourceRoot}/auth/decorators/public.decorator.ts` },
819
+ { template: "decorators/current-user.decorator.ts.hbs", output: `${config.sourceRoot}/auth/decorators/current-user.decorator.ts` }
820
+ );
821
+ plan.push(
822
+ { template: "dto/login.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/login.dto.ts` },
823
+ { template: "dto/register.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/register.dto.ts` },
824
+ { template: "dto/change-password.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/change-password.dto.ts` },
825
+ { template: "dto/auth-response.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/auth-response.dto.ts` },
826
+ { template: "dto/create-user.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/create-user.dto.ts` }
827
+ );
828
+ if (config.features.resetPassword) {
829
+ plan.push(
830
+ { template: "dto/forgot-password.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/forgot-password.dto.ts` },
831
+ { template: "dto/reset-password.dto.ts.hbs", output: `${config.sourceRoot}/auth/dto/reset-password.dto.ts` }
832
+ );
833
+ }
834
+ plan.push(
835
+ { template: "users/users.module.ts.hbs", output: `${config.sourceRoot}/users/users.module.ts` },
836
+ { template: "users/users.service.ts.hbs", output: `${config.sourceRoot}/users/users.service.ts` },
837
+ { template: "users/users.controller.ts.hbs", output: `${config.sourceRoot}/users/users.controller.ts` }
838
+ );
839
+ if (config.orm === "typeorm") {
840
+ plan.push(
841
+ { template: "entities/user.entity.typeorm.hbs", output: `${config.sourceRoot}/users/entities/user.entity.ts` }
842
+ );
843
+ if (config.features.refreshTokens) {
844
+ plan.push({
845
+ template: "entities/refresh-token.entity.typeorm.hbs",
846
+ output: `${config.sourceRoot}/users/entities/refresh-token.entity.ts`
847
+ });
848
+ }
849
+ } else if (config.orm === "prisma") {
850
+ plan.push(
851
+ { template: "prisma/prisma.service.ts.hbs", output: `${config.sourceRoot}/prisma/prisma.service.ts` },
852
+ { template: "prisma/prisma.module.ts.hbs", output: `${config.sourceRoot}/prisma/prisma.module.ts` },
853
+ { template: "prisma/schema.prisma.additions.hbs", output: "prisma-schema-additions.prisma" }
854
+ );
855
+ }
856
+ if (config.features.unitTests) {
857
+ plan.push(
858
+ { template: "tests/auth.service.spec.ts.hbs", output: `${config.sourceRoot}/auth/auth.service.spec.ts` },
859
+ { template: "tests/auth.controller.spec.ts.hbs", output: `${config.sourceRoot}/auth/auth.controller.spec.ts` }
860
+ );
861
+ }
862
+ plan.push(
863
+ { template: "shared/env.template.hbs", output: ".env.example" },
864
+ { template: "shared/env.hbs", output: ".env" },
865
+ { template: "shared/README.auth.md.hbs", output: `${config.sourceRoot}/auth/README.md` },
866
+ { template: "shared/main.ts.snippet.hbs", output: "main.ts.example" }
867
+ );
868
+ return plan;
869
+ }
870
+ };
871
+ }
872
+ });
873
+
874
+ // src/generator/index.ts
875
+ var generator_exports = {};
876
+ __export(generator_exports, {
877
+ FileWriter: () => FileWriter,
878
+ Generator: () => Generator,
879
+ TemplateEngine: () => TemplateEngine
880
+ });
881
+ var init_generator2 = __esm({
882
+ "src/generator/index.ts"() {
883
+ "use strict";
884
+ init_cjs_shims();
885
+ init_generator();
886
+ init_template_engine();
887
+ init_file_writer();
888
+ }
889
+ });
890
+
891
+ // src/installer/ast-updater.ts
892
+ var import_ts_morph, fs4, AppModuleUpdater;
893
+ var init_ast_updater = __esm({
894
+ "src/installer/ast-updater.ts"() {
895
+ "use strict";
896
+ init_cjs_shims();
897
+ import_ts_morph = require("ts-morph");
898
+ fs4 = __toESM(require("fs-extra"));
899
+ AppModuleUpdater = class {
900
+ constructor(appModulePath) {
901
+ this.appModulePath = appModulePath;
902
+ this.project = new import_ts_morph.Project({
903
+ skipAddingFilesFromTsConfig: true,
904
+ manipulationSettings: {
905
+ indentationText: import_ts_morph.IndentationText.TwoSpaces
906
+ }
907
+ });
908
+ }
909
+ project;
910
+ sourceFile;
911
+ backupPath = null;
912
+ /**
913
+ * Update app.module.ts with auth modules
914
+ */
915
+ async update(config) {
916
+ await this.createBackup();
917
+ try {
918
+ this.sourceFile = this.project.addSourceFileAtPath(this.appModulePath);
919
+ this.addImports(config);
920
+ this.addModulesToDecorator(config);
921
+ this.sourceFile.formatText();
922
+ await this.sourceFile.save();
923
+ } catch (error) {
924
+ await this.restoreBackup();
925
+ throw error;
926
+ }
927
+ }
928
+ /**
929
+ * Add necessary imports
930
+ */
931
+ addImports(config) {
932
+ if (!this.sourceFile) {
933
+ throw new Error("Source file not loaded");
934
+ }
935
+ this.addImport("@nestjs/config", ["ConfigModule"]);
936
+ if (config && config.orm === "typeorm") {
937
+ this.addImport("@nestjs/typeorm", ["TypeOrmModule"]);
938
+ this.addImport("./users/entities/user.entity", ["User"]);
939
+ if (config.features.refreshTokens) {
940
+ this.addImport("./users/entities/refresh-token.entity", ["RefreshToken"]);
941
+ }
942
+ } else if (config && config.orm === "prisma") {
943
+ this.addImport("./prisma/prisma.module", ["PrismaModule"]);
944
+ }
945
+ this.addImport("./auth/auth.module", ["AuthModule"]);
946
+ this.addImport("./users/users.module", ["UsersModule"]);
947
+ }
948
+ /**
949
+ * Add an import statement if it doesn't exist
950
+ */
951
+ addImport(moduleSpecifier, namedImports) {
952
+ if (!this.sourceFile) return;
953
+ const existingImport = this.sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue() === moduleSpecifier);
954
+ if (existingImport) {
955
+ const existingNames = existingImport.getNamedImports().map((ni) => ni.getName());
956
+ const missingImports = namedImports.filter(
957
+ (name) => !existingNames.includes(name)
958
+ );
959
+ if (missingImports.length > 0) {
960
+ existingImport.addNamedImports(missingImports);
961
+ }
962
+ } else {
963
+ this.sourceFile.addImportDeclaration({
964
+ moduleSpecifier,
965
+ namedImports
966
+ });
967
+ }
968
+ }
969
+ /**
970
+ * Add modules to @Module decorator imports array
971
+ */
972
+ addModulesToDecorator(config) {
973
+ if (!this.sourceFile) return;
974
+ const appModuleClass = this.sourceFile.getClass("AppModule");
975
+ if (!appModuleClass) {
976
+ throw new Error("AppModule class not found");
977
+ }
978
+ const moduleDecorator = appModuleClass.getDecorator("Module");
979
+ if (!moduleDecorator) {
980
+ throw new Error("@Module decorator not found");
981
+ }
982
+ const decoratorArgs = moduleDecorator.getArguments()[0];
983
+ if (!decoratorArgs || !import_ts_morph.Node.isObjectLiteralExpression(decoratorArgs)) {
984
+ throw new Error("Invalid @Module decorator structure");
985
+ }
986
+ let importsProperty = decoratorArgs.getProperty("imports");
987
+ if (!importsProperty) {
988
+ decoratorArgs.addPropertyAssignment({
989
+ name: "imports",
990
+ initializer: "[]"
991
+ });
992
+ importsProperty = decoratorArgs.getProperty("imports");
993
+ }
994
+ if (!importsProperty || !import_ts_morph.Node.isPropertyAssignment(importsProperty)) {
995
+ throw new Error("Invalid imports property");
996
+ }
997
+ const importsArray = importsProperty.getInitializer();
998
+ if (!import_ts_morph.Node.isArrayLiteralExpression(importsArray)) {
999
+ throw new Error("imports is not an array");
1000
+ }
1001
+ const existingModules = this.getExistingModuleNames(importsArray);
1002
+ const existingElements = importsArray.getElements().map((e) => e.getText());
1003
+ const allElements = [...existingElements];
1004
+ if (!existingModules.has("ConfigModule")) {
1005
+ allElements.push("ConfigModule.forRoot({ isGlobal: true })");
1006
+ }
1007
+ if (config && config.orm === "typeorm" && !existingModules.has("TypeOrmModule")) {
1008
+ const entities = config.features.refreshTokens ? "[User, RefreshToken]" : "[User]";
1009
+ allElements.push(this.buildTypeOrmConfig(config.database, entities));
1010
+ } else if (config && config.orm === "prisma" && !existingModules.has("PrismaModule")) {
1011
+ allElements.push("PrismaModule");
1012
+ }
1013
+ if (!existingModules.has("AuthModule")) {
1014
+ allElements.push("AuthModule");
1015
+ }
1016
+ if (!existingModules.has("UsersModule")) {
1017
+ allElements.push("UsersModule");
1018
+ }
1019
+ const indent = " ";
1020
+ const formattedElements = allElements.map((el) => `${indent}${el}`).join(",\n");
1021
+ const multiLineArray = `[
1022
+ ${formattedElements},
1023
+ ]`;
1024
+ importsProperty.setInitializer(multiLineArray);
1025
+ }
1026
+ /**
1027
+ * Build TypeORM.forRoot() configuration string based on database type
1028
+ */
1029
+ buildTypeOrmConfig(database, entities) {
1030
+ switch (database) {
1031
+ case "sqlite":
1032
+ return `TypeOrmModule.forRoot({
1033
+ type: 'sqlite',
1034
+ database: 'database.sqlite',
1035
+ entities: ${entities},
1036
+ synchronize: true, // WARNING: disable in production!
1037
+ })`;
1038
+ case "mysql":
1039
+ return `TypeOrmModule.forRoot({
1040
+ type: 'mysql',
1041
+ host: process.env.DATABASE_HOST || 'localhost',
1042
+ port: parseInt(process.env.DATABASE_PORT || '3306'),
1043
+ username: process.env.DATABASE_USER || 'root',
1044
+ password: process.env.DATABASE_PASSWORD || '',
1045
+ database: process.env.DATABASE_NAME || 'auth_db',
1046
+ entities: ${entities},
1047
+ synchronize: true, // WARNING: disable in production!
1048
+ })`;
1049
+ case "postgres":
1050
+ default:
1051
+ return `TypeOrmModule.forRoot({
1052
+ type: 'postgres',
1053
+ host: process.env.DATABASE_HOST || 'localhost',
1054
+ port: parseInt(process.env.DATABASE_PORT || '5432'),
1055
+ username: process.env.DATABASE_USER || 'postgres',
1056
+ password: process.env.DATABASE_PASSWORD || 'postgres',
1057
+ database: process.env.DATABASE_NAME || 'auth_db',
1058
+ entities: ${entities},
1059
+ synchronize: true, // WARNING: disable in production!
1060
+ })`;
1061
+ }
1062
+ }
1063
+ /**
1064
+ * Get existing module names from imports array
1065
+ */
1066
+ getExistingModuleNames(importsArray) {
1067
+ const moduleNames = /* @__PURE__ */ new Set();
1068
+ if (!import_ts_morph.Node.isArrayLiteralExpression(importsArray)) {
1069
+ return moduleNames;
1070
+ }
1071
+ for (const element of importsArray.getElements()) {
1072
+ const text = element.getText();
1073
+ const match = text.match(/^(\w+)/);
1074
+ if (match) {
1075
+ moduleNames.add(match[1]);
1076
+ }
1077
+ }
1078
+ return moduleNames;
1079
+ }
1080
+ /**
1081
+ * Create backup of app.module.ts
1082
+ */
1083
+ async createBackup() {
1084
+ this.backupPath = `${this.appModulePath}.backup`;
1085
+ await fs4.copy(this.appModulePath, this.backupPath);
1086
+ }
1087
+ /**
1088
+ * Restore backup
1089
+ */
1090
+ async restoreBackup() {
1091
+ if (this.backupPath && await fs4.pathExists(this.backupPath)) {
1092
+ await fs4.copy(this.backupPath, this.appModulePath, { overwrite: true });
1093
+ await fs4.remove(this.backupPath);
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Clean up backup
1098
+ */
1099
+ async cleanupBackup() {
1100
+ if (this.backupPath && await fs4.pathExists(this.backupPath)) {
1101
+ await fs4.remove(this.backupPath);
1102
+ }
1103
+ }
1104
+ };
1105
+ }
1106
+ });
1107
+
1108
+ // src/installer/main-ts-updater.ts
1109
+ var import_ts_morph2, fs5, MainTsUpdater;
1110
+ var init_main_ts_updater = __esm({
1111
+ "src/installer/main-ts-updater.ts"() {
1112
+ "use strict";
1113
+ init_cjs_shims();
1114
+ import_ts_morph2 = require("ts-morph");
1115
+ fs5 = __toESM(require("fs-extra"));
1116
+ MainTsUpdater = class {
1117
+ constructor(mainTsPath) {
1118
+ this.mainTsPath = mainTsPath;
1119
+ this.project = new import_ts_morph2.Project({
1120
+ skipAddingFilesFromTsConfig: true,
1121
+ manipulationSettings: {
1122
+ indentationText: import_ts_morph2.IndentationText.TwoSpaces
1123
+ }
1124
+ });
1125
+ }
1126
+ project;
1127
+ sourceFile;
1128
+ backupPath = null;
1129
+ /**
1130
+ * Update main.ts with global guards, validation pipe, and optionally Swagger
1131
+ */
1132
+ async update(config) {
1133
+ if (!await fs5.pathExists(this.mainTsPath)) {
1134
+ throw new Error(`main.ts not found at ${this.mainTsPath}`);
1135
+ }
1136
+ await this.createBackup();
1137
+ try {
1138
+ this.sourceFile = this.project.addSourceFileAtPath(this.mainTsPath);
1139
+ this.addImports(config);
1140
+ this.addGlobalGuardsAndPipes(config);
1141
+ this.sourceFile.formatText();
1142
+ await this.sourceFile.save();
1143
+ } catch (error) {
1144
+ await this.restoreBackup();
1145
+ throw error;
1146
+ }
1147
+ }
1148
+ /**
1149
+ * Add necessary imports
1150
+ */
1151
+ addImports(config) {
1152
+ if (!this.sourceFile) {
1153
+ throw new Error("Source file not loaded");
1154
+ }
1155
+ this.addImport("@nestjs/core", ["Reflector"]);
1156
+ this.addImport("@nestjs/common", ["ValidationPipe"]);
1157
+ this.addImport("./auth/guards/jwt-auth.guard", ["JwtAuthGuard"]);
1158
+ if (config?.features?.swagger) {
1159
+ this.addImport("@nestjs/swagger", ["SwaggerModule", "DocumentBuilder"]);
1160
+ }
1161
+ }
1162
+ /**
1163
+ * Add an import statement if it doesn't exist
1164
+ */
1165
+ addImport(moduleSpecifier, namedImports) {
1166
+ if (!this.sourceFile) return;
1167
+ const existingImport = this.sourceFile.getImportDeclarations().find((imp) => imp.getModuleSpecifierValue() === moduleSpecifier);
1168
+ if (existingImport) {
1169
+ const existingNames = existingImport.getNamedImports().map((ni) => ni.getName());
1170
+ const missingImports = namedImports.filter(
1171
+ (name) => !existingNames.includes(name)
1172
+ );
1173
+ if (missingImports.length > 0) {
1174
+ existingImport.addNamedImports(missingImports);
1175
+ }
1176
+ } else {
1177
+ this.sourceFile.addImportDeclaration({
1178
+ moduleSpecifier,
1179
+ namedImports
1180
+ });
1181
+ }
1182
+ }
1183
+ /**
1184
+ * Add global guards and validation pipe to bootstrap function
1185
+ */
1186
+ addGlobalGuardsAndPipes(config) {
1187
+ if (!this.sourceFile) return;
1188
+ const bootstrapFunc = this.sourceFile.getFunction("bootstrap");
1189
+ if (!bootstrapFunc) {
1190
+ throw new Error("bootstrap function not found in main.ts");
1191
+ }
1192
+ const body = bootstrapFunc.getBody();
1193
+ if (!body || !import_ts_morph2.Node.isBlock(body)) {
1194
+ throw new Error("bootstrap function has no body");
1195
+ }
1196
+ const bodyText = body.getText();
1197
+ if (bodyText.includes("useGlobalGuards") || bodyText.includes("JwtAuthGuard")) {
1198
+ return;
1199
+ }
1200
+ const statements = body.getStatements();
1201
+ let listenIndex = -1;
1202
+ for (let i = 0; i < statements.length; i++) {
1203
+ const text = statements[i].getText();
1204
+ if (text.includes(".listen(") || text.includes(".listen (")) {
1205
+ listenIndex = i;
1206
+ break;
1207
+ }
1208
+ }
1209
+ if (listenIndex === -1) {
1210
+ listenIndex = statements.length;
1211
+ }
1212
+ const codeLines = [
1213
+ "",
1214
+ "// Enable global validation pipe",
1215
+ "app.useGlobalPipes(",
1216
+ " new ValidationPipe({",
1217
+ " whitelist: true,",
1218
+ " forbidNonWhitelisted: true,",
1219
+ " transform: true,",
1220
+ " }),",
1221
+ ");",
1222
+ "",
1223
+ "// Enable global JWT guard (all routes protected by default)",
1224
+ "// Use @Public() decorator on routes that should be accessible without auth",
1225
+ "const reflector = app.get(Reflector);",
1226
+ "app.useGlobalGuards(new JwtAuthGuard(reflector));"
1227
+ ];
1228
+ if (config?.features?.swagger) {
1229
+ codeLines.push(
1230
+ "",
1231
+ "// Swagger API documentation",
1232
+ "const swaggerConfig = new DocumentBuilder()",
1233
+ " .setTitle('API Documentation')",
1234
+ " .setDescription('JWT Authentication API')",
1235
+ " .setVersion('1.0')",
1236
+ " .addBearerAuth()",
1237
+ " .build();",
1238
+ "const document = SwaggerModule.createDocument(app, swaggerConfig);",
1239
+ "SwaggerModule.setup('api', app, document);"
1240
+ );
1241
+ }
1242
+ codeLines.push("");
1243
+ const codeToInsert = codeLines.join("\n");
1244
+ body.insertStatements(listenIndex, codeToInsert);
1245
+ }
1246
+ /**
1247
+ * Create backup of main.ts
1248
+ */
1249
+ async createBackup() {
1250
+ this.backupPath = `${this.mainTsPath}.backup`;
1251
+ await fs5.copy(this.mainTsPath, this.backupPath);
1252
+ }
1253
+ /**
1254
+ * Restore backup
1255
+ */
1256
+ async restoreBackup() {
1257
+ if (this.backupPath && await fs5.pathExists(this.backupPath)) {
1258
+ await fs5.copy(this.backupPath, this.mainTsPath, { overwrite: true });
1259
+ await fs5.remove(this.backupPath);
1260
+ }
1261
+ }
1262
+ /**
1263
+ * Clean up backup
1264
+ */
1265
+ async cleanupBackup() {
1266
+ if (this.backupPath && await fs5.pathExists(this.backupPath)) {
1267
+ await fs5.remove(this.backupPath);
1268
+ }
1269
+ }
1270
+ };
1271
+ }
1272
+ });
1273
+
1274
+ // src/installer/package-updater.ts
1275
+ var fs6, PackageUpdater;
1276
+ var init_package_updater = __esm({
1277
+ "src/installer/package-updater.ts"() {
1278
+ "use strict";
1279
+ init_cjs_shims();
1280
+ fs6 = __toESM(require("fs-extra"));
1281
+ PackageUpdater = class {
1282
+ constructor(packageJsonPath) {
1283
+ this.packageJsonPath = packageJsonPath;
1284
+ }
1285
+ backupPath = null;
1286
+ /**
1287
+ * Update package.json with auth dependencies
1288
+ */
1289
+ async update(config) {
1290
+ await this.createBackup();
1291
+ try {
1292
+ const packageJson = await fs6.readJSON(this.packageJsonPath);
1293
+ const deps = this.getDependencies(config);
1294
+ packageJson.dependencies = {
1295
+ ...packageJson.dependencies,
1296
+ ...deps.dependencies
1297
+ };
1298
+ packageJson.devDependencies = {
1299
+ ...packageJson.devDependencies,
1300
+ ...deps.devDependencies
1301
+ };
1302
+ packageJson.dependencies = this.sortObject(packageJson.dependencies);
1303
+ packageJson.devDependencies = this.sortObject(
1304
+ packageJson.devDependencies
1305
+ );
1306
+ await fs6.writeJSON(this.packageJsonPath, packageJson, { spaces: 2 });
1307
+ } catch (error) {
1308
+ await this.restoreBackup();
1309
+ throw error;
1310
+ }
1311
+ }
1312
+ /**
1313
+ * Get dependencies based on configuration
1314
+ */
1315
+ getDependencies(config) {
1316
+ const dependencies = {
1317
+ "@nestjs/jwt": "^11.0.0",
1318
+ "@nestjs/passport": "^11.0.0",
1319
+ "@nestjs/config": "^4.0.0",
1320
+ passport: "^0.7.0",
1321
+ "passport-jwt": "^4.0.1",
1322
+ "passport-local": "^1.0.0",
1323
+ bcrypt: "^5.1.1",
1324
+ "class-validator": "^0.14.0",
1325
+ "class-transformer": "^0.5.1"
1326
+ };
1327
+ const devDependencies = {
1328
+ "@types/passport-jwt": "^4.0.0",
1329
+ "@types/passport-local": "^1.0.36",
1330
+ "@types/bcrypt": "^5.0.2"
1331
+ };
1332
+ if (config.orm === "typeorm") {
1333
+ dependencies["@nestjs/typeorm"] = "^11.0.0";
1334
+ dependencies["typeorm"] = "^0.3.20";
1335
+ switch (config.database) {
1336
+ case "postgres":
1337
+ dependencies["pg"] = "^8.11.3";
1338
+ break;
1339
+ case "mysql":
1340
+ dependencies["mysql2"] = "^3.9.1";
1341
+ break;
1342
+ case "sqlite":
1343
+ dependencies["sqlite3"] = "^5.1.7";
1344
+ break;
1345
+ case "mongodb":
1346
+ dependencies["mongodb"] = "^6.3.0";
1347
+ break;
1348
+ }
1349
+ }
1350
+ if (config.orm === "prisma") {
1351
+ dependencies["@prisma/client"] = "^6.0.0";
1352
+ devDependencies["prisma"] = "^6.0.0";
1353
+ }
1354
+ if (config.features.rateLimiting) {
1355
+ dependencies["@nestjs/throttler"] = "^6.0.0";
1356
+ }
1357
+ if (config.features.swagger) {
1358
+ dependencies["@nestjs/swagger"] = "^11.0.0";
1359
+ }
1360
+ return { dependencies, devDependencies };
1361
+ }
1362
+ /**
1363
+ * Sort object keys alphabetically
1364
+ */
1365
+ sortObject(obj) {
1366
+ return Object.keys(obj).sort().reduce((sorted, key) => {
1367
+ sorted[key] = obj[key];
1368
+ return sorted;
1369
+ }, {});
1370
+ }
1371
+ /**
1372
+ * Create backup
1373
+ */
1374
+ async createBackup() {
1375
+ this.backupPath = `${this.packageJsonPath}.backup`;
1376
+ await fs6.copy(this.packageJsonPath, this.backupPath);
1377
+ }
1378
+ /**
1379
+ * Restore backup
1380
+ */
1381
+ async restoreBackup() {
1382
+ if (this.backupPath && await fs6.pathExists(this.backupPath)) {
1383
+ await fs6.copy(this.backupPath, this.packageJsonPath, { overwrite: true });
1384
+ await fs6.remove(this.backupPath);
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Clean up backup
1389
+ */
1390
+ async cleanupBackup() {
1391
+ if (this.backupPath && await fs6.pathExists(this.backupPath)) {
1392
+ await fs6.remove(this.backupPath);
1393
+ }
1394
+ }
1395
+ };
1396
+ }
1397
+ });
1398
+
1399
+ // src/installer/dependency-installer.ts
1400
+ var import_execa, import_detect_package_manager, DependencyInstaller;
1401
+ var init_dependency_installer = __esm({
1402
+ "src/installer/dependency-installer.ts"() {
1403
+ "use strict";
1404
+ init_cjs_shims();
1405
+ import_execa = require("execa");
1406
+ import_detect_package_manager = require("detect-package-manager");
1407
+ DependencyInstaller = class {
1408
+ /**
1409
+ * Install dependencies using the detected package manager
1410
+ */
1411
+ async install(cwd) {
1412
+ const packageManager = await this.detectPackageManager(cwd);
1413
+ console.log(`\u{1F4E6} Installing dependencies with ${packageManager}...`);
1414
+ try {
1415
+ await (0, import_execa.execa)(packageManager, ["install"], {
1416
+ cwd,
1417
+ stdio: "inherit"
1418
+ });
1419
+ console.log("\u2705 Dependencies installed successfully");
1420
+ } catch (error) {
1421
+ throw new Error(
1422
+ `Failed to install dependencies with ${packageManager}: ${error instanceof Error ? error.message : "Unknown error"}`
1423
+ );
1424
+ }
1425
+ }
1426
+ /**
1427
+ * Detect which package manager is being used
1428
+ */
1429
+ async detectPackageManager(cwd) {
1430
+ try {
1431
+ return await (0, import_detect_package_manager.detect)({ cwd });
1432
+ } catch (error) {
1433
+ return "npm";
1434
+ }
1435
+ }
1436
+ };
1437
+ }
1438
+ });
1439
+
1440
+ // src/installer/index.ts
1441
+ var installer_exports = {};
1442
+ __export(installer_exports, {
1443
+ AppModuleUpdater: () => AppModuleUpdater,
1444
+ DependencyInstaller: () => DependencyInstaller,
1445
+ MainTsUpdater: () => MainTsUpdater,
1446
+ PackageUpdater: () => PackageUpdater
1447
+ });
1448
+ var init_installer = __esm({
1449
+ "src/installer/index.ts"() {
1450
+ "use strict";
1451
+ init_cjs_shims();
1452
+ init_ast_updater();
1453
+ init_main_ts_updater();
1454
+ init_package_updater();
1455
+ init_dependency_installer();
1456
+ }
1457
+ });
1458
+
1459
+ // src/index.ts
1460
+ var index_exports = {};
1461
+ __export(index_exports, {
1462
+ run: () => run
1463
+ });
1464
+ async function run(cwd = process.cwd(), options = {}) {
1465
+ showBanner();
1466
+ const spinner = createSpinner("Analyzing project...").start();
1467
+ const projectInfo = await detectProject(cwd);
1468
+ if (!projectInfo.isValid) {
1469
+ spinner.fail("Project validation failed");
1470
+ showError("Not a valid NestJS project", projectInfo.errors);
1471
+ showNestJSHelp();
1472
+ process.exit(1);
1473
+ }
1474
+ spinner.succeed("Project analyzed");
1475
+ showProjectInfo({
1476
+ nestVersion: projectInfo.nestVersion,
1477
+ orm: projectInfo.orm,
1478
+ sourceRoot: projectInfo.sourceRoot
1479
+ });
1480
+ if (projectInfo.authExists) {
1481
+ if (options.yes) {
1482
+ console.log("\n auth/ directory already exists. Use interactive mode to overwrite.\n");
1483
+ process.exit(0);
1484
+ }
1485
+ const inquirer2 = (await import("inquirer")).default;
1486
+ const { overwrite } = await inquirer2.prompt([{
1487
+ type: "confirm",
1488
+ name: "overwrite",
1489
+ message: "auth/ directory already exists. Overwrite existing files?",
1490
+ default: false
1491
+ }]);
1492
+ if (!overwrite) {
1493
+ console.log("\n\u23ED\uFE0F Cancelled. Existing auth module unchanged.\n");
1494
+ process.exit(0);
1495
+ }
1496
+ }
1497
+ const answers = options.yes ? getDefaultAnswers(projectInfo.orm, projectInfo.database) : await promptConfig(projectInfo.orm, projectInfo.database);
1498
+ const config = buildConfig(
1499
+ answers,
1500
+ projectInfo.root.split(/[/\\]/).pop() || "project",
1501
+ projectInfo.sourceRoot,
1502
+ projectInfo.orm,
1503
+ projectInfo.database
1504
+ );
1505
+ console.log();
1506
+ console.log("\u2699\uFE0F Generating authentication module...");
1507
+ console.log();
1508
+ const { Generator: Generator2 } = await Promise.resolve().then(() => (init_generator2(), generator_exports));
1509
+ const generator = new Generator2();
1510
+ const genSpinner = createSpinner("Generating files from templates...").start();
1511
+ const result = await generator.generate(config, projectInfo, !!projectInfo.authExists);
1512
+ if (!result.success) {
1513
+ genSpinner.fail("Generation failed");
1514
+ showError("Failed to generate files", [result.error || "Unknown error"]);
1515
+ process.exit(1);
1516
+ }
1517
+ genSpinner.succeed(`Generated ${result.filesCreated.length} files`);
1518
+ if (result.filesSkipped.length > 0) {
1519
+ console.log(` \u26A0\uFE0F Skipped ${result.filesSkipped.length} existing file(s)`);
1520
+ }
1521
+ const astSpinner = createSpinner("Updating app.module.ts...").start();
1522
+ try {
1523
+ const { AppModuleUpdater: AppModuleUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1524
+ const astUpdater = new AppModuleUpdater2(projectInfo.appModulePath);
1525
+ await astUpdater.update(config);
1526
+ await astUpdater.cleanupBackup();
1527
+ astSpinner.succeed("Updated app.module.ts");
1528
+ } catch (error) {
1529
+ astSpinner.fail("Failed to update app.module.ts");
1530
+ showError(
1531
+ "AST modification failed",
1532
+ [error instanceof Error ? error.message : "Unknown error"]
1533
+ );
1534
+ process.exit(1);
1535
+ }
1536
+ const mainSpinner = createSpinner("Updating main.ts with global guards...").start();
1537
+ try {
1538
+ const { MainTsUpdater: MainTsUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1539
+ const mainUpdater = new MainTsUpdater2(projectInfo.mainTsPath);
1540
+ await mainUpdater.update(config);
1541
+ await mainUpdater.cleanupBackup();
1542
+ mainSpinner.succeed("Updated main.ts with global JWT guard");
1543
+ } catch (error) {
1544
+ mainSpinner.warn("Could not auto-update main.ts (see main.ts.example for manual setup)");
1545
+ }
1546
+ const pkgSpinner = createSpinner("Updating package.json...").start();
1547
+ try {
1548
+ const { PackageUpdater: PackageUpdater2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1549
+ const pkgUpdater = new PackageUpdater2(projectInfo.packageJsonPath);
1550
+ await pkgUpdater.update(config);
1551
+ await pkgUpdater.cleanupBackup();
1552
+ pkgSpinner.succeed("Updated package.json");
1553
+ } catch (error) {
1554
+ pkgSpinner.fail("Failed to update package.json");
1555
+ showError(
1556
+ "Package update failed",
1557
+ [error instanceof Error ? error.message : "Unknown error"]
1558
+ );
1559
+ process.exit(1);
1560
+ }
1561
+ if (config.autoInstall) {
1562
+ const installSpinner = createSpinner("Installing dependencies...").start();
1563
+ try {
1564
+ const { DependencyInstaller: DependencyInstaller2 } = await Promise.resolve().then(() => (init_installer(), installer_exports));
1565
+ const installer = new DependencyInstaller2();
1566
+ await installer.install(projectInfo.root);
1567
+ installSpinner.succeed("Dependencies installed");
1568
+ } catch (error) {
1569
+ installSpinner.fail("Failed to install dependencies");
1570
+ console.log(
1571
+ "\n\u26A0\uFE0F Please run npm install manually to install dependencies\n"
1572
+ );
1573
+ }
1574
+ }
1575
+ showSuccess({
1576
+ filesCreated: result.filesCreated.length,
1577
+ dependenciesAdded: 8,
1578
+ jwt: {
1579
+ accessExpiration: config.jwt.accessExpiration,
1580
+ refreshExpiration: config.features.refreshTokens ? config.jwt.refreshExpiration : void 0
1581
+ },
1582
+ orm: config.orm,
1583
+ swagger: config.features.swagger,
1584
+ emailVerification: config.features.emailVerification,
1585
+ resetPassword: config.features.resetPassword
1586
+ });
1587
+ console.log("\u{1F41B} Issues? https://github.com/Islamawad132/add-nest-auth/issues");
1588
+ console.log("\u2B50 Like it? https://www.npmjs.com/package/nest-authme");
1589
+ console.log();
1590
+ }
1591
+ var init_index = __esm({
1592
+ "src/index.ts"() {
1593
+ "use strict";
1594
+ init_cjs_shims();
1595
+ init_analyzer();
1596
+ init_prompts();
1597
+ init_ui();
1598
+ }
1599
+ });
1600
+
1601
+ // src/cli.ts
1602
+ init_cjs_shims();
1603
+ var import_commander = require("commander");
1604
+ process.on("unhandledRejection", (error) => {
1605
+ console.error("Unhandled rejection:", error);
1606
+ process.exit(1);
1607
+ });
1608
+ var program = new import_commander.Command();
1609
+ program.name("nest-authme").description("Add production-ready authentication to any NestJS project").version("1.1.0").option("-y, --yes", "Skip all prompts and use sensible defaults").action(async (options) => {
1610
+ try {
1611
+ const { run: run2 } = await Promise.resolve().then(() => (init_index(), index_exports));
1612
+ await run2(process.cwd(), { yes: options.yes || false });
1613
+ } catch (error) {
1614
+ console.error("Fatal error:", error);
1615
+ process.exit(1);
1616
+ }
1617
+ });
1618
+ program.parse();
1619
+ //# sourceMappingURL=cli.js.map