nest-graph-inspector 0.3.0 → 0.5.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/README.md +20 -11
- package/package.json +4 -1
- package/src/adapters/file-output.adapter.d.ts +3 -4
- package/src/adapters/file-output.adapter.js +30 -89
- package/src/adapters/file-output.adapter.js.map +1 -1
- package/src/adapters/http-output.adapter.js +7 -0
- package/src/adapters/http-output.adapter.js.map +1 -1
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/index.js.map +1 -1
- package/src/nest-graph-inspector.setup.d.ts +42 -8
- package/src/nest-graph-inspector.setup.js +445 -102
- package/src/nest-graph-inspector.setup.js.map +1 -1
- package/src/types/graph-output.schema.d.ts +228 -0
- package/src/types/graph-output.schema.js +232 -0
- package/src/types/graph-output.schema.js.map +1 -0
- package/src/types/graph-output.type.d.ts +30 -0
- package/src/types/module-controller.type.d.ts +1 -0
- package/src/types/module-provider.type.d.ts +1 -0
- package/src/types/module.type.d.ts +1 -0
|
@@ -14,6 +14,8 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
|
14
14
|
var NestGraphInspectorSetup_1;
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
16
|
exports.NestGraphInspectorSetup = void 0;
|
|
17
|
+
const node_fs_1 = require("node:fs");
|
|
18
|
+
const node_path_1 = require("node:path");
|
|
17
19
|
const common_1 = require("@nestjs/common");
|
|
18
20
|
const core_1 = require("@nestjs/core");
|
|
19
21
|
const nest_graph_inspector_config_1 = require("./nest-graph-inspector.config");
|
|
@@ -22,6 +24,7 @@ const http_output_adapter_1 = require("./adapters/http-output.adapter");
|
|
|
22
24
|
const file_output_adapter_1 = require("./adapters/file-output.adapter");
|
|
23
25
|
const json_output_adapter_1 = require("./adapters/json-output.adapter");
|
|
24
26
|
const viewer_output_adapter_1 = require("./adapters/viewer-output.adapter");
|
|
27
|
+
const ts_morph_1 = require("ts-morph");
|
|
25
28
|
let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspectorSetup {
|
|
26
29
|
options;
|
|
27
30
|
modulesContainer;
|
|
@@ -35,6 +38,7 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
35
38
|
ignoreImport;
|
|
36
39
|
nestCoreModuleName;
|
|
37
40
|
nestCoreProviders;
|
|
41
|
+
tsMorphProject = this.createTsMorphProject();
|
|
38
42
|
constructor(options, modulesContainer, httpOutputAdapter, fileOutputAdapter, jsonOutputAdapter, viewerOutputAdapter) {
|
|
39
43
|
this.options = options;
|
|
40
44
|
this.modulesContainer = modulesContainer;
|
|
@@ -65,30 +69,70 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
65
69
|
];
|
|
66
70
|
}
|
|
67
71
|
async onModuleInit() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (!this.options.outputs?.length) {
|
|
72
|
+
await this.inspectAndPublishGraph();
|
|
73
|
+
}
|
|
74
|
+
async inspectAndPublishGraph() {
|
|
75
|
+
if (!this.hasOutput) {
|
|
73
76
|
return;
|
|
74
77
|
}
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
78
|
+
const rootModule = this.getRootModule();
|
|
79
|
+
const moduleTree = this.getModuleTree(rootModule);
|
|
80
|
+
await this.publishModuleTree(moduleTree);
|
|
81
|
+
}
|
|
82
|
+
get hasOutput() {
|
|
83
|
+
return !!this.options.outputs?.length;
|
|
84
|
+
}
|
|
85
|
+
getRootModule() {
|
|
86
|
+
const rootModuleClass = this.options.rootModule;
|
|
87
|
+
return rootModuleClass
|
|
88
|
+
? this.getRootModuleFromClass(rootModuleClass)
|
|
89
|
+
: this.findRootModule();
|
|
90
|
+
}
|
|
91
|
+
getRootModuleFromClass(rootModuleClass) {
|
|
92
|
+
const root = [...this.modulesContainer.values()].find((m) => m.metatype === rootModuleClass);
|
|
93
|
+
if (!root) {
|
|
94
|
+
throw new Error(`Root module not found: ${rootModuleClass.name}`);
|
|
95
|
+
}
|
|
96
|
+
return root;
|
|
97
|
+
}
|
|
98
|
+
getModuleTree(rootModule) {
|
|
99
|
+
const moduleTree = this.resolveModuleTree(rootModule);
|
|
100
|
+
this.resolveModuleMembers(moduleTree);
|
|
101
|
+
this.appendNestCoreModule(moduleTree);
|
|
102
|
+
return moduleTree;
|
|
103
|
+
}
|
|
104
|
+
async publishModuleTree(moduleTree) {
|
|
105
|
+
const moduleMap = this.createModuleMapFromModuleTree(moduleTree);
|
|
106
|
+
const graphOutput = this.createGraphOutput(moduleMap);
|
|
107
|
+
const outputs = this.prepareOutputs();
|
|
108
|
+
await this.publishOutputs({ graphOutput, outputs });
|
|
109
|
+
}
|
|
110
|
+
createGraphOutput(moduleMap) {
|
|
111
|
+
return this.enrichModuleMap(moduleMap);
|
|
112
|
+
}
|
|
113
|
+
prepareOutputs() {
|
|
114
|
+
return this.options.outputs ?? [];
|
|
115
|
+
}
|
|
116
|
+
async publishOutputs(param) {
|
|
117
|
+
await Promise.all(param.outputs.map(async (output) => {
|
|
118
|
+
await this.publishSingleOutput(param.graphOutput, output);
|
|
90
119
|
}));
|
|
91
120
|
}
|
|
121
|
+
async publishSingleOutput(graphOutput, output) {
|
|
122
|
+
output = this.withDefaultOutputOptions(output);
|
|
123
|
+
const adapter = this.outputAdapters[output.type];
|
|
124
|
+
if (!adapter) {
|
|
125
|
+
this.logger.warn(`Unsupported output type: ${output.type}`);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const { message } = await adapter.execute(graphOutput, output);
|
|
130
|
+
this.logger.debug(message);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
this.logger.error(`Failed to execute output adapter for type ${output.type}`, err);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
92
136
|
withDefaultOutputOptions(output) {
|
|
93
137
|
if (output.type !== 'viewer') {
|
|
94
138
|
return output;
|
|
@@ -107,37 +151,19 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
107
151
|
}
|
|
108
152
|
buildModuleMapFromAutoDetect() {
|
|
109
153
|
const root = this.findRootModule();
|
|
110
|
-
|
|
154
|
+
const moduleTree = this.getModuleTree(root);
|
|
155
|
+
return this.createModuleMapFromModuleTree(moduleTree);
|
|
111
156
|
}
|
|
112
157
|
buildModuleMap(rootModuleClass) {
|
|
113
|
-
const root =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
117
|
-
return this.buildModuleMapFromRef(root);
|
|
158
|
+
const root = this.getRootModuleFromClass(rootModuleClass);
|
|
159
|
+
const moduleTree = this.getModuleTree(root);
|
|
160
|
+
return this.createModuleMapFromModuleTree(moduleTree);
|
|
118
161
|
}
|
|
119
|
-
|
|
120
|
-
const
|
|
121
|
-
const modules = {};
|
|
122
|
-
for (const moduleRef of reachable) {
|
|
123
|
-
const moduleName = this.moduleName(moduleRef);
|
|
124
|
-
if (this.ignoreImport.includes(moduleName)) {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
modules[moduleName] = {
|
|
128
|
-
imports: this.extractImports(moduleRef),
|
|
129
|
-
exports: this.extractExports(moduleRef),
|
|
130
|
-
providers: this.extractProviders(moduleRef, moduleName),
|
|
131
|
-
controllers: this.extractControllers(moduleRef, moduleName),
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
const usedNestCoreProviders = this.findUsedNestCoreProviders(modules);
|
|
135
|
-
if (usedNestCoreProviders.length > 0) {
|
|
136
|
-
modules[this.nestCoreModuleName] = this.buildNestCoreModule(usedNestCoreProviders);
|
|
137
|
-
}
|
|
162
|
+
createModuleMapFromModuleTree(moduleTree) {
|
|
163
|
+
const modules = this.flattenModuleTree(moduleTree);
|
|
138
164
|
return {
|
|
139
|
-
version: '
|
|
140
|
-
root:
|
|
165
|
+
version: '3',
|
|
166
|
+
root: moduleTree.name,
|
|
141
167
|
modules,
|
|
142
168
|
};
|
|
143
169
|
}
|
|
@@ -156,21 +182,69 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
156
182
|
}
|
|
157
183
|
throw new Error('Could not auto-detect root module. No module imports NestGraphInspectorModule.');
|
|
158
184
|
}
|
|
159
|
-
|
|
185
|
+
resolveModuleTree(moduleRef, visited = new Set()) {
|
|
186
|
+
if (visited.has(moduleRef)) {
|
|
187
|
+
return this.createModuleTreeReference(moduleRef);
|
|
188
|
+
}
|
|
189
|
+
visited.add(moduleRef);
|
|
190
|
+
return {
|
|
191
|
+
name: this.moduleName(moduleRef),
|
|
192
|
+
jsdoc: this.extractModuleJsDoc(moduleRef),
|
|
193
|
+
moduleRef,
|
|
194
|
+
imports: [],
|
|
195
|
+
exports: [],
|
|
196
|
+
providers: [],
|
|
197
|
+
controllers: [],
|
|
198
|
+
children: [...moduleRef.imports.values()]
|
|
199
|
+
.filter((childModule) => !this.shouldIgnoreModule(childModule))
|
|
200
|
+
.map((childModule) => this.resolveModuleTree(childModule, visited)),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
createModuleTreeReference(moduleRef) {
|
|
160
204
|
return {
|
|
205
|
+
name: this.moduleName(moduleRef),
|
|
206
|
+
jsdoc: this.extractModuleJsDoc(moduleRef),
|
|
207
|
+
moduleRef,
|
|
208
|
+
imports: [],
|
|
209
|
+
exports: [],
|
|
210
|
+
providers: [],
|
|
211
|
+
controllers: [],
|
|
212
|
+
children: [],
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
resolveModuleMembers(moduleTree) {
|
|
216
|
+
this.walkModuleTree(moduleTree, (node) => {
|
|
217
|
+
if (!node.moduleRef) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
node.imports = node.children.map((child) => child.name);
|
|
221
|
+
node.exports = this.extractExports(node.moduleRef);
|
|
222
|
+
node.providers = this.extractProviders(node.moduleRef, node.name);
|
|
223
|
+
node.controllers = this.extractControllers(node.moduleRef, node.name);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
appendNestCoreModule(moduleTree) {
|
|
227
|
+
const usedProviders = this.findUsedNestCoreProvidersFromTree(moduleTree);
|
|
228
|
+
if (!usedProviders.length) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
moduleTree.children.push({
|
|
232
|
+
name: this.nestCoreModuleName,
|
|
233
|
+
moduleRef: null,
|
|
161
234
|
imports: [],
|
|
162
235
|
exports: [...usedProviders],
|
|
163
|
-
providers: usedProviders.map((
|
|
164
|
-
name
|
|
236
|
+
providers: usedProviders.map((name) => ({
|
|
237
|
+
name,
|
|
165
238
|
dependencies: [],
|
|
166
239
|
})),
|
|
167
240
|
controllers: [],
|
|
168
|
-
|
|
241
|
+
children: [],
|
|
242
|
+
});
|
|
169
243
|
}
|
|
170
|
-
|
|
244
|
+
findUsedNestCoreProvidersFromTree(moduleTree) {
|
|
171
245
|
const usedProviders = new Set();
|
|
172
|
-
|
|
173
|
-
for (const provider of
|
|
246
|
+
this.walkModuleTree(moduleTree, (node) => {
|
|
247
|
+
for (const provider of node.providers) {
|
|
174
248
|
for (const dependencyName of provider.dependencies) {
|
|
175
249
|
const nestCoreProviderName = this.extractNestCoreProviderName(dependencyName);
|
|
176
250
|
if (nestCoreProviderName) {
|
|
@@ -178,7 +252,7 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
178
252
|
}
|
|
179
253
|
}
|
|
180
254
|
}
|
|
181
|
-
for (const controller of
|
|
255
|
+
for (const controller of node.controllers) {
|
|
182
256
|
for (const dependencyName of controller.dependencies) {
|
|
183
257
|
const nestCoreProviderName = this.extractNestCoreProviderName(dependencyName);
|
|
184
258
|
if (nestCoreProviderName) {
|
|
@@ -186,9 +260,34 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
186
260
|
}
|
|
187
261
|
}
|
|
188
262
|
}
|
|
189
|
-
}
|
|
263
|
+
});
|
|
190
264
|
return this.nestCoreProviders.filter((providerName) => usedProviders.has(providerName));
|
|
191
265
|
}
|
|
266
|
+
flattenModuleTree(moduleTree) {
|
|
267
|
+
const modules = {};
|
|
268
|
+
this.walkModuleTree(moduleTree, (node) => {
|
|
269
|
+
if (modules[node.name] || this.ignoreImport.includes(node.name)) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
modules[node.name] = {
|
|
273
|
+
...(node.jsdoc ? { jsdoc: node.jsdoc } : {}),
|
|
274
|
+
imports: node.imports,
|
|
275
|
+
exports: node.exports,
|
|
276
|
+
providers: node.providers,
|
|
277
|
+
controllers: node.controllers,
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
return modules;
|
|
281
|
+
}
|
|
282
|
+
walkModuleTree(moduleTree, visit) {
|
|
283
|
+
visit(moduleTree);
|
|
284
|
+
for (const child of moduleTree.children) {
|
|
285
|
+
this.walkModuleTree(child, visit);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
shouldIgnoreModule(moduleRef) {
|
|
289
|
+
return this.ignoreImport.includes(this.moduleName(moduleRef));
|
|
290
|
+
}
|
|
192
291
|
extractNestCoreProviderName(dependencyName) {
|
|
193
292
|
const prefix = `${this.nestCoreModuleName}:`;
|
|
194
293
|
if (!dependencyName.startsWith(prefix)) {
|
|
@@ -203,16 +302,6 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
203
302
|
}
|
|
204
303
|
return providerName;
|
|
205
304
|
}
|
|
206
|
-
collectReachableModules(root, visited) {
|
|
207
|
-
if (visited.has(root))
|
|
208
|
-
return [];
|
|
209
|
-
visited.add(root);
|
|
210
|
-
const result = [root];
|
|
211
|
-
for (const imported of root.imports.values()) {
|
|
212
|
-
result.push(...this.collectReachableModules(imported, visited));
|
|
213
|
-
}
|
|
214
|
-
return result;
|
|
215
|
-
}
|
|
216
305
|
extractImports(moduleRef) {
|
|
217
306
|
return [...moduleRef.imports.values()]
|
|
218
307
|
.map((importedModuleRef) => this.moduleName(importedModuleRef))
|
|
@@ -224,59 +313,116 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
224
313
|
.filter((exportName) => !!exportName && !this.ignoreProvider.includes(exportName));
|
|
225
314
|
}
|
|
226
315
|
extractProviders(moduleRef, moduleName) {
|
|
227
|
-
return
|
|
228
|
-
.
|
|
229
|
-
|
|
316
|
+
return this.extractModuleMembers({
|
|
317
|
+
wrappers: moduleRef.providers.values(),
|
|
318
|
+
moduleRef,
|
|
319
|
+
extract: (wrapper) => this.extractModuleMember({
|
|
320
|
+
wrapper,
|
|
321
|
+
moduleRef,
|
|
322
|
+
shouldIgnore: (name) => this.ignoreProvider.includes(name),
|
|
323
|
+
}),
|
|
324
|
+
shouldSkip: (provider) => provider.name === moduleName,
|
|
325
|
+
});
|
|
230
326
|
}
|
|
231
327
|
extractControllers(moduleRef, moduleName) {
|
|
232
|
-
return
|
|
233
|
-
.
|
|
234
|
-
|
|
328
|
+
return this.extractModuleMembers({
|
|
329
|
+
wrappers: moduleRef.controllers.values(),
|
|
330
|
+
moduleRef,
|
|
331
|
+
extract: (wrapper) => this.extractModuleMember({ wrapper, moduleRef }),
|
|
332
|
+
});
|
|
235
333
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
334
|
+
extractModuleMembers(param) {
|
|
335
|
+
return [...param.wrappers].reduce((items, wrapper) => {
|
|
336
|
+
const executableWrapper = wrapper;
|
|
337
|
+
const item = param.extract(executableWrapper, param.moduleRef);
|
|
338
|
+
if (item) {
|
|
339
|
+
if (param.shouldSkip?.(item)) {
|
|
340
|
+
return items;
|
|
341
|
+
}
|
|
342
|
+
items.push(item);
|
|
343
|
+
}
|
|
344
|
+
return items;
|
|
345
|
+
}, []);
|
|
346
|
+
}
|
|
347
|
+
extractModuleMember(param) {
|
|
348
|
+
const { wrapper, moduleRef, shouldIgnore = () => false } = param;
|
|
349
|
+
const name = this.wrapperClassName(wrapper) || this.tokenName(wrapper.token);
|
|
350
|
+
if (!name || shouldIgnore(name)) {
|
|
239
351
|
return null;
|
|
240
352
|
}
|
|
353
|
+
const jsdoc = this.extractClassJsDoc(wrapper);
|
|
241
354
|
return {
|
|
242
355
|
name,
|
|
356
|
+
...(jsdoc ? { jsdoc } : {}),
|
|
243
357
|
dependencies: this.extractDependencies(wrapper, moduleRef),
|
|
244
358
|
};
|
|
245
359
|
}
|
|
246
|
-
|
|
247
|
-
const
|
|
248
|
-
if (!
|
|
249
|
-
|
|
360
|
+
createTsMorphProject() {
|
|
361
|
+
const tsConfigFilePath = (0, node_path_1.join)(process.cwd(), 'tsconfig.json');
|
|
362
|
+
if (!(0, node_fs_1.existsSync)(tsConfigFilePath)) {
|
|
363
|
+
this.logger.warn(`Could not find tsconfig.json at ${tsConfigFilePath}; JSDoc metadata will be skipped.`);
|
|
364
|
+
return new ts_morph_1.Project();
|
|
250
365
|
}
|
|
251
|
-
return {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
};
|
|
366
|
+
return new ts_morph_1.Project({
|
|
367
|
+
tsConfigFilePath,
|
|
368
|
+
skipAddingFilesFromTsConfig: false,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
extractModuleJsDoc(moduleRef) {
|
|
372
|
+
return moduleRef.metatype
|
|
373
|
+
? this.extractClassJsDocByName(moduleRef.metatype.name)
|
|
374
|
+
: undefined;
|
|
375
|
+
}
|
|
376
|
+
extractClassJsDoc(wrapper) {
|
|
377
|
+
const className = this.wrapperClassName(wrapper);
|
|
378
|
+
return className ? this.extractClassJsDocByName(className) : undefined;
|
|
379
|
+
}
|
|
380
|
+
extractClassJsDocByName(className) {
|
|
381
|
+
const targetClass = this.tsMorphProject
|
|
382
|
+
.getSourceFiles()
|
|
383
|
+
.flatMap((sourceFile) => sourceFile.getClasses())
|
|
384
|
+
.find((classDeclaration) => classDeclaration.getName() === className);
|
|
385
|
+
return targetClass
|
|
386
|
+
?.getJsDocs()
|
|
387
|
+
.map((doc) => doc.getCommentText())
|
|
388
|
+
.filter((comment) => !!comment)
|
|
389
|
+
.join('\n');
|
|
390
|
+
}
|
|
391
|
+
wrapperClassName(wrapper) {
|
|
392
|
+
if (wrapper.metatype?.name) {
|
|
393
|
+
return wrapper.metatype.name;
|
|
394
|
+
}
|
|
395
|
+
const instance = wrapper.instance;
|
|
396
|
+
if (instance &&
|
|
397
|
+
(typeof instance === 'object' || typeof instance === 'function')) {
|
|
398
|
+
return instance.constructor?.name ?? null;
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
255
401
|
}
|
|
256
402
|
extractDependencies(wrapper, moduleRef) {
|
|
257
403
|
const dependencies = new Set();
|
|
258
404
|
if (Array.isArray(wrapper?.inject)) {
|
|
259
405
|
for (const token of wrapper.inject) {
|
|
260
|
-
const dependencyName = this.resolveDependencyName(token, moduleRef);
|
|
406
|
+
const dependencyName = this.resolveDependencyName(this.resolveInjectionToken(token), moduleRef);
|
|
261
407
|
if (dependencyName) {
|
|
262
408
|
dependencies.add(dependencyName);
|
|
263
409
|
}
|
|
264
410
|
}
|
|
265
411
|
}
|
|
266
|
-
const ctorDeps = wrapper
|
|
412
|
+
const ctorDeps = wrapper.getCtorMetadata?.() ?? [];
|
|
267
413
|
for (const depWrapper of ctorDeps) {
|
|
268
414
|
if (depWrapper) {
|
|
269
|
-
const dependencyName = this.resolveDependencyName(depWrapper.token
|
|
415
|
+
const dependencyName = this.resolveDependencyName(depWrapper.token, moduleRef);
|
|
270
416
|
if (dependencyName && dependencyName !== 'Object') {
|
|
271
417
|
dependencies.add(dependencyName);
|
|
272
418
|
}
|
|
273
419
|
}
|
|
274
420
|
}
|
|
275
|
-
const propertyDeps = wrapper
|
|
421
|
+
const propertyDeps = wrapper.getPropertiesMetadata?.() ?? [];
|
|
276
422
|
for (const propertyDep of propertyDeps) {
|
|
277
423
|
const depWrapper = propertyDep?.wrapper;
|
|
278
424
|
if (depWrapper) {
|
|
279
|
-
const dependencyName = this.resolveDependencyName(depWrapper.token
|
|
425
|
+
const dependencyName = this.resolveDependencyName(depWrapper.token, moduleRef);
|
|
280
426
|
if (dependencyName && dependencyName !== 'Object') {
|
|
281
427
|
dependencies.add(dependencyName);
|
|
282
428
|
}
|
|
@@ -284,6 +430,12 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
284
430
|
}
|
|
285
431
|
return [...dependencies];
|
|
286
432
|
}
|
|
433
|
+
resolveInjectionToken(token) {
|
|
434
|
+
if (typeof token === 'object' && token !== null && 'token' in token) {
|
|
435
|
+
return token.token;
|
|
436
|
+
}
|
|
437
|
+
return token;
|
|
438
|
+
}
|
|
287
439
|
resolveDependencyName(token, moduleRef) {
|
|
288
440
|
const tokenName = this.tokenName(token);
|
|
289
441
|
if (!tokenName) {
|
|
@@ -313,7 +465,14 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
313
465
|
if (!this.isSameProviderToken(wrapper, token)) {
|
|
314
466
|
continue;
|
|
315
467
|
}
|
|
316
|
-
const
|
|
468
|
+
const providerInstance = wrapper.instance &&
|
|
469
|
+
(typeof wrapper.instance === 'object' ||
|
|
470
|
+
typeof wrapper.instance === 'function')
|
|
471
|
+
? wrapper.instance
|
|
472
|
+
: null;
|
|
473
|
+
const providerName = wrapper.metatype?.name ||
|
|
474
|
+
providerInstance?.constructor?.name ||
|
|
475
|
+
this.tokenName(wrapper.token);
|
|
317
476
|
if (!providerName) {
|
|
318
477
|
return null;
|
|
319
478
|
}
|
|
@@ -331,7 +490,9 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
331
490
|
if (wrapper?.metatype === token) {
|
|
332
491
|
return true;
|
|
333
492
|
}
|
|
334
|
-
if (wrapper
|
|
493
|
+
if (wrapper.instance &&
|
|
494
|
+
typeof wrapper.instance === 'object' &&
|
|
495
|
+
wrapper.instance.constructor === token) {
|
|
335
496
|
return true;
|
|
336
497
|
}
|
|
337
498
|
return false;
|
|
@@ -342,15 +503,12 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
342
503
|
.filter((exportName) => !!exportName));
|
|
343
504
|
}
|
|
344
505
|
exportName(exportedItem) {
|
|
345
|
-
return
|
|
346
|
-
this.tokenName(exportedItem?.token || exportedItem));
|
|
506
|
+
return this.tokenName(exportedItem);
|
|
347
507
|
}
|
|
348
508
|
isExportedProviderToken(moduleRef, token) {
|
|
349
509
|
const tokenName = this.tokenName(token);
|
|
350
510
|
for (const exportedItem of moduleRef.exports.values()) {
|
|
351
|
-
if (exportedItem === token
|
|
352
|
-
exportedItem?.token === token ||
|
|
353
|
-
exportedItem?.metatype === token) {
|
|
511
|
+
if (exportedItem === token) {
|
|
354
512
|
return true;
|
|
355
513
|
}
|
|
356
514
|
if (tokenName && this.exportName(exportedItem) === tokenName) {
|
|
@@ -377,12 +535,6 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
377
535
|
this.tokenName(moduleRef.token) ||
|
|
378
536
|
'AnonymousModule');
|
|
379
537
|
}
|
|
380
|
-
wrapperName(wrapper) {
|
|
381
|
-
return (wrapper?.metatype?.name ||
|
|
382
|
-
wrapper?.instance?.constructor?.name ||
|
|
383
|
-
this.tokenName(wrapper?.token) ||
|
|
384
|
-
null);
|
|
385
|
-
}
|
|
386
538
|
tokenName(token) {
|
|
387
539
|
if (!token)
|
|
388
540
|
return null;
|
|
@@ -392,7 +544,7 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
392
544
|
return token.toString();
|
|
393
545
|
if (typeof token === 'function')
|
|
394
546
|
return token.name;
|
|
395
|
-
return
|
|
547
|
+
return null;
|
|
396
548
|
}
|
|
397
549
|
enrichModuleMap(moduleMap) {
|
|
398
550
|
if (!moduleMap.modules) {
|
|
@@ -415,8 +567,199 @@ let NestGraphInspectorSetup = NestGraphInspectorSetup_1 = class NestGraphInspect
|
|
|
415
567
|
return {
|
|
416
568
|
...moduleMap,
|
|
417
569
|
modules: enrichedModules,
|
|
570
|
+
cycles: this.findGraphCycles(enrichedModules),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
findGraphCycles(modules) {
|
|
574
|
+
let nextId = 1;
|
|
575
|
+
const nextCycleId = () => nextId++;
|
|
576
|
+
return {
|
|
577
|
+
modules: this.findModuleCycles(modules, nextCycleId),
|
|
578
|
+
...this.findDependencyCycles(modules, nextCycleId),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
findModuleCycles(modules, nextCycleId) {
|
|
582
|
+
const graph = this.createGraph(Object.keys(modules));
|
|
583
|
+
for (const [moduleName, moduleData] of Object.entries(modules)) {
|
|
584
|
+
for (const importedModuleName of moduleData.imports) {
|
|
585
|
+
if (modules[importedModuleName]) {
|
|
586
|
+
graph.get(moduleName)?.add(importedModuleName);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return this.findCycles(graph, nextCycleId);
|
|
591
|
+
}
|
|
592
|
+
findDependencyCycles(modules, nextCycleId) {
|
|
593
|
+
const nodes = new Map();
|
|
594
|
+
for (const [moduleName, moduleData] of Object.entries(modules)) {
|
|
595
|
+
for (const provider of moduleData.providers) {
|
|
596
|
+
const key = this.dependencyNodeKey(moduleName, provider.name);
|
|
597
|
+
nodes.set(key, {
|
|
598
|
+
key,
|
|
599
|
+
kind: 'provider',
|
|
600
|
+
moduleName,
|
|
601
|
+
name: provider.name,
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
for (const controller of moduleData.controllers) {
|
|
605
|
+
const key = this.dependencyNodeKey(moduleName, controller.name);
|
|
606
|
+
nodes.set(key, {
|
|
607
|
+
key,
|
|
608
|
+
kind: 'controller',
|
|
609
|
+
moduleName,
|
|
610
|
+
name: controller.name,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
const graph = this.createGraph([...nodes.keys()]);
|
|
615
|
+
for (const [moduleName, moduleData] of Object.entries(modules)) {
|
|
616
|
+
for (const provider of moduleData.providers) {
|
|
617
|
+
this.addDependencyEdges(graph, this.dependencyNodeKey(moduleName, provider.name), provider.dependencies, nodes);
|
|
618
|
+
}
|
|
619
|
+
for (const controller of moduleData.controllers) {
|
|
620
|
+
this.addDependencyEdges(graph, this.dependencyNodeKey(moduleName, controller.name), controller.dependencies, nodes);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const cycles = this.findCycles(graph, nextCycleId);
|
|
624
|
+
return {
|
|
625
|
+
providers: cycles
|
|
626
|
+
.filter((cycle) => nodes.get(cycle.from)?.kind === 'provider')
|
|
627
|
+
.map((cycle) => this.toProviderCycle(cycle, nodes)),
|
|
628
|
+
controllers: cycles.filter((cycle) => nodes.get(cycle.from)?.kind === 'controller'),
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
addDependencyEdges(graph, sourceKey, dependencies, nodes) {
|
|
632
|
+
const sourceEdges = graph.get(sourceKey);
|
|
633
|
+
if (!sourceEdges) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
for (const dependency of dependencies) {
|
|
637
|
+
const targetKey = this.dependencyNodeKey(dependency.providedBy.name, dependency.token);
|
|
638
|
+
if (nodes.has(targetKey)) {
|
|
639
|
+
sourceEdges.add(targetKey);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
createGraph(keys) {
|
|
644
|
+
const graph = new Map();
|
|
645
|
+
for (const key of keys) {
|
|
646
|
+
graph.set(key, new Set());
|
|
647
|
+
}
|
|
648
|
+
return graph;
|
|
649
|
+
}
|
|
650
|
+
findCycles(graph, nextCycleId) {
|
|
651
|
+
const cycles = [];
|
|
652
|
+
const seenCycleKeys = new Set();
|
|
653
|
+
const reachableKeys = new Map();
|
|
654
|
+
for (const source of graph.keys()) {
|
|
655
|
+
reachableKeys.set(source, this.findReachableKeys(source, graph));
|
|
656
|
+
}
|
|
657
|
+
for (const [source, targets] of graph) {
|
|
658
|
+
for (const target of targets) {
|
|
659
|
+
if (source !== target && !reachableKeys.get(target)?.has(source)) {
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
const path = source === target
|
|
663
|
+
? [source, source]
|
|
664
|
+
: [source, ...this.findPath(target, source, graph)];
|
|
665
|
+
const cycleKey = this.getCanonicalCycleKey(path);
|
|
666
|
+
if (seenCycleKeys.has(cycleKey)) {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
seenCycleKeys.add(cycleKey);
|
|
670
|
+
cycles.push({
|
|
671
|
+
id: nextCycleId(),
|
|
672
|
+
from: source,
|
|
673
|
+
to: target,
|
|
674
|
+
type: this.getCycleType(source, target, graph),
|
|
675
|
+
path,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
return cycles;
|
|
680
|
+
}
|
|
681
|
+
getCanonicalCycleKey(path) {
|
|
682
|
+
const cyclePath = path.slice(0, -1);
|
|
683
|
+
if (cyclePath.length <= 1) {
|
|
684
|
+
return cyclePath.join('->');
|
|
685
|
+
}
|
|
686
|
+
const rotations = cyclePath.map((_, index) => [
|
|
687
|
+
...cyclePath.slice(index),
|
|
688
|
+
...cyclePath.slice(0, index),
|
|
689
|
+
]);
|
|
690
|
+
return rotations
|
|
691
|
+
.map((rotation) => rotation.join('->'))
|
|
692
|
+
.sort((a, b) => a.localeCompare(b))[0];
|
|
693
|
+
}
|
|
694
|
+
toProviderCycle(cycle, nodes) {
|
|
695
|
+
return {
|
|
696
|
+
...cycle,
|
|
697
|
+
path: cycle.path.map((key) => this.toProviderCyclePathItem(key, nodes)),
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
toProviderCyclePathItem(key, nodes) {
|
|
701
|
+
const node = nodes.get(key);
|
|
702
|
+
if (node) {
|
|
703
|
+
return {
|
|
704
|
+
module: { name: node.moduleName },
|
|
705
|
+
provider: { name: node.name },
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
const separatorIndex = key.indexOf(':');
|
|
709
|
+
if (separatorIndex === -1) {
|
|
710
|
+
return {
|
|
711
|
+
module: { name: '' },
|
|
712
|
+
provider: { name: key },
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
return {
|
|
716
|
+
module: { name: key.slice(0, separatorIndex) },
|
|
717
|
+
provider: { name: key.slice(separatorIndex + 1) },
|
|
418
718
|
};
|
|
419
719
|
}
|
|
720
|
+
getCycleType(source, target, graph) {
|
|
721
|
+
if (source === target || graph.get(target)?.has(source)) {
|
|
722
|
+
return 'direct';
|
|
723
|
+
}
|
|
724
|
+
return 'indirect';
|
|
725
|
+
}
|
|
726
|
+
findPath(source, target, graph) {
|
|
727
|
+
const visited = new Set();
|
|
728
|
+
const pendingPaths = [[source]];
|
|
729
|
+
while (pendingPaths.length > 0) {
|
|
730
|
+
const currentPath = pendingPaths.shift();
|
|
731
|
+
const current = currentPath?.[currentPath.length - 1];
|
|
732
|
+
if (!currentPath || !current || visited.has(current)) {
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (current === target) {
|
|
736
|
+
return currentPath;
|
|
737
|
+
}
|
|
738
|
+
visited.add(current);
|
|
739
|
+
for (const next of graph.get(current) ?? []) {
|
|
740
|
+
pendingPaths.push([...currentPath, next]);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return [source, target];
|
|
744
|
+
}
|
|
745
|
+
findReachableKeys(source, graph) {
|
|
746
|
+
const reachableKeys = new Set();
|
|
747
|
+
const pendingKeys = [...(graph.get(source) ?? [])];
|
|
748
|
+
while (pendingKeys.length > 0) {
|
|
749
|
+
const currentKey = pendingKeys.pop();
|
|
750
|
+
if (!currentKey || reachableKeys.has(currentKey)) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
reachableKeys.add(currentKey);
|
|
754
|
+
for (const nextKey of graph.get(currentKey) ?? []) {
|
|
755
|
+
pendingKeys.push(nextKey);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return reachableKeys;
|
|
759
|
+
}
|
|
760
|
+
dependencyNodeKey(moduleName, dependencyName) {
|
|
761
|
+
return `${moduleName}:${dependencyName}`;
|
|
762
|
+
}
|
|
420
763
|
enrichDependency(dependency, currentModule) {
|
|
421
764
|
const colonIndex = dependency.indexOf(':');
|
|
422
765
|
if (colonIndex !== -1) {
|