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