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.
- package/LICENSE +21 -0
- package/README.md +305 -0
- package/bin/cli.js +11 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1619 -0
- package/dist/cli.js.map +1 -0
- package/dist/generator/templates/decorators/current-user.decorator.ts.hbs +8 -0
- package/dist/generator/templates/decorators/public.decorator.ts.hbs +4 -0
- package/dist/generator/templates/decorators/roles.decorator.ts.hbs +4 -0
- package/dist/generator/templates/dto/auth-response.dto.ts.hbs +42 -0
- package/dist/generator/templates/dto/change-password.dto.ts.hbs +22 -0
- package/dist/generator/templates/dto/create-user.dto.ts.hbs +38 -0
- package/dist/generator/templates/dto/forgot-password.dto.ts.hbs +13 -0
- package/dist/generator/templates/dto/login.dto.ts.hbs +21 -0
- package/dist/generator/templates/dto/register.dto.ts.hbs +33 -0
- package/dist/generator/templates/dto/reset-password.dto.ts.hbs +22 -0
- package/dist/generator/templates/entities/refresh-token.entity.typeorm.hbs +24 -0
- package/dist/generator/templates/entities/user.entity.typeorm.hbs +51 -0
- package/dist/generator/templates/jwt/auth.controller.ts.hbs +177 -0
- package/dist/generator/templates/jwt/auth.module.ts.hbs +81 -0
- package/dist/generator/templates/jwt/auth.service.ts.hbs +416 -0
- package/dist/generator/templates/jwt/jwt-auth.guard.ts.hbs +24 -0
- package/dist/generator/templates/jwt/jwt.strategy.ts.hbs +61 -0
- package/dist/generator/templates/jwt/local-auth.guard.ts.hbs +5 -0
- package/dist/generator/templates/jwt/local.strategy.ts.hbs +22 -0
- package/dist/generator/templates/prisma/prisma.module.ts.hbs +9 -0
- package/dist/generator/templates/prisma/prisma.service.ts.hbs +9 -0
- package/dist/generator/templates/prisma/schema.prisma.additions.hbs +40 -0
- package/dist/generator/templates/rbac/role.enum.ts.hbs +5 -0
- package/dist/generator/templates/rbac/roles.guard.ts.hbs +22 -0
- package/dist/generator/templates/shared/README.auth.md.hbs +306 -0
- package/dist/generator/templates/shared/env.hbs +36 -0
- package/dist/generator/templates/shared/env.template.hbs +36 -0
- package/dist/generator/templates/shared/main.ts.snippet.hbs +49 -0
- package/dist/generator/templates/tests/auth.controller.spec.ts.hbs +189 -0
- package/dist/generator/templates/tests/auth.service.spec.ts.hbs +334 -0
- package/dist/generator/templates/users/users.controller.ts.hbs +55 -0
- package/dist/generator/templates/users/users.module.ts.hbs +31 -0
- package/dist/generator/templates/users/users.service.ts.hbs +192 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1566 -0
- package/dist/index.js.map +1 -0
- 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
|