impact-analysis 2.0.0 → 2.0.2
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 +9 -1
- package/dist/ai/explain.js +29 -0
- package/dist/cache/graphCache.js +39 -0
- package/dist/cli.js +136 -0
- package/dist/core/analyzer.js +20 -0
- package/dist/git/getChangedFiles.js +21 -0
- package/{src/graph/buildGraph.ts → dist/graph/buildGraph.js} +44 -63
- package/dist/graph/types.js +2 -0
- package/dist/parser/parseJS.js +70 -0
- package/dist/parser/parseVue.js +24 -0
- package/dist/parser/parseVueTemplate.js +15 -0
- package/{src/report/html-generator.ts → dist/report/html-generator.js} +17 -19
- package/dist/scanner/scanRepo.js +30 -0
- package/package.json +32 -1
- package/git/core/analyzer.ts +0 -28
- package/git/getChangedFiles.ts +0 -15
- package/index.mjs +0 -457
- package/src/ai/explain.ts +0 -29
- package/src/cache/graphCache.ts +0 -49
- package/src/cli.ts +0 -110
- package/src/graph/types.ts +0 -5
- package/src/parser/parseJS.ts +0 -69
- package/src/parser/parseVue.ts +0 -23
- package/src/parser/parseVueTemplate.ts +0 -16
- package/src/scanner/scanRepo.ts +0 -25
- package/tsconfig.json +0 -10
package/src/cli.ts
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import 'dotenv/config';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { exec } from 'child_process';
|
|
5
|
-
import { promisify } from 'util';
|
|
6
|
-
import { getChangedFiles } from './git/getChangedFiles';
|
|
7
|
-
import { scanRepo } from './scanner/scanRepo';
|
|
8
|
-
import { buildGraph } from './graph/buildGraph';
|
|
9
|
-
import { analyzeImpact } from './core/analyzer';
|
|
10
|
-
import { generateHtmlReport } from './report/html-generator';
|
|
11
|
-
import { explainImpact } from './ai/explain';
|
|
12
|
-
|
|
13
|
-
const execAsync = promisify(exec);
|
|
14
|
-
|
|
15
|
-
async function main() {
|
|
16
|
-
const args = process.argv.slice(2);
|
|
17
|
-
const showHtml = args.includes('--html');
|
|
18
|
-
const useAI = args.includes('--ai');
|
|
19
|
-
const clearCache = args.includes('--clear-cache');
|
|
20
|
-
const baseBranch = args.find(arg => !arg.startsWith('--')) || 'main';
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
if (clearCache) {
|
|
24
|
-
const fs = await import('fs');
|
|
25
|
-
if (fs.existsSync('.impact-analysis-cache.json')) {
|
|
26
|
-
fs.unlinkSync('.impact-analysis-cache.json');
|
|
27
|
-
console.log('Cache cleared.');
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log('Scanning repository...');
|
|
32
|
-
const allFiles = await scanRepo();
|
|
33
|
-
|
|
34
|
-
console.log(`Building dependency graph from ${allFiles.length} files...`);
|
|
35
|
-
const graph = await buildGraph(allFiles);
|
|
36
|
-
|
|
37
|
-
console.log(`Getting changed files against ${baseBranch}...`);
|
|
38
|
-
const changedFiles = await getChangedFiles(baseBranch);
|
|
39
|
-
|
|
40
|
-
if (changedFiles.length === 0) {
|
|
41
|
-
console.log('No changed files found.');
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
console.log(`Found ${changedFiles.length} changed files. Analyzing impact...`);
|
|
46
|
-
|
|
47
|
-
const dependencyMap: Record<string, string[]> = {};
|
|
48
|
-
graph.importedBy.forEach((importers, file) => {
|
|
49
|
-
dependencyMap[file] = Array.from(importers);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
const results = analyzeImpact(changedFiles, dependencyMap);
|
|
53
|
-
|
|
54
|
-
if (useAI) {
|
|
55
|
-
try {
|
|
56
|
-
console.log('Getting AI explanation...');
|
|
57
|
-
const explanation = await explainImpact(results);
|
|
58
|
-
if (explanation) {
|
|
59
|
-
(results as any).aiExplanation = explanation;
|
|
60
|
-
}
|
|
61
|
-
} catch (error) {
|
|
62
|
-
console.warn('AI explanation failed:', error instanceof Error ? error.message : String(error));
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
console.log('\n--- Impact Analysis Results ---\n');
|
|
67
|
-
results.forEach((result, index) => {
|
|
68
|
-
console.log(`${index + 1}. ${path.basename(result.changedFile)}`);
|
|
69
|
-
console.log(` Risk: ${result.risk}`);
|
|
70
|
-
console.log(` Impacted files: ${result.impactedFiles.length}`);
|
|
71
|
-
if (result.impactedFiles.length > 0) {
|
|
72
|
-
result.impactedFiles.slice(0, 5).forEach(file => {
|
|
73
|
-
console.log(` - ${path.basename(file)}`);
|
|
74
|
-
});
|
|
75
|
-
if (result.impactedFiles.length > 5) {
|
|
76
|
-
console.log(` ... and ${result.impactedFiles.length - 5} more`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
console.log('');
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// Generate HTML by default
|
|
83
|
-
console.log('Generating HTML report...');
|
|
84
|
-
const htmlPath = generateHtmlReport(results);
|
|
85
|
-
console.log(`Report saved: ${htmlPath}`);
|
|
86
|
-
|
|
87
|
-
if (showHtml) {
|
|
88
|
-
try {
|
|
89
|
-
// Use platform-specific command to open the file
|
|
90
|
-
const command = process.platform === 'win32'
|
|
91
|
-
? `start "" "${htmlPath}"`
|
|
92
|
-
: process.platform === 'darwin'
|
|
93
|
-
? `open "${htmlPath}"`
|
|
94
|
-
: `xdg-open "${htmlPath}"`;
|
|
95
|
-
|
|
96
|
-
await execAsync(command);
|
|
97
|
-
console.log(`Report opened in browser`);
|
|
98
|
-
} catch (err) {
|
|
99
|
-
console.log(`Could not auto-open browser. Please open: ${htmlPath}`);
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
console.log(`\nTo view the graphical report, run: impact-analysis --html`);
|
|
103
|
-
}
|
|
104
|
-
} catch (error) {
|
|
105
|
-
console.error('Error:', error instanceof Error ? error.message : String(error));
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
main();
|
package/src/graph/types.ts
DELETED
package/src/parser/parseJS.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { parse } from "@babel/parser";
|
|
2
|
-
import traverse from "@babel/traverse";
|
|
3
|
-
|
|
4
|
-
export function extractImports(code: string): string[] {
|
|
5
|
-
const imports: string[] = [];
|
|
6
|
-
|
|
7
|
-
try {
|
|
8
|
-
const ast = parse(code, {
|
|
9
|
-
sourceType: "unambiguous",
|
|
10
|
-
plugins: [
|
|
11
|
-
"typescript",
|
|
12
|
-
"jsx",
|
|
13
|
-
"decorators-legacy",
|
|
14
|
-
"classProperties",
|
|
15
|
-
"dynamicImport",
|
|
16
|
-
"importMeta",
|
|
17
|
-
"topLevelAwait",
|
|
18
|
-
"classStaticBlock",
|
|
19
|
-
"optionalChaining",
|
|
20
|
-
"nullishCoalescingOperator"
|
|
21
|
-
],
|
|
22
|
-
errorRecovery: true
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
traverse(ast as any, {
|
|
26
|
-
// Static imports: import x from 'module'
|
|
27
|
-
ImportDeclaration(path) {
|
|
28
|
-
imports.push(path.node.source.value);
|
|
29
|
-
},
|
|
30
|
-
// Dynamic imports: import('module')
|
|
31
|
-
Import(path) {
|
|
32
|
-
const parent = path.parent;
|
|
33
|
-
if (parent.type === 'CallExpression' && parent.arguments[0]) {
|
|
34
|
-
const arg = parent.arguments[0];
|
|
35
|
-
if (arg.type === 'StringLiteral') {
|
|
36
|
-
imports.push(arg.value);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
// Export from: export { x } from 'module'
|
|
41
|
-
ExportNamedDeclaration(path) {
|
|
42
|
-
if (path.node.source) {
|
|
43
|
-
imports.push(path.node.source.value);
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
// Export all: export * from 'module'
|
|
47
|
-
ExportAllDeclaration(path) {
|
|
48
|
-
imports.push(path.node.source.value);
|
|
49
|
-
},
|
|
50
|
-
// CommonJS require: const x = require('module')
|
|
51
|
-
CallExpression(path) {
|
|
52
|
-
if (
|
|
53
|
-
path.node.callee.type === 'Identifier' &&
|
|
54
|
-
path.node.callee.name === 'require' &&
|
|
55
|
-
path.node.arguments[0] &&
|
|
56
|
-
path.node.arguments[0].type === 'StringLiteral'
|
|
57
|
-
) {
|
|
58
|
-
imports.push(path.node.arguments[0].value);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
} catch (error) {
|
|
63
|
-
// Silently skip files with parse errors
|
|
64
|
-
// Most errors are from node_modules or generated files
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Remove duplicates
|
|
68
|
-
return [...new Set(imports)];
|
|
69
|
-
}
|
package/src/parser/parseVue.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { parse as parseSFC } from "@vue/compiler-sfc";
|
|
2
|
-
import { extractImports } from "./parseJS";
|
|
3
|
-
import { extractTemplateComponents } from "./parseVueTemplate";
|
|
4
|
-
|
|
5
|
-
export function parseVue(code: string) {
|
|
6
|
-
try {
|
|
7
|
-
const { descriptor } = parseSFC(code);
|
|
8
|
-
|
|
9
|
-
const scriptContent = descriptor.script?.content || '';
|
|
10
|
-
const scriptSetupContent = descriptor.scriptSetup?.content || '';
|
|
11
|
-
const allScript = scriptContent + '\n' + scriptSetupContent;
|
|
12
|
-
|
|
13
|
-
return {
|
|
14
|
-
imports: allScript.trim() ? extractImports(allScript) : [],
|
|
15
|
-
components: extractTemplateComponents(code)
|
|
16
|
-
};
|
|
17
|
-
} catch (error) {
|
|
18
|
-
return {
|
|
19
|
-
imports: [],
|
|
20
|
-
components: []
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { parse as parseSFC } from "@vue/compiler-sfc";
|
|
2
|
-
|
|
3
|
-
export function extractTemplateComponents(code: string): string[] {
|
|
4
|
-
const { descriptor } = parseSFC(code);
|
|
5
|
-
const template = descriptor.template?.content || "";
|
|
6
|
-
|
|
7
|
-
const componentRegex = /<([A-Z][\w-]*)/g;
|
|
8
|
-
const components = new Set<string>();
|
|
9
|
-
|
|
10
|
-
let match;
|
|
11
|
-
while ((match = componentRegex.exec(template))) {
|
|
12
|
-
components.add(match[1]);
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return Array.from(components);
|
|
16
|
-
}
|
package/src/scanner/scanRepo.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import fg from "fast-glob";
|
|
2
|
-
import path from "path";
|
|
3
|
-
|
|
4
|
-
export async function scanRepo(): Promise<string[]> {
|
|
5
|
-
try {
|
|
6
|
-
const files = await fg("**/*.{js,jsx,ts,tsx,vue}", {
|
|
7
|
-
ignore: [
|
|
8
|
-
"node_modules/**",
|
|
9
|
-
"dist/**",
|
|
10
|
-
"build/**",
|
|
11
|
-
"*.min.js",
|
|
12
|
-
"**/*.min.js",
|
|
13
|
-
".next/**",
|
|
14
|
-
"coverage/**",
|
|
15
|
-
".cache/**",
|
|
16
|
-
"out/**",
|
|
17
|
-
".nuxt/**"
|
|
18
|
-
]
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return files.map(file => path.resolve(process.cwd(), file));
|
|
22
|
-
} catch (error) {
|
|
23
|
-
throw new Error(`Failed to scan repository: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
-
}
|
|
25
|
-
}
|