arcvision 0.2.0 → 0.2.1
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/arcvision.context.json +36 -56
- package/dist/index.js +100 -30
- package/package.json +1 -1
- package/src/core/parser.js +41 -0
- package/src/core/path-resolver.js +8 -8
- package/src/core/scanner.js +63 -25
- package/src/engine/context_builder.js +20 -3
package/arcvision.context.json
CHANGED
|
@@ -1,119 +1,99 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema_version": "1.0.0",
|
|
3
|
-
"generated_at": "2026-01-
|
|
3
|
+
"generated_at": "2026-01-06T06:57:00.243Z",
|
|
4
4
|
"system": {
|
|
5
|
-
"name": "
|
|
6
|
-
"root_path": "C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\
|
|
5
|
+
"name": "plugins",
|
|
6
|
+
"root_path": "C:\\Users\\AHMAD RAZA\\Downloads\\ArcVision\\cli\\src\\plugins",
|
|
7
7
|
"language": "javascript"
|
|
8
8
|
},
|
|
9
9
|
"nodes": [
|
|
10
10
|
{
|
|
11
|
-
"id": "
|
|
11
|
+
"id": "9788ca3218e16ddf",
|
|
12
12
|
"type": "file",
|
|
13
|
-
"path": "
|
|
14
|
-
"role": "
|
|
13
|
+
"path": "express-plugin.js",
|
|
14
|
+
"role": "Structure",
|
|
15
15
|
"dependencies": [
|
|
16
|
-
"
|
|
16
|
+
"@babel/parser",
|
|
17
|
+
"@babel/traverse",
|
|
18
|
+
"fs"
|
|
17
19
|
],
|
|
18
|
-
"blast_radius": 1
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
"id": "906fe6a206ee8ed6",
|
|
22
|
-
"type": "file",
|
|
23
|
-
"path": "context_sorter.js",
|
|
24
|
-
"role": "Implementation",
|
|
25
|
-
"dependencies": [],
|
|
26
20
|
"blast_radius": 0
|
|
27
21
|
},
|
|
28
22
|
{
|
|
29
|
-
"id": "
|
|
23
|
+
"id": "d3f4923fd01ce58d",
|
|
30
24
|
"type": "file",
|
|
31
|
-
"path": "
|
|
32
|
-
"role": "
|
|
25
|
+
"path": "react-plugin.js",
|
|
26
|
+
"role": "Structure",
|
|
33
27
|
"dependencies": [
|
|
34
|
-
"
|
|
35
|
-
"
|
|
28
|
+
"@babel/parser",
|
|
29
|
+
"@babel/traverse",
|
|
30
|
+
"fs"
|
|
36
31
|
],
|
|
37
32
|
"blast_radius": 0
|
|
38
33
|
},
|
|
39
34
|
{
|
|
40
|
-
"id": "
|
|
35
|
+
"id": "d6515f66bafa0b42",
|
|
41
36
|
"type": "file",
|
|
42
|
-
"path": "
|
|
37
|
+
"path": "plugin-manager.js",
|
|
43
38
|
"role": "Implementation",
|
|
44
39
|
"dependencies": [
|
|
45
40
|
"fs",
|
|
46
|
-
"path"
|
|
47
|
-
"ajv"
|
|
41
|
+
"path"
|
|
48
42
|
],
|
|
49
43
|
"blast_radius": 0
|
|
50
44
|
}
|
|
51
45
|
],
|
|
52
|
-
"edges": [
|
|
53
|
-
{
|
|
54
|
-
"from": "a90facc61fceac47",
|
|
55
|
-
"to": "18f3b408c9f517b4",
|
|
56
|
-
"relation": "imports"
|
|
57
|
-
}
|
|
58
|
-
],
|
|
46
|
+
"edges": [],
|
|
59
47
|
"metrics": {
|
|
60
|
-
"files_with_functions":
|
|
48
|
+
"files_with_functions": 1,
|
|
61
49
|
"files_with_high_blast_radius": 0,
|
|
62
|
-
"total_dependencies":
|
|
63
|
-
"total_edges":
|
|
64
|
-
"total_files":
|
|
65
|
-
"total_imports":
|
|
66
|
-
"total_nodes":
|
|
50
|
+
"total_dependencies": 0,
|
|
51
|
+
"total_edges": 0,
|
|
52
|
+
"total_files": 3,
|
|
53
|
+
"total_imports": 0,
|
|
54
|
+
"total_nodes": 3
|
|
67
55
|
},
|
|
68
56
|
"contextSurface": {
|
|
69
57
|
"topBlastRadiusFiles": [
|
|
70
58
|
{
|
|
71
|
-
"file": "
|
|
72
|
-
"blastRadius":
|
|
73
|
-
"percentOfGraph":
|
|
59
|
+
"file": "express-plugin.js",
|
|
60
|
+
"blastRadius": 0,
|
|
61
|
+
"percentOfGraph": 0
|
|
74
62
|
},
|
|
75
63
|
{
|
|
76
|
-
"file": "
|
|
64
|
+
"file": "plugin-manager.js",
|
|
77
65
|
"blastRadius": 0,
|
|
78
66
|
"percentOfGraph": 0
|
|
79
67
|
},
|
|
80
68
|
{
|
|
81
|
-
"file": "
|
|
69
|
+
"file": "react-plugin.js",
|
|
82
70
|
"blastRadius": 0,
|
|
83
71
|
"percentOfGraph": 0
|
|
84
72
|
}
|
|
85
73
|
],
|
|
86
74
|
"criticalityAnalysis": [
|
|
87
75
|
{
|
|
88
|
-
"file": "
|
|
89
|
-
"blastRadius": 1,
|
|
90
|
-
"dependencies": 1,
|
|
91
|
-
"dependencyStrength": 0,
|
|
92
|
-
"criticalityScore": 2,
|
|
93
|
-
"isHub": false
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"file": "context_validator.js",
|
|
76
|
+
"file": "react-plugin.js",
|
|
97
77
|
"blastRadius": 0,
|
|
98
78
|
"dependencies": 0,
|
|
99
79
|
"dependencyStrength": 0,
|
|
100
|
-
"criticalityScore":
|
|
80
|
+
"criticalityScore": 0,
|
|
101
81
|
"isHub": false
|
|
102
82
|
},
|
|
103
83
|
{
|
|
104
|
-
"file": "
|
|
84
|
+
"file": "plugin-manager.js",
|
|
105
85
|
"blastRadius": 0,
|
|
106
86
|
"dependencies": 0,
|
|
107
87
|
"dependencyStrength": 0,
|
|
108
|
-
"criticalityScore":
|
|
88
|
+
"criticalityScore": 0,
|
|
109
89
|
"isHub": false
|
|
110
90
|
},
|
|
111
91
|
{
|
|
112
|
-
"file": "
|
|
92
|
+
"file": "express-plugin.js",
|
|
113
93
|
"blastRadius": 0,
|
|
114
94
|
"dependencies": 0,
|
|
115
95
|
"dependencyStrength": 0,
|
|
116
|
-
"criticalityScore":
|
|
96
|
+
"criticalityScore": 0,
|
|
117
97
|
"isHub": false
|
|
118
98
|
}
|
|
119
99
|
]
|
package/dist/index.js
CHANGED
|
@@ -56407,6 +56407,16 @@ var require_parser = __commonJS({
|
|
|
56407
56407
|
});
|
|
56408
56408
|
}
|
|
56409
56409
|
});
|
|
56410
|
+
} else if (decl.id.type === "ArrayPattern") {
|
|
56411
|
+
decl.id.elements.forEach((element) => {
|
|
56412
|
+
if (element && element.name) {
|
|
56413
|
+
metadata.exports.push({
|
|
56414
|
+
name: element.name,
|
|
56415
|
+
type: "variable",
|
|
56416
|
+
loc: element.loc
|
|
56417
|
+
});
|
|
56418
|
+
}
|
|
56419
|
+
});
|
|
56410
56420
|
}
|
|
56411
56421
|
});
|
|
56412
56422
|
}
|
|
@@ -56530,6 +56540,27 @@ var require_parser = __commonJS({
|
|
|
56530
56540
|
loc: node.loc
|
|
56531
56541
|
});
|
|
56532
56542
|
}
|
|
56543
|
+
if (node.callee.type === "Import" && node.arguments.length > 0) {
|
|
56544
|
+
const sourceArg = node.arguments[0];
|
|
56545
|
+
if (sourceArg && sourceArg.type === "StringLiteral") {
|
|
56546
|
+
metadata.imports.push({
|
|
56547
|
+
source: sourceArg.value,
|
|
56548
|
+
specifiers: [],
|
|
56549
|
+
type: "dynamic-import"
|
|
56550
|
+
});
|
|
56551
|
+
}
|
|
56552
|
+
}
|
|
56553
|
+
},
|
|
56554
|
+
VariableDeclarator({ node }) {
|
|
56555
|
+
if (node.init && node.init.type === "CallExpression" && node.init.callee.name === "require" && node.init.arguments.length > 0 && node.init.arguments[0].type === "StringLiteral") {
|
|
56556
|
+
metadata.imports.push({
|
|
56557
|
+
source: node.init.arguments[0].value,
|
|
56558
|
+
specifiers: [],
|
|
56559
|
+
type: "require-assignment"
|
|
56560
|
+
});
|
|
56561
|
+
}
|
|
56562
|
+
},
|
|
56563
|
+
MemberExpression({ node }) {
|
|
56533
56564
|
}
|
|
56534
56565
|
});
|
|
56535
56566
|
return metadata;
|
|
@@ -56635,13 +56666,13 @@ var require_path_resolver = __commonJS({
|
|
|
56635
56666
|
}
|
|
56636
56667
|
if (importPath.startsWith(".")) {
|
|
56637
56668
|
let resolvedPath = path2.resolve(path2.dirname(importerPath), importPath);
|
|
56638
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
56669
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
56639
56670
|
for (const ext of extensions) {
|
|
56640
56671
|
if (fs2.existsSync(resolvedPath + ext)) {
|
|
56641
56672
|
return resolvedPath + ext;
|
|
56642
56673
|
}
|
|
56643
56674
|
}
|
|
56644
|
-
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
|
|
56675
|
+
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
|
|
56645
56676
|
for (const idxFile of indexFiles) {
|
|
56646
56677
|
const indexPath = path2.join(resolvedPath, idxFile);
|
|
56647
56678
|
if (fs2.existsSync(indexPath)) {
|
|
@@ -56665,13 +56696,13 @@ var require_path_resolver = __commonJS({
|
|
|
56665
56696
|
for (const target of aliasTargets) {
|
|
56666
56697
|
const targetBase = target.replace(/\*.*$/, "");
|
|
56667
56698
|
const targetPath = path2.resolve(projectRoot, targetBase) + suffix;
|
|
56668
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
56699
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
56669
56700
|
for (const ext of extensions) {
|
|
56670
56701
|
if (fs2.existsSync(targetPath + ext)) {
|
|
56671
56702
|
return targetPath + ext;
|
|
56672
56703
|
}
|
|
56673
56704
|
}
|
|
56674
|
-
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
|
|
56705
|
+
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
|
|
56675
56706
|
for (const idxFile of indexFiles) {
|
|
56676
56707
|
const indexPath = path2.join(targetPath, idxFile);
|
|
56677
56708
|
if (fs2.existsSync(indexPath)) {
|
|
@@ -56691,13 +56722,13 @@ var require_path_resolver = __commonJS({
|
|
|
56691
56722
|
const remainingPath = importPath.substring(aliasPattern.length);
|
|
56692
56723
|
targetPath = path2.join(targetPath, remainingPath);
|
|
56693
56724
|
}
|
|
56694
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
56725
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
56695
56726
|
for (const ext of extensions) {
|
|
56696
56727
|
if (fs2.existsSync(targetPath + ext)) {
|
|
56697
56728
|
return targetPath + ext;
|
|
56698
56729
|
}
|
|
56699
56730
|
}
|
|
56700
|
-
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
|
|
56731
|
+
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
|
|
56701
56732
|
for (const idxFile of indexFiles) {
|
|
56702
56733
|
const indexPath = path2.join(targetPath, idxFile);
|
|
56703
56734
|
if (fs2.existsSync(indexPath)) {
|
|
@@ -56714,13 +56745,13 @@ var require_path_resolver = __commonJS({
|
|
|
56714
56745
|
}
|
|
56715
56746
|
if (tsconfig && tsconfig.baseUrl && !importPath.startsWith(".")) {
|
|
56716
56747
|
let resolvedPath = path2.resolve(projectRoot, tsconfig.baseUrl, importPath);
|
|
56717
|
-
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
56748
|
+
const extensions = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
|
|
56718
56749
|
for (const ext of extensions) {
|
|
56719
56750
|
if (fs2.existsSync(resolvedPath + ext)) {
|
|
56720
56751
|
return resolvedPath + ext;
|
|
56721
56752
|
}
|
|
56722
56753
|
}
|
|
56723
|
-
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx"];
|
|
56754
|
+
const indexFiles = ["index.ts", "index.tsx", "index.js", "index.jsx", "index.json"];
|
|
56724
56755
|
for (const idxFile of indexFiles) {
|
|
56725
56756
|
const indexPath = path2.join(resolvedPath, idxFile);
|
|
56726
56757
|
if (fs2.existsSync(indexPath)) {
|
|
@@ -56927,10 +56958,14 @@ var require_context_builder = __commonJS({
|
|
|
56927
56958
|
role = "Interface";
|
|
56928
56959
|
}
|
|
56929
56960
|
const dependencies = [];
|
|
56961
|
+
const uniqueDeps = /* @__PURE__ */ new Set();
|
|
56930
56962
|
if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
|
|
56931
56963
|
file.metadata.imports.forEach((imp) => {
|
|
56932
56964
|
if (imp.source && typeof imp.source === "string") {
|
|
56933
|
-
|
|
56965
|
+
if (!uniqueDeps.has(imp.source)) {
|
|
56966
|
+
uniqueDeps.add(imp.source);
|
|
56967
|
+
dependencies.push(imp.source);
|
|
56968
|
+
}
|
|
56934
56969
|
}
|
|
56935
56970
|
});
|
|
56936
56971
|
}
|
|
@@ -56948,10 +56983,20 @@ var require_context_builder = __commonJS({
|
|
|
56948
56983
|
const sourceNode = nodes.find((n) => n.path === edge.source);
|
|
56949
56984
|
const targetNode = nodes.find((n) => n.path === edge.target);
|
|
56950
56985
|
if (sourceNode && targetNode) {
|
|
56986
|
+
let relationType = "imports";
|
|
56987
|
+
if (edge.type === "imports" || edge.type === "require" || edge.type === "export-from" || edge.type === "export-all" || edge.type === "dynamic-import" || edge.type === "require-assignment") {
|
|
56988
|
+
relationType = "imports";
|
|
56989
|
+
} else if (edge.type === "calls") {
|
|
56990
|
+
relationType = "calls";
|
|
56991
|
+
} else if (edge.type === "owns") {
|
|
56992
|
+
relationType = "owns";
|
|
56993
|
+
} else if (edge.type === "depends_on") {
|
|
56994
|
+
relationType = "depends_on";
|
|
56995
|
+
}
|
|
56951
56996
|
schemaEdges.push({
|
|
56952
56997
|
from: sourceNode.id,
|
|
56953
56998
|
to: targetNode.id,
|
|
56954
|
-
relation:
|
|
56999
|
+
relation: relationType
|
|
56955
57000
|
});
|
|
56956
57001
|
}
|
|
56957
57002
|
}
|
|
@@ -63599,34 +63644,59 @@ var require_scanner = __commonJS({
|
|
|
63599
63644
|
}
|
|
63600
63645
|
}
|
|
63601
63646
|
const tsconfig = loadTSConfig(directory);
|
|
63602
|
-
const
|
|
63603
|
-
architectureMap.nodes.forEach((n) => normalizedFileMap.set(normalize(n.id), n.id));
|
|
63647
|
+
const pathToNodeIdMap = /* @__PURE__ */ new Map();
|
|
63604
63648
|
architectureMap.nodes.forEach((node) => {
|
|
63605
|
-
node.
|
|
63606
|
-
|
|
63607
|
-
|
|
63608
|
-
|
|
63609
|
-
|
|
63610
|
-
|
|
63611
|
-
|
|
63612
|
-
|
|
63613
|
-
|
|
63614
|
-
|
|
63615
|
-
|
|
63616
|
-
|
|
63617
|
-
|
|
63618
|
-
|
|
63619
|
-
|
|
63620
|
-
|
|
63649
|
+
const normalizedPath = normalize(node.id);
|
|
63650
|
+
pathToNodeIdMap.set(normalizedPath, node.id);
|
|
63651
|
+
});
|
|
63652
|
+
architectureMap.nodes.forEach((node) => {
|
|
63653
|
+
if (node.metadata.imports && Array.isArray(node.metadata.imports)) {
|
|
63654
|
+
node.metadata.imports.forEach((imp) => {
|
|
63655
|
+
if (imp.source && typeof imp.source === "string") {
|
|
63656
|
+
const resolvedPath = resolveImport(
|
|
63657
|
+
imp.source,
|
|
63658
|
+
path2.join(directory, node.id),
|
|
63659
|
+
directory,
|
|
63660
|
+
tsconfig
|
|
63661
|
+
);
|
|
63662
|
+
if (resolvedPath) {
|
|
63663
|
+
const relativeResolvedPath = path2.relative(directory, resolvedPath);
|
|
63664
|
+
const normalizedResolvedPath = normalize(relativeResolvedPath);
|
|
63665
|
+
if (pathToNodeIdMap.has(normalizedResolvedPath)) {
|
|
63666
|
+
architectureMap.edges.push({
|
|
63667
|
+
source: normalize(node.id),
|
|
63668
|
+
target: normalizedResolvedPath,
|
|
63669
|
+
type: imp.type || "import"
|
|
63670
|
+
});
|
|
63671
|
+
}
|
|
63672
|
+
}
|
|
63621
63673
|
}
|
|
63622
|
-
}
|
|
63623
|
-
}
|
|
63674
|
+
});
|
|
63675
|
+
}
|
|
63624
63676
|
});
|
|
63625
63677
|
const reverseGraph = buildReverseDependencyGraph(architectureMap);
|
|
63626
63678
|
const blastRadiusMap = computeBlastRadius(reverseGraph);
|
|
63627
63679
|
architectureMap.nodes.forEach((node) => {
|
|
63628
63680
|
node.metadata.blast_radius = blastRadiusMap[node.id] || 0;
|
|
63629
63681
|
});
|
|
63682
|
+
const validEdges = [];
|
|
63683
|
+
const edgeSet = /* @__PURE__ */ new Set();
|
|
63684
|
+
architectureMap.edges.forEach((edge) => {
|
|
63685
|
+
const normalizedSource = normalize(edge.source);
|
|
63686
|
+
const normalizedTarget = normalize(edge.target);
|
|
63687
|
+
const edgeKey = `${normalizedSource}\u2192${normalizedTarget}`;
|
|
63688
|
+
const sourceNodeExists = architectureMap.nodes.some((node) => node.id === normalizedSource);
|
|
63689
|
+
const targetNodeExists = architectureMap.nodes.some((node) => node.id === normalizedTarget);
|
|
63690
|
+
if (sourceNodeExists && targetNodeExists && !edgeSet.has(edgeKey)) {
|
|
63691
|
+
edgeSet.add(edgeKey);
|
|
63692
|
+
validEdges.push({
|
|
63693
|
+
source: normalizedSource,
|
|
63694
|
+
target: normalizedTarget,
|
|
63695
|
+
type: edge.type
|
|
63696
|
+
});
|
|
63697
|
+
}
|
|
63698
|
+
});
|
|
63699
|
+
architectureMap.edges = validEdges;
|
|
63630
63700
|
const totalFiles = architectureMap.nodes.length;
|
|
63631
63701
|
const { computeBlastRadiusWithPercentage: computeBlastRadiusWithPercentage2, analyzeCriticality } = require_blastRadius();
|
|
63632
63702
|
const allFilesWithPercentage = computeBlastRadiusWithPercentage2(blastRadiusMap, totalFiles);
|
package/package.json
CHANGED
package/src/core/parser.js
CHANGED
|
@@ -159,6 +159,17 @@ function parseFile(filePath) {
|
|
|
159
159
|
});
|
|
160
160
|
}
|
|
161
161
|
});
|
|
162
|
+
} else if (decl.id.type === 'ArrayPattern') {
|
|
163
|
+
// Handle array destructuring exports: export const [a, b] = arr
|
|
164
|
+
decl.id.elements.forEach(element => {
|
|
165
|
+
if (element && element.name) {
|
|
166
|
+
metadata.exports.push({
|
|
167
|
+
name: element.name,
|
|
168
|
+
type: 'variable',
|
|
169
|
+
loc: element.loc
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
162
173
|
}
|
|
163
174
|
});
|
|
164
175
|
}
|
|
@@ -293,6 +304,36 @@ function parseFile(filePath) {
|
|
|
293
304
|
loc: node.loc
|
|
294
305
|
});
|
|
295
306
|
}
|
|
307
|
+
|
|
308
|
+
// Handle other common import patterns like import() in function calls
|
|
309
|
+
if (node.callee.type === 'Import' && node.arguments.length > 0) {
|
|
310
|
+
const sourceArg = node.arguments[0];
|
|
311
|
+
if (sourceArg && sourceArg.type === 'StringLiteral') {
|
|
312
|
+
metadata.imports.push({
|
|
313
|
+
source: sourceArg.value,
|
|
314
|
+
specifiers: [],
|
|
315
|
+
type: 'dynamic-import'
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
VariableDeclarator({ node }) {
|
|
322
|
+
// Handle import assignments like: const { something } = require('module')
|
|
323
|
+
if (node.init && node.init.type === 'CallExpression' &&
|
|
324
|
+
node.init.callee.name === 'require' &&
|
|
325
|
+
node.init.arguments.length > 0 &&
|
|
326
|
+
node.init.arguments[0].type === 'StringLiteral') {
|
|
327
|
+
metadata.imports.push({
|
|
328
|
+
source: node.init.arguments[0].value,
|
|
329
|
+
specifiers: [],
|
|
330
|
+
type: 'require-assignment'
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
MemberExpression({ node }) {
|
|
336
|
+
// Additional member expression handling for complex import patterns
|
|
296
337
|
}
|
|
297
338
|
});
|
|
298
339
|
|
|
@@ -23,7 +23,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
23
23
|
let resolvedPath = path.resolve(path.dirname(importerPath), importPath);
|
|
24
24
|
|
|
25
25
|
// Try with various extensions in order of preference
|
|
26
|
-
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
26
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
|
|
27
27
|
for (const ext of extensions) {
|
|
28
28
|
if (fs.existsSync(resolvedPath + ext)) {
|
|
29
29
|
return resolvedPath + ext;
|
|
@@ -31,7 +31,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Try as directory with index files
|
|
34
|
-
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
|
|
34
|
+
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
|
|
35
35
|
for (const idxFile of indexFiles) {
|
|
36
36
|
const indexPath = path.join(resolvedPath, idxFile);
|
|
37
37
|
if (fs.existsSync(indexPath)) {
|
|
@@ -69,7 +69,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
69
69
|
const targetPath = path.resolve(projectRoot, targetBase) + suffix;
|
|
70
70
|
|
|
71
71
|
// Try with extensions
|
|
72
|
-
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
72
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
|
|
73
73
|
for (const ext of extensions) {
|
|
74
74
|
if (fs.existsSync(targetPath + ext)) {
|
|
75
75
|
return targetPath + ext;
|
|
@@ -77,7 +77,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
// Try as directory with index files
|
|
80
|
-
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
|
|
80
|
+
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
|
|
81
81
|
for (const idxFile of indexFiles) {
|
|
82
82
|
const indexPath = path.join(targetPath, idxFile);
|
|
83
83
|
if (fs.existsSync(indexPath)) {
|
|
@@ -104,7 +104,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// Try with extensions
|
|
107
|
-
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
107
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
|
|
108
108
|
for (const ext of extensions) {
|
|
109
109
|
if (fs.existsSync(targetPath + ext)) {
|
|
110
110
|
return targetPath + ext;
|
|
@@ -112,7 +112,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
// Try as directory with index files
|
|
115
|
-
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
|
|
115
|
+
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
|
|
116
116
|
for (const idxFile of indexFiles) {
|
|
117
117
|
const indexPath = path.join(targetPath, idxFile);
|
|
118
118
|
if (fs.existsSync(indexPath)) {
|
|
@@ -135,7 +135,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
135
135
|
let resolvedPath = path.resolve(projectRoot, tsconfig.baseUrl, importPath);
|
|
136
136
|
|
|
137
137
|
// Try with extensions
|
|
138
|
-
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'];
|
|
138
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json'];
|
|
139
139
|
for (const ext of extensions) {
|
|
140
140
|
if (fs.existsSync(resolvedPath + ext)) {
|
|
141
141
|
return resolvedPath + ext;
|
|
@@ -143,7 +143,7 @@ function resolveImport(importPath, importerPath, projectRoot, tsconfig) {
|
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
// Try as directory with index files
|
|
146
|
-
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx'];
|
|
146
|
+
const indexFiles = ['index.ts', 'index.tsx', 'index.js', 'index.jsx', 'index.json'];
|
|
147
147
|
for (const idxFile of indexFiles) {
|
|
148
148
|
const indexPath = path.join(resolvedPath, idxFile);
|
|
149
149
|
if (fs.existsSync(indexPath)) {
|
package/src/core/scanner.js
CHANGED
|
@@ -88,34 +88,43 @@ async function scan(directory) {
|
|
|
88
88
|
// Load tsconfig for path resolution
|
|
89
89
|
const tsconfig = loadTSConfig(directory);
|
|
90
90
|
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// Create a map of normalized relative paths to actual node IDs
|
|
92
|
+
const pathToNodeIdMap = new Map();
|
|
93
|
+
architectureMap.nodes.forEach(node => {
|
|
94
|
+
const normalizedPath = normalize(node.id);
|
|
95
|
+
pathToNodeIdMap.set(normalizedPath, node.id);
|
|
96
|
+
});
|
|
93
97
|
|
|
98
|
+
// Process imports to create edges
|
|
94
99
|
architectureMap.nodes.forEach(node => {
|
|
95
|
-
node.metadata.imports.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
100
|
+
if (node.metadata.imports && Array.isArray(node.metadata.imports)) {
|
|
101
|
+
node.metadata.imports.forEach(imp => {
|
|
102
|
+
if (imp.source && typeof imp.source === 'string') {
|
|
103
|
+
// Resolve the import path using the new resolver
|
|
104
|
+
const resolvedPath = resolveImport(
|
|
105
|
+
imp.source,
|
|
106
|
+
path.join(directory, node.id),
|
|
107
|
+
directory,
|
|
108
|
+
tsconfig
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (resolvedPath) {
|
|
112
|
+
// Convert resolved absolute path back to relative path
|
|
113
|
+
const relativeResolvedPath = path.relative(directory, resolvedPath);
|
|
114
|
+
const normalizedResolvedPath = normalize(relativeResolvedPath);
|
|
115
|
+
|
|
116
|
+
// Check if the resolved file exists in our scanned files
|
|
117
|
+
if (pathToNodeIdMap.has(normalizedResolvedPath)) {
|
|
118
|
+
architectureMap.edges.push({
|
|
119
|
+
source: normalize(node.id),
|
|
120
|
+
target: normalizedResolvedPath,
|
|
121
|
+
type: imp.type || 'import'
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
116
125
|
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
119
128
|
});
|
|
120
129
|
|
|
121
130
|
// Calculate blast radius for each file
|
|
@@ -127,6 +136,35 @@ async function scan(directory) {
|
|
|
127
136
|
node.metadata.blast_radius = blastRadiusMap[node.id] || 0;
|
|
128
137
|
});
|
|
129
138
|
|
|
139
|
+
// Process edges to ensure they match existing nodes and remove duplicates
|
|
140
|
+
const validEdges = [];
|
|
141
|
+
const edgeSet = new Set(); // To track unique edges
|
|
142
|
+
|
|
143
|
+
architectureMap.edges.forEach(edge => {
|
|
144
|
+
// Normalize both source and target paths for consistent matching
|
|
145
|
+
const normalizedSource = normalize(edge.source);
|
|
146
|
+
const normalizedTarget = normalize(edge.target);
|
|
147
|
+
|
|
148
|
+
// Create a unique key for the edge to avoid duplicates
|
|
149
|
+
const edgeKey = `${normalizedSource}→${normalizedTarget}`;
|
|
150
|
+
|
|
151
|
+
// Check if both source and target nodes exist in our architecture map
|
|
152
|
+
const sourceNodeExists = architectureMap.nodes.some(node => node.id === normalizedSource);
|
|
153
|
+
const targetNodeExists = architectureMap.nodes.some(node => node.id === normalizedTarget);
|
|
154
|
+
|
|
155
|
+
if (sourceNodeExists && targetNodeExists && !edgeSet.has(edgeKey)) {
|
|
156
|
+
edgeSet.add(edgeKey);
|
|
157
|
+
validEdges.push({
|
|
158
|
+
source: normalizedSource,
|
|
159
|
+
target: normalizedTarget,
|
|
160
|
+
type: edge.type
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Replace the edges with only valid ones
|
|
166
|
+
architectureMap.edges = validEdges;
|
|
167
|
+
|
|
130
168
|
// Add top blast radius files to the architecture map
|
|
131
169
|
const totalFiles = architectureMap.nodes.length;
|
|
132
170
|
const { computeBlastRadiusWithPercentage, analyzeCriticality } = require('./blastRadius');
|
|
@@ -33,12 +33,17 @@ function buildContext(files, edges, options = {}) {
|
|
|
33
33
|
role = 'Interface';
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
// Extract dependencies more accurately
|
|
36
|
+
// Extract dependencies more accurately and remove duplicates
|
|
37
37
|
const dependencies = [];
|
|
38
|
+
const uniqueDeps = new Set();
|
|
38
39
|
if (file.metadata.imports && Array.isArray(file.metadata.imports)) {
|
|
39
40
|
file.metadata.imports.forEach(imp => {
|
|
40
41
|
if (imp.source && typeof imp.source === 'string') {
|
|
41
|
-
dependencies
|
|
42
|
+
// Only add unique dependencies
|
|
43
|
+
if (!uniqueDeps.has(imp.source)) {
|
|
44
|
+
uniqueDeps.add(imp.source);
|
|
45
|
+
dependencies.push(imp.source);
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
48
|
});
|
|
44
49
|
}
|
|
@@ -62,10 +67,22 @@ function buildContext(files, edges, options = {}) {
|
|
|
62
67
|
|
|
63
68
|
// Only include edges where both source and target nodes exist
|
|
64
69
|
if (sourceNode && targetNode) {
|
|
70
|
+
// Normalize the edge type to fit the schema
|
|
71
|
+
let relationType = 'imports';
|
|
72
|
+
if (edge.type === 'imports' || edge.type === 'require' || edge.type === 'export-from' || edge.type === 'export-all' || edge.type === 'dynamic-import' || edge.type === 'require-assignment') {
|
|
73
|
+
relationType = 'imports';
|
|
74
|
+
} else if (edge.type === 'calls') {
|
|
75
|
+
relationType = 'calls';
|
|
76
|
+
} else if (edge.type === 'owns') {
|
|
77
|
+
relationType = 'owns';
|
|
78
|
+
} else if (edge.type === 'depends_on') {
|
|
79
|
+
relationType = 'depends_on';
|
|
80
|
+
}
|
|
81
|
+
|
|
65
82
|
schemaEdges.push({
|
|
66
83
|
from: sourceNode.id,
|
|
67
84
|
to: targetNode.id,
|
|
68
|
-
relation:
|
|
85
|
+
relation: relationType
|
|
69
86
|
});
|
|
70
87
|
}
|
|
71
88
|
}
|