entkapp 5.5.0 → 5.6.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 +1 -1
- package/bin/cli.js +2 -2
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.mjs +428 -0
- package/src/Initializer.mjs +82 -0
- package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
- package/src/analyzers/OxcAnalyzer.mjs +11 -0
- package/src/api/HeadlessAPI.js +31 -16
- package/src/api/HeadlessAPI.mjs +369 -0
- package/src/api/PluginSDK.mjs +135 -0
- package/src/ast/ASTAnalyzer.js +17 -3
- package/src/ast/ASTAnalyzer.mjs +742 -0
- package/src/ast/AdvancedAnalysis.mjs +586 -0
- package/src/ast/BarrelParser.mjs +230 -0
- package/src/ast/DeadCodeDetector.js +7 -5
- package/src/ast/DeadCodeDetector.mjs +92 -0
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.mjs +188 -0
- package/src/ast/SecretScanner.mjs +374 -0
- package/src/healing/GitSandbox.mjs +82 -0
- package/src/healing/SelfHealer.mjs +48 -0
- package/src/index.js +37 -1
- package/src/index.mjs +1176 -0
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.mjs +203 -0
- package/src/plugins/ecosystems/BackendServices.mjs +197 -0
- package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
- package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
- package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
- package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
- package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
- package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
- package/src/refractor/ImpactAnalyzer.mjs +92 -0
- package/src/refractor/SourceRewriter.mjs +86 -0
- package/src/refractor/TransactionManager.mjs +5 -0
- package/src/refractor/TypeIntegrity.mjs +4 -0
- package/src/resolution/BuildOrchestrator.mjs +46 -0
- package/src/resolution/CircularDetector.mjs +91 -0
- package/src/resolution/ConfigGenerator.mjs +83 -0
- package/src/resolution/ConfigLoader.mjs +94 -0
- package/src/resolution/DepencyResolver.mjs +66 -0
- package/src/resolution/DependencyFixer.mjs +88 -0
- package/src/resolution/DependencyProfiler.mjs +286 -0
- package/src/resolution/EntryPointDetector.mjs +134 -0
- package/src/resolution/FrameworkConfigParser.mjs +119 -0
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
package/src/api/HeadlessAPI.js
CHANGED
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
import EventEmitter from 'events';
|
|
16
16
|
import path from 'path';
|
|
17
|
-
|
|
17
|
+
import fs from 'fs/promises';
|
|
18
|
+
import { RefactoringEngine } from '../index.js';
|
|
18
19
|
|
|
19
20
|
export class HeadlessAPI extends EventEmitter {
|
|
20
21
|
constructor(options = {}) {
|
|
@@ -105,16 +106,7 @@ export class HeadlessAPI extends EventEmitter {
|
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
//
|
|
109
|
-
if (sourceCodeFilesList.length > 0) {
|
|
110
|
-
try {
|
|
111
|
-
this.engine.analyzer.initProgram(sourceCodeFilesList);
|
|
112
|
-
} catch (e) {
|
|
113
|
-
if (this.engine.context.verbose) {
|
|
114
|
-
console.warn('Warning: Failed to initialize TypeScript program:', e.message);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
109
|
+
// ASTAnalyzer doesn't need initProgram in this version
|
|
118
110
|
|
|
119
111
|
// Parallel processing
|
|
120
112
|
let parallelParseCompleted = false;
|
|
@@ -139,7 +131,9 @@ export class HeadlessAPI extends EventEmitter {
|
|
|
139
131
|
this.engine.hydrateNodeFromCache(node, cacheManifest[filePath]);
|
|
140
132
|
} else if (!parallelParseCompleted) {
|
|
141
133
|
this.engine.context.metrics.cacheMisses++;
|
|
142
|
-
await
|
|
134
|
+
const fs = await import('fs/promises');
|
|
135
|
+
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
136
|
+
await this.engine.analyzer.parseFile(filePath, fileContent, node);
|
|
143
137
|
}
|
|
144
138
|
|
|
145
139
|
this.engine.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
|
|
@@ -161,7 +155,7 @@ export class HeadlessAPI extends EventEmitter {
|
|
|
161
155
|
this.emit('analysis:graph-linking-complete');
|
|
162
156
|
|
|
163
157
|
// Generate summary
|
|
164
|
-
this.analysisResults = this.engine.context.generateSummaryReport();
|
|
158
|
+
this.analysisResults = await this.engine.context.generateSummaryReport();
|
|
165
159
|
this.emit('analysis:complete', this.analysisResults);
|
|
166
160
|
|
|
167
161
|
this.isRunning = false;
|
|
@@ -310,9 +304,30 @@ export class HeadlessAPI extends EventEmitter {
|
|
|
310
304
|
for (const dep of depsToRemove) {
|
|
311
305
|
try {
|
|
312
306
|
const absPath = path.resolve(this.engine.context.cwd, dep.manifest);
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
307
|
+
|
|
308
|
+
const pkgContent = await fs.readFile(absPath, 'utf8');
|
|
309
|
+
const pkg = JSON.parse(pkgContent);
|
|
310
|
+
|
|
311
|
+
let removed = false;
|
|
312
|
+
if (pkg.dependencies && pkg.dependencies[dep.package]) {
|
|
313
|
+
delete pkg.dependencies[dep.package];
|
|
314
|
+
removed = true;
|
|
315
|
+
} else if (pkg.devDependencies && pkg.devDependencies[dep.package]) {
|
|
316
|
+
delete pkg.devDependencies[dep.package];
|
|
317
|
+
removed = true;
|
|
318
|
+
} else if (pkg.peerDependencies && pkg.peerDependencies[dep.package]) {
|
|
319
|
+
delete pkg.peerDependencies[dep.package];
|
|
320
|
+
removed = true;
|
|
321
|
+
} else if (pkg.optionalDependencies && pkg.optionalDependencies[dep.package]) {
|
|
322
|
+
delete pkg.optionalDependencies[dep.package];
|
|
323
|
+
removed = true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (removed) {
|
|
327
|
+
await this.engine.txManager.stageWrite(absPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
328
|
+
refactoringResults.dependenciesRemoved.push(dep);
|
|
329
|
+
this.emit('refactoring:dependency-removed', dep);
|
|
330
|
+
}
|
|
316
331
|
} catch (error) {
|
|
317
332
|
refactoringResults.errors.push({
|
|
318
333
|
type: 'dependency-removal',
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Headless API for entkapp v4.1.0
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Provides a programmatic interface for integrating entkapp into
|
|
6
|
+
* custom workflows, CI/CD pipelines, and third-party tools.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Full control over analysis and refactoring operations
|
|
10
|
+
* - Event-driven architecture for real-time feedback
|
|
11
|
+
* - Streaming results for large codebases
|
|
12
|
+
* - Plugin SDK integration
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import EventEmitter from 'events';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
import { RefactoringEngine } from '../index.mjs';
|
|
18
|
+
|
|
19
|
+
export class HeadlessAPI extends EventEmitter {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.engine = null;
|
|
24
|
+
this.analysisResults = null;
|
|
25
|
+
this.isRunning = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Initialize the API with a project context
|
|
30
|
+
* @param {string} projectRoot - Root directory of the project
|
|
31
|
+
* @param {Object} config - Configuration options
|
|
32
|
+
* @returns {Promise<void>}
|
|
33
|
+
*/
|
|
34
|
+
async initialize(projectRoot, config = {}) {
|
|
35
|
+
try {
|
|
36
|
+
this.emit('initialize:start', { projectRoot });
|
|
37
|
+
|
|
38
|
+
const engineOptions = {
|
|
39
|
+
cwd: projectRoot,
|
|
40
|
+
autoFix: config.autoFix !== false,
|
|
41
|
+
skipConfirm: config.skipConfirm || false,
|
|
42
|
+
verbose: config.verbose || false,
|
|
43
|
+
...config
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.engine = new RefactoringEngine(engineOptions);
|
|
47
|
+
|
|
48
|
+
this.emit('initialize:complete', {
|
|
49
|
+
projectRoot,
|
|
50
|
+
config: this.engine.context
|
|
51
|
+
});
|
|
52
|
+
} catch (error) {
|
|
53
|
+
this.emit('initialize:error', { error });
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Analyze the codebase without making changes
|
|
60
|
+
* @returns {Promise<Object>} Analysis results
|
|
61
|
+
*/
|
|
62
|
+
async analyze() {
|
|
63
|
+
if (!this.engine) {
|
|
64
|
+
throw new Error('API not initialized. Call initialize() first.');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
this.isRunning = true;
|
|
69
|
+
this.emit('analysis:start');
|
|
70
|
+
|
|
71
|
+
// Initialize path mappings and workspace graph
|
|
72
|
+
await this.engine.pathMapper.loadMappings(this.engine.context.tsconfigFilename);
|
|
73
|
+
|
|
74
|
+
if (this.engine.context.isWorkspaceEnabled) {
|
|
75
|
+
this.emit('analysis:workspace-mapping-start');
|
|
76
|
+
await this.engine.workspaceGraph.initializeWorkspaceMesh();
|
|
77
|
+
this.emit('analysis:workspace-mapping-complete');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Load cache manifest
|
|
81
|
+
const cacheManifest = await this.engine.cacheManager.loadCacheManifest();
|
|
82
|
+
|
|
83
|
+
// Discover source files
|
|
84
|
+
this.emit('analysis:file-discovery-start');
|
|
85
|
+
const fileList = [];
|
|
86
|
+
await this.engine.discoverSourceFiles(this.engine.context.cwd, fileList);
|
|
87
|
+
this.engine.context.metrics.totalFilesScanned = fileList.length;
|
|
88
|
+
this.emit('analysis:file-discovery-complete', { fileCount: fileList.length });
|
|
89
|
+
|
|
90
|
+
// Identify framework ecosystems
|
|
91
|
+
this.emit('analysis:framework-detection-start');
|
|
92
|
+
const activeFrameworkEcosystems = await this.engine.magicDetector.identifyActiveProjectEcosystems(
|
|
93
|
+
this.engine.context.cwd
|
|
94
|
+
);
|
|
95
|
+
this.emit('analysis:framework-detection-complete', { ecosystems: activeFrameworkEcosystems });
|
|
96
|
+
|
|
97
|
+
// Process files
|
|
98
|
+
this.emit('analysis:file-processing-start');
|
|
99
|
+
const sourceCodeFilesList = [];
|
|
100
|
+
for (const file of fileList) {
|
|
101
|
+
if (file.endsWith('package.json')) {
|
|
102
|
+
await this.engine.auditManifestSupplyChain(file);
|
|
103
|
+
} else {
|
|
104
|
+
sourceCodeFilesList.push(file);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ASTAnalyzer doesn't need initProgram in this version
|
|
109
|
+
|
|
110
|
+
// Parallel processing
|
|
111
|
+
let parallelParseCompleted = false;
|
|
112
|
+
if (sourceCodeFilesList.length > 10) {
|
|
113
|
+
parallelParseCompleted = await this.engine.workerPool.parallelAnalyzeCodebase(
|
|
114
|
+
sourceCodeFilesList,
|
|
115
|
+
this.engine
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Sequential processing
|
|
120
|
+
for (let i = 0; i < sourceCodeFilesList.length; i++) {
|
|
121
|
+
const filePath = sourceCodeFilesList[i];
|
|
122
|
+
const node = this.engine.context.getOrCreateNode(filePath);
|
|
123
|
+
const currentHash = await this.engine.cacheManager.computeHash(filePath);
|
|
124
|
+
node.contentHash = currentHash;
|
|
125
|
+
|
|
126
|
+
const isFileCached = cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash;
|
|
127
|
+
|
|
128
|
+
if (isFileCached) {
|
|
129
|
+
this.engine.context.metrics.cacheHits++;
|
|
130
|
+
this.engine.hydrateNodeFromCache(node, cacheManifest[filePath]);
|
|
131
|
+
} else if (!parallelParseCompleted) {
|
|
132
|
+
this.engine.context.metrics.cacheMisses++;
|
|
133
|
+
const fs = await import('fs/promises');
|
|
134
|
+
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
135
|
+
await this.engine.analyzer.parseFile(filePath, fileContent, node);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.engine.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
|
|
139
|
+
node.externalPackageUsage.forEach(pkg => this.engine.context.usedExternalPackages.add(pkg));
|
|
140
|
+
|
|
141
|
+
// Emit progress
|
|
142
|
+
if ((i + 1) % Math.ceil(sourceCodeFilesList.length / 10) === 0) {
|
|
143
|
+
this.emit('analysis:file-processing-progress', {
|
|
144
|
+
processed: i + 1,
|
|
145
|
+
total: sourceCodeFilesList.length,
|
|
146
|
+
percentage: Math.round(((i + 1) / sourceCodeFilesList.length) * 100)
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Link dependency graph
|
|
152
|
+
this.emit('analysis:graph-linking-start');
|
|
153
|
+
await this.engine.linkDependencyGraph();
|
|
154
|
+
this.emit('analysis:graph-linking-complete');
|
|
155
|
+
|
|
156
|
+
// Generate summary
|
|
157
|
+
this.analysisResults = await this.engine.context.generateSummaryReport();
|
|
158
|
+
this.emit('analysis:complete', this.analysisResults);
|
|
159
|
+
|
|
160
|
+
this.isRunning = false;
|
|
161
|
+
return this.analysisResults;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
this.isRunning = false;
|
|
164
|
+
this.emit('analysis:error', { error });
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get detailed impact analysis for a specific file or export
|
|
171
|
+
* @param {string} filePath - Path to the file
|
|
172
|
+
* @param {string} symbol - Optional: specific export symbol to analyze
|
|
173
|
+
* @returns {Promise<Object>} Impact analysis results
|
|
174
|
+
*/
|
|
175
|
+
async getImpactAnalysis(filePath, symbol = null) {
|
|
176
|
+
if (!this.engine) {
|
|
177
|
+
throw new Error('API not initialized. Call initialize() first.');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
const node = this.engine.context.projectGraph.get(filePath);
|
|
182
|
+
if (!node) {
|
|
183
|
+
throw new Error(`File not found in analysis graph: ${filePath}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const impact = {
|
|
187
|
+
file: filePath,
|
|
188
|
+
symbol,
|
|
189
|
+
directDependents: Array.from(node.incomingEdges),
|
|
190
|
+
dependencies: Array.from(node.outgoingEdges),
|
|
191
|
+
internalExports: symbol
|
|
192
|
+
? [symbol]
|
|
193
|
+
: Array.from(node.internalExports.keys()),
|
|
194
|
+
externalPackages: Array.from(node.externalPackageUsage)
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
if (symbol) {
|
|
198
|
+
const safety = await this.engine.impactAnalyzer.verifyRefactorSafety(
|
|
199
|
+
filePath,
|
|
200
|
+
symbol,
|
|
201
|
+
this.engine.context.projectGraph
|
|
202
|
+
);
|
|
203
|
+
impact.refactorSafety = safety;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return impact;
|
|
207
|
+
} catch (error) {
|
|
208
|
+
this.emit('impact-analysis:error', { error, filePath, symbol });
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Apply refactoring changes with automatic rollback on test failure
|
|
215
|
+
* @param {Object} changes - Changes to apply
|
|
216
|
+
* @returns {Promise<Object>} Refactoring results
|
|
217
|
+
*/
|
|
218
|
+
async applyRefactoring(changes = {}) {
|
|
219
|
+
if (!this.engine) {
|
|
220
|
+
throw new Error('API not initialized. Call initialize() first.');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!this.analysisResults) {
|
|
224
|
+
throw new Error('No analysis results available. Call analyze() first.');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
this.isRunning = true;
|
|
229
|
+
this.emit('refactoring:start', changes);
|
|
230
|
+
|
|
231
|
+
const refactoringResults = {
|
|
232
|
+
filesDeleted: [],
|
|
233
|
+
exportsRemoved: [],
|
|
234
|
+
dependenciesRemoved: [],
|
|
235
|
+
errors: []
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
await this.engine.selfHealer.runSelfHealingLifecycle(async () => {
|
|
239
|
+
// Delete dead files
|
|
240
|
+
const filesToDelete = changes.deleteDeadFiles !== false
|
|
241
|
+
? this.analysisResults.orphanedFiles
|
|
242
|
+
: [];
|
|
243
|
+
|
|
244
|
+
for (const relPath of filesToDelete) {
|
|
245
|
+
try {
|
|
246
|
+
const absPath = path.resolve(this.engine.context.cwd, relPath);
|
|
247
|
+
await this.engine.txManager.stageDeletion(absPath);
|
|
248
|
+
refactoringResults.filesDeleted.push(relPath);
|
|
249
|
+
this.emit('refactoring:file-deleted', { file: relPath });
|
|
250
|
+
} catch (error) {
|
|
251
|
+
refactoringResults.errors.push({ type: 'file-deletion', file: relPath, error: error.message });
|
|
252
|
+
this.emit('refactoring:error', { type: 'file-deletion', file: relPath, error });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Remove unused exports
|
|
257
|
+
const exportsToRemove = changes.removeUnusedExports !== false
|
|
258
|
+
? this.analysisResults.deadExports
|
|
259
|
+
: [];
|
|
260
|
+
|
|
261
|
+
for (const unusedExport of exportsToRemove) {
|
|
262
|
+
try {
|
|
263
|
+
const absPath = path.resolve(this.engine.context.cwd, unusedExport.file);
|
|
264
|
+
const node = this.engine.context.projectGraph.get(absPath);
|
|
265
|
+
|
|
266
|
+
if (node) {
|
|
267
|
+
const safety = await this.engine.impactAnalyzer.verifyRefactorSafety(
|
|
268
|
+
absPath,
|
|
269
|
+
unusedExport.symbol,
|
|
270
|
+
this.engine.context.projectGraph
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (safety.isSafeToPrune) {
|
|
274
|
+
const nextText = await this.engine.sourceRewriter.stripNamedExportSignature(
|
|
275
|
+
absPath,
|
|
276
|
+
unusedExport.symbol,
|
|
277
|
+
node.internalExports.get(unusedExport.symbol)
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
await this.engine.txManager.stageWrite(absPath, nextText);
|
|
281
|
+
await this.engine.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
|
|
282
|
+
|
|
283
|
+
refactoringResults.exportsRemoved.push(unusedExport);
|
|
284
|
+
this.emit('refactoring:export-removed', unusedExport);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
refactoringResults.errors.push({
|
|
289
|
+
type: 'export-removal',
|
|
290
|
+
export: unusedExport.symbol,
|
|
291
|
+
file: unusedExport.file,
|
|
292
|
+
error: error.message
|
|
293
|
+
});
|
|
294
|
+
this.emit('refactoring:error', { type: 'export-removal', export: unusedExport, error });
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Remove unused dependencies
|
|
299
|
+
const depsToRemove = changes.removeUnusedDependencies !== false
|
|
300
|
+
? this.analysisResults.unusedDependencies
|
|
301
|
+
: [];
|
|
302
|
+
|
|
303
|
+
for (const dep of depsToRemove) {
|
|
304
|
+
try {
|
|
305
|
+
const absPath = path.resolve(this.engine.context.cwd, dep.manifest);
|
|
306
|
+
// TODO: Implement package.json dependency removal
|
|
307
|
+
refactoringResults.dependenciesRemoved.push(dep);
|
|
308
|
+
this.emit('refactoring:dependency-removed', dep);
|
|
309
|
+
} catch (error) {
|
|
310
|
+
refactoringResults.errors.push({
|
|
311
|
+
type: 'dependency-removal',
|
|
312
|
+
package: dep.package,
|
|
313
|
+
error: error.message
|
|
314
|
+
});
|
|
315
|
+
this.emit('refactoring:error', { type: 'dependency-removal', package: dep.package, error });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
this.isRunning = false;
|
|
321
|
+
this.emit('refactoring:complete', refactoringResults);
|
|
322
|
+
return refactoringResults;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
this.isRunning = false;
|
|
325
|
+
this.emit('refactoring:error', { error });
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get current analysis metrics
|
|
332
|
+
* @returns {Object} Metrics
|
|
333
|
+
*/
|
|
334
|
+
getMetrics() {
|
|
335
|
+
if (!this.engine) {
|
|
336
|
+
throw new Error('API not initialized. Call initialize() first.');
|
|
337
|
+
}
|
|
338
|
+
return this.engine.context.metrics;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Get all registered plugins
|
|
343
|
+
* @returns {Array} Plugin instances
|
|
344
|
+
*/
|
|
345
|
+
getPlugins() {
|
|
346
|
+
if (!this.engine) {
|
|
347
|
+
throw new Error('API not initialized. Call initialize() first.');
|
|
348
|
+
}
|
|
349
|
+
return this.engine.context.pluginRegistry?.getPlugins() || [];
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Get analysis results
|
|
354
|
+
* @returns {Object} Analysis results
|
|
355
|
+
*/
|
|
356
|
+
getAnalysisResults() {
|
|
357
|
+
return this.analysisResults;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Check if API is currently running
|
|
362
|
+
* @returns {boolean}
|
|
363
|
+
*/
|
|
364
|
+
isProcessing() {
|
|
365
|
+
return this.isRunning;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export default HeadlessAPI;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Plugin SDK for entkapp v4.7.0 (Enterprise Edition)
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Provides utilities and helpers for developing custom plugins that extend
|
|
6
|
+
* entkapp's analysis and healing capabilities.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { BasePlugin } from '../plugins/BasePlugin.mjs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extended plugin base class with SDK utilities
|
|
13
|
+
*/
|
|
14
|
+
export class PluginSDKBase extends BasePlugin {
|
|
15
|
+
constructor(context) {
|
|
16
|
+
super(context);
|
|
17
|
+
this.hooks = new Map();
|
|
18
|
+
this.transformers = [];
|
|
19
|
+
this.validators = [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Register a hook for a specific lifecycle event
|
|
24
|
+
*/
|
|
25
|
+
registerHook(eventName, handler) {
|
|
26
|
+
if (!this.hooks.has(eventName)) {
|
|
27
|
+
this.hooks.set(eventName, []);
|
|
28
|
+
}
|
|
29
|
+
this.hooks.get(eventName).push(handler);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Emit a hook event
|
|
34
|
+
*/
|
|
35
|
+
async emitHook(eventName, data) {
|
|
36
|
+
const handlers = this.hooks.get(eventName) || [];
|
|
37
|
+
for (const handler of handlers) {
|
|
38
|
+
await handler(data);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Register a code transformer
|
|
44
|
+
*/
|
|
45
|
+
registerTransformer(transformer) {
|
|
46
|
+
this.transformers.push(transformer);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Register a validator
|
|
51
|
+
*/
|
|
52
|
+
registerValidator(validator) {
|
|
53
|
+
this.validators.push(validator);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Enterprise: Register a Call Graph visitor
|
|
58
|
+
*/
|
|
59
|
+
registerCallGraphVisitor(visitor) {
|
|
60
|
+
this.context.callGraphVisitors = this.context.callGraphVisitors || [];
|
|
61
|
+
this.context.callGraphVisitors.push(visitor);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* SDK utilities for plugin development
|
|
67
|
+
*/
|
|
68
|
+
export class PluginSDK {
|
|
69
|
+
/**
|
|
70
|
+
* Create a custom plugin class
|
|
71
|
+
*/
|
|
72
|
+
static createPlugin(config) {
|
|
73
|
+
return class CustomPlugin extends PluginSDKBase {
|
|
74
|
+
get name() {
|
|
75
|
+
return config.name;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getConfigFiles() {
|
|
79
|
+
return config.configFiles || [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
getRoutePatterns() {
|
|
83
|
+
return config.routePatterns || [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getRequiredSystemContracts() {
|
|
87
|
+
return config.requiredContracts || ['default'];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async isActive(baseDir) {
|
|
91
|
+
if (config.isActive) {
|
|
92
|
+
return await config.isActive(baseDir);
|
|
93
|
+
}
|
|
94
|
+
return super.isActive(baseDir);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async initialize() {
|
|
98
|
+
if (config.initialize) {
|
|
99
|
+
await config.initialize(this.context);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async analyze(node, filePath) {
|
|
104
|
+
if (config.analyze) {
|
|
105
|
+
return await config.analyze(node, filePath, this.context);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Create a specialized member usage analyzer
|
|
113
|
+
*/
|
|
114
|
+
static createMemberAnalyzer(config = {}) {
|
|
115
|
+
return this.createPlugin({
|
|
116
|
+
name: config.name || 'custom-member-analyzer',
|
|
117
|
+
async analyze(node, filePath, context) {
|
|
118
|
+
// Custom logic to protect or flag specific members
|
|
119
|
+
if (config.protectMembers) {
|
|
120
|
+
for (const [symbol, meta] of node.internalExports.entries()) {
|
|
121
|
+
if (meta.members) {
|
|
122
|
+
meta.members.forEach(m => {
|
|
123
|
+
if (config.protectMembers.includes(m.name)) {
|
|
124
|
+
m.isPublic = true; // Mark as public to protect from deletion
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export default PluginSDK;
|
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -66,6 +66,17 @@ export class ASTAnalyzer {
|
|
|
66
66
|
fileNode.isPureBarrel = true;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// ADVANCED: Deep reachability analysis for dynamic imports
|
|
70
|
+
if (fileNode.dynamicImports.size > 0) {
|
|
71
|
+
fileNode.dynamicImports.forEach(specifier => {
|
|
72
|
+
const resolved = this.resolveAbsoluteTargetFile(specifier, filePath);
|
|
73
|
+
if (resolved) {
|
|
74
|
+
fileNode.outgoingEdges.add(resolved);
|
|
75
|
+
this.context.getOrCreateNode(resolved).isDynamicEntry = true;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
69
80
|
this.scopeStack = [];
|
|
70
81
|
this.currentScope = null;
|
|
71
82
|
}
|
|
@@ -222,6 +233,9 @@ export class ASTAnalyzer {
|
|
|
222
233
|
case ts.SyntaxKind.ExpressionStatement:
|
|
223
234
|
this.handleAssignmentExpressionPass1(node, fileNode, sourceFile);
|
|
224
235
|
break;
|
|
236
|
+
case ts.SyntaxKind.ExportDeclaration:
|
|
237
|
+
this.handleExportDeclaration(node, fileNode, sourceFile);
|
|
238
|
+
break;
|
|
225
239
|
}
|
|
226
240
|
} else {
|
|
227
241
|
// --- PASS 2: REFERENCE TRACKING ---
|
|
@@ -591,10 +605,10 @@ export class ASTAnalyzer {
|
|
|
591
605
|
}
|
|
592
606
|
} else {
|
|
593
607
|
// Non-literal dynamic import
|
|
594
|
-
if (!Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
595
|
-
fileNode.calculatedDynamicImports =
|
|
608
|
+
if (!fileNode.calculatedDynamicImports || Array.isArray(fileNode.calculatedDynamicImports)) {
|
|
609
|
+
fileNode.calculatedDynamicImports = new Set();
|
|
596
610
|
}
|
|
597
|
-
fileNode.calculatedDynamicImports.
|
|
611
|
+
fileNode.calculatedDynamicImports.add({ kind: ts.SyntaxKind[arg.kind], start: arg.getStart(sourceFile) });
|
|
598
612
|
|
|
599
613
|
// UPGRADE 2: Try to perform Glob-Analysis on Template Strings
|
|
600
614
|
this.handlePartialEvaluation(arg, fileNode, sourceFile);
|