entkapp 4.1.0 ā 4.3.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 +15 -2
- package/bin/cli.js +2 -2
- package/index.js +5 -5
- package/logo.png +0 -0
- package/package.json +7 -3
- package/src/EngineContext.js +12 -2
- package/src/api/HeadlessAPI.js +1 -1
- package/src/api/PluginSDK.js +1 -1
- package/src/ast/ASTAnalyzer.js +9 -5
- package/src/ast/BarrelParser.js +1 -19
- package/src/ast/DeadCodeDetector.js +2 -2
- package/src/ast/OxcAnalyzer.js +92 -87
- package/src/index.js +148 -55
- package/src/performance/WorkerTaskRunner.js +87 -0
- package/src/plugins/ecosystems/BackendServices.js +1 -1
- package/src/plugins/ecosystems/ModernFrameworks.js +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,19 @@ It represents the process of stripping away the unnecessary layers of a codebase
|
|
|
28
28
|
## š¦ Installation
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
npm install -
|
|
31
|
+
npm install -g entkapp
|
|
32
32
|
# or
|
|
33
|
-
pnpm add -
|
|
33
|
+
pnpm add -g entkapp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Usage
|
|
37
|
+
```bash
|
|
38
|
+
npx entkapp
|
|
39
|
+
# or
|
|
40
|
+
entkapp
|
|
41
|
+
# and then
|
|
42
|
+
npx entkapp -r
|
|
43
|
+
# or
|
|
44
|
+
entkapp -r
|
|
45
|
+
# It is recommended to run with --no-fix tag on your first run!
|
|
46
|
+
```
|
package/bin/cli.js
CHANGED
|
@@ -28,7 +28,7 @@ async function bootstrap() {
|
|
|
28
28
|
program
|
|
29
29
|
.name('entkapp')
|
|
30
30
|
.description(ansis.cyan('Enterprise-Grade AST Syntax Refactoring & Self-Healing Engine'))
|
|
31
|
-
.version(packageJsonContent.version || '4.
|
|
31
|
+
.version(packageJsonContent.version || '4.3.0');
|
|
32
32
|
|
|
33
33
|
program
|
|
34
34
|
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
@@ -132,7 +132,7 @@ async function bootstrap() {
|
|
|
132
132
|
}, timeoutMs);
|
|
133
133
|
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
134
134
|
|
|
135
|
-
console.log(ansis.bold.green(`\nš¦ entkapp v${packageJsonContent.version || '4.
|
|
135
|
+
console.log(ansis.bold.green(`\nš¦ entkapp v${packageJsonContent.version || '4.3.0'} Engine Activation`));
|
|
136
136
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
137
137
|
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
|
|
138
138
|
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
package/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ============================================================================
|
|
5
|
-
* š¦ entkapp v4.
|
|
5
|
+
* š¦ entkapp v4.2.0: Ultimate Enterprise Codebase Janitor & Self-Healing Engine
|
|
6
6
|
* ============================================================================
|
|
7
7
|
* * Eine hochgradig integrierte Code-Analyse- und Projektbootstrapping-Engine.
|
|
8
8
|
* Kombiniert rekursive Erreichbarkeitsanalysen (Reachability Graphs) auf
|
|
@@ -648,7 +648,7 @@ function smartPrepend(originalCode, declarationBlock) {
|
|
|
648
648
|
async function inspectNpmPackage(pkgName) {
|
|
649
649
|
try {
|
|
650
650
|
const response = await fetch(`https://registry.npmjs.org/${pkgName}/latest`, {
|
|
651
|
-
headers: { 'User-Agent': 'entkapp-dx-client/4.
|
|
651
|
+
headers: { 'User-Agent': 'entkapp-dx-client/4.2.0' },
|
|
652
652
|
signal: AbortSignal.timeout(4000)
|
|
653
653
|
});
|
|
654
654
|
if (response.status === 200) {
|
|
@@ -682,7 +682,7 @@ async function inspectNpmPackage(pkgName) {
|
|
|
682
682
|
async function fetchRemoteLicense(licenseKey) {
|
|
683
683
|
try {
|
|
684
684
|
const response = await fetch(`https://api.github.com/licenses/${licenseKey.toLowerCase()}`, {
|
|
685
|
-
headers: { 'User-Agent': 'entkapp-dx-client/4.
|
|
685
|
+
headers: { 'User-Agent': 'entkapp-dx-client/4.2.0' },
|
|
686
686
|
signal: AbortSignal.timeout(5000)
|
|
687
687
|
});
|
|
688
688
|
if (response.status === 200) {
|
|
@@ -1681,7 +1681,7 @@ function postProcessAnalysis(stats, graphEngine) {
|
|
|
1681
1681
|
// ============================================================
|
|
1682
1682
|
async function main() {
|
|
1683
1683
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
1684
|
-
console.log(`\nš¦ entkapp v4.
|
|
1684
|
+
console.log(`\nš¦ entkapp v4.2.0: Advanced Dependency Intelligence Engine\n`);
|
|
1685
1685
|
console.log(`Usage: npx entkapp [options]\n`);
|
|
1686
1686
|
console.log(`Options:`);
|
|
1687
1687
|
console.log(` -h, --help Show this comprehensive workspace helper panel`);
|
|
@@ -1766,7 +1766,7 @@ async function main() {
|
|
|
1766
1766
|
let detectedFrameworks = [];
|
|
1767
1767
|
|
|
1768
1768
|
console.log(`\n${'ā'.repeat(67)}`);
|
|
1769
|
-
console.log(`š entkapp v4.
|
|
1769
|
+
console.log(`š entkapp v4.2.0: Enterprise Graph Intelligence Analyzer`);
|
|
1770
1770
|
console.log(`${'ā'.repeat(67)}\n`);
|
|
1771
1771
|
|
|
1772
1772
|
const topLevelItems = fs.readdirSync(targetDir);
|
package/logo.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"name": "entkapp",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.3.0",
|
|
5
5
|
"description": "The Ultimate Enterprise Codebase Janitor. Faster than Knip with OXC integration, type-aware analysis, and automated structural healing. Fully standalone - solving what Knip cannot.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
|
@@ -64,7 +64,12 @@
|
|
|
64
64
|
"unresolved",
|
|
65
65
|
"unused",
|
|
66
66
|
"unused-packages",
|
|
67
|
-
"workspace"
|
|
67
|
+
"workspace",
|
|
68
|
+
"entkapp",
|
|
69
|
+
"cut",
|
|
70
|
+
"kapp",
|
|
71
|
+
"dreamlong",
|
|
72
|
+
"DreamLong"
|
|
68
73
|
],
|
|
69
74
|
"author": "DreamLongYT",
|
|
70
75
|
"license": "Apache-2.0",
|
|
@@ -86,7 +91,6 @@
|
|
|
86
91
|
},
|
|
87
92
|
"devDependencies": {
|
|
88
93
|
"@types/node": "^25.9.3",
|
|
89
|
-
"knip": "^6.17.1",
|
|
90
94
|
"vitepress": "^1.6.4",
|
|
91
95
|
"vue": ">=3.5.0"
|
|
92
96
|
},
|
package/src/EngineContext.js
CHANGED
|
@@ -31,8 +31,16 @@ export class GraphNode {
|
|
|
31
31
|
this.localSuppressedRules = new Set();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
isSymbolReferencedExternally(symbolName, projectGraph) {
|
|
34
|
+
isSymbolReferencedExternally(symbolName, projectGraph, context) {
|
|
35
35
|
if (this.isLibraryEntry) return true;
|
|
36
|
+
|
|
37
|
+
// Check global registry if provided
|
|
38
|
+
if (context && context.importUsageRegistry) {
|
|
39
|
+
if (context.importUsageRegistry.has(`${this.filePath}:${symbolName}`) ||
|
|
40
|
+
context.importUsageRegistry.has(`${this.filePath}:*`)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
36
44
|
|
|
37
45
|
// --- NEW: Self-reference check (Fix for SecretSeverity bug) ---
|
|
38
46
|
// If the symbol is used within the same file, it is NOT dead.
|
|
@@ -137,12 +145,14 @@ export class EngineContext {
|
|
|
137
145
|
report.orphanedFiles.push(path.relative(this.cwd, filePath));
|
|
138
146
|
}
|
|
139
147
|
|
|
148
|
+
if (node.isEntry || node.isLibraryEntry) continue;
|
|
149
|
+
|
|
140
150
|
for (const [symbol, meta] of node.internalExports.entries()) {
|
|
141
151
|
if (symbol === '*' || symbol === 'default' || node.localSuppressedRules.has('unused-export') || node.localSuppressedRules.has(`unused-export:${symbol}`)) {
|
|
142
152
|
continue;
|
|
143
153
|
}
|
|
144
154
|
|
|
145
|
-
if (!node.isSymbolReferencedExternally(symbol, this.projectGraph)) {
|
|
155
|
+
if (!node.isSymbolReferencedExternally(symbol, this.projectGraph, this)) {
|
|
146
156
|
const loc = node.symbolSourceLocations.get(symbol) || { line: 0, column: 0 };
|
|
147
157
|
report.deadExports.push({
|
|
148
158
|
symbol,
|
package/src/api/HeadlessAPI.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* Headless API for entkapp v4.
|
|
3
|
+
* Headless API for entkapp v4.2.0
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Provides a programmatic interface for integrating entkapp into
|
|
6
6
|
* custom workflows, CI/CD pipelines, and third-party tools.
|
package/src/api/PluginSDK.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* Plugin SDK for entkapp v4.
|
|
3
|
+
* Plugin SDK for entkapp v4.2.0
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Provides utilities and helpers for developing custom plugins that extend
|
|
6
6
|
* entkapp's analysis and healing capabilities.
|
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
+
import ansis from 'ansis';
|
|
4
5
|
|
|
5
6
|
export class ASTAnalyzer {
|
|
6
7
|
constructor(context) {
|
|
@@ -398,6 +399,14 @@ export class ASTAnalyzer {
|
|
|
398
399
|
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
399
400
|
const arg = node.arguments[0];
|
|
400
401
|
if (arg) {
|
|
402
|
+
if (!fileNode.calculatedDynamicImports) fileNode.calculatedDynamicImports = [];
|
|
403
|
+
fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
404
|
+
|
|
405
|
+
// Ensure the argument text is also in rawStringReferences if it's a literal
|
|
406
|
+
if (ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg)) {
|
|
407
|
+
fileNode.rawStringReferences.add(arg.text);
|
|
408
|
+
}
|
|
409
|
+
|
|
401
410
|
if (ts.isStringLiteral(arg)) {
|
|
402
411
|
fileNode.explicitImports.add(arg.text);
|
|
403
412
|
fileNode.dynamicImports.add(arg.text);
|
|
@@ -405,11 +414,6 @@ export class ASTAnalyzer {
|
|
|
405
414
|
if (!arg.text.startsWith('.') && !arg.text.startsWith('/')) {
|
|
406
415
|
fileNode.externalPackageUsage.add(this._extractPackageName(arg.text));
|
|
407
416
|
}
|
|
408
|
-
} else {
|
|
409
|
-
// Dynamic import with a non-literal expression (e.g., variable or template literal).
|
|
410
|
-
if (fileNode.calculatedDynamicImports) {
|
|
411
|
-
fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
412
|
-
}
|
|
413
417
|
}
|
|
414
418
|
}
|
|
415
419
|
} else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|
package/src/ast/BarrelParser.js
CHANGED
|
@@ -178,15 +178,6 @@ export class BarrelParser {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
// Rule D: Sweep through anonymous star re-exports vectors (export * from 'module')
|
|
181
|
-
//
|
|
182
|
-
// Algorithm:
|
|
183
|
-
// 1. For each `export * from './child'`, recursively resolve the symbol in the child.
|
|
184
|
-
// 2. A non-barrel child immediately returns `{ originFile: childPath }` regardless of
|
|
185
|
-
// whether it actually declares the symbol. We therefore must verify that the
|
|
186
|
-
// returned origin file actually contains the symbol in its declaredLocalExports
|
|
187
|
-
// before accepting the result.
|
|
188
|
-
// 3. If the child is itself a barrel, the recursive call already performs the full
|
|
189
|
-
// chain walk, so we only need to verify the final origin.
|
|
190
181
|
for (const relativePath of spec.wildcardExports) {
|
|
191
182
|
const fullyResolvedPath = this.resolver.resolveModulePath(contextFilePath, relativePath);
|
|
192
183
|
|
|
@@ -195,24 +186,15 @@ export class BarrelParser {
|
|
|
195
186
|
fullyResolvedPath,
|
|
196
187
|
targetSymbolName,
|
|
197
188
|
activeProjectGraph,
|
|
198
|
-
new Set(protectionStack)
|
|
189
|
+
new Set(protectionStack)
|
|
199
190
|
);
|
|
200
191
|
|
|
201
192
|
if (!continuousResolutionTrace) continue;
|
|
202
|
-
if (continuousResolutionTrace.originFile === contextFilePath) continue;
|
|
203
193
|
|
|
204
|
-
// Verify that the resolved origin actually declares the symbol.
|
|
205
|
-
// This prevents a non-barrel sibling (e.g. constants.ts) from being
|
|
206
|
-
// incorrectly returned for a symbol it does not export (e.g. formatData).
|
|
207
194
|
const originSpec = await this.parseBarrelSpecification(continuousResolutionTrace.originFile);
|
|
208
195
|
if (originSpec.declaredLocalExports.has(continuousResolutionTrace.originSymbol)) {
|
|
209
196
|
return continuousResolutionTrace;
|
|
210
197
|
}
|
|
211
|
-
// The origin spec is itself a barrel (isBarrelInstance = true) and the
|
|
212
|
-
// recursive call already resolved through it ā accept the result.
|
|
213
|
-
if (originSpec.isBarrelInstance) {
|
|
214
|
-
return continuousResolutionTrace;
|
|
215
|
-
}
|
|
216
198
|
}
|
|
217
199
|
}
|
|
218
200
|
|
|
@@ -10,7 +10,7 @@ export class DeadCodeDetector {
|
|
|
10
10
|
// Find all entry points
|
|
11
11
|
const entryPoints = new Set();
|
|
12
12
|
for (const [filePath, node] of graph.entries()) {
|
|
13
|
-
if (node.isEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
|
|
13
|
+
if (node.isEntry || node.isLibraryEntry || node.isNextJsRoute || node.isSvelteComponent || node.isAstroPage) {
|
|
14
14
|
entryPoints.add(filePath);
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -45,7 +45,7 @@ export class DeadCodeDetector {
|
|
|
45
45
|
if (!node) continue;
|
|
46
46
|
|
|
47
47
|
// If it's an entry point, we consider its exports used (unless strictly configured otherwise)
|
|
48
|
-
if (node.isEntry) continue;
|
|
48
|
+
if (node.isEntry || node.isLibraryEntry) continue;
|
|
49
49
|
|
|
50
50
|
for (const [exportName, exportInfo] of node.internalExports.entries()) {
|
|
51
51
|
if (exportName === '*' || exportName === 'default') continue; // Skip wildcards for now
|
package/src/ast/OxcAnalyzer.js
CHANGED
|
@@ -3,20 +3,17 @@ export class OxcAnalyzer {
|
|
|
3
3
|
this.context = context;
|
|
4
4
|
this.oxc = null;
|
|
5
5
|
this.isAvailable = false;
|
|
6
|
-
// Initialization is handled via init() or lazily during parseFile
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
async init() {
|
|
10
9
|
if (this.isAvailable) return true;
|
|
11
10
|
try {
|
|
12
|
-
// In ESM, we use dynamic import()
|
|
13
11
|
const oxc = await import("oxc-parser");
|
|
14
12
|
this.oxc = oxc;
|
|
15
13
|
this.isAvailable = true;
|
|
16
14
|
return true;
|
|
17
15
|
} catch (e) {
|
|
18
16
|
try {
|
|
19
|
-
// Fallback for older node versions or specific bundling setups
|
|
20
17
|
const { createRequire } = await import('module');
|
|
21
18
|
const require = createRequire(import.meta.url);
|
|
22
19
|
this.oxc = require("oxc-parser");
|
|
@@ -25,7 +22,7 @@ export class OxcAnalyzer {
|
|
|
25
22
|
} catch (err) {
|
|
26
23
|
this.isAvailable = false;
|
|
27
24
|
if (this.context.verbose) {
|
|
28
|
-
console.warn("[OxcAnalyzer] oxc-parser not found or failed to load
|
|
25
|
+
console.warn("[OxcAnalyzer] oxc-parser not found or failed to load.");
|
|
29
26
|
}
|
|
30
27
|
return false;
|
|
31
28
|
}
|
|
@@ -39,24 +36,18 @@ export class OxcAnalyzer {
|
|
|
39
36
|
}
|
|
40
37
|
|
|
41
38
|
try {
|
|
42
|
-
if (this.context.verbose) {
|
|
43
|
-
console.log(`[OXC] Fast-parsing: ${filePath}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
39
|
const ast = this.oxc.parseSync(content, {
|
|
47
40
|
sourceType: "module",
|
|
48
41
|
sourceFilename: filePath,
|
|
49
42
|
ecmaVersion: "latest",
|
|
50
43
|
});
|
|
51
44
|
|
|
52
|
-
// Initialize new properties for JSX and Decorator analysis
|
|
53
45
|
fileNode.jsxComponents = new Set();
|
|
54
46
|
fileNode.jsxProps = new Set();
|
|
55
47
|
fileNode.decorators = new Set();
|
|
56
48
|
|
|
57
49
|
this.walkOxcAst(ast.program, fileNode, content);
|
|
58
50
|
|
|
59
|
-
// Compute line/column for each export start position
|
|
60
51
|
const lines = content.split('\n');
|
|
61
52
|
const getLineCol = (pos) => {
|
|
62
53
|
let count = 0;
|
|
@@ -100,19 +91,17 @@ export class OxcAnalyzer {
|
|
|
100
91
|
this.handleCallExpression(node, fileNode);
|
|
101
92
|
break;
|
|
102
93
|
case "JSXElement":
|
|
103
|
-
case "JSXFragment":
|
|
94
|
+
case "JSXFragment":
|
|
104
95
|
this.handleJsxElement(node, fileNode);
|
|
105
96
|
break;
|
|
106
97
|
case "Decorator":
|
|
107
98
|
this.handleDecorator(node, fileNode);
|
|
108
99
|
break;
|
|
109
100
|
case "StringLiteral":
|
|
110
|
-
// Track for secret scanning if it looks like a secret
|
|
111
101
|
fileNode.rawStringReferences.add(node.value);
|
|
112
102
|
break;
|
|
113
103
|
}
|
|
114
104
|
|
|
115
|
-
// Traverse children
|
|
116
105
|
for (const key in node) {
|
|
117
106
|
if (node[key] && typeof node[key] === "object") {
|
|
118
107
|
if (Array.isArray(node[key])) {
|
|
@@ -128,7 +117,6 @@ export class OxcAnalyzer {
|
|
|
128
117
|
const specifier = node.source.value;
|
|
129
118
|
fileNode.explicitImports.add(specifier);
|
|
130
119
|
|
|
131
|
-
// Track external package usage for dependency analysis
|
|
132
120
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
133
121
|
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
134
122
|
}
|
|
@@ -136,7 +124,7 @@ export class OxcAnalyzer {
|
|
|
136
124
|
if (node.specifiers) {
|
|
137
125
|
node.specifiers.forEach((spec) => {
|
|
138
126
|
if (spec.type === "ImportSpecifier") {
|
|
139
|
-
const importedName = spec.imported.name;
|
|
127
|
+
const importedName = spec.imported.name || (spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value);
|
|
140
128
|
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
141
129
|
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
142
130
|
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
@@ -148,86 +136,83 @@ export class OxcAnalyzer {
|
|
|
148
136
|
}
|
|
149
137
|
|
|
150
138
|
handleExportDeclaration(node, fileNode, content) {
|
|
139
|
+
// 1. Default Exports
|
|
151
140
|
if (node.type === "ExportDefaultDeclaration") {
|
|
152
|
-
fileNode.internalExports.set("default", {
|
|
141
|
+
fileNode.internalExports.set("default", {
|
|
142
|
+
type: "default",
|
|
143
|
+
start: node.start,
|
|
144
|
+
end: node.end
|
|
145
|
+
});
|
|
153
146
|
return;
|
|
154
147
|
}
|
|
155
148
|
|
|
149
|
+
// 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
|
|
156
150
|
if (node.type === "ExportAllDeclaration") {
|
|
157
|
-
const sourceSpecifier = node.source
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
// Track external package usage from re-exports
|
|
164
|
-
if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
|
|
165
|
-
fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
|
|
166
|
-
}
|
|
151
|
+
const sourceSpecifier = node.source.value;
|
|
152
|
+
fileNode.explicitImports.add(sourceSpecifier);
|
|
153
|
+
if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
|
|
154
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
|
|
155
|
+
}
|
|
167
156
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
157
|
+
if (node.exported) {
|
|
158
|
+
// export * as ns from 'mod'
|
|
159
|
+
const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
|
|
160
|
+
if (name) {
|
|
161
|
+
fileNode.internalExports.set(name, {
|
|
162
|
+
type: "re-export-namespace",
|
|
163
|
+
source: sourceSpecifier,
|
|
164
|
+
originalName: "*",
|
|
165
|
+
start: node.start,
|
|
166
|
+
end: node.end
|
|
167
|
+
});
|
|
179
168
|
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
180
169
|
}
|
|
170
|
+
} else {
|
|
171
|
+
// export * from 'mod'
|
|
172
|
+
fileNode.internalExports.set("*", {
|
|
173
|
+
type: "re-export-all",
|
|
174
|
+
source: sourceSpecifier
|
|
175
|
+
});
|
|
176
|
+
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
181
177
|
}
|
|
182
178
|
return;
|
|
183
179
|
}
|
|
184
180
|
|
|
181
|
+
// 3. Named Exports & Re-exports with specifiers
|
|
185
182
|
if (node.source) {
|
|
186
|
-
// Re-export
|
|
183
|
+
// Re-export: export { x } from 'mod'
|
|
187
184
|
const specifier = node.source.value;
|
|
188
|
-
// Register re-export source as an explicit import
|
|
189
185
|
fileNode.explicitImports.add(specifier);
|
|
190
|
-
|
|
191
|
-
// Track external package usage from re-exports
|
|
192
186
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
193
187
|
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
194
188
|
}
|
|
195
189
|
|
|
196
190
|
if (node.specifiers) {
|
|
197
191
|
node.specifiers.forEach((spec) => {
|
|
198
|
-
const exportedName = spec.exported.name;
|
|
199
|
-
const localName = spec.local.name;
|
|
192
|
+
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
193
|
+
const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
|
|
200
194
|
fileNode.internalExports.set(exportedName, {
|
|
201
195
|
type: "re-export",
|
|
202
196
|
source: specifier,
|
|
203
197
|
originalName: localName,
|
|
204
|
-
start:
|
|
205
|
-
end:
|
|
198
|
+
start: spec.start,
|
|
199
|
+
end: spec.end,
|
|
206
200
|
});
|
|
207
|
-
// Register as importedSymbol so barrel-tracer can resolve origin file
|
|
208
201
|
fileNode.importedSymbols.add(`${specifier}:${localName}`);
|
|
209
202
|
});
|
|
210
203
|
}
|
|
211
204
|
} else if (node.declaration) {
|
|
212
|
-
// Direct export
|
|
205
|
+
// Direct declaration export: export const x = 1, export function f() {}
|
|
213
206
|
const decl = node.declaration;
|
|
214
207
|
if (decl.type === "VariableDeclaration") {
|
|
215
208
|
decl.declarations.forEach((d) => {
|
|
216
|
-
|
|
217
|
-
fileNode.internalExports.set(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
fileNode.internalExports.set(p.value.name, { type: "variable", start: p.start, end: p.end });
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
} else if (d.id.type === "ArrayPattern") {
|
|
225
|
-
d.id.elements.forEach((e) => {
|
|
226
|
-
if (e && e.type === "Identifier") {
|
|
227
|
-
fileNode.internalExports.set(e.name, { type: "variable", start: e.start, end: e.end });
|
|
228
|
-
}
|
|
209
|
+
this._extractNamesFromPattern(d.id, (name) => {
|
|
210
|
+
fileNode.internalExports.set(name, {
|
|
211
|
+
type: "variable",
|
|
212
|
+
start: d.start,
|
|
213
|
+
end: d.end
|
|
229
214
|
});
|
|
230
|
-
}
|
|
215
|
+
});
|
|
231
216
|
});
|
|
232
217
|
} else if (decl.id && decl.id.name) {
|
|
233
218
|
let type = "unknown";
|
|
@@ -238,49 +223,70 @@ export class OxcAnalyzer {
|
|
|
238
223
|
else if (decl.type === "TSTypeAliasDeclaration") type = "type";
|
|
239
224
|
else if (decl.type === "TSModuleDeclaration") type = "namespace";
|
|
240
225
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
} else if (decl.type === "TSInterfaceDeclaration" || decl.type === "ClassDeclaration") {
|
|
247
|
-
exportInfo.members = decl.body.body.filter((m) => m.key && m.key.name).map((m) => m.key.name);
|
|
248
|
-
}
|
|
226
|
+
fileNode.internalExports.set(decl.id.name, {
|
|
227
|
+
type,
|
|
228
|
+
start: decl.start,
|
|
229
|
+
end: decl.end
|
|
230
|
+
});
|
|
249
231
|
}
|
|
250
232
|
} else if (node.specifiers) {
|
|
233
|
+
// Export existing locals: export { x, y as z }
|
|
251
234
|
node.specifiers.forEach((spec) => {
|
|
252
|
-
const exportedName = spec.exported.name;
|
|
253
|
-
const localName = spec.local.name;
|
|
235
|
+
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
236
|
+
const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
|
|
254
237
|
fileNode.internalExports.set(exportedName, {
|
|
255
238
|
type: "export",
|
|
256
239
|
originalName: localName,
|
|
257
|
-
start:
|
|
258
|
-
end:
|
|
240
|
+
start: spec.start,
|
|
241
|
+
end: spec.end,
|
|
259
242
|
});
|
|
260
243
|
});
|
|
261
244
|
}
|
|
262
245
|
}
|
|
263
246
|
|
|
247
|
+
_extractNamesFromPattern(node, callback) {
|
|
248
|
+
if (!node) return;
|
|
249
|
+
if (node.type === "Identifier") {
|
|
250
|
+
callback(node.name);
|
|
251
|
+
} else if (node.type === "ObjectPattern") {
|
|
252
|
+
node.properties.forEach(p => {
|
|
253
|
+
if (p.type === "Property") {
|
|
254
|
+
this._extractNamesFromPattern(p.value, callback);
|
|
255
|
+
} else if (p.type === "RestElement") {
|
|
256
|
+
this._extractNamesFromPattern(p.argument, callback);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
} else if (node.type === "ArrayPattern") {
|
|
260
|
+
node.elements.forEach(e => {
|
|
261
|
+
if (e) this._extractNamesFromPattern(e, callback);
|
|
262
|
+
});
|
|
263
|
+
} else if (node.type === "AssignmentPattern") {
|
|
264
|
+
this._extractNamesFromPattern(node.left, callback);
|
|
265
|
+
} else if (node.type === "RestElement") {
|
|
266
|
+
this._extractNamesFromPattern(node.argument, callback);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
264
270
|
handleCallExpression(node, fileNode) {
|
|
265
271
|
// Dynamic import(): import('./module')
|
|
266
272
|
if (node.callee.type === "Import" && node.arguments.length > 0) {
|
|
267
273
|
const arg = node.arguments[0];
|
|
274
|
+
if (!fileNode.calculatedDynamicImports) fileNode.calculatedDynamicImports = [];
|
|
275
|
+
fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
|
|
276
|
+
|
|
268
277
|
if (arg.type === "StringLiteral") {
|
|
269
278
|
const specifier = arg.value;
|
|
270
279
|
fileNode.explicitImports.add(specifier);
|
|
271
280
|
fileNode.dynamicImports.add(specifier);
|
|
281
|
+
fileNode.importedSymbols.add(`${specifier}:*`); // Dynamic import usually consumes the whole namespace
|
|
272
282
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
273
283
|
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
274
284
|
}
|
|
275
|
-
} else {
|
|
276
|
-
// Non-literal dynamic import: record as calculated
|
|
277
|
-
if (fileNode.calculatedDynamicImports) {
|
|
278
|
-
fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
|
|
279
|
-
}
|
|
280
285
|
}
|
|
281
286
|
} else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
|
|
282
287
|
const specifier = node.arguments[0].value;
|
|
283
288
|
fileNode.explicitImports.add(specifier);
|
|
289
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
284
290
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
285
291
|
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
286
292
|
}
|
|
@@ -291,6 +297,7 @@ export class OxcAnalyzer {
|
|
|
291
297
|
const getElementName = (nameNode) => {
|
|
292
298
|
if (nameNode.type === "JSXIdentifier") return nameNode.name;
|
|
293
299
|
if (nameNode.type === "JSXMemberExpression") return `${getElementName(nameNode.object)}.${nameNode.property.name}`;
|
|
300
|
+
if (nameNode.type === "JSXNamespacedName") return `${nameNode.namespace.name}:${nameNode.name.name}`;
|
|
294
301
|
return "unknown";
|
|
295
302
|
};
|
|
296
303
|
|
|
@@ -311,19 +318,17 @@ export class OxcAnalyzer {
|
|
|
311
318
|
handleDecorator(node, fileNode) {
|
|
312
319
|
const getDecoratorName = (expr) => {
|
|
313
320
|
if (expr.type === "Identifier") return expr.name;
|
|
314
|
-
if (expr.type === "CallExpression"
|
|
315
|
-
if (expr.type === "
|
|
321
|
+
if (expr.type === "CallExpression") return getDecoratorName(expr.callee);
|
|
322
|
+
if (expr.type === "MemberExpression") {
|
|
323
|
+
const prop = expr.property.name || expr.property.value;
|
|
324
|
+
return prop || "unknown";
|
|
325
|
+
}
|
|
316
326
|
return "unknown";
|
|
317
327
|
};
|
|
318
328
|
|
|
319
329
|
const decoratorName = getDecoratorName(node.expression);
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
// Optionally, extract decorator arguments
|
|
323
|
-
if (node.expression.type === "CallExpression") {
|
|
324
|
-
node.expression.arguments.forEach(arg => {
|
|
325
|
-
// Further analysis of arguments can be done here if needed
|
|
326
|
-
});
|
|
330
|
+
if (decoratorName !== "unknown") {
|
|
331
|
+
fileNode.decorators.add(decoratorName);
|
|
327
332
|
}
|
|
328
333
|
}
|
|
329
334
|
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { OxcAnalyzer } from "./ast/OxcAnalyzer.js";
|
|
|
3
3
|
import { SecretScanner } from './ast/SecretScanner.js';
|
|
4
4
|
/**
|
|
5
5
|
* ============================================================================
|
|
6
|
-
* š¦ entkapp v4.
|
|
6
|
+
* š¦ entkapp v4.2.0: Unified Architectural Refactoring Orchestrator
|
|
7
7
|
* ============================================================================
|
|
8
8
|
* Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
|
|
9
9
|
* supply-chain validation audits, and automated structural healing rollbacks.
|
|
@@ -87,6 +87,8 @@ export class RefactoringEngine {
|
|
|
87
87
|
try {
|
|
88
88
|
console.log(ansis.bold.green('šÆ Starting entkapp Operational Optimization Cycle...'));
|
|
89
89
|
|
|
90
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
91
|
+
|
|
90
92
|
let rl;
|
|
91
93
|
if (!this.context.skipConfirm) {
|
|
92
94
|
rl = readline.createInterface({
|
|
@@ -136,6 +138,7 @@ export class RefactoringEngine {
|
|
|
136
138
|
|
|
137
139
|
for (const filePath of sourceCodeFilesList) {
|
|
138
140
|
const node = this.context.getOrCreateNode(filePath);
|
|
141
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
139
142
|
const currentHash = await this.cacheManager.computeHash(filePath);
|
|
140
143
|
node.contentHash = currentHash;
|
|
141
144
|
|
|
@@ -217,6 +220,21 @@ export class RefactoringEngine {
|
|
|
217
220
|
// Pass 4: Evaluate graph edges and link connections across the codebase mesh
|
|
218
221
|
console.log(ansis.dim('š Linking graph edges and checking structural usage paths...'));
|
|
219
222
|
await this.linkDependencyGraph();
|
|
223
|
+
|
|
224
|
+
// Update entry points and seeds based on link analysis
|
|
225
|
+
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
226
|
+
if (node.isEntry || node.isLibraryEntry) {
|
|
227
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
228
|
+
this.context.importUsageRegistry.add(`${filePath}:*`);
|
|
229
|
+
|
|
230
|
+
// Also protect all symbols in library entries
|
|
231
|
+
if (node.internalExports) {
|
|
232
|
+
for (const symbol of node.internalExports.keys()) {
|
|
233
|
+
this.context.importUsageRegistry.add(`${filePath}:${symbol}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
220
238
|
|
|
221
239
|
// NEW: Circular Dependency Detection
|
|
222
240
|
console.log(ansis.dim('š Detecting circular dependencies...'));
|
|
@@ -262,7 +280,11 @@ export class RefactoringEngine {
|
|
|
262
280
|
if (!this.context.unlistedDependencies) this.context.unlistedDependencies = [];
|
|
263
281
|
|
|
264
282
|
// Simple internal helper to guarantee matching forward slash strings across all platforms
|
|
265
|
-
const slashify = (p) =>
|
|
283
|
+
const slashify = (p) => {
|
|
284
|
+
if (!p) return p;
|
|
285
|
+
if (Array.isArray(p)) p = p[0];
|
|
286
|
+
return path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
287
|
+
};
|
|
266
288
|
|
|
267
289
|
if (this.context.projectGraph && typeof this.context.projectGraph.entries === 'function') {
|
|
268
290
|
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
@@ -271,12 +293,10 @@ export class RefactoringEngine {
|
|
|
271
293
|
const cleanFilePath = slashify(filePath);
|
|
272
294
|
|
|
273
295
|
// š ROOT DEPS HARVESTER SHADOW TRACKING:
|
|
274
|
-
// If external packages are parsed from ANY file node in the monorepo,
|
|
275
|
-
// ensure the auditor registries register their footprint so root checking works!
|
|
276
296
|
if (fileNode.externalPackageUsage) {
|
|
277
297
|
fileNode.externalPackageUsage.forEach(pkg => {
|
|
278
|
-
const relativeToRoot = path.relative(this.context.cwd, filePath);
|
|
279
|
-
if (relativeToRoot.startsWith('packages
|
|
298
|
+
const relativeToRoot = path.relative(this.context.cwd, filePath).replace(/\\/g, '/');
|
|
299
|
+
if (relativeToRoot.startsWith('packages/')) {
|
|
280
300
|
this.context.consumedWorkspacePackages.add(pkg);
|
|
281
301
|
} else {
|
|
282
302
|
this.context.consumedRootPackages.add(pkg);
|
|
@@ -294,17 +314,17 @@ export class RefactoringEngine {
|
|
|
294
314
|
if (!this.context.exportRegistry.has(cleanFilePath)) {
|
|
295
315
|
this.context.exportRegistry.set(cleanFilePath, new Set());
|
|
296
316
|
}
|
|
297
|
-
exportKeys.forEach(key =>
|
|
317
|
+
exportKeys.forEach(key => {
|
|
318
|
+
if (key !== '*') { // Don't track wildcard as a dead export symbol
|
|
319
|
+
this.context.exportRegistry.get(cleanFilePath).add(key);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
298
322
|
}
|
|
299
323
|
}
|
|
300
324
|
|
|
301
325
|
// 2. Gather cross-file usage tokens using unified slashes
|
|
302
|
-
if (fileNode.
|
|
303
|
-
const
|
|
304
|
-
? Array.from(fileNode.importedSymbols)
|
|
305
|
-
: (Array.isArray(fileNode.importedSymbols) ? fileNode.importedSymbols : []);
|
|
306
|
-
|
|
307
|
-
for (const symbolToken of symbolsArray) {
|
|
326
|
+
if (fileNode.importedSymbols) {
|
|
327
|
+
for (const symbolToken of fileNode.importedSymbols) {
|
|
308
328
|
if (typeof symbolToken !== 'string') continue;
|
|
309
329
|
|
|
310
330
|
const splitIndex = symbolToken.indexOf(':');
|
|
@@ -314,31 +334,20 @@ export class RefactoringEngine {
|
|
|
314
334
|
const symbolName = symbolToken.slice(splitIndex + 1);
|
|
315
335
|
|
|
316
336
|
let targetFile = null;
|
|
317
|
-
|
|
337
|
+
// If specifier is already an absolute path (resolved during linkDependencyGraph)
|
|
338
|
+
if (path.isAbsolute(specifier)) {
|
|
339
|
+
targetFile = specifier;
|
|
340
|
+
} else if (this.workspaceGraph && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
318
341
|
const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
|
|
319
342
|
if (match && match.entryPoints && match.entryPoints.length > 0) {
|
|
320
|
-
targetFile =
|
|
343
|
+
targetFile = match.entryPoints[0];
|
|
321
344
|
}
|
|
322
345
|
} else if (specifier.startsWith('.')) {
|
|
323
|
-
targetFile =
|
|
324
|
-
|
|
325
|
-
// š COMPILE-TO-SOURCE EXTENSION SWAP:
|
|
326
|
-
// If a barrel file imports relative paths using compiled targets (like './used-fn.js'),
|
|
327
|
-
// replace the extension to check for active source components directly ('.ts' / '.tsx')
|
|
328
|
-
if (targetFile.endsWith('.js')) {
|
|
329
|
-
targetFile = targetFile.slice(0, -3);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
if (!path.extname(targetFile)) {
|
|
333
|
-
if (existsSync(targetFile + '.ts')) targetFile += '.ts';
|
|
334
|
-
else if (existsSync(targetFile + '.tsx')) targetFile += '.tsx';
|
|
335
|
-
else if (existsSync(targetFile + '.js')) targetFile += '.js';
|
|
336
|
-
}
|
|
346
|
+
targetFile = this.resolver.resolveModulePath(filePath, specifier);
|
|
337
347
|
}
|
|
338
348
|
|
|
339
349
|
if (targetFile) {
|
|
340
|
-
|
|
341
|
-
const cleanTargetFile = Array.isArray(targetFile) ? slashify(targetFile[0]) : slashify(targetFile);
|
|
350
|
+
const cleanTargetFile = slashify(targetFile);
|
|
342
351
|
this.context.importUsageRegistry.add(`${cleanTargetFile}:${symbolName}`);
|
|
343
352
|
}
|
|
344
353
|
}
|
|
@@ -478,16 +487,35 @@ export class RefactoringEngine {
|
|
|
478
487
|
}
|
|
479
488
|
if (isPackageEntryPoint) continue;
|
|
480
489
|
|
|
490
|
+
const originalNode = this.context.projectGraph.get(cleanExportedFile);
|
|
491
|
+
if (originalNode && originalNode.isLibraryEntry) continue;
|
|
492
|
+
|
|
493
|
+
const unusedExportsInThisFile = [];
|
|
494
|
+
|
|
481
495
|
for (const symbol of exportsSet) {
|
|
482
496
|
const consumptionToken = `${cleanExportedFile}:${symbol}`;
|
|
483
497
|
if (!this.context.importUsageRegistry?.has(consumptionToken)) {
|
|
484
|
-
|
|
498
|
+
// Retrieve the real source location if available
|
|
499
|
+
const loc = (originalNode && originalNode.symbolSourceLocations) ? originalNode.symbolSourceLocations.get(symbol) || { line: 0 } : { line: 0 };
|
|
500
|
+
unusedExportsInThisFile.push({
|
|
485
501
|
symbol: symbol,
|
|
486
502
|
file: relativeExportedFile,
|
|
487
|
-
line:
|
|
503
|
+
line: loc.line
|
|
488
504
|
});
|
|
489
505
|
}
|
|
490
506
|
}
|
|
507
|
+
|
|
508
|
+
// --- NEW: Orphaned File Detection based on Export Coverage ---
|
|
509
|
+
// If every single export in this file is unused, and it's not an entry point,
|
|
510
|
+
// we suggest deleting the entire file instead of pruning individual exports.
|
|
511
|
+
if (unusedExportsInThisFile.length > 0 && unusedExportsInThisFile.length === exportsSet.size) {
|
|
512
|
+
if (!analysisSummary.orphanedFiles.includes(relativeExportedFile)) {
|
|
513
|
+
analysisSummary.orphanedFiles.push(relativeExportedFile);
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
// Otherwise, just append the individual dead exports as usual
|
|
517
|
+
analysisSummary.deadExports.push(...unusedExportsInThisFile);
|
|
518
|
+
}
|
|
491
519
|
}
|
|
492
520
|
}
|
|
493
521
|
analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
|
|
@@ -592,40 +620,68 @@ export class RefactoringEngine {
|
|
|
592
620
|
const ext = path.extname(entry.name);
|
|
593
621
|
if (['.js', '.jsx', '.ts', '.tsx', '.vue', '.svelte'].includes(ext) || entry.name === 'package.json') {
|
|
594
622
|
fileList.push(res);
|
|
623
|
+
// Auto-detect entry points: files named index.js/ts/jsx/tsx in the root are entry points
|
|
624
|
+
if (dir === this.context.cwd && /^index\.(js|ts|jsx|tsx)$/.test(entry.name)) {
|
|
625
|
+
if (!this.context.entryPoints.includes(res)) {
|
|
626
|
+
this.context.entryPoints.push(res);
|
|
627
|
+
}
|
|
628
|
+
const node = this.context.getOrCreateNode(res);
|
|
629
|
+
node.isEntry = true;
|
|
630
|
+
}
|
|
595
631
|
}
|
|
596
632
|
}
|
|
597
633
|
}
|
|
598
634
|
}
|
|
599
635
|
|
|
600
636
|
async linkDependencyGraph() {
|
|
637
|
+
// Pass 1: Global Entry-Point Protection (Seeds)
|
|
638
|
+
// Mark all package entry points and explicit config entry points as library entries first.
|
|
639
|
+
const seeds = new Set();
|
|
640
|
+
|
|
641
|
+
// Add workspace entry points
|
|
642
|
+
if (this.workspaceGraph && this.workspaceGraph.packageManifests) {
|
|
643
|
+
for (const pkg of this.workspaceGraph.packageManifests.values()) {
|
|
644
|
+
if (pkg.entryPoints) {
|
|
645
|
+
pkg.entryPoints.forEach(ep => seeds.add(path.resolve(ep)));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Add explicit config entry points
|
|
651
|
+
if (this.context.entryPoints) {
|
|
652
|
+
this.context.entryPoints.forEach(ep => seeds.add(path.resolve(this.context.cwd, ep)));
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
for (const seedPath of seeds) {
|
|
656
|
+
if (this.context.projectGraph.has(seedPath)) {
|
|
657
|
+
const node = this.context.projectGraph.get(seedPath);
|
|
658
|
+
node.isLibraryEntry = true;
|
|
659
|
+
node.isEntry = true;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Pass 2: Edge Linking
|
|
601
664
|
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
602
|
-
//
|
|
665
|
+
// A. Explicit Imports (Files)
|
|
603
666
|
for (const specifier of node.explicitImports) {
|
|
604
667
|
const resolvedPath = this.resolver.resolveModulePath(filePath, specifier);
|
|
605
668
|
if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
|
|
606
|
-
this.context.projectGraph.get(resolvedPath)
|
|
669
|
+
const targetNode = this.context.projectGraph.get(resolvedPath);
|
|
670
|
+
targetNode.incomingEdges.add(filePath);
|
|
607
671
|
node.outgoingEdges.add(resolvedPath);
|
|
608
672
|
|
|
609
|
-
//
|
|
610
|
-
//
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
673
|
+
// Re-export protection: If this file re-exports everything from another file,
|
|
674
|
+
// the target file should be treated as an entry point for its own exports.
|
|
675
|
+
const isReExportAll = Array.from(node.internalExports.values()).some(exp =>
|
|
676
|
+
(exp.type === "re-export-all" || exp.type === "re-export-namespace") && exp.source === specifier
|
|
677
|
+
);
|
|
678
|
+
if (isReExportAll) {
|
|
679
|
+
targetNode.isLibraryEntry = true;
|
|
615
680
|
}
|
|
616
681
|
}
|
|
617
682
|
}
|
|
618
683
|
|
|
619
|
-
//
|
|
620
|
-
for (const pkg of this.workspaceGraph.packageManifests.values()) {
|
|
621
|
-
for (const entryPath of pkg.entryPoints) {
|
|
622
|
-
if (this.context.projectGraph.has(entryPath)) {
|
|
623
|
-
this.context.projectGraph.get(entryPath).isLibraryEntry = true;
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// Pass B: Link named-symbol imports through barrel/re-export chains
|
|
684
|
+
// B. Symbol-level Linking (Tracing through barrels)
|
|
629
685
|
for (const specToken of node.importedSymbols) {
|
|
630
686
|
const delimiterIndex = specToken.indexOf(':');
|
|
631
687
|
if (delimiterIndex === -1) continue;
|
|
@@ -636,18 +692,55 @@ export class RefactoringEngine {
|
|
|
636
692
|
if (!resolvedPath) continue;
|
|
637
693
|
|
|
638
694
|
if (symbol === '*') {
|
|
639
|
-
// Wildcard import / re-export-all: add a direct edge to the resolved file.
|
|
640
695
|
if (this.context.projectGraph.has(resolvedPath)) {
|
|
641
696
|
this.context.projectGraph.get(resolvedPath).incomingEdges.add(filePath);
|
|
642
697
|
node.outgoingEdges.add(resolvedPath);
|
|
643
698
|
}
|
|
644
699
|
} else {
|
|
645
|
-
// Named import: trace through barrel files to the actual declaration origin.
|
|
646
700
|
const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(resolvedPath, symbol, this.context.projectGraph);
|
|
647
701
|
if (traceResolution && this.context.projectGraph.has(traceResolution.originFile)) {
|
|
648
|
-
this.context.projectGraph.get(traceResolution.originFile)
|
|
702
|
+
const originNode = this.context.projectGraph.get(traceResolution.originFile);
|
|
703
|
+
originNode.incomingEdges.add(filePath);
|
|
649
704
|
node.outgoingEdges.add(traceResolution.originFile);
|
|
650
|
-
|
|
705
|
+
// Register the actual resolved symbol for dead export detection
|
|
706
|
+
node.importedSymbols.add(`${traceResolution.originFile}:${traceResolution.originSymbol}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// C. Dynamic Import Heuristics (Fix for non-literal dynamic imports)
|
|
712
|
+
// If a file has calculated dynamic imports (e.g. import(variable)),
|
|
713
|
+
// we check all raw strings in that file as potential targets.
|
|
714
|
+
if (node.calculatedDynamicImports && node.calculatedDynamicImports.length > 0) {
|
|
715
|
+
for (const candidate of node.rawStringReferences) {
|
|
716
|
+
// Rule 1: Try resolving it directly
|
|
717
|
+
const resolvedPath = this.resolver.resolveModulePath(filePath, candidate);
|
|
718
|
+
if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
|
|
719
|
+
const targetNode = this.context.projectGraph.get(resolvedPath);
|
|
720
|
+
targetNode.incomingEdges.add(filePath);
|
|
721
|
+
node.outgoingEdges.add(resolvedPath);
|
|
722
|
+
targetNode.isLibraryEntry = true;
|
|
723
|
+
if (this.context.verbose) console.log(ansis.dim(`š Dynamic Heuristic (Resolved): Linked ${candidate} from ${filePath}`));
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Rule 2: Check all possible internal files for partial matches
|
|
728
|
+
if (candidate.length > 3) { // Avoid very short strings
|
|
729
|
+
for (const [targetPath, targetNode] of this.context.projectGraph.entries()) {
|
|
730
|
+
const relToCwd = path.relative(this.context.cwd, targetPath).replace(/\\/g, '/');
|
|
731
|
+
const relNoExt = relToCwd.replace(/\.[^/.]+$/, "");
|
|
732
|
+
|
|
733
|
+
// Check for exact relative path matches or partial matches that look intentional
|
|
734
|
+
if (candidate === relToCwd || candidate === relNoExt ||
|
|
735
|
+
candidate === `./${relToCwd}` || candidate === `./${relNoExt}` ||
|
|
736
|
+
(candidate.includes('/') && targetPath.endsWith(candidate.startsWith('/') ? candidate : `/${candidate}`))) {
|
|
737
|
+
|
|
738
|
+
targetNode.incomingEdges.add(filePath);
|
|
739
|
+
node.outgoingEdges.add(targetPath);
|
|
740
|
+
targetNode.isLibraryEntry = true;
|
|
741
|
+
if (this.context.verbose) console.log(ansis.dim(`š Dynamic Heuristic (Partial): Linked ${candidate} to ${relToCwd} from ${filePath}`));
|
|
742
|
+
}
|
|
743
|
+
}
|
|
651
744
|
}
|
|
652
745
|
}
|
|
653
746
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'worker_threads';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import { ASTAnalyzer } from '../ast/ASTAnalyzer.js';
|
|
4
|
+
import { OxcAnalyzer } from '../ast/OxcAnalyzer.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Worker Thread Execution Script
|
|
8
|
+
* Handles parallel AST parsing for a chunk of files and returns serialized results.
|
|
9
|
+
*/
|
|
10
|
+
async function runTask() {
|
|
11
|
+
const { files, contextOptions } = workerData;
|
|
12
|
+
const results = [];
|
|
13
|
+
|
|
14
|
+
// Create a minimal context for analyzers
|
|
15
|
+
const mockContext = {
|
|
16
|
+
verbose: contextOptions.verbose,
|
|
17
|
+
projectGraph: new Map(),
|
|
18
|
+
getOrCreateNode: (path) => ({
|
|
19
|
+
filePath: path,
|
|
20
|
+
explicitImports: new Set(),
|
|
21
|
+
dynamicImports: new Set(),
|
|
22
|
+
importedSymbols: new Set(),
|
|
23
|
+
rawStringReferences: new Set(),
|
|
24
|
+
instantiatedIdentifiers: new Set(),
|
|
25
|
+
propertyAccessChains: new Set(),
|
|
26
|
+
internalExports: new Map(),
|
|
27
|
+
securityThreats: [],
|
|
28
|
+
localSuppressedRules: new Set(),
|
|
29
|
+
externalPackageUsage: new Set(),
|
|
30
|
+
symbolSourceLocations: new Map(),
|
|
31
|
+
jsxComponents: new Set(),
|
|
32
|
+
jsxProps: new Set(),
|
|
33
|
+
decorators: new Set(),
|
|
34
|
+
isFrameworkContract: false
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const astAnalyzer = new ASTAnalyzer(mockContext);
|
|
39
|
+
const oxcAnalyzer = new OxcAnalyzer(mockContext);
|
|
40
|
+
|
|
41
|
+
for (const filePath of files) {
|
|
42
|
+
try {
|
|
43
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
44
|
+
const node = mockContext.getOrCreateNode(filePath);
|
|
45
|
+
|
|
46
|
+
// Use OXC if available, fallback to TS AST
|
|
47
|
+
if (oxcAnalyzer.isAvailable) {
|
|
48
|
+
await oxcAnalyzer.parseFile(filePath, content, node);
|
|
49
|
+
} else {
|
|
50
|
+
await astAnalyzer.parseFile(filePath, content, node);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Serialize the node data for transfer back to main thread
|
|
54
|
+
results.push({
|
|
55
|
+
filePath: node.filePath,
|
|
56
|
+
explicitImports: Array.from(node.explicitImports),
|
|
57
|
+
dynamicImports: Array.from(node.dynamicImports),
|
|
58
|
+
importedSymbols: Array.from(node.importedSymbols),
|
|
59
|
+
rawStringReferences: Array.from(node.rawStringReferences),
|
|
60
|
+
instantiatedIdentifiers: Array.from(node.instantiatedIdentifiers),
|
|
61
|
+
propertyAccessChains: Array.from(node.propertyAccessChains),
|
|
62
|
+
internalExports: Object.fromEntries(node.internalExports),
|
|
63
|
+
securityThreats: node.securityThreats,
|
|
64
|
+
localSuppressedRules: Array.from(node.localSuppressedRules),
|
|
65
|
+
externalPackageUsage: Array.from(node.externalPackageUsage),
|
|
66
|
+
symbolSourceLocations: Object.fromEntries(node.symbolSourceLocations),
|
|
67
|
+
jsxComponents: Array.from(node.jsxComponents),
|
|
68
|
+
jsxProps: Array.from(node.jsxProps),
|
|
69
|
+
decorators: Array.from(node.decorators),
|
|
70
|
+
isFrameworkContract: node.isFrameworkContract
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (contextOptions.verbose) {
|
|
74
|
+
console.error(`[Worker] Failed to parse ${filePath}: ${err.message}`);
|
|
75
|
+
}
|
|
76
|
+
// Push null to keep array indices if needed, or just skip
|
|
77
|
+
results.push(null);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
parentPort.postMessage(results);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
runTask().catch(err => {
|
|
85
|
+
console.error(`[Worker Critical Fault] ${err.stack}`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* Backend Services Plugins for entkapp v4.
|
|
3
|
+
* Backend Services Plugins for entkapp v4.2.0
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Built-in support for GraphQL, REST APIs, and Databases.
|
|
6
6
|
*/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ============================================================================
|
|
3
|
-
* Modern Frameworks Plugins for entkapp v4.
|
|
3
|
+
* Modern Frameworks Plugins for entkapp v4.2.0
|
|
4
4
|
* ============================================================================
|
|
5
5
|
* Built-in support for React, Vue, Svelte, and Angular.
|
|
6
6
|
*/
|