@sun-asterisk/impact-analyzer 1.0.3 → 1.0.5
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/.github/copilot-instructions.md +116 -0
- package/.github/prompts/README.md +91 -0
- package/.github/prompts/task-001-refactor.prompt.md +241 -0
- package/.specify/bugs/bug-001-database-detector.md +222 -0
- package/.specify/plans/architecture.md +186 -0
- package/.specify/specs/features/api-impact-detection.md +317 -0
- package/.specify/specs/features/component-impact-detection.md +263 -0
- package/.specify/specs/features/database-impact-detection.md +247 -0
- package/.specify/tasks/task-001-refactor-api-detector.md +284 -0
- package/.specify/tasks/task-002-database-detector.md +593 -0
- package/.specify/tasks/task-003-component-detector.md +0 -0
- package/.specify/tasks/task-004-report.md +484 -0
- package/README.md +13 -19
- package/core/detectors/database-detector.js +702 -0
- package/{modules → core}/detectors/endpoint-detector.js +11 -8
- package/{modules → core}/report-generator.js +112 -23
- package/core/utils/logger.js +12 -0
- package/{modules → core}/utils/method-call-graph.js +20 -0
- package/index.js +6 -5
- package/package.json +1 -1
- package/modules/detectors/database-detector.js +0 -182
- /package/{modules → core}/change-detector.js +0 -0
- /package/{modules → core}/impact-analyzer.js +0 -0
- /package/{modules → core}/utils/ast-parser.js +0 -0
- /package/{modules → core}/utils/dependency-graph.js +0 -0
- /package/{modules → core}/utils/file-utils.js +0 -0
- /package/{modules → core}/utils/git-utils.js +0 -0
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Endpoint Impact Detector
|
|
3
|
-
* Detects affected API endpoints using method-level call graph
|
|
3
|
+
* Detects affected API endpoints using method-level call graph with performance optimization
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { createLogger } from '../utils/logger.js';
|
|
7
|
+
|
|
6
8
|
export class EndpointDetector {
|
|
7
9
|
constructor(methodCallGraph, config) {
|
|
8
10
|
this.methodCallGraph = methodCallGraph;
|
|
9
11
|
this.config = config;
|
|
12
|
+
this.logger = createLogger(config.verbose);
|
|
10
13
|
}
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Detect affected endpoints from changed files
|
|
14
17
|
*/
|
|
15
18
|
async detect(changedFiles) {
|
|
19
|
+
const startTime = Date.now();
|
|
16
20
|
const allChangedMethods = [];
|
|
17
21
|
|
|
18
22
|
for (const changedFile of changedFiles) {
|
|
@@ -25,19 +29,18 @@ export class EndpointDetector {
|
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
if (allChangedMethods.length === 0) {
|
|
28
|
-
|
|
29
|
-
console.log(' ℹ️ No method-level changes detected');
|
|
30
|
-
}
|
|
32
|
+
this.logger.info(' ℹ️ No method-level changes detected');
|
|
31
33
|
return [];
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
console.log(` Call graph size: ${this.methodCallGraph.methodCallMap.size} entries`);
|
|
37
|
-
}
|
|
36
|
+
this.logger.info(`\n 🔍 Finding affected endpoints for ${allChangedMethods.length} changed methods...`);
|
|
37
|
+
this.logger.verbose('EndpointDetector', `Call graph size: ${this.methodCallGraph.methodCallMap.size} entries`);
|
|
38
38
|
|
|
39
39
|
const affectedEndpoints = this.methodCallGraph.findAffectedEndpoints(allChangedMethods);
|
|
40
40
|
|
|
41
|
+
const duration = Date.now() - startTime;
|
|
42
|
+
this.logger.verbose('EndpointDetector', `Completed in ${duration}ms`);
|
|
43
|
+
|
|
41
44
|
if (this.config.verbose) {
|
|
42
45
|
for (const endpoint of affectedEndpoints) {
|
|
43
46
|
console.log(`\n 📍 Analyzing: ${endpoint.affectedBy}`);
|
|
@@ -35,9 +35,14 @@ export class ReportGenerator {
|
|
|
35
35
|
// Summary
|
|
36
36
|
console.log('📊 SUMMARY:');
|
|
37
37
|
console.log(` Files Changed: ${changes.changedFiles.length}`);
|
|
38
|
-
console.log(` Symbols Modified: ${changes.changedSymbols.length}`);
|
|
39
38
|
console.log(` Impact Score: ${impact.impactScore}`);
|
|
40
39
|
console.log(` Severity: ${this.getSeverityEmoji(impact.severity)} ${impact.severity.toUpperCase()}`);
|
|
40
|
+
if (impact.affectedEndpoints.length > 0) {
|
|
41
|
+
console.log(` Affected Endpoints: ${impact.affectedEndpoints.length}`);
|
|
42
|
+
}
|
|
43
|
+
if (impact.databaseImpact.length > 0) {
|
|
44
|
+
console.log(` Database Tables: ${impact.databaseImpact.length}`);
|
|
45
|
+
}
|
|
41
46
|
console.log('');
|
|
42
47
|
|
|
43
48
|
// Endpoints
|
|
@@ -56,9 +61,10 @@ export class ReportGenerator {
|
|
|
56
61
|
if (impact.databaseImpact.length > 0) {
|
|
57
62
|
console.log('💾 DATABASE IMPACT:');
|
|
58
63
|
impact.databaseImpact.forEach(d => {
|
|
59
|
-
console.log(` • ${d.
|
|
64
|
+
console.log(` • ${d.tableName} (${d.impactType}, ${d.severity})`);
|
|
65
|
+
console.log(` Operations: ${d.operations.join(', ')}`);
|
|
60
66
|
if (d.fields && d.fields.length > 0) {
|
|
61
|
-
console.log(` Fields: ${d.fields.slice(0,
|
|
67
|
+
console.log(` Fields: ${d.fields.slice(0, 5).join(', ')}${d.fields.length > 5 ? '...' : ''}`);
|
|
62
68
|
}
|
|
63
69
|
});
|
|
64
70
|
console.log('');
|
|
@@ -106,7 +112,8 @@ export class ReportGenerator {
|
|
|
106
112
|
timestamp: new Date().toISOString(),
|
|
107
113
|
summary: {
|
|
108
114
|
filesChanged: changes.changedFiles.length,
|
|
109
|
-
|
|
115
|
+
affectedEndpoints: impact.affectedEndpoints.length,
|
|
116
|
+
databaseTables: impact.databaseImpact.length,
|
|
110
117
|
impactScore: impact.impactScore,
|
|
111
118
|
severity: impact.severity,
|
|
112
119
|
},
|
|
@@ -124,7 +131,8 @@ export class ReportGenerator {
|
|
|
124
131
|
| Metric | Value |
|
|
125
132
|
|--------|-------|
|
|
126
133
|
| Files Changed | ${changes.changedFiles.length} |
|
|
127
|
-
|
|
|
134
|
+
| Affected Endpoints | ${impact.affectedEndpoints.length} |
|
|
135
|
+
| Database Tables | ${impact.databaseImpact.length} |
|
|
128
136
|
| Impact Score | **${impact.impactScore}** |
|
|
129
137
|
| Severity | ${this.getSeverityEmoji(impact.severity)} **${impact.severity.toUpperCase()}** |
|
|
130
138
|
|
|
@@ -155,31 +163,32 @@ export class ReportGenerator {
|
|
|
155
163
|
|
|
156
164
|
lines.push(`**Total Tables Affected:** ${dbImpacts.length}\n`);
|
|
157
165
|
|
|
158
|
-
//
|
|
159
|
-
lines.push('| Table |
|
|
160
|
-
lines.push('
|
|
166
|
+
// Summary table with new fields
|
|
167
|
+
lines.push('| Table | Model | Impact Type | Severity | Operations |');
|
|
168
|
+
lines.push('|-------|-------|-------------|----------|-----------|');
|
|
161
169
|
|
|
162
170
|
dbImpacts.forEach(db => {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
: '-';
|
|
166
|
-
lines.push(`| \`${db.table}\` | ${db.operations.join(', ')} | ${fieldsDisplay} |`);
|
|
171
|
+
const opsDisplay = db.operations.join(', ');
|
|
172
|
+
lines.push(`| \`${db.tableName}\` | ${db.modelName} | ${db.impactType} | ${db.severity} | ${opsDisplay} |`);
|
|
167
173
|
});
|
|
168
174
|
|
|
169
175
|
lines.push('');
|
|
170
176
|
|
|
171
|
-
// Detailed breakdown
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (tablesWithFields.length > 0) {
|
|
175
|
-
lines.push('### Field Details\n');
|
|
177
|
+
// Detailed breakdown
|
|
178
|
+
lines.push('### Details\n');
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
});
|
|
182
|
-
|
|
180
|
+
dbImpacts.forEach(db => {
|
|
181
|
+
lines.push(`#### \`${db.tableName}\` (${db.modelName})\n`);
|
|
182
|
+
lines.push(`- **Impact Type:** ${db.impactType}`);
|
|
183
|
+
lines.push(`- **Severity:** ${db.severity}`);
|
|
184
|
+
lines.push(`- **Operations:** ${db.operations.join(', ')}`);
|
|
185
|
+
lines.push(`- **Entity File:** ${db.modelPath}`);
|
|
186
|
+
|
|
187
|
+
if (db.fields && db.fields.length > 0) {
|
|
188
|
+
lines.push(`- **Affected Fields:** ${db.fields.map(f => `\`${f}\``).join(', ')}`);
|
|
189
|
+
}
|
|
190
|
+
lines.push('');
|
|
191
|
+
});
|
|
183
192
|
|
|
184
193
|
return lines.join('\n');
|
|
185
194
|
}
|
|
@@ -370,4 +379,84 @@ export class ReportGenerator {
|
|
|
370
379
|
capitalizeFirst(str) {
|
|
371
380
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
372
381
|
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Generate clean JSON report suitable for CI/CD
|
|
385
|
+
* Excludes source code, diffs, and internal data structures
|
|
386
|
+
*/
|
|
387
|
+
generateJSONReport(changes, impact) {
|
|
388
|
+
return {
|
|
389
|
+
metadata: {
|
|
390
|
+
timestamp: new Date().toISOString(),
|
|
391
|
+
filesChanged: changes.changedFiles.length,
|
|
392
|
+
symbolsChanged: changes.changedSymbols?.length || 0,
|
|
393
|
+
},
|
|
394
|
+
summary: {
|
|
395
|
+
impactScore: impact.impactScore,
|
|
396
|
+
severity: impact.severity,
|
|
397
|
+
affectedEndpoints: impact.affectedEndpoints?.length || 0,
|
|
398
|
+
affectedTables: impact.databaseImpact?.length || 0,
|
|
399
|
+
affectedComponents: this.countComponents(changes.changedFiles),
|
|
400
|
+
},
|
|
401
|
+
endpoints: this.formatEndpointsForJSON(impact.affectedEndpoints),
|
|
402
|
+
database: this.formatDatabaseForJSON(impact.databaseImpact),
|
|
403
|
+
components: this.formatComponentsForJSON(changes.changedFiles),
|
|
404
|
+
logic: {
|
|
405
|
+
riskLevel: impact.logicImpact?.riskLevel || 'low',
|
|
406
|
+
directCallers: impact.logicImpact?.directCallers?.length || 0,
|
|
407
|
+
indirectCallers: impact.logicImpact?.indirectCallers?.length || 0,
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
formatEndpointsForJSON(endpoints) {
|
|
413
|
+
if (!endpoints || endpoints.length === 0) return [];
|
|
414
|
+
|
|
415
|
+
return endpoints.map(e => ({
|
|
416
|
+
method: e.method,
|
|
417
|
+
path: e.path,
|
|
418
|
+
controller: e.controller,
|
|
419
|
+
impactLevel: e.impactLevel,
|
|
420
|
+
layers: e.layers,
|
|
421
|
+
}));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
formatDatabaseForJSON(dbImpacts) {
|
|
425
|
+
if (!dbImpacts || dbImpacts.length === 0) return [];
|
|
426
|
+
|
|
427
|
+
return dbImpacts.map(db => ({
|
|
428
|
+
tableName: db.tableName,
|
|
429
|
+
modelName: db.modelName,
|
|
430
|
+
modelPath: db.modelPath,
|
|
431
|
+
impactType: db.impactType,
|
|
432
|
+
severity: db.severity,
|
|
433
|
+
operations: db.operations,
|
|
434
|
+
fields: db.fields || [],
|
|
435
|
+
}));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
formatComponentsForJSON(changedFiles) {
|
|
439
|
+
const components = changedFiles.filter(f => this.isComponentFile(f));
|
|
440
|
+
|
|
441
|
+
return components.map(c => ({
|
|
442
|
+
path: c.path,
|
|
443
|
+
type: this.detectPageType(c.path),
|
|
444
|
+
status: c.status,
|
|
445
|
+
linesAdded: c.changes?.added || 0,
|
|
446
|
+
linesDeleted: c.changes?.deleted || 0,
|
|
447
|
+
}));
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
isComponentFile(file) {
|
|
451
|
+
const path = file.path.toLowerCase();
|
|
452
|
+
return (
|
|
453
|
+
path.includes('/page') || path.includes('/component') ||
|
|
454
|
+
path.includes('/view') || path.includes('/screen') ||
|
|
455
|
+
path.includes('.page.') || path.includes('.component.')
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
countComponents(changedFiles) {
|
|
460
|
+
return changedFiles.filter(f => this.isComponentFile(f)).length;
|
|
461
|
+
}
|
|
373
462
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Utility
|
|
3
|
+
* Provides structured logging with verbose mode support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function createLogger(verbose = false) {
|
|
7
|
+
return {
|
|
8
|
+
info: (msg) => console.log(msg),
|
|
9
|
+
debug: (msg) => verbose && console.log(`[DEBUG] ${msg}`),
|
|
10
|
+
verbose: (component, msg) => verbose && console.log(`[${component}] ${msg}`)
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -704,6 +704,7 @@ export class MethodCallGraph {
|
|
|
704
704
|
affectedBy: changedMethod,
|
|
705
705
|
callChain: callChain,
|
|
706
706
|
layers: this.getCallChainLayers(callChain),
|
|
707
|
+
impactLevel: this.calculateImpactLevel(callChain),
|
|
707
708
|
});
|
|
708
709
|
}
|
|
709
710
|
}
|
|
@@ -721,6 +722,7 @@ export class MethodCallGraph {
|
|
|
721
722
|
affectedBy: changedMethod,
|
|
722
723
|
callChain: [changedMethod],
|
|
723
724
|
layers: [startLayer],
|
|
725
|
+
impactLevel: 'high', // Direct endpoint change is always high impact
|
|
724
726
|
});
|
|
725
727
|
}
|
|
726
728
|
|
|
@@ -793,6 +795,7 @@ export class MethodCallGraph {
|
|
|
793
795
|
layers: [this.getMethodLayer(changedMethod), 'Command', this.getMethodLayer(endpointMethod)],
|
|
794
796
|
viaCommand: commandName,
|
|
795
797
|
endpointMethod: endpointMethod,
|
|
798
|
+
impactLevel: this.calculateImpactLevel([changedMethod, `Command: '${commandName}'`, endpointMethod]),
|
|
796
799
|
});
|
|
797
800
|
}
|
|
798
801
|
}
|
|
@@ -924,4 +927,21 @@ export class MethodCallGraph {
|
|
|
924
927
|
.reduce((sum, callers) => sum + callers.length, 0),
|
|
925
928
|
};
|
|
926
929
|
}
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* Calculate impact level based on call chain length
|
|
933
|
+
* Shorter chain = higher impact (closer to endpoint)
|
|
934
|
+
*/
|
|
935
|
+
calculateImpactLevel(callChain) {
|
|
936
|
+
const chainLength = callChain.length;
|
|
937
|
+
|
|
938
|
+
// Direct endpoint change or very short chain
|
|
939
|
+
if (chainLength <= 1) return 'high';
|
|
940
|
+
|
|
941
|
+
// Short chain (2-3 hops)
|
|
942
|
+
if (chainLength <= 3) return 'medium';
|
|
943
|
+
|
|
944
|
+
// Longer chain
|
|
945
|
+
return 'low';
|
|
946
|
+
}
|
|
927
947
|
}
|
package/index.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import { CLI } from './cli.js';
|
|
9
9
|
import { loadConfig } from './config/default-config.js';
|
|
10
|
-
import { ChangeDetector } from './
|
|
11
|
-
import { ImpactAnalyzer } from './
|
|
12
|
-
import { ReportGenerator } from './
|
|
13
|
-
import { GitUtils } from './
|
|
10
|
+
import { ChangeDetector } from './core/change-detector.js';
|
|
11
|
+
import { ImpactAnalyzer } from './core/impact-analyzer.js';
|
|
12
|
+
import { ReportGenerator } from './core/report-generator.js';
|
|
13
|
+
import { GitUtils } from './core/utils/git-utils.js';
|
|
14
14
|
import fs from 'fs';
|
|
15
15
|
import path from 'path';
|
|
16
16
|
|
|
@@ -102,7 +102,8 @@ async function main() {
|
|
|
102
102
|
// JSON output (optional)
|
|
103
103
|
if (cli.hasArg('json')) {
|
|
104
104
|
const jsonOutput = cli.getArg('json');
|
|
105
|
-
|
|
105
|
+
const jsonReport = reporter.generateJSONReport(changes, impact);
|
|
106
|
+
fs.writeFileSync(jsonOutput, JSON.stringify(jsonReport, null, 2));
|
|
106
107
|
console.log(`✅ JSON report saved to: ${jsonOutput}\n`);
|
|
107
108
|
}
|
|
108
109
|
|
package/package.json
CHANGED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Database Impact Detector
|
|
3
|
-
* Detects database changes using layer-aware method tracking
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import path from 'path';
|
|
7
|
-
|
|
8
|
-
export class DatabaseDetector {
|
|
9
|
-
constructor(methodCallGraph, config) {
|
|
10
|
-
this.methodCallGraph = methodCallGraph;
|
|
11
|
-
this.config = config;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Detect database impact from changed files using layer-aware tracking
|
|
16
|
-
*/
|
|
17
|
-
async detect(changedFiles) {
|
|
18
|
-
const databaseChanges = {
|
|
19
|
-
tables: new Set(),
|
|
20
|
-
fields: new Map(), // table -> Set of fields
|
|
21
|
-
operations: new Map(), // table -> Set of operations
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
// Extract changed methods
|
|
25
|
-
const allChangedMethods = [];
|
|
26
|
-
for (const changedFile of changedFiles) {
|
|
27
|
-
const changedMethods = this.methodCallGraph.getChangedMethods(
|
|
28
|
-
changedFile.diff || '',
|
|
29
|
-
changedFile.absolutePath
|
|
30
|
-
);
|
|
31
|
-
allChangedMethods.push(...changedMethods);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (this.config.verbose) {
|
|
35
|
-
console.log(`\n 🔍 Analyzing database impact for ${allChangedMethods.length} changed methods`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Find affected repository methods through call graph
|
|
39
|
-
const affectedRepoMethods = this.findAffectedRepositoryMethods(allChangedMethods);
|
|
40
|
-
|
|
41
|
-
if (this.config.verbose) {
|
|
42
|
-
console.log(`\n 📊 Found ${affectedRepoMethods.length} affected repository methods`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Analyze repository methods for database operations
|
|
46
|
-
for (const repoMethod of affectedRepoMethods) {
|
|
47
|
-
const dbOps = this.extractDatabaseOperationsFromCallGraph(repoMethod);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
for (const op of dbOps) {
|
|
52
|
-
databaseChanges.tables.add(op.table);
|
|
53
|
-
|
|
54
|
-
if (!databaseChanges.operations.has(op.table)) {
|
|
55
|
-
databaseChanges.operations.set(op.table, new Set());
|
|
56
|
-
}
|
|
57
|
-
databaseChanges.operations.get(op.table).add(op.operation);
|
|
58
|
-
|
|
59
|
-
if (op.fields && op.fields.length > 0) {
|
|
60
|
-
if (!databaseChanges.fields.has(op.table)) {
|
|
61
|
-
databaseChanges.fields.set(op.table, new Set());
|
|
62
|
-
}
|
|
63
|
-
op.fields.forEach(field => databaseChanges.fields.get(op.table).add(field));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return this.formatDatabaseImpact(databaseChanges);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
findAffectedRepositoryMethods(changedMethods) {
|
|
72
|
-
const visited = new Set();
|
|
73
|
-
const repoMethods = [];
|
|
74
|
-
const queue = [...changedMethods];
|
|
75
|
-
|
|
76
|
-
while (queue.length > 0) {
|
|
77
|
-
const method = queue.shift();
|
|
78
|
-
|
|
79
|
-
if (!method || !method.file) continue;
|
|
80
|
-
|
|
81
|
-
const key = `${method.file}:${method.className}.${method.methodName}`;
|
|
82
|
-
if (visited.has(key)) continue;
|
|
83
|
-
visited.add(key);
|
|
84
|
-
|
|
85
|
-
const isRepository = method.file.includes('repository') ||
|
|
86
|
-
(method.className && method.className.toLowerCase().includes('repository'));
|
|
87
|
-
|
|
88
|
-
if (isRepository) {
|
|
89
|
-
repoMethods.push(method);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const callers = this.methodCallGraph.getCallers(method);
|
|
93
|
-
queue.push(...callers);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return repoMethods;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
extractDatabaseOperationsFromCallGraph(repoMethod) {
|
|
100
|
-
const operations = [];
|
|
101
|
-
|
|
102
|
-
const methodKey = `${repoMethod.className}.${repoMethod.methodName}`;
|
|
103
|
-
const methodCalls = this.methodCallGraph.methodCallsMap?.get(methodKey) || [];
|
|
104
|
-
|
|
105
|
-
if (this.config.verbose) {
|
|
106
|
-
console.log(` 📞 Analyzing ${methodCalls.length} calls in ${repoMethod.className}.${repoMethod.methodName}`);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
for (const call of methodCalls) {
|
|
110
|
-
const dbOp = this.detectDatabaseOperationFromCall(call);
|
|
111
|
-
|
|
112
|
-
if (dbOp) {
|
|
113
|
-
operations.push({
|
|
114
|
-
...dbOp,
|
|
115
|
-
method: `${repoMethod.className}.${repoMethod.methodName}`,
|
|
116
|
-
file: repoMethod.file,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return operations;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
detectDatabaseOperationFromCall(call) {
|
|
125
|
-
const typeOrmOps = {
|
|
126
|
-
'insert': 'INSERT',
|
|
127
|
-
'save': 'INSERT/UPDATE',
|
|
128
|
-
'create': 'INSERT',
|
|
129
|
-
'update': 'UPDATE',
|
|
130
|
-
'merge': 'UPDATE',
|
|
131
|
-
'delete': 'DELETE',
|
|
132
|
-
'remove': 'DELETE',
|
|
133
|
-
'softDelete': 'SOFT_DELETE',
|
|
134
|
-
'find': 'SELECT',
|
|
135
|
-
'findOne': 'SELECT',
|
|
136
|
-
'findAndCount': 'SELECT',
|
|
137
|
-
'findBy': 'SELECT',
|
|
138
|
-
'findOneBy': 'SELECT',
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const methodName = call.method;
|
|
142
|
-
if (!typeOrmOps[methodName]) return null;
|
|
143
|
-
|
|
144
|
-
const target = call.target;
|
|
145
|
-
const entityMatch = target?.match(/(\w+Repository)/);
|
|
146
|
-
|
|
147
|
-
if (!entityMatch) return null;
|
|
148
|
-
|
|
149
|
-
const entityName = entityMatch[1].replace(/Repository$/, '') + 'Entity';
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
table: this.entityToTableName(entityName),
|
|
153
|
-
operation: typeOrmOps[methodName],
|
|
154
|
-
fields: [],
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
entityToTableName(entityName) {
|
|
159
|
-
return entityName
|
|
160
|
-
.replace(/Entity$/, '')
|
|
161
|
-
.replace(/([A-Z])/g, '_$1')
|
|
162
|
-
.toLowerCase()
|
|
163
|
-
.replace(/^_/, '');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
formatDatabaseImpact(databaseChanges) {
|
|
167
|
-
const tables = [];
|
|
168
|
-
|
|
169
|
-
for (const tableName of databaseChanges.tables) {
|
|
170
|
-
const operations = Array.from(databaseChanges.operations.get(tableName) || []);
|
|
171
|
-
const fields = Array.from(databaseChanges.fields.get(tableName) || []);
|
|
172
|
-
|
|
173
|
-
tables.push({
|
|
174
|
-
name: tableName,
|
|
175
|
-
operations,
|
|
176
|
-
fields: fields.length > 0 ? fields : undefined,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return tables;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|