entkapp 4.3.0 → 4.4.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/.scaffold-ignore +1 -1
- package/README.md +2 -15
- package/bin/cli.js +10 -7
- package/index.js +5 -5
- package/logo.png +0 -0
- package/package.json +3 -7
- package/src/EngineContext.js +23 -13
- package/src/analyzers/OxcAnalyzer.js +383 -0
- package/src/api/HeadlessAPI.js +1 -1
- package/src/api/PluginSDK.js +1 -1
- package/src/ast/ASTAnalyzer.js +6 -9
- package/src/ast/AdvancedAnalysis.js +156 -0
- package/src/ast/BarrelParser.js +19 -1
- package/src/ast/DeadCodeDetector.js +2 -2
- package/src/ast/OxcAnalyzer.js +50 -9
- package/src/ast/SecretScanner.js +8 -0
- package/src/index.js +165 -155
- package/src/performance/WorkerTaskRunner.js +7 -4
- package/src/plugins/ecosystems/BackendServices.js +1 -1
- package/src/plugins/ecosystems/ModernFrameworks.js +1 -1
- package/src/resolution/DepencyResolver.js +12 -0
- package/src/resolution/PathMapper.js +1 -1
- package/src/resolution/WorkspaceDiagnostic.js +59 -0
package/.scaffold-ignore
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# ============================================================================
|
|
2
|
-
# ⚙️
|
|
2
|
+
# ⚙️ loui Rule Suppression & Intent Profiles
|
|
3
3
|
# ============================================================================
|
|
4
4
|
# Define exact symbols, file patterns, or dependency keys that must remain
|
|
5
5
|
# preserved during dead-code scanning and transactional refactoring pruning.
|
package/README.md
CHANGED
|
@@ -28,19 +28,6 @@ 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 -D entkapp
|
|
32
32
|
# or
|
|
33
|
-
pnpm add -
|
|
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
|
-
```
|
|
33
|
+
pnpm add -D entkapp
|
package/bin/cli.js
CHANGED
|
@@ -28,15 +28,15 @@ 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.4.1');
|
|
32
32
|
|
|
33
33
|
program
|
|
34
34
|
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
35
35
|
.option('-d, --debug', 'Developer`s comprehensive telemetry debug diagnostics', false)
|
|
36
|
-
.option('--fix', 'Enable atomic code updates, structural file pruning, and active type sanitization',
|
|
37
|
-
.option('--no-fix', 'Disable direct file manipulation modifications (dry-run reporting mode)')
|
|
36
|
+
.option('--fix', 'Enable atomic code updates, structural file pruning, and active type sanitization', false)
|
|
38
37
|
.option('--tsconfig <filename>', 'Specify path to custom layout configurations', 'tsconfig.json')
|
|
39
38
|
.option('--test-command <command>', 'Integrated continuous safety test validation script execution path', 'npm test')
|
|
39
|
+
.option('--no-fix', 'Deprecated flag, this is now default')
|
|
40
40
|
.option('--workspace', 'Enable high-density workspace workspace/monorepo cluster mesh evaluation parsing', false)
|
|
41
41
|
.option('--verbose', 'Toggle expanded trace telemetry for debug operational diagnostics', false)
|
|
42
42
|
.option('-r, --run', 'Execute the primary operational pipeline loop', false)
|
|
@@ -45,7 +45,10 @@ async function bootstrap() {
|
|
|
45
45
|
|
|
46
46
|
program.parse(process.argv);
|
|
47
47
|
const options = program.opts();
|
|
48
|
-
|
|
48
|
+
if (args.includes('--no-fix')) {
|
|
49
|
+
console.warn("\n⚠️ [DEPRECATION] The --no-fix flag is deprecated. No-fix is now the default behavior.");
|
|
50
|
+
console.warn("👉 To apply structural changes, use the --fix flag instead.\n");
|
|
51
|
+
}
|
|
49
52
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
53
|
|
|
51
54
|
// --- Onboarding Check (Skipped in Non-Interactive Mode) ---
|
|
@@ -66,7 +69,7 @@ async function bootstrap() {
|
|
|
66
69
|
const answer = await rl.question(ansis.bold.yellow('❓ No "entkapp:run" script found in package.json. Install it? (y/n): '));
|
|
67
70
|
if (answer.toLowerCase() === 'y') {
|
|
68
71
|
pkgJson.scripts = pkgJson.scripts || {};
|
|
69
|
-
pkgJson.scripts['entkapp:run'] = 'npx entkapp
|
|
72
|
+
pkgJson.scripts['entkapp:run'] = 'npx entkapp';
|
|
70
73
|
await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
71
74
|
console.log(ansis.green('✅ "entkapp:run" script added to package.json.'));
|
|
72
75
|
}
|
|
@@ -90,7 +93,7 @@ async function bootstrap() {
|
|
|
90
93
|
enabledPlugins: ["nextjs", "nuxt", "remix", "sveltekit", "astro"]
|
|
91
94
|
};
|
|
92
95
|
await fs.writeFile(path.join(configDirPath, 'config.json'), JSON.stringify(defaultConfig, null, 2));
|
|
93
|
-
console.log(ansis.green('✅ "/entkapp" folder
|
|
96
|
+
console.log(ansis.green('✅ "/entkapp" folder and default config created.'));
|
|
94
97
|
configInstalled = true;
|
|
95
98
|
}
|
|
96
99
|
}
|
|
@@ -132,7 +135,7 @@ async function bootstrap() {
|
|
|
132
135
|
}, timeoutMs);
|
|
133
136
|
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
134
137
|
|
|
135
|
-
console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '4.
|
|
138
|
+
console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '4.4.1'} Engine Activation`));
|
|
136
139
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
137
140
|
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
|
|
138
141
|
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.1.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.1.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.1.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.1.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.1.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.4.1",
|
|
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,12 +64,7 @@
|
|
|
64
64
|
"unresolved",
|
|
65
65
|
"unused",
|
|
66
66
|
"unused-packages",
|
|
67
|
-
"workspace"
|
|
68
|
-
"entkapp",
|
|
69
|
-
"cut",
|
|
70
|
-
"kapp",
|
|
71
|
-
"dreamlong",
|
|
72
|
-
"DreamLong"
|
|
67
|
+
"workspace"
|
|
73
68
|
],
|
|
74
69
|
"author": "DreamLongYT",
|
|
75
70
|
"license": "Apache-2.0",
|
|
@@ -91,6 +86,7 @@
|
|
|
91
86
|
},
|
|
92
87
|
"devDependencies": {
|
|
93
88
|
"@types/node": "^25.9.3",
|
|
89
|
+
"knip": "^6.17.1",
|
|
94
90
|
"vitepress": "^1.6.4",
|
|
95
91
|
"vue": ">=3.5.0"
|
|
96
92
|
},
|
package/src/EngineContext.js
CHANGED
|
@@ -31,16 +31,12 @@ export class GraphNode {
|
|
|
31
31
|
this.localSuppressedRules = new Set();
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
isSymbolReferencedExternally(symbolName, projectGraph,
|
|
34
|
+
isSymbolReferencedExternally(symbolName, projectGraph, visited = new Set()) {
|
|
35
|
+
const visitKey = `${this.filePath}:${symbolName}`;
|
|
36
|
+
if (visited.has(visitKey)) return false;
|
|
37
|
+
visited.add(visitKey);
|
|
38
|
+
|
|
35
39
|
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
|
-
}
|
|
44
40
|
|
|
45
41
|
// --- NEW: Self-reference check (Fix for SecretSeverity bug) ---
|
|
46
42
|
// If the symbol is used within the same file, it is NOT dead.
|
|
@@ -80,6 +76,20 @@ export class GraphNode {
|
|
|
80
76
|
return true;
|
|
81
77
|
}
|
|
82
78
|
|
|
79
|
+
// NEW: Check for re-exports that might use this symbol (Recursive Barrel Resolution)
|
|
80
|
+
for (const [exportedName, exportMeta] of parentNode.internalExports.entries()) {
|
|
81
|
+
const isMatch = (exportMeta.type === 're-export' || exportMeta.type === 're-export-namespace') &&
|
|
82
|
+
(exportMeta.originalName === symbolName || exportMeta.originalName === '*') &&
|
|
83
|
+
(exportMeta.source === this.filePath || (exportMeta.source && path.resolve(path.dirname(parentPath), exportMeta.source) === this.filePath));
|
|
84
|
+
|
|
85
|
+
if (isMatch) {
|
|
86
|
+
// If this parent re-exports our symbol, check if the re-exported symbol is used
|
|
87
|
+
if (parentNode.isSymbolReferencedExternally(exportedName, projectGraph, visited)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
83
93
|
if (parentNode.instantiatedIdentifiers.has(symbolName)) return true;
|
|
84
94
|
|
|
85
95
|
for (const accessChain of parentNode.propertyAccessChains) {
|
|
@@ -145,14 +155,12 @@ export class EngineContext {
|
|
|
145
155
|
report.orphanedFiles.push(path.relative(this.cwd, filePath));
|
|
146
156
|
}
|
|
147
157
|
|
|
148
|
-
if (node.isEntry || node.isLibraryEntry) continue;
|
|
149
|
-
|
|
150
158
|
for (const [symbol, meta] of node.internalExports.entries()) {
|
|
151
159
|
if (symbol === '*' || symbol === 'default' || node.localSuppressedRules.has('unused-export') || node.localSuppressedRules.has(`unused-export:${symbol}`)) {
|
|
152
160
|
continue;
|
|
153
161
|
}
|
|
154
162
|
|
|
155
|
-
if (!node.isSymbolReferencedExternally(symbol, this.projectGraph
|
|
163
|
+
if (!node.isSymbolReferencedExternally(symbol, this.projectGraph)) {
|
|
156
164
|
const loc = node.symbolSourceLocations.get(symbol) || { line: 0, column: 0 };
|
|
157
165
|
report.deadExports.push({
|
|
158
166
|
symbol,
|
|
@@ -168,9 +176,11 @@ export class EngineContext {
|
|
|
168
176
|
for (const [manifestPath, deps] of this.manifestDependencies.entries()) {
|
|
169
177
|
const allDeps = [...(deps.dependencies || []), ...(deps.devDependencies || [])];
|
|
170
178
|
|
|
179
|
+
const hasTypeScriptFiles = Array.from(this.projectGraph.keys()).some(f => f.endsWith('.ts') || f.endsWith('.tsx'));
|
|
180
|
+
|
|
171
181
|
for (const dep of allDeps) {
|
|
172
182
|
// Skip @types packages and known safe packages
|
|
173
|
-
if (dep.startsWith('@types/') || dep === 'entkapp') {
|
|
183
|
+
if (dep.startsWith('@types/') || dep === 'entkapp' || (dep === 'typescript' && hasTypeScriptFiles)) {
|
|
174
184
|
continue;
|
|
175
185
|
}
|
|
176
186
|
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
export class OxcAnalyzer {
|
|
2
|
+
constructor(context) {
|
|
3
|
+
this.context = context;
|
|
4
|
+
this.oxc = null;
|
|
5
|
+
this.isAvailable = false;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async init() {
|
|
9
|
+
if (this.isAvailable) return true;
|
|
10
|
+
try {
|
|
11
|
+
const oxc = await import("oxc-parser");
|
|
12
|
+
this.oxc = oxc;
|
|
13
|
+
this.isAvailable = true;
|
|
14
|
+
return true;
|
|
15
|
+
} catch (e) {
|
|
16
|
+
try {
|
|
17
|
+
const { createRequire } = await import('module');
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
this.oxc = require("oxc-parser");
|
|
20
|
+
this.isAvailable = true;
|
|
21
|
+
return true;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
this.isAvailable = false;
|
|
24
|
+
if (this.context.verbose) {
|
|
25
|
+
console.warn(`[OxcAnalyzer] oxc-parser failed to load: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async parseFile(filePath, content, fileNode) {
|
|
33
|
+
if (!this.isAvailable) {
|
|
34
|
+
const initialized = await this.init();
|
|
35
|
+
if (!initialized) return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
// Fix: Ensure filePath is correctly handled for OXC (Windows path issue)
|
|
40
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
41
|
+
|
|
42
|
+
// Try passing the path directly as the second argument if the object-based options fail
|
|
43
|
+
// Some versions of oxc-parser expect a string as the second argument for the filename
|
|
44
|
+
let result;
|
|
45
|
+
try {
|
|
46
|
+
result = this.oxc.parseSync(content, {
|
|
47
|
+
sourceType: "module",
|
|
48
|
+
sourceFilename: normalizedPath,
|
|
49
|
+
lang: "typescript"
|
|
50
|
+
});
|
|
51
|
+
} catch (e) {
|
|
52
|
+
// Fallback for versions that expect the path as second argument
|
|
53
|
+
result = this.oxc.parseSync(content, normalizedPath);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Fix: Handle cases where OXC returns a JSON string instead of an object
|
|
57
|
+
// Stabilize result through JSON round-trip to fix N-API conversion issues on Windows
|
|
58
|
+
let parsedResult;
|
|
59
|
+
try {
|
|
60
|
+
parsedResult = typeof result === 'string' ? JSON.parse(result) : JSON.parse(JSON.stringify(result));
|
|
61
|
+
} catch (err) {
|
|
62
|
+
if (typeof result === 'object') {
|
|
63
|
+
parsedResult = result; // Last resort
|
|
64
|
+
} else {
|
|
65
|
+
throw new Error("OXC returned an invalid format");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let ast;
|
|
70
|
+
if (parsedResult && typeof parsedResult === 'object') {
|
|
71
|
+
if (parsedResult.program) {
|
|
72
|
+
ast = parsedResult;
|
|
73
|
+
} else if (parsedResult.ast) {
|
|
74
|
+
ast = { program: parsedResult.ast };
|
|
75
|
+
} else {
|
|
76
|
+
ast = { program: parsedResult };
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error("OXC returned an invalid AST format");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fileNode.ast = ast.program; // Store the AST for advanced analysis
|
|
83
|
+
fileNode.jsxComponents = new Set();
|
|
84
|
+
fileNode.jsxProps = new Set();
|
|
85
|
+
fileNode.decorators = new Set();
|
|
86
|
+
|
|
87
|
+
this.walkOxcAst(ast.program, fileNode, content);
|
|
88
|
+
|
|
89
|
+
const lines = content.split('\n');
|
|
90
|
+
const getLineCol = (pos) => {
|
|
91
|
+
let count = 0;
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
|
+
if (count + lines[i].length + 1 > pos) {
|
|
94
|
+
return { line: i + 1, column: pos - count + 1 };
|
|
95
|
+
}
|
|
96
|
+
count += lines[i].length + 1;
|
|
97
|
+
}
|
|
98
|
+
return { line: 1, column: 1 };
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
for (const [name, meta] of fileNode.internalExports.entries()) {
|
|
102
|
+
if (meta.start !== undefined) {
|
|
103
|
+
fileNode.symbolSourceLocations.set(name, getLineCol(meta.start));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return true;
|
|
108
|
+
} catch (e) {
|
|
109
|
+
if (this.context.verbose) {
|
|
110
|
+
console.warn(`[OXC] Failed to parse ${filePath}. Error: ${e.message}`);
|
|
111
|
+
if (e.stack) console.debug(e.stack);
|
|
112
|
+
console.info(`[OXC] Switching back to TypeScript Compiler API for ${filePath}`);
|
|
113
|
+
}
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
walkOxcAst(node, fileNode, content) {
|
|
119
|
+
if (!node) return;
|
|
120
|
+
|
|
121
|
+
switch (node.type) {
|
|
122
|
+
case "ImportDeclaration":
|
|
123
|
+
this.handleImportDeclaration(node, fileNode);
|
|
124
|
+
break;
|
|
125
|
+
case "ExportNamedDeclaration":
|
|
126
|
+
case "ExportDefaultDeclaration":
|
|
127
|
+
case "ExportAllDeclaration":
|
|
128
|
+
this.handleExportDeclaration(node, fileNode, content);
|
|
129
|
+
break;
|
|
130
|
+
case "CallExpression":
|
|
131
|
+
this.handleCallExpression(node, fileNode);
|
|
132
|
+
break;
|
|
133
|
+
case "JSXElement":
|
|
134
|
+
case "JSXFragment":
|
|
135
|
+
this.handleJsxElement(node, fileNode);
|
|
136
|
+
break;
|
|
137
|
+
case "Decorator":
|
|
138
|
+
this.handleDecorator(node, fileNode);
|
|
139
|
+
break;
|
|
140
|
+
case "StringLiteral":
|
|
141
|
+
fileNode.rawStringReferences.add(node.value);
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
for (const key in node) {
|
|
146
|
+
if (node[key] && typeof node[key] === "object") {
|
|
147
|
+
if (Array.isArray(node[key])) {
|
|
148
|
+
node[key].forEach((child) => this.walkOxcAst(child, fileNode, content));
|
|
149
|
+
} else {
|
|
150
|
+
this.walkOxcAst(node[key], fileNode, content);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
handleImportDeclaration(node, fileNode) {
|
|
157
|
+
const specifier = node.source.value;
|
|
158
|
+
fileNode.explicitImports.add(specifier);
|
|
159
|
+
|
|
160
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
161
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (node.specifiers) {
|
|
165
|
+
node.specifiers.forEach((spec) => {
|
|
166
|
+
if (spec.type === "ImportSpecifier") {
|
|
167
|
+
const importedName = spec.imported.name || (spec.imported.type === "Identifier" ? spec.imported.name : spec.imported.value);
|
|
168
|
+
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
169
|
+
} else if (spec.type === "ImportDefaultSpecifier") {
|
|
170
|
+
fileNode.importedSymbols.add(`${specifier}:default`);
|
|
171
|
+
} else if (spec.type === "ImportNamespaceSpecifier") {
|
|
172
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
handleExportDeclaration(node, fileNode, content) {
|
|
179
|
+
// 1. Default Exports
|
|
180
|
+
if (node.type === "ExportDefaultDeclaration") {
|
|
181
|
+
fileNode.internalExports.set("default", {
|
|
182
|
+
type: "default",
|
|
183
|
+
start: node.start,
|
|
184
|
+
end: node.end
|
|
185
|
+
});
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 2. Re-export All: export * from 'mod' or export * as ns from 'mod'
|
|
190
|
+
if (node.type === "ExportAllDeclaration") {
|
|
191
|
+
const sourceSpecifier = node.source.value;
|
|
192
|
+
fileNode.explicitImports.add(sourceSpecifier);
|
|
193
|
+
if (!sourceSpecifier.startsWith('.') && !sourceSpecifier.startsWith('/')) {
|
|
194
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(sourceSpecifier));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (node.exported) {
|
|
198
|
+
// export * as ns from 'mod'
|
|
199
|
+
const name = node.exported.name || (node.exported.type === "Identifier" ? node.exported.name : null);
|
|
200
|
+
if (name) {
|
|
201
|
+
fileNode.internalExports.set(name, {
|
|
202
|
+
type: "re-export-namespace",
|
|
203
|
+
source: sourceSpecifier,
|
|
204
|
+
originalName: "*",
|
|
205
|
+
start: node.start,
|
|
206
|
+
end: node.end
|
|
207
|
+
});
|
|
208
|
+
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
// export * from 'mod'
|
|
212
|
+
fileNode.internalExports.set("*", {
|
|
213
|
+
type: "re-export-all",
|
|
214
|
+
source: sourceSpecifier
|
|
215
|
+
});
|
|
216
|
+
fileNode.importedSymbols.add(`${sourceSpecifier}:*`);
|
|
217
|
+
}
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 3. Named Exports & Re-exports with specifiers
|
|
222
|
+
if (node.source) {
|
|
223
|
+
// Re-export: export { x } from 'mod'
|
|
224
|
+
const specifier = node.source.value;
|
|
225
|
+
fileNode.explicitImports.add(specifier);
|
|
226
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
227
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (node.specifiers) {
|
|
231
|
+
node.specifiers.forEach((spec) => {
|
|
232
|
+
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
233
|
+
const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
|
|
234
|
+
fileNode.internalExports.set(exportedName, {
|
|
235
|
+
type: "re-export",
|
|
236
|
+
source: specifier,
|
|
237
|
+
originalName: localName,
|
|
238
|
+
start: spec.start,
|
|
239
|
+
end: spec.end,
|
|
240
|
+
});
|
|
241
|
+
fileNode.importedSymbols.add(`${specifier}:${localName}`);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
} else if (node.declaration) {
|
|
245
|
+
// Direct declaration export: export const x = 1, export function f() {}
|
|
246
|
+
const decl = node.declaration;
|
|
247
|
+
if (decl.type === "VariableDeclaration") {
|
|
248
|
+
decl.declarations.forEach((d) => {
|
|
249
|
+
this._extractNamesFromPattern(d.id, (name) => {
|
|
250
|
+
fileNode.internalExports.set(name, {
|
|
251
|
+
type: "variable",
|
|
252
|
+
start: d.start,
|
|
253
|
+
end: d.end
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
} else if (decl.id && decl.id.name) {
|
|
258
|
+
let type = "unknown";
|
|
259
|
+
if (decl.type === "FunctionDeclaration") type = "function";
|
|
260
|
+
else if (decl.type === "ClassDeclaration") type = "class";
|
|
261
|
+
else if (decl.type === "TSEnumDeclaration") type = "enum";
|
|
262
|
+
else if (decl.type === "TSInterfaceDeclaration") type = "interface";
|
|
263
|
+
else if (decl.type === "TSTypeAliasDeclaration") type = "type";
|
|
264
|
+
else if (decl.type === "TSModuleDeclaration") type = "namespace";
|
|
265
|
+
|
|
266
|
+
fileNode.internalExports.set(decl.id.name, {
|
|
267
|
+
type,
|
|
268
|
+
start: decl.start,
|
|
269
|
+
end: decl.end
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
} else if (node.specifiers) {
|
|
273
|
+
// Export existing locals: export { x, y as z }
|
|
274
|
+
node.specifiers.forEach((spec) => {
|
|
275
|
+
const exportedName = spec.exported.name || (spec.exported.type === "Identifier" ? spec.exported.name : spec.exported.value);
|
|
276
|
+
const localName = spec.local.name || (spec.local.type === "Identifier" ? spec.local.name : spec.local.value);
|
|
277
|
+
fileNode.internalExports.set(exportedName, {
|
|
278
|
+
type: "export",
|
|
279
|
+
originalName: localName,
|
|
280
|
+
start: spec.start,
|
|
281
|
+
end: spec.end,
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
_extractNamesFromPattern(node, callback) {
|
|
288
|
+
if (!node) return;
|
|
289
|
+
if (node.type === "Identifier") {
|
|
290
|
+
callback(node.name);
|
|
291
|
+
} else if (node.type === "ObjectPattern") {
|
|
292
|
+
node.properties.forEach(p => {
|
|
293
|
+
if (p.type === "Property") {
|
|
294
|
+
this._extractNamesFromPattern(p.value, callback);
|
|
295
|
+
} else if (p.type === "RestElement") {
|
|
296
|
+
this._extractNamesFromPattern(p.argument, callback);
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
} else if (node.type === "ArrayPattern") {
|
|
300
|
+
node.elements.forEach(e => {
|
|
301
|
+
if (e) this._extractNamesFromPattern(e, callback);
|
|
302
|
+
});
|
|
303
|
+
} else if (node.type === "AssignmentPattern") {
|
|
304
|
+
this._extractNamesFromPattern(node.left, callback);
|
|
305
|
+
} else if (node.type === "RestElement") {
|
|
306
|
+
this._extractNamesFromPattern(node.argument, callback);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
handleCallExpression(node, fileNode) {
|
|
311
|
+
// Dynamic import(): import('./module')
|
|
312
|
+
if (node.callee.type === "Import" && node.arguments.length > 0) {
|
|
313
|
+
const arg = node.arguments[0];
|
|
314
|
+
if (arg.type === "StringLiteral") {
|
|
315
|
+
const specifier = arg.value;
|
|
316
|
+
fileNode.explicitImports.add(specifier);
|
|
317
|
+
fileNode.dynamicImports.add(specifier);
|
|
318
|
+
fileNode.importedSymbols.add(`${specifier}:*`); // Dynamic import usually consumes the whole namespace
|
|
319
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
320
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
if (fileNode.calculatedDynamicImports) {
|
|
324
|
+
fileNode.calculatedDynamicImports.push({ kind: arg.type, start: arg.start });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} else if (node.callee.type === "Identifier" && node.callee.name === "require" && node.arguments.length > 0 && node.arguments[0].type === "StringLiteral") {
|
|
328
|
+
const specifier = node.arguments[0].value;
|
|
329
|
+
fileNode.explicitImports.add(specifier);
|
|
330
|
+
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
331
|
+
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
332
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
handleJsxElement(node, fileNode) {
|
|
338
|
+
const getElementName = (nameNode) => {
|
|
339
|
+
if (nameNode.type === "JSXIdentifier") return nameNode.name;
|
|
340
|
+
if (nameNode.type === "JSXMemberExpression") return `${getElementName(nameNode.object)}.${nameNode.property.name}`;
|
|
341
|
+
if (nameNode.type === "JSXNamespacedName") return `${nameNode.namespace.name}:${nameNode.name.name}`;
|
|
342
|
+
return "unknown";
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (node.openingElement) {
|
|
346
|
+
const tagName = getElementName(node.openingElement.name);
|
|
347
|
+
fileNode.jsxComponents.add(tagName);
|
|
348
|
+
|
|
349
|
+
if (node.openingElement.attributes) {
|
|
350
|
+
node.openingElement.attributes.forEach(attr => {
|
|
351
|
+
if (attr.type === "JSXAttribute" && attr.name.type === "JSXIdentifier") {
|
|
352
|
+
fileNode.jsxProps.add(`${tagName}:${attr.name.name}`);
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
handleDecorator(node, fileNode) {
|
|
360
|
+
const getDecoratorName = (expr) => {
|
|
361
|
+
if (expr.type === "Identifier") return expr.name;
|
|
362
|
+
if (expr.type === "CallExpression") return getDecoratorName(expr.callee);
|
|
363
|
+
if (expr.type === "MemberExpression") {
|
|
364
|
+
const prop = expr.property.name || expr.property.value;
|
|
365
|
+
return prop || "unknown";
|
|
366
|
+
}
|
|
367
|
+
return "unknown";
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const decoratorName = getDecoratorName(node.expression);
|
|
371
|
+
if (decoratorName !== "unknown") {
|
|
372
|
+
fileNode.decorators.add(decoratorName);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
_extractPackageName(specifier) {
|
|
377
|
+
if (specifier.startsWith('@')) {
|
|
378
|
+
const parts = specifier.split('/');
|
|
379
|
+
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : specifier;
|
|
380
|
+
}
|
|
381
|
+
return specifier.split('/')[0];
|
|
382
|
+
}
|
|
383
|
+
}
|
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.1.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.0.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,7 +1,6 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
-
import ansis from 'ansis';
|
|
5
4
|
|
|
6
5
|
export class ASTAnalyzer {
|
|
7
6
|
constructor(context) {
|
|
@@ -36,6 +35,7 @@ export class ASTAnalyzer {
|
|
|
36
35
|
this.scopeStack.push(this.currentScope);
|
|
37
36
|
|
|
38
37
|
this.extractTopLevelJSDocSuppreessions(sourceFile, fileNode);
|
|
38
|
+
fileNode.ast = sourceFile; // Store the AST for advanced analysis
|
|
39
39
|
this.walkAST(sourceFile, fileNode, sourceFile);
|
|
40
40
|
|
|
41
41
|
this.scopeStack.pop(); // Pop the global scope
|
|
@@ -399,14 +399,6 @@ export class ASTAnalyzer {
|
|
|
399
399
|
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
|
|
400
400
|
const arg = node.arguments[0];
|
|
401
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
|
-
|
|
410
402
|
if (ts.isStringLiteral(arg)) {
|
|
411
403
|
fileNode.explicitImports.add(arg.text);
|
|
412
404
|
fileNode.dynamicImports.add(arg.text);
|
|
@@ -414,6 +406,11 @@ export class ASTAnalyzer {
|
|
|
414
406
|
if (!arg.text.startsWith('.') && !arg.text.startsWith('/')) {
|
|
415
407
|
fileNode.externalPackageUsage.add(this._extractPackageName(arg.text));
|
|
416
408
|
}
|
|
409
|
+
} else {
|
|
410
|
+
// Dynamic import with a non-literal expression (e.g., variable or template literal).
|
|
411
|
+
if (fileNode.calculatedDynamicImports) {
|
|
412
|
+
fileNode.calculatedDynamicImports.push({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
413
|
+
}
|
|
417
414
|
}
|
|
418
415
|
}
|
|
419
416
|
} else if (ts.isIdentifier(node.expression) && node.expression.text === 'require') {
|