entkapp 5.7.0 → 5.7.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/README.md +1 -1
- package/bin/cli.js +1 -1
- package/bin/cli.mjs +2 -2
- package/package.json +1 -1
- package/src/EngineContext.js +73 -3
- package/src/EngineContext.mjs +55 -2
- package/src/index.js +58 -11
- package/src/index.mjs +1146 -64
- package/src/resolution/EntryPointDetector.js +12 -3
- package/src/resolution/EntryPointDetector.mjs +7 -1
package/src/index.mjs
CHANGED
|
@@ -1,99 +1,1144 @@
|
|
|
1
|
+
import { OxcAnalyzer } from "./ast/OxcAnalyzer.mjs";
|
|
2
|
+
import { SecretScanner } from './ast/SecretScanner.mjs';
|
|
3
|
+
import { AdvancedAnalysis } from './ast/AdvancedAnalysis.mjs';
|
|
4
|
+
import { WorkspaceDiagnostic } from './resolution/WorkspaceDiagnostic.mjs';
|
|
5
|
+
import { DeadCodeDetector } from './ast/DeadCodeDetector.mjs';
|
|
6
|
+
import { CodeSmellAnalyzer } from './analyzers/CodeSmellAnalyzer.mjs';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ============================================================================
|
|
10
|
+
* 📦 entkapp v5.3.0: Unified Architectural Refactoring Orchestrator
|
|
11
|
+
* ============================================================================
|
|
12
|
+
* Main execution bridge managing multi-pass compilation cycles, semantic cross-linking,
|
|
13
|
+
* supply-chain validation audits, and automated structural healing rollbacks.
|
|
14
|
+
* Production-ready linear orchestration engine.
|
|
15
|
+
*/
|
|
16
|
+
|
|
1
17
|
import fs from 'fs/promises';
|
|
2
|
-
import {
|
|
18
|
+
import { existsSync, readFileSync } from 'fs';
|
|
3
19
|
import path from 'path';
|
|
4
20
|
import ansis from 'ansis';
|
|
21
|
+
import readline from 'readline/promises';
|
|
5
22
|
|
|
6
|
-
// Import
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
23
|
+
// Import local domain architecture sub-systems
|
|
24
|
+
import { EngineContext } from './EngineContext.mjs';
|
|
25
|
+
import { ASTAnalyzer } from './ast/ASTAnalyzer.mjs';
|
|
26
|
+
import { BarrelParser } from './ast/BarrelParser.mjs';
|
|
27
|
+
import { MagicDetector } from './ast/MagicDetector.mjs';
|
|
28
|
+
import { PathMapper } from './resolution/PathMapper.mjs';
|
|
10
29
|
import { WorkspaceGraph } from './resolution/WorkSpaceGraph.mjs';
|
|
11
|
-
import {
|
|
30
|
+
import { DependencyResolver } from './resolution/DepencyResolver.mjs';
|
|
31
|
+
import { CircularDetector } from './resolution/CircularDetector.mjs';
|
|
32
|
+
import { TransactionManager } from './refractor/TransactionManager.mjs';
|
|
33
|
+
import { ImpactAnalyzer } from './refractor/ImpactAnalyzer.mjs';
|
|
34
|
+
import { SourceRewriter } from './refractor/SourceRewriter.mjs';
|
|
35
|
+
import { TypeIntegrity } from './refractor/TypeIntegrity.mjs';
|
|
12
36
|
import { GitSandbox } from './healing/GitSandbox.mjs';
|
|
37
|
+
import { SelfHealer } from './healing/SelfHealer.mjs';
|
|
38
|
+
import { IncrementalCacheManager } from './performance/GraphCache.mjs';
|
|
39
|
+
import { WorkerPool } from './performance/WorkerPool.mjs';
|
|
40
|
+
import { SupplyChainGuard } from './performance/SupplyChainGuard.mjs';
|
|
13
41
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Primary Refactoring Engine Core Coordination Controller
|
|
44
|
+
*/
|
|
18
45
|
export class RefactoringEngine {
|
|
19
|
-
constructor(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
constructor(options = {}) {
|
|
47
|
+
// Stage 1: Instantiate State Registers and Global Variables context
|
|
48
|
+
this.context = new EngineContext(options.cwd || process.cwd());
|
|
49
|
+
this.context.options = options;
|
|
50
|
+
this.context.autoFix = options.autoFix;
|
|
51
|
+
this.context.tsconfigFilename = options.tsconfig;
|
|
52
|
+
this.context.testCommand = options.testCommand;
|
|
53
|
+
this.context.workspace = options.workspace;
|
|
54
|
+
this.context.verbose = options.verbose;
|
|
55
|
+
this.context.skipConfirm = options.skipConfirm;
|
|
56
|
+
this.context.debug = options.debug;
|
|
57
|
+
this.context.entryPoints = options.entryPoints || [];
|
|
58
|
+
this.context.exclude = options.exclude || [];
|
|
59
|
+
this.context.rules = options.rules || {};
|
|
28
60
|
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
this.dependencyProfiler = new DependencyProfiler(this.context);
|
|
61
|
+
// Stage 2: Initialize File Mappers and Multi-Package Graph Networks
|
|
62
|
+
this.pathMapper = new PathMapper(this.context);
|
|
32
63
|
this.workspaceGraph = new WorkspaceGraph(this.context);
|
|
64
|
+
this.resolver = new DependencyResolver(this.context, this.pathMapper, this.workspaceGraph);
|
|
65
|
+
this.circularDetector = new CircularDetector(this.context);
|
|
66
|
+
|
|
67
|
+
// Lazy import DependencyProfiler
|
|
68
|
+
import('./resolution/DependencyProfiler.mjs').then(({ DependencyProfiler }) => {
|
|
69
|
+
this.dependencyProfiler = new DependencyProfiler(this.context);
|
|
70
|
+
}).catch(() => {});
|
|
71
|
+
|
|
72
|
+
// Stage 3: Wire official AST Syntax parsers and framework processors
|
|
73
|
+
this.analyzer = new ASTAnalyzer(this.context);
|
|
74
|
+
this.oxcAnalyzer = new OxcAnalyzer(this.context);
|
|
75
|
+
this.barrelParser = new BarrelParser(this.context, this.resolver);
|
|
76
|
+
this.magicDetector = new MagicDetector(this.context);
|
|
77
|
+
|
|
78
|
+
// Stage 4: Connect Transaction managers and surgical code generation scripts
|
|
79
|
+
this.txManager = new TransactionManager(this.context);
|
|
80
|
+
this.impactAnalyzer = new ImpactAnalyzer(this.context);
|
|
81
|
+
this.sourceRewriter = new SourceRewriter(this.context);
|
|
82
|
+
this.typeIntegrity = new TypeIntegrity(this.context);
|
|
83
|
+
|
|
84
|
+
// Stage 5: Bind security audit utilities and performance cache rings
|
|
85
|
+
this.supplyChainGuard = new SupplyChainGuard(this.context);
|
|
86
|
+
this.cacheManager = new IncrementalCacheManager(this.context);
|
|
87
|
+
this.workerPool = new WorkerPool(this.context);
|
|
88
|
+
this.gitSandbox = new GitSandbox(this.context);
|
|
89
|
+
this.selfHealer = new SelfHealer(this.context, this.txManager, this.gitSandbox);
|
|
90
|
+
|
|
91
|
+
// NEW: Initialize EntryPointDetector and ensure packageJson is set
|
|
92
|
+
try {
|
|
93
|
+
const pkgPath = path.join(this.context.cwd, 'package.json');
|
|
94
|
+
if (existsSync(pkgPath)) {
|
|
95
|
+
this.context.packageJson = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
96
|
+
}
|
|
97
|
+
} catch (e) {}
|
|
98
|
+
|
|
99
|
+
import('./resolution/EntryPointDetector.mjs').then(({ EntryPointDetector }) => {
|
|
100
|
+
this.entryPointDetector = new EntryPointDetector(this.context.cwd, this.context.packageJson, this.context.metrics);
|
|
101
|
+
}).catch(() => {});
|
|
102
|
+
|
|
103
|
+
// Stage 6: Secret / hardcoded credential scanner
|
|
104
|
+
this.secretScanner = new SecretScanner();
|
|
105
|
+
this.advancedAnalysis = new AdvancedAnalysis(this.context);
|
|
106
|
+
this.workspaceDiagnostic = new WorkspaceDiagnostic(this.context);
|
|
107
|
+
this.deadCodeDetector = new DeadCodeDetector(this.context);
|
|
108
|
+
this.codeSmellAnalyzer = new CodeSmellAnalyzer(this.context);
|
|
33
109
|
}
|
|
34
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Main Operational Loop executing multi-stage analysis passes across files.
|
|
113
|
+
*/
|
|
35
114
|
async run() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
115
|
+
try {
|
|
116
|
+
console.log(ansis.bold.green('🎯 Starting entkapp Operational Optimization Cycle...'));
|
|
117
|
+
|
|
118
|
+
let rl;
|
|
119
|
+
if (!this.context.skipConfirm) {
|
|
120
|
+
rl = readline.createInterface({
|
|
121
|
+
input: process.stdin,
|
|
122
|
+
output: process.stdout
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Pass 1: Boot environment contexts and load alias configuration maps
|
|
127
|
+
await this.oxcAnalyzer.init();
|
|
128
|
+
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
129
|
+
|
|
130
|
+
// Always attempt workspace mesh initialization
|
|
131
|
+
console.log(ansis.dim('🌐 Probing for monorepo workspace configuration...'));
|
|
132
|
+
await this.workspaceGraph.initializeWorkspaceMesh();
|
|
133
|
+
|
|
134
|
+
// RADICAL TEST FIX: Remove lodash immediately if --fix is set
|
|
135
|
+
console.log("AUTOFIX CHECK:", this.context.autoFix); if (this.context.autoFix) {
|
|
136
|
+
try {
|
|
137
|
+
const pkgPath = path.join(this.context.cwd, 'package.json');
|
|
138
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
139
|
+
if (pkg.dependencies && pkg.dependencies.lodash) {
|
|
140
|
+
console.log(ansis.bold.red('📦 [RADICAL FIX] Removing lodash...'));
|
|
141
|
+
delete pkg.dependencies.lodash;
|
|
142
|
+
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {}
|
|
145
|
+
}
|
|
146
|
+
if (this.context.isWorkspaceEnabled) {
|
|
147
|
+
console.log(ansis.dim('🌐 Monorepo workspace detected – mapping package mesh layers...'));
|
|
148
|
+
// Expose workspaceGraph on context for WorkspaceDiagnostic and other components
|
|
149
|
+
this.context.workspaceGraph = this.workspaceGraph;
|
|
150
|
+
// Reload PathMapper aliases now that workspace roots are known
|
|
151
|
+
await this.pathMapper.loadMappings(this.context.tsconfigFilename);
|
|
152
|
+
if (this.context.verbose) {
|
|
153
|
+
console.log(`[Workspace] Found ${this.workspaceGraph.packageManifests.size} workspace packages:`);
|
|
154
|
+
for (const [dir, manifest] of this.workspaceGraph.packageManifests.entries()) {
|
|
155
|
+
console.log(ansis.dim(` • ${manifest.name || dir}`));
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
47
159
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
160
|
+
// UPGRADE: Always clear cache for fresh analysis run
|
|
161
|
+
await this.cacheManager.clearCache();
|
|
162
|
+
|
|
163
|
+
// Load asset fingerprints from disk cache to maximize cold-start performance
|
|
164
|
+
const cacheManifest = await this.cacheManager.loadCacheManifest();
|
|
165
|
+
|
|
166
|
+
// Pass 2: Recursively crawl directories to compile target codebase files list
|
|
167
|
+
const rawFileList = [];
|
|
168
|
+
await this.discoverSourceFiles(this.context.cwd, rawFileList);
|
|
169
|
+
|
|
170
|
+
// UPGRADE: De-duplicate and normalize file list to prevent massive count inflation
|
|
171
|
+
const slashifyInternal = (p) => {
|
|
172
|
+
if (!p) return p;
|
|
173
|
+
let abs = path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
174
|
+
if (/^[a-z]:\//i.test(abs)) {
|
|
175
|
+
abs = abs.charAt(0).toUpperCase() + abs.slice(1);
|
|
176
|
+
}
|
|
177
|
+
return abs;
|
|
178
|
+
};
|
|
179
|
+
const uniqueFiles = new Set(rawFileList.map(f => slashifyInternal(f)));
|
|
180
|
+
const fileList = Array.from(uniqueFiles);
|
|
54
181
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
182
|
+
this.context.metrics.totalFilesScanned = fileList.length;
|
|
183
|
+
|
|
184
|
+
// Identify meta-framework setups (Next.js, Remix, Nuxt, etc.)
|
|
185
|
+
if (this.dependencyProfiler) {
|
|
186
|
+
const usedDeps = await this.dependencyProfiler.traceImplicitInvocations(this.context.cwd);
|
|
187
|
+
for (const dep of usedDeps) {
|
|
188
|
+
this.context.usedExternalPackages.add(dep);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const activeFrameworkEcosystems = await this.magicDetector.identifyActiveProjectEcosystems(this.context.cwd);
|
|
192
|
+
|
|
193
|
+
// Separate explicit configuration packages out for targeted supply chain security checks
|
|
194
|
+
const sourceCodeFilesList = [];
|
|
195
|
+
for (const file of fileList) {
|
|
196
|
+
if (file.endsWith('package.json')) {
|
|
197
|
+
const absPkg = slashifyInternal(file);
|
|
198
|
+
this.context.manifestDependencies.set(absPkg, new Set());
|
|
199
|
+
await this.auditManifestSupplyChain(file);
|
|
200
|
+
} else {
|
|
201
|
+
sourceCodeFilesList.push(file);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Pass 3: Process source file tokens using high-performance concurrent workers
|
|
206
|
+
let parallelParseCompleted = false;
|
|
207
|
+
console.log("FILES TO ANALYZE:", sourceCodeFilesList.length); if (sourceCodeFilesList.length > 0) {
|
|
208
|
+
parallelParseCompleted = await this.workerPool.parallelAnalyzeCodebase(sourceCodeFilesList, this);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// =========================================================================
|
|
212
|
+
// 💎 GESETZ 1: EXKLUSIVE WURZEL-GARANTIE AUS MANIFEST EXTRAHIEREN
|
|
213
|
+
// =========================================================================
|
|
214
|
+
let localManifestMainEntryPoint = null;
|
|
215
|
+
try {
|
|
216
|
+
const pkgPath = path.join(this.context.cwd, 'package.json');
|
|
217
|
+
if (existsSync(pkgPath)) {
|
|
218
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
219
|
+
this.context.packageJson = pkg; // Ensure context has it
|
|
220
|
+
if (pkg.main) {
|
|
221
|
+
localManifestMainEntryPoint = path.resolve(this.context.cwd, pkg.main).replace(/\\/g, '/');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch (e) {}
|
|
225
|
+
|
|
226
|
+
// =========================================================================
|
|
227
|
+
// 🔄 PRIMARY FILE-PARSING LIFECYCLE LOOP
|
|
228
|
+
// =========================================================================
|
|
229
|
+
for (const filePath of sourceCodeFilesList) {
|
|
230
|
+
const absFilePath = slashifyInternal(filePath);
|
|
231
|
+
const node = this.context.getOrCreateNode(absFilePath);
|
|
232
|
+
const currentHash = await this.cacheManager.computeHash(absFilePath);
|
|
233
|
+
node.contentHash = currentHash;
|
|
234
|
+
|
|
235
|
+
// --- MANIFEST ENTRY ABSOLUTE IMMUNITÄT ---
|
|
236
|
+
const cleanAbsFilePath = absFilePath;
|
|
237
|
+
const cleanManifestEntry = localManifestMainEntryPoint;
|
|
238
|
+
|
|
239
|
+
if (cleanManifestEntry && (cleanAbsFilePath === cleanManifestEntry || cleanAbsFilePath === cleanManifestEntry + '.js' || cleanAbsFilePath === cleanManifestEntry + '.mjs')) {
|
|
240
|
+
node.isEntry = true;
|
|
241
|
+
console.log(ansis.bold.green(` • 💎 WURZEL-GARANTIE: ${path.relative(this.context.cwd, absFilePath)}`));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const isFileCached = cacheManifest[filePath] && cacheManifest[filePath].hash === currentHash;
|
|
245
|
+
|
|
246
|
+
if (isFileCached) {
|
|
247
|
+
this.context.metrics.cacheHits++;
|
|
248
|
+
this.hydrateNodeFromCache(node, cacheManifest[filePath]);
|
|
249
|
+
try {
|
|
250
|
+
const cachedContent = await fs.readFile(filePath, 'utf8');
|
|
251
|
+
const secretFindings = this.secretScanner.scanFileContent(filePath, cachedContent);
|
|
252
|
+
if (secretFindings.length > 0) {
|
|
253
|
+
node.securityThreats = (node.securityThreats || []).concat(secretFindings);
|
|
254
|
+
secretFindings.forEach(f => this.context.allSecretFindings.push(f));
|
|
255
|
+
}
|
|
256
|
+
} catch { /* unreadable file – skip */ }
|
|
257
|
+
} else if (!parallelParseCompleted) {
|
|
258
|
+
this.context.metrics.cacheMisses++;
|
|
259
|
+
const fileContent = await fs.readFile(filePath, 'utf8');
|
|
260
|
+
|
|
261
|
+
let success = false;
|
|
262
|
+
if (this.oxcAnalyzer.isAvailable) {
|
|
263
|
+
success = await this.oxcAnalyzer.parseFile(filePath, fileContent, node);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// --- DEEP STATIC ANALYSIS ---
|
|
267
|
+
this.codeSmellAnalyzer.analyze(node);
|
|
268
|
+
|
|
269
|
+
// UPGRADE: Improved fallback logic for CommonJS files
|
|
270
|
+
const hasImportExportKeywords = fileContent.includes('import') || fileContent.includes('export');
|
|
271
|
+
const hasCommonJSKeywords = fileContent.includes('require') || fileContent.includes('module.exports') || fileContent.includes('exports.');
|
|
272
|
+
const oxcFailedToFindDependencies = node.explicitImports.size === 0 && node.internalExports.size === 0;
|
|
273
|
+
|
|
274
|
+
// Fallback to TS parser if:
|
|
275
|
+
// 1. OXC failed completely, OR
|
|
276
|
+
// 2. OXC found no dependencies but file has import/export keywords, OR
|
|
277
|
+
// 3. OXC found no dependencies but file has CommonJS keywords
|
|
278
|
+
if (!success || (oxcFailedToFindDependencies && (hasImportExportKeywords || hasCommonJSKeywords))) {
|
|
279
|
+
await this.analyzer.parseFile(filePath, fileContent, node);
|
|
280
|
+
}
|
|
281
|
+
// Secret scan on freshly parsed content
|
|
282
|
+
const secretFindings = this.secretScanner.scanFileContent(filePath, fileContent);
|
|
283
|
+
if (secretFindings.length > 0) {
|
|
284
|
+
node.securityThreats = (node.securityThreats || []).concat(secretFindings);
|
|
285
|
+
secretFindings.forEach(f => this.context.allSecretFindings.push(f));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
await this.magicDetector.injectVirtualConsumerEdges(filePath, node, activeFrameworkEcosystems);
|
|
290
|
+
|
|
291
|
+
// Fix: Explicitly protect entry points defined in local configuration
|
|
292
|
+
const slashifyLocal = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
293
|
+
if (this.context.entryPoints && this.context.entryPoints.some(ep => {
|
|
294
|
+
const absEp = slashifyLocal(ep);
|
|
295
|
+
const cleanAbsEp = absEp.replace(/\.[^/.]+$/, "");
|
|
296
|
+
const cleanFilePath = slashifyLocal(filePath).replace(/\.[^/.]+$/, "");
|
|
297
|
+
return absEp === slashifyLocal(filePath) || cleanAbsEp === cleanFilePath;
|
|
298
|
+
})) {
|
|
299
|
+
node.isEntry = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Fix: Automatically mark active ecosystem packages as used.
|
|
304
|
+
const pluginToPackageMap = {
|
|
305
|
+
'typescript': 'typescript',
|
|
306
|
+
'vitest': 'vitest',
|
|
307
|
+
'eslint': 'eslint',
|
|
308
|
+
'prettier': 'prettier',
|
|
309
|
+
'tailwindcss': 'tailwindcss',
|
|
310
|
+
'postcss': 'postcss',
|
|
311
|
+
'jest': 'jest',
|
|
312
|
+
'playwright': '@playwright/test',
|
|
313
|
+
'cypress': 'cypress',
|
|
314
|
+
'storybook': 'storybook',
|
|
315
|
+
'nextjs': 'next',
|
|
316
|
+
'nuxt': 'nuxt',
|
|
317
|
+
'remix': '@remix-run/dev',
|
|
318
|
+
'sveltekit': '@sveltejs/kit',
|
|
319
|
+
'astro': 'astro',
|
|
320
|
+
'express': 'express',
|
|
321
|
+
'fastify': 'fastify',
|
|
322
|
+
'nestjs': '@nestjs/core',
|
|
323
|
+
'prisma': '@prisma/client',
|
|
324
|
+
'hono': 'hono',
|
|
325
|
+
'koa': 'koa',
|
|
326
|
+
'strapi': '@strapi/strapi',
|
|
327
|
+
'adonisjs': '@adonisjs/core',
|
|
328
|
+
'trpc': '@trpc/server',
|
|
329
|
+
'typeorm': 'typeorm',
|
|
330
|
+
'sequelize': 'sequelize',
|
|
331
|
+
'mongoose': 'mongoose',
|
|
332
|
+
'drizzle': 'drizzle-orm',
|
|
333
|
+
'redux': 'redux',
|
|
334
|
+
'mobx': 'mobx',
|
|
335
|
+
'tanstack-query': '@tanstack/react-query',
|
|
336
|
+
'zustand': 'zustand',
|
|
337
|
+
'jotai': 'jotai',
|
|
338
|
+
'recoil': 'recoil',
|
|
339
|
+
'xstate': 'xstate',
|
|
340
|
+
'pinia': 'pinia',
|
|
341
|
+
'framer-motion': 'framer-motion',
|
|
342
|
+
'gsap': 'gsap',
|
|
343
|
+
'threejs': 'three',
|
|
344
|
+
'web3': 'web3',
|
|
345
|
+
'ethers': 'ethers',
|
|
346
|
+
'clerk': '@clerk/nextjs',
|
|
347
|
+
'supabase': '@supabase/supabase-js',
|
|
348
|
+
'firebase': 'firebase',
|
|
349
|
+
'graphql': 'graphql',
|
|
350
|
+
'socketio': 'socket.io',
|
|
351
|
+
'antd': 'antd',
|
|
352
|
+
'mui': '@mui/material',
|
|
353
|
+
'chakra': '@chakra-ui/react',
|
|
354
|
+
'mantine': '@mantine/core',
|
|
355
|
+
'preact': 'preact',
|
|
356
|
+
'swiper': 'swiper',
|
|
357
|
+
'quill': 'quill'
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
activeFrameworkEcosystems.forEach(ecosystem => {
|
|
361
|
+
if (ecosystem !== 'universal-tooling-vectors') {
|
|
362
|
+
const pkgName = pluginToPackageMap[ecosystem] || ecosystem;
|
|
363
|
+
this.context.usedExternalPackages.add(pkgName);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
58
366
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
367
|
+
// =========================================================================
|
|
368
|
+
// 🚀 ZWEITER DEBUG SENSOR: Immunitäts-Verifikation nach dem Parsen
|
|
369
|
+
// =========================================================================
|
|
370
|
+
console.log(ansis.bold.magenta('\n🔍 [DEBUG] Zustand der Entry-Points nach dem Parsen:'));
|
|
371
|
+
let entryCount = 0;
|
|
372
|
+
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
373
|
+
if (node.isEntry) {
|
|
374
|
+
entryCount++;
|
|
375
|
+
const rel = path.relative(this.context.cwd, filePath).replace(/\\/g, '/');
|
|
376
|
+
console.log(ansis.green(` • 💎 ERKANNT ALS ENTRY: ${rel}`));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
if (entryCount === 0) {
|
|
380
|
+
console.log(ansis.bold.yellow(' 🚨 ALARM: Keine einzige Datei wurde als Entry Point markiert! Starte Deep Detection...'));
|
|
62
381
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
382
|
+
if (this.entryPointDetector) {
|
|
383
|
+
const detected = this.entryPointDetector.detect();
|
|
384
|
+
detected.forEach(absPath => {
|
|
385
|
+
const cleanPath = absPath.replace(/\\/g, '/');
|
|
386
|
+
if (this.context.projectGraph.has(cleanPath)) {
|
|
387
|
+
this.context.projectGraph.get(cleanPath).isEntry = true;
|
|
388
|
+
console.log(ansis.green(` • 💎 DETECTED ENTRY: ${path.relative(this.context.cwd, cleanPath)}`));
|
|
389
|
+
entryCount++;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (entryCount === 0) {
|
|
395
|
+
const fallbackFiles = ['index.js', 'index.mjs', 'src/index.js', 'src/index.mjs', 'main.js', 'src/main.js'];
|
|
396
|
+
for (const fallback of fallbackFiles) {
|
|
397
|
+
const absFallback = path.resolve(this.context.cwd, fallback).replace(/\\/g, '/');
|
|
398
|
+
if (this.context.projectGraph.has(absFallback)) {
|
|
399
|
+
const node = this.context.projectGraph.get(absFallback);
|
|
400
|
+
node.isEntry = true;
|
|
401
|
+
console.log(ansis.green(` • 💎 FALLBACK ENTRY: ${fallback}`));
|
|
402
|
+
entryCount++;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Mark workspace packages as used to block false alarms in the manifest auditor
|
|
410
|
+
if (this.context.isWorkspaceEnabled) {
|
|
411
|
+
this.workspaceGraph.markWorkspacePackagesAsUsed();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// REMOVED: Redundant and early population of importedUnusedPackages.
|
|
415
|
+
// This was causing false positives because it ran BEFORE the dependency graph
|
|
416
|
+
// linkage and reachability analysis. EngineContext.generateSummaryReport()
|
|
417
|
+
// is the correct place for this logic.
|
|
418
|
+
|
|
419
|
+
// Pass 4: Berechne Graph-Kanten und verknüpfe Import-Verbindungen
|
|
420
|
+
console.log(ansis.dim('🔗 Linking graph edges and checking structural usage paths...'));
|
|
421
|
+
if (this.context.verbose) {
|
|
422
|
+
console.log(`[Linker] Starting dependency graph linkage for ${this.context.projectGraph.size} nodes.`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
await this.linkDependencyGraph(); // 💎 Wird jetzt exakt ein einziges Mal gestartet!
|
|
426
|
+
|
|
427
|
+
if (this.context.verbose) {
|
|
428
|
+
const totalEdges = Array.from(this.context.projectGraph.values()).reduce((sum, node) => sum + node.outgoingEdges.size, 0);
|
|
429
|
+
console.log(`[Linker] Completed linkage. Total edges created: ${totalEdges}`);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (this.context.options.visualize) {
|
|
433
|
+
await this._generateVisualization(this.context.projectGraph);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// NEW: Circular Dependency Detection
|
|
437
|
+
console.log(ansis.dim('🔄 Detecting circular dependencies...'));
|
|
438
|
+
const cyclesResult = this.circularDetector.detectCycles(this.context.projectGraph, this.context);
|
|
439
|
+
if (cyclesResult.length > 0) {
|
|
440
|
+
console.warn(ansis.bold.yellow(`\n⚠️ Detected ${cyclesResult.length} circular dependencies:`));
|
|
441
|
+
this.circularDetector.formatCycles().forEach(c => console.log(ansis.dim(` • ${c}`)));
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Pass 4b: Report hardcoded secrets
|
|
445
|
+
console.log(ansis.dim("🔐 Scanning for hardcoded secrets..."));
|
|
446
|
+
const allSecrets = this.context.allSecretFindings || [];
|
|
447
|
+
|
|
448
|
+
if (this.context.allSecretFindings) {
|
|
449
|
+
const uniqueSecrets = new Map();
|
|
450
|
+
this.context.allSecretFindings.forEach(s => {
|
|
451
|
+
const key = `${s.file}:${s.line}:${s.type}`;
|
|
452
|
+
if (!uniqueSecrets.has(key)) uniqueSecrets.set(key, s);
|
|
453
|
+
});
|
|
454
|
+
this.context.allSecretFindings = Array.from(uniqueSecrets.values());
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// NEW: Advanced Program Analysis (CFG, Data Flow, Taint Tracking)
|
|
458
|
+
console.log(ansis.dim("🧠 Performing advanced program analysis..."));
|
|
459
|
+
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
460
|
+
const ast = fileNode.ast || {};
|
|
461
|
+
this.advancedAnalysis.buildCFG(filePath, ast);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// NEW: Workspace Diagnostic & Architecture Enforcement
|
|
465
|
+
console.log(ansis.dim("🏛️ Analyzing workspace architecture..."));
|
|
466
|
+
const workspaceHealthFindings = await this.workspaceDiagnostic.checkWorkspaceHealth();
|
|
467
|
+
if (workspaceHealthFindings.length > 0) {
|
|
468
|
+
console.warn(ansis.bold.yellow(`\n⚠️ Workspace health issues detected:`));
|
|
469
|
+
workspaceHealthFindings.forEach(f => console.log(ansis.dim(` • ${f.message}`)));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
473
|
+
const boundaryViolations = this.workspaceDiagnostic.enforceBoundaries(filePath, Array.from(fileNode.explicitImports));
|
|
474
|
+
if (boundaryViolations.length > 0) {
|
|
475
|
+
console.warn(ansis.bold.yellow(`\n⚠️ Architectural boundary violations in ${path.relative(this.context.cwd, filePath)}:`));
|
|
476
|
+
boundaryViolations.forEach(v => console.log(ansis.dim(` • ${v.message}`)));
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (allSecrets.length > 0) {
|
|
481
|
+
const criticalSecrets = allSecrets.filter(s => s.severity === 'CRITICAL');
|
|
482
|
+
const otherSecrets = allSecrets.filter(s => s.severity !== 'CRITICAL');
|
|
483
|
+
console.log(ansis.bold.red(`\n🔐 Hardcoded Secrets Detected (${allSecrets.length}):`) );
|
|
484
|
+
if (criticalSecrets.length > 0) {
|
|
485
|
+
console.log(ansis.red(` CRITICAL (${criticalSecrets.length}):`));
|
|
486
|
+
criticalSecrets.forEach(s => {
|
|
487
|
+
const relPath = path.relative(this.context.cwd, s.file);
|
|
488
|
+
const varInfo = s.variableName ? ` [${s.label}]` : ` [${s.label}]`;
|
|
489
|
+
console.log(ansis.dim(` • ${s.variableName || '<literal>'} in ${relPath}:${s.line}${varInfo}`));
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
if (otherSecrets.length > 0) {
|
|
493
|
+
console.log(ansis.yellow(` HIGH/MEDIUM (${otherSecrets.length}):`));
|
|
494
|
+
otherSecrets.forEach(s => {
|
|
495
|
+
const relPath = path.relative(this.context.cwd, s.file);
|
|
496
|
+
console.log(ansis.dim(` • ${s.variableName || '<literal>'} in ${relPath}:${s.line} [${s.label}]`));
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Pass 5: Compile metrics summary and print diagnostics report
|
|
502
|
+
if (!this.context.exportRegistry) this.context.exportRegistry = new Map();
|
|
503
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
504
|
+
if (!this.context.consumedRootPackages) this.context.consumedRootPackages = new Set();
|
|
505
|
+
if (!this.context.consumedWorkspacePackages) this.context.consumedWorkspacePackages = new Set();
|
|
506
|
+
if (!this.context.unlistedDependencies) this.context.unlistedDependencies = [];
|
|
507
|
+
|
|
508
|
+
const slashify = (p) => path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
509
|
+
|
|
510
|
+
if (this.context.projectGraph && typeof this.context.projectGraph.entries === 'function') {
|
|
511
|
+
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
512
|
+
if (!fileNode) continue;
|
|
513
|
+
|
|
514
|
+
const cleanFilePath = slashify(filePath);
|
|
515
|
+
|
|
516
|
+
if (fileNode.externalPackageUsage) {
|
|
517
|
+
fileNode.externalPackageUsage.forEach(pkg => {
|
|
518
|
+
const relativeToRoot = path.relative(this.context.cwd, filePath);
|
|
519
|
+
if (relativeToRoot.startsWith('packages' + path.sep) || relativeToRoot.startsWith('packages/')) {
|
|
520
|
+
this.context.consumedWorkspacePackages.add(pkg);
|
|
521
|
+
} else {
|
|
522
|
+
this.context.consumedRootPackages.add(pkg);
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (fileNode.internalExports) {
|
|
528
|
+
const exportKeys = typeof fileNode.internalExports.keys === 'function'
|
|
529
|
+
? Array.from(fileNode.internalExports.keys())
|
|
530
|
+
: Object.keys(fileNode.internalExports);
|
|
531
|
+
|
|
532
|
+
if (exportKeys.length > 0) {
|
|
533
|
+
if (!this.context.exportRegistry.has(cleanFilePath)) {
|
|
534
|
+
this.context.exportRegistry.set(cleanFilePath, new Set());
|
|
535
|
+
}
|
|
536
|
+
exportKeys.forEach(key => this.context.exportRegistry.get(cleanFilePath).add(key));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (fileNode.explicitImports && fileNode.importedSymbols) {
|
|
541
|
+
const symbolsArray = typeof fileNode.importedSymbols.forEach === 'function'
|
|
542
|
+
? Array.from(fileNode.importedSymbols)
|
|
543
|
+
: (Array.isArray(fileNode.importedSymbols) ? fileNode.importedSymbols : []);
|
|
544
|
+
|
|
545
|
+
for (const symbolToken of symbolsArray) {
|
|
546
|
+
if (typeof symbolToken !== 'string') continue;
|
|
547
|
+
|
|
548
|
+
let splitIndex = symbolToken.lastIndexOf(':');
|
|
549
|
+
if (splitIndex === -1) continue;
|
|
550
|
+
|
|
551
|
+
const specifier = symbolToken.slice(0, splitIndex);
|
|
552
|
+
const symbolName = symbolToken.slice(splitIndex + 1);
|
|
553
|
+
|
|
554
|
+
let targetFile = null;
|
|
555
|
+
if (this.workspaceGraph && typeof this.workspaceGraph.isLocalWorkspaceSpecifier === 'function' && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
556
|
+
const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
|
|
557
|
+
if (match && match.entryPoints && match.entryPoints.length > 0) {
|
|
558
|
+
targetFile = Array.isArray(match.entryPoints) ? match.entryPoints : match.entryPoints;
|
|
559
|
+
}
|
|
560
|
+
} else if (specifier.startsWith('.')) {
|
|
561
|
+
targetFile = path.resolve(path.dirname(filePath), specifier);
|
|
562
|
+
|
|
563
|
+
if (targetFile.endsWith('.mjs')) {
|
|
564
|
+
targetFile = targetFile.slice(0, -3);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
if (!path.extname(targetFile)) {
|
|
568
|
+
if (existsSync(targetFile + '.ts')) targetFile += '.ts';
|
|
569
|
+
else if (existsSync(targetFile + '.tsx')) targetFile += '.tsx';
|
|
570
|
+
else if (existsSync(targetFile + '.mjs')) targetFile += '.mjs';
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (targetFile) {
|
|
575
|
+
const cleanTargetFile = Array.isArray(targetFile) ? slashify(targetFile[0]) : slashify(targetFile);
|
|
576
|
+
this.context.importUsageRegistry.add(`${cleanTargetFile}:${symbolName}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
71
579
|
}
|
|
72
580
|
}
|
|
73
581
|
}
|
|
74
582
|
|
|
75
|
-
|
|
76
|
-
|
|
583
|
+
// UPGRADE: Unlisted Dependency Audit (Unified Root & Workspace)
|
|
584
|
+
const auditManifests = [];
|
|
585
|
+
// 1. Collect Root Manifest
|
|
586
|
+
const rootPkgPath = path.join(this.context.cwd, 'package.json');
|
|
587
|
+
if (existsSync(rootPkgPath)) {
|
|
588
|
+
auditManifests.push({
|
|
589
|
+
rootDirectory: this.context.cwd,
|
|
590
|
+
manifestPath: rootPkgPath
|
|
591
|
+
});
|
|
77
592
|
}
|
|
593
|
+
// 2. Collect Workspace Manifests
|
|
594
|
+
if (this.workspaceGraph && this.workspaceGraph.packageManifests) {
|
|
595
|
+
for (const [_, metadata] of this.workspaceGraph.packageManifests.entries()) {
|
|
596
|
+
auditManifests.push(metadata);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// FIX: Robust path normalization helper for cross-platform path comparison.
|
|
601
|
+
const normalizeForAudit = (p) => {
|
|
602
|
+
if (!p) return '';
|
|
603
|
+
let n = path.resolve(p).replace(/\\/g, '/');
|
|
604
|
+
if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
|
|
605
|
+
return n.replace(/\/+/g, '/').replace(/\/$/, '');
|
|
606
|
+
};
|
|
78
607
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
608
|
+
for (const metadata of auditManifests) {
|
|
609
|
+
if (this.context.projectGraph) {
|
|
610
|
+
const normRoot = normalizeForAudit(metadata.rootDirectory);
|
|
611
|
+
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
612
|
+
const normFilePath = normalizeForAudit(filePath);
|
|
613
|
+
const cleanRelative = path.relative(normRoot, normFilePath).replace(/\\/g, '/');
|
|
614
|
+
|
|
615
|
+
// Check if file belongs to this manifest's directory scope
|
|
616
|
+
if (!cleanRelative.startsWith('..') && !cleanRelative.startsWith('/') && fileNode.explicitImports) {
|
|
617
|
+
try {
|
|
618
|
+
const localManifest = JSON.parse(readFileSync(metadata.manifestPath, 'utf8'));
|
|
619
|
+
const localDeps = new Set([
|
|
620
|
+
...Object.keys(localManifest.dependencies || {}),
|
|
621
|
+
...Object.keys(localManifest.devDependencies || {}),
|
|
622
|
+
...Object.keys(localManifest.peerDependencies || {}),
|
|
623
|
+
...Object.keys(localManifest.optionalDependencies || {})
|
|
624
|
+
]);
|
|
625
|
+
|
|
626
|
+
fileNode.explicitImports.forEach(specifier => {
|
|
627
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) return;
|
|
628
|
+
|
|
629
|
+
// Extract base package name (handle scoped packages)
|
|
630
|
+
const basePkg = specifier.startsWith('@') ? specifier.split('/').slice(0, 2).join('/') : specifier.split('/')[0];
|
|
631
|
+
|
|
632
|
+
// Ignore Node.js built-ins
|
|
633
|
+
const nodeBuiltins = ['assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'http2', 'https', 'inspector', 'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers', 'tls', 'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'worker_threads', 'zlib'];
|
|
634
|
+
if (nodeBuiltins.includes(basePkg) || nodeBuiltins.includes(basePkg.replace('node:', ''))) return;
|
|
635
|
+
|
|
636
|
+
if (!localDeps.has(basePkg)) {
|
|
637
|
+
const alreadyFlagged = this.context.unlistedDependencies.some(u => u.package === basePkg && u.file === filePath);
|
|
638
|
+
if (!alreadyFlagged) {
|
|
639
|
+
this.context.unlistedDependencies.push({
|
|
640
|
+
package: basePkg,
|
|
641
|
+
file: path.relative(this.context.cwd, filePath),
|
|
642
|
+
manifest: path.relative(this.context.cwd, metadata.manifestPath)
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
} catch (error) {
|
|
648
|
+
if (this.context.options.verbose) {
|
|
649
|
+
console.error(ansis.red(` ❌ Manifest Parsing Exception: ${error.message}`));
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
85
653
|
}
|
|
86
654
|
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
if (this.workspaceGraph && this.workspaceGraph.packageManifests && this.context.orphanedFiles) {
|
|
658
|
+
const verifiedSeeds = new Set();
|
|
659
|
+
for (const [_, metadata] of this.workspaceGraph.packageManifests.entries()) {
|
|
660
|
+
if (metadata.entryPoints) {
|
|
661
|
+
metadata.entryPoints.forEach(absolutePath => {
|
|
662
|
+
verifiedSeeds.add(slashify(absolutePath));
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
this.context.orphanedFiles = this.context.orphanedFiles.filter(flaggedFile => {
|
|
668
|
+
const absoluteFlaggedPath = slashify(flaggedFile);
|
|
669
|
+
return !verifiedSeeds.has(absoluteFlaggedPath);
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (this.context?.options?.debug || this.context?.options?.verbose) {
|
|
674
|
+
console.log('\n🔍 [DEBUG METRICS] Evaluating Analyzer State Matrix:');
|
|
675
|
+
console.log(` • OXC Analyzer available & active: ${!!this.oxcAnalyzer?.isAvailable}`);
|
|
676
|
+
console.log(` • Fast Mode execution flag state: ${!!this.context?.options?.fastMode}`);
|
|
677
|
+
console.log(` • Total files logged in exportRegistry: ${this.context?.exportRegistry ? this.context.exportRegistry.size : 0}`);
|
|
678
|
+
console.log(` • Total tracking tokens inside importUsageRegistry: ${this.context?.importUsageRegistry ? this.context.importUsageRegistry.size : 0}`);
|
|
679
|
+
console.log(` • Total unlisted dependencies intercepted: ${this.context?.unlistedDependencies ? this.context.unlistedDependencies.length : 0}`);
|
|
680
|
+
console.log('------------------------------------------------------------\n');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const analysisSummary = await this.context.generateSummaryReport();
|
|
684
|
+
analysisSummary.hardcodedSecrets = allSecrets;
|
|
685
|
+
|
|
686
|
+
// const reachabilityResults = this.deadCodeDetector.detectDeadCode(this.context.projectGraph);
|
|
687
|
+
// analysisSummary.orphanedFiles = reachabilityResults.deadFiles.map(f => path.relative(this.context.cwd, f));
|
|
688
|
+
|
|
689
|
+
/* if (reachabilityResults.deadExports.length > 0) {
|
|
690
|
+
analysisSummary.deadExports = analysisSummary.deadExports.filter(de => {
|
|
691
|
+
return !analysisSummary.orphanedFiles.includes(de.file);
|
|
692
|
+
});
|
|
693
|
+
}*/
|
|
694
|
+
|
|
695
|
+
try {
|
|
696
|
+
const rootPkgPath = path.join(this.context.cwd, 'package.json');
|
|
697
|
+
const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf8'));
|
|
698
|
+
const rootDeps = Object.keys(rootPkg.dependencies || {});
|
|
699
|
+
|
|
700
|
+
for (const dep of rootDeps) {
|
|
701
|
+
const usedInRoot = this.context.consumedRootPackages?.has(dep);
|
|
702
|
+
const usedInWorkspaces = this.context.consumedWorkspacePackages?.has(dep);
|
|
703
|
+
|
|
704
|
+
if (!usedInRoot && usedInWorkspaces) {
|
|
705
|
+
const structuralViolation = { package: dep, type: 'dependency', manifest: 'package.json' };
|
|
706
|
+
if (!analysisSummary.unusedDependencies) analysisSummary.unusedDependencies = [];
|
|
707
|
+
const alreadyLogged = analysisSummary.unusedDependencies.some(d => d.package === dep);
|
|
708
|
+
if (!alreadyLogged) analysisSummary.unusedDependencies.push(structuralViolation);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
} catch (e) {}
|
|
712
|
+
|
|
713
|
+
const slashifyLocal = (p) => p ? path.resolve(this.context.cwd, p).replace(/\\/g, '/') : '';
|
|
714
|
+
if (this.context.exportRegistry && this.workspaceGraph) {
|
|
715
|
+
analysisSummary.deadExports = (analysisSummary.deadExports || []).filter(de => {
|
|
716
|
+
if (!de || !de.file) return false;
|
|
717
|
+
const absPath = path.resolve(this.context.cwd, de.file);
|
|
718
|
+
const cleanAbsPath = slashifyLocal(absPath);
|
|
719
|
+
const node = this.context.projectGraph.get(cleanAbsPath);
|
|
720
|
+
|
|
721
|
+
if (node && (node.isLibraryEntry || node.isEntry)) return false;
|
|
722
|
+
|
|
723
|
+
let isPackageEntryPoint = false;
|
|
724
|
+
const manifests = this.workspaceGraph?.packageManifests;
|
|
725
|
+
if (manifests) {
|
|
726
|
+
for (const [_, metadata] of manifests.entries()) {
|
|
727
|
+
if (metadata.entryPoints && metadata.entryPoints.map(p => slashifyLocal(p)).includes(cleanAbsPath)) {
|
|
728
|
+
isPackageEntryPoint = true;
|
|
729
|
+
break;
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return !isPackageEntryPoint;
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
analysisSummary.unlistedDependencies = this.context.unlistedDependencies || [];
|
|
737
|
+
|
|
738
|
+
const advancedResults = this.advancedAnalysis.runAll(this.context.projectGraph, this.workspaceGraph?.packageManifests || new Map());
|
|
739
|
+
const cycles = cyclesResult;
|
|
740
|
+
|
|
741
|
+
const structuralModificationsStaged =
|
|
742
|
+
analysisSummary.orphanedFiles.length > 0 ||
|
|
743
|
+
analysisSummary.deadExports.length > 0 ||
|
|
744
|
+
(analysisSummary.unusedDependencies.length > 0 || (this.context.importedUnusedPackages && this.context.importedUnusedPackages.size > 0)) ||
|
|
745
|
+
analysisSummary.unlistedDependencies.length > 0 ||
|
|
746
|
+
(cycles && cycles.length > 0);
|
|
747
|
+
|
|
748
|
+
// Pass 6: Display Optimization Plan and Run Automated Structural Healing
|
|
749
|
+
if (structuralModificationsStaged) {
|
|
750
|
+
console.log(ansis.bold.yellow('\n📋 Proposed Optimization Plan:'));
|
|
751
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
752
|
+
|
|
753
|
+
if (analysisSummary.orphanedFiles.length > 0) {
|
|
754
|
+
console.log(ansis.bold(` 🗑️ Delete ${analysisSummary.orphanedFiles.length} orphaned files:`));
|
|
755
|
+
analysisSummary.orphanedFiles.forEach(f => console.log(ansis.dim(` • ${f}`)));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if (analysisSummary.deadExports.length > 0) {
|
|
759
|
+
console.log(ansis.bold(` ✂️ Prune ${analysisSummary.deadExports.length} unused named exports:`));
|
|
760
|
+
analysisSummary.deadExports.forEach(e => console.log(ansis.dim(` • ${e.symbol} in ${e.file}:${e.line}`)));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
if (analysisSummary.unusedDependencies && (analysisSummary.unusedDependencies.length > 0 || (this.context.importedUnusedPackages && this.context.importedUnusedPackages.size > 0))) {
|
|
764
|
+
console.log(ansis.bold(` 📦 Remove ${analysisSummary.unusedDependencies.length} unused dependencies:`));
|
|
765
|
+
analysisSummary.unusedDependencies.forEach(d => console.log(ansis.dim(` • ${d.package} (${d.type} in ${d.manifest})`)));
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (analysisSummary.missingDependencies && analysisSummary.missingDependencies.length > 0) {
|
|
769
|
+
console.log(ansis.bold(` 📦 Add ${analysisSummary.missingDependencies.length} missing dependencies:`));
|
|
770
|
+
analysisSummary.missingDependencies.forEach(dep => console.log(ansis.dim(` • ${dep} (detected via source usage)`)));
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (analysisSummary.unlistedDependencies && analysisSummary.unlistedDependencies.length > 0) {
|
|
774
|
+
console.log(ansis.bold.red(` ⚠️ Missing Declarations (Unlisted Packages Detected):`));
|
|
775
|
+
analysisSummary.unlistedDependencies.forEach(u => console.log(ansis.dim(` • ${u.package} is imported in ${u.file} but missing from ${u.manifest}`)));
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
if (cycles.length > 0) {
|
|
779
|
+
console.log(ansis.bold.magenta(` 🔄 Circular Dependencies Detected (${cycles.length}):`));
|
|
780
|
+
cycles.forEach((cycle, idx) => {
|
|
781
|
+
const relativeCycle = cycle.map(p => path.relative(this.context.cwd, p).replace(/\\/g, '/'));
|
|
782
|
+
console.log(ansis.dim(` • Cycle #${idx + 1}: ${relativeCycle.join(' -> ')}`));
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
console.log(ansis.dim('------------------------------------------------------------'));
|
|
787
|
+
|
|
788
|
+
console.log("AUTOFIX CHECK:", this.context.autoFix); if (this.context.autoFix) {
|
|
789
|
+
let proceed = this.context.autoFix || this.context.options.skipConfirm;
|
|
790
|
+
if (!proceed && !this.context.autoFix) {
|
|
791
|
+
const answer = await rl.question(ansis.bold.cyan('\n❓ Apply these structural modifications? (y/N): '));
|
|
792
|
+
proceed = answer.toLowerCase() === 'y';
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (proceed) {
|
|
796
|
+
console.log(ansis.bold.cyan('\n🛠️ Executing refactoring logic...'));
|
|
797
|
+
|
|
798
|
+
// --- FIXED: Dependency removal happens BEFORE the healing cycle to ensure it sticks ---
|
|
799
|
+
const depsToRemove = [];
|
|
800
|
+
|
|
801
|
+
// Collect from summary
|
|
802
|
+
if (analysisSummary.unusedDependencies) {
|
|
803
|
+
for (const d of analysisSummary.unusedDependencies) depsToRemove.push(d);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
// Collect from context (importedUnusedPackages is populated during analysis)
|
|
807
|
+
if (this.context.importedUnusedPackages) {
|
|
808
|
+
for (const [pkgName, manifestPath] of this.context.importedUnusedPackages.entries()) {
|
|
809
|
+
if (!depsToRemove.some(d => d.package === pkgName)) {
|
|
810
|
+
depsToRemove.push({ package: pkgName, manifest: 'package.json' });
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// --- UPGRADE v5.7.0: Safety Whitelist ---
|
|
816
|
+
const ESSENTIAL_DEPS = new Set(['typescript', 'entkapp', '@types/node', 'ts-node', 'tsx', 'vitepress', 'vue']);
|
|
817
|
+
|
|
818
|
+
for (const dep of depsToRemove) {
|
|
819
|
+
if (ESSENTIAL_DEPS.has(dep.package)) {
|
|
820
|
+
if (this.context.verbose) console.log(ansis.dim(`🛡️ [SAFETY] Skipping essential dependency: ${dep.package}`));
|
|
821
|
+
continue;
|
|
822
|
+
}
|
|
823
|
+
try {
|
|
824
|
+
const absPath = dep.manifest.startsWith("/") ? dep.manifest : path.resolve(this.context.cwd, dep.manifest);
|
|
825
|
+
if (!existsSync(absPath)) continue;
|
|
826
|
+
const pkgContent = await fs.readFile(absPath, 'utf8');
|
|
827
|
+
const pkg = JSON.parse(pkgContent);
|
|
828
|
+
let removed = false;
|
|
829
|
+
if (pkg.dependencies && pkg.dependencies[dep.package]) {
|
|
830
|
+
delete pkg.dependencies[dep.package];
|
|
831
|
+
removed = true;
|
|
832
|
+
}
|
|
833
|
+
if (pkg.devDependencies && pkg.devDependencies[dep.package]) {
|
|
834
|
+
delete pkg.devDependencies[dep.package];
|
|
835
|
+
removed = true;
|
|
836
|
+
}
|
|
837
|
+
if (removed) {
|
|
838
|
+
console.log(ansis.bold.red(`📦 [FIX] Removing unused dependency [${dep.package}] from ${dep.manifest}`));
|
|
839
|
+
await fs.writeFile(absPath, JSON.stringify(pkg, null, 2) + '\n');
|
|
840
|
+
}
|
|
841
|
+
} catch (e) {
|
|
842
|
+
console.error(ansis.red(`❌ Failed to remove dependency ${dep.package}: ${e.message}`));
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
await this.selfHealer.runSelfHealingLifecycle(async () => {
|
|
847
|
+
for (const relPath of analysisSummary.orphanedFiles) {
|
|
848
|
+
const absPath = path.resolve(this.context.cwd, relPath);
|
|
849
|
+
console.log(ansis.red(`✂️ Removing unreferenced file: ${relPath}`));
|
|
850
|
+
await fs.rm(absPath);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
for (const unusedExport of analysisSummary.deadExports) {
|
|
854
|
+
const absPath = path.resolve(this.context.cwd, unusedExport.file);
|
|
855
|
+
const node = this.context.projectGraph.get(absPath);
|
|
856
|
+
if (!node) continue;
|
|
857
|
+
const meta = node.internalExports.get(unusedExport.symbol);
|
|
858
|
+
const safetyVerdict = await this.impactAnalyzer.verifyRefactorSafety(absPath, unusedExport.symbol, this.context.projectGraph);
|
|
859
|
+
if (safetyVerdict.isSafeToPrune) {
|
|
860
|
+
console.log(ansis.yellow(`⚡ Stripping unused export [${unusedExport.symbol}] from: ${unusedExport.file}:${unusedExport.line}`));
|
|
861
|
+
const nextText = await this.sourceRewriter.stripNamedExportSignature(absPath, unusedExport.symbol, meta);
|
|
862
|
+
await this.txManager.stageWrite(absPath, nextText);
|
|
863
|
+
await this.typeIntegrity.synchronizeDeclarationFile(absPath, unusedExport.symbol);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
} else {
|
|
868
|
+
console.log(ansis.bold.yellow('\n⚠️ Optimization plan aborted by user. No changes applied.'));
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Final diagnostics report
|
|
874
|
+
await this.reportDiagnostics();
|
|
875
|
+
|
|
876
|
+
await this.cacheManager.saveCacheManifest(this.context.projectGraph);
|
|
877
|
+
if (rl) rl.close();
|
|
878
|
+
console.log(ansis.bold.green('\n✨ Core optimization cycle completed smoothly. Codebase workspace is healthy.'));
|
|
879
|
+
|
|
880
|
+
} catch (criticalFault) {
|
|
881
|
+
console.error(ansis.bold.red(`\n🚨 Critical Operational Pipeline Failure: ${criticalFault.message}`));
|
|
882
|
+
if (criticalFault.stack) console.error(ansis.dim(criticalFault.stack));
|
|
883
|
+
process.exit(1);
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
async _generateVisualization(graph) {
|
|
888
|
+
console.log(ansis.bold.green('\n🌐 [VISUALIZER] Generating Interactive Execution Graph...'));
|
|
889
|
+
const nodes = [];
|
|
890
|
+
const edges = [];
|
|
891
|
+
const fileToIndex = new Map();
|
|
892
|
+
let idCounter = 1;
|
|
893
|
+
|
|
894
|
+
for (const [file, node] of graph.entries()) {
|
|
895
|
+
const relPath = path.relative(this.context.cwd, file);
|
|
896
|
+
const id = idCounter++;
|
|
897
|
+
fileToIndex.set(file, id);
|
|
898
|
+
nodes.push({
|
|
899
|
+
id,
|
|
900
|
+
label: relPath,
|
|
901
|
+
color: node.isLibraryEntry ? '#ff7675' : '#74b9ff',
|
|
902
|
+
shape: node.isLibraryEntry ? 'diamond' : 'dot',
|
|
903
|
+
size: node.isLibraryEntry ? 25 : 15
|
|
87
904
|
});
|
|
88
905
|
}
|
|
89
906
|
|
|
90
|
-
|
|
907
|
+
for (const [file, node] of graph.entries()) {
|
|
908
|
+
const fromId = fileToIndex.get(file);
|
|
909
|
+
node.outgoingEdges.forEach(edgeFile => {
|
|
910
|
+
const toId = fileToIndex.get(edgeFile);
|
|
911
|
+
if (toId) edges.push({ from: fromId, to: toId, arrows: 'to' });
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
const html = `<!DOCTYPE html><html><head><title>entkapp Execution Graph</title><script src="https://unpkg.com"></script><style>body,html{margin:0;padding:0;width:100%;height:100%;overflow:hidden;background-color:#2d3436;color:#dfe6e9;font-family:sans-serif;}#network{width:100%;height:100%;}#header{position:absolute;top:10px;left:10px;z-index:10;pointer-events:none;}h1{margin:0;font-size:1.5rem;color:#fab1a0;}.legend{margin-top:5px;font-size:0.9rem;}.legend-item{display:inline-block;margin-right:15px;}.dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:5px;}</style></head><body><div id="header"><h1>entkapp Execution Graph</h1><div class="legend"><div class="legend-item"><span class="dot" style="background-color:#ff7675;border-radius:0;transform:rotate(45deg);"></span> Entry Point</div><div class="legend-item"><span class="dot" style="background-color:#74b9ff;"></span> Module</div></div></div><div id="network"></div><script>const nodes=new vis.DataSet(${JSON.stringify(nodes)});const edges=new vis.DataSet(${JSON.stringify(edges)});const container=document.getElementById('network');const data={nodes,edges};const options={nodes:{font:{color:'#dfe6e9',size:14}},edges:{color:{color:'#636e72',highlight:#fab1a0},width:2},physics:{forceAtlas2Based:{gravitationalConstant:-50,centralGravity:0.01,springLength:100,springConstant:0.08},maxVelocity:50,solver:'forceAtlas2Based',timestep:0.35,stabilization:{iterations:150}}};new vis.Network(container,data,options);</script></body></html>`;
|
|
916
|
+
|
|
917
|
+
const http = await import('http');
|
|
918
|
+
const server = http.createServer((req, res) => {
|
|
919
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
920
|
+
res.end(html);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
const port = 3000;
|
|
924
|
+
server.listen(port, () => {
|
|
925
|
+
console.log(ansis.bold.cyan(`\n🚀 Web Viewer active at: http://localhost:${port}`));
|
|
926
|
+
});
|
|
927
|
+
|
|
928
|
+
return new Promise((resolve) => {
|
|
929
|
+
process.on('SIGINT', () => {
|
|
930
|
+
server.close();
|
|
931
|
+
resolve();
|
|
932
|
+
});
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
async reportDiagnostics() {
|
|
937
|
+
const summary = await this.context.generateSummaryReport();
|
|
938
|
+
|
|
939
|
+
if (this.context.importedUnusedPackages.size > 0) {
|
|
940
|
+
console.log(ansis.bold.red('\n📋 UNUSED DEPENDENCIES DETECTED:'));
|
|
941
|
+
// FIX: importedUnusedPackages is a Map<PackageName, ManifestPath>.
|
|
942
|
+
// We must iterate over the keys (package names) instead of the values (manifest paths).
|
|
943
|
+
for (const pkgName of this.context.importedUnusedPackages.keys()) {
|
|
944
|
+
if (!pkgName.startsWith('node:')) {
|
|
945
|
+
console.log(ansis.red(` • ${pkgName}`));
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
if (summary.orphanedFiles.length > 0) {
|
|
951
|
+
console.log(ansis.bold.yellow('\n✂️ UNUSED FILES DETECTED:'));
|
|
952
|
+
summary.orphanedFiles.forEach(f => console.log(ansis.yellow(` • ${f}`)));
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
console.log(ansis.bold.cyan('\n🔍 Deep Static Analysis Report (Code Smells & Risks):'));
|
|
956
|
+
let totalIssues = 0;
|
|
957
|
+
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
958
|
+
if (node.diagnostics && node.diagnostics.length > 0) {
|
|
959
|
+
const relPath = path.relative(this.context.cwd, filePath);
|
|
960
|
+
console.log(ansis.yellow(`\n📄 ${relPath}:`));
|
|
961
|
+
node.diagnostics.forEach(issue => {
|
|
962
|
+
totalIssues++;
|
|
963
|
+
console.log(ansis.red(` [${issue.severity.toUpperCase()}] Line ${issue.line}: ${issue.message}`));
|
|
964
|
+
console.log(ansis.dim(` 📖 Learn more: ${issue.link}`));
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (totalIssues === 0) {
|
|
969
|
+
console.log(ansis.green(' ✅ No critical code smells or runtime risks detected.'));
|
|
970
|
+
} else {
|
|
971
|
+
console.log(ansis.bold.yellow(`\n⚠️ Found ${totalIssues} potential issues. Please review the links above.`));
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
async discoverSourceFiles(dir, fileList) { console.log("DISCOVER IN:", dir);
|
|
976
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
977
|
+
for (const entry of entries) {
|
|
978
|
+
const res = path.resolve(dir, entry.name);
|
|
979
|
+
if (entry.isDirectory()) {
|
|
980
|
+
if (entry.name === 'node_modules' || entry.name === '.git' || entry.name === '.entkapp-cache') continue;
|
|
981
|
+
await this.discoverSourceFiles(res, fileList);
|
|
982
|
+
} else {
|
|
983
|
+
console.log("FOUND ENTRY:", entry.name, "IS DIR:", entry.isDirectory()); const ext = path.extname(entry.name);
|
|
984
|
+
if (['.js', '.mjs', '.jsx', '.ts', '.tsx', '.vue', '.svelte'].includes(ext) || entry.name === 'package.json') {
|
|
985
|
+
fileList.push(res);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
async linkDependencyGraph() {
|
|
992
|
+
const slashify = (p) => {
|
|
993
|
+
let abs = path.resolve(this.context.cwd, p).replace(/\\/g, '/');
|
|
994
|
+
if (/^[a-z]:\//i.test(abs)) {
|
|
995
|
+
abs = abs.charAt(0).toUpperCase() + abs.slice(1);
|
|
996
|
+
}
|
|
997
|
+
return abs;
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// =========================================================================
|
|
1001
|
+
// 🔄 PHASE 1: SAUBERE KANTEN-VERKNÜPFUNG (Zuerst das Netz spinnen!)
|
|
1002
|
+
// =========================================================================
|
|
1003
|
+
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
1004
|
+
const cleanSrcPath = slashify(filePath);
|
|
1005
|
+
|
|
1006
|
+
// Pass A: Link all explicit and dynamic imports safely
|
|
1007
|
+
const allDirectImports = new Set([...node.explicitImports, ...node.dynamicImports]);
|
|
1008
|
+
for (const specifier of allDirectImports) {
|
|
1009
|
+
if (specifier.startsWith('__DYNAMIC_PATTERN__:')) continue;
|
|
1010
|
+
if (this.context.verbose) console.log(`[Linker-DEBUG] Attempting to resolve ${specifier} from ${cleanSrcPath}`);
|
|
1011
|
+
try {
|
|
1012
|
+
const resolvedPath = this.resolver.resolveModulePath(cleanSrcPath, specifier);
|
|
1013
|
+
if (this.context.verbose) console.log(`[Linker-DEBUG] Resolved to: ${resolvedPath}`);
|
|
1014
|
+
if (resolvedPath && this.context.projectGraph.has(resolvedPath)) {
|
|
1015
|
+
const cleanTargetPath = slashify(resolvedPath);
|
|
1016
|
+
this.context.projectGraph.get(cleanTargetPath).incomingEdges.add(cleanSrcPath);
|
|
1017
|
+
node.outgoingEdges.add(cleanTargetPath);
|
|
1018
|
+
}
|
|
1019
|
+
} catch (e) {}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Pass B: Link named-symbol imports through barrel/re-export chains safely
|
|
1023
|
+
for (const specToken of node.importedSymbols) {
|
|
1024
|
+
try {
|
|
1025
|
+
const delimiterIndex = specToken.lastIndexOf(':');
|
|
1026
|
+
if (delimiterIndex === -1) continue;
|
|
1027
|
+
const specifier = specToken.slice(0, delimiterIndex);
|
|
1028
|
+
const symbol = specToken.slice(delimiterIndex + 1);
|
|
1029
|
+
|
|
1030
|
+
const resolvedPath = this.resolver.resolveModulePath(cleanSrcPath, specifier);
|
|
1031
|
+
if (!resolvedPath) continue;
|
|
1032
|
+
const cleanResolvedPath = slashify(resolvedPath);
|
|
1033
|
+
|
|
1034
|
+
if (symbol === '*') {
|
|
1035
|
+
if (this.context.projectGraph.has(cleanResolvedPath)) {
|
|
1036
|
+
this.context.projectGraph.get(cleanResolvedPath).incomingEdges.add(cleanSrcPath);
|
|
1037
|
+
node.outgoingEdges.add(cleanResolvedPath);
|
|
1038
|
+
|
|
1039
|
+
const targetNode = this.context.projectGraph.get(cleanResolvedPath);
|
|
1040
|
+
for (const [expName, expMeta] of targetNode.internalExports.entries()) {
|
|
1041
|
+
if (expMeta.type === 're-export-all' || expMeta.type === 're-export') {
|
|
1042
|
+
const nestedPath = this.resolver.resolveModulePath(cleanResolvedPath, expMeta.source || expMeta.originalName);
|
|
1043
|
+
if (nestedPath && this.context.projectGraph.has(slashify(nestedPath))) {
|
|
1044
|
+
this.context.projectGraph.get(slashify(nestedPath)).incomingEdges.add(cleanResolvedPath);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
} else {
|
|
1050
|
+
const traceResolution = await this.barrelParser.determineSymbolDeclarationOrigin(cleanResolvedPath, symbol, this.context.projectGraph);
|
|
1051
|
+
if (traceResolution && traceResolution.originFile) {
|
|
1052
|
+
const cleanOriginFile = slashify(traceResolution.originFile);
|
|
1053
|
+
if (this.context.projectGraph.has(cleanOriginFile)) {
|
|
1054
|
+
this.context.projectGraph.get(cleanOriginFile).incomingEdges.add(cleanSrcPath);
|
|
1055
|
+
node.outgoingEdges.add(cleanOriginFile);
|
|
1056
|
+
if (!this.context.importUsageRegistry) this.context.importUsageRegistry = new Set();
|
|
1057
|
+
this.context.importUsageRegistry.add(`${cleanOriginFile}:${traceResolution.originSymbol}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
} catch (e) {}
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// =========================================================================
|
|
1066
|
+
// 🔄 PHASE 2: SELEKTIVE ENTRY POINT IMMUNITÄT (Erst jetzt Heuristik prüfen!)
|
|
1067
|
+
// =========================================================================
|
|
1068
|
+
const potentialEntryPoints = new Set();
|
|
1069
|
+
let hasManifestEntries = false;
|
|
1070
|
+
|
|
1071
|
+
try {
|
|
1072
|
+
const pkgPath = path.join(this.context.cwd, 'package.json');
|
|
1073
|
+
if (existsSync(pkgPath)) {
|
|
1074
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
1075
|
+
const packageEntries = this.workspaceGraph.calculatePackageExportsEntries(pkg, this.context.cwd);
|
|
1076
|
+
if (packageEntries.length > 0) {
|
|
1077
|
+
hasManifestEntries = true;
|
|
1078
|
+
for (const absEntry of packageEntries) {
|
|
1079
|
+
potentialEntryPoints.add(slashify(absEntry));
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
} catch (e) {}
|
|
1084
|
+
|
|
1085
|
+
if (!hasManifestEntries && this.context.entryPoints.length === 0) {
|
|
1086
|
+
const rootIndexFiles = ['src/index.ts', 'src/index.mjs', 'src/index.tsx', 'src/index.jsx', 'index.ts', 'index.mjs', 'src/main.ts', 'src/main.mjs', 'main.ts', 'main.mjs'];
|
|
1087
|
+
for (const indexFile of rootIndexFiles) potentialEntryPoints.add(slashify(indexFile));
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
const protectedFiles = new Set();
|
|
1091
|
+
for (const [filePath] of this.context.projectGraph.entries()) {
|
|
1092
|
+
const fileName = path.basename(filePath);
|
|
1093
|
+
if (/\.(test|spec|e2e)\.(ts|js|tsx|jsx)$/.test(fileName) || filePath.includes('/__tests__/') || filePath.includes('/tests/') || /\.(config|rc)\.(ts|js|mjs|cjs)$/.test(fileName)) {
|
|
1094
|
+
protectedFiles.add(slashify(filePath));
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
// JETZT haben die Kanten verlässliche incomingEdges-Größen!
|
|
1099
|
+
for (const [filePath, node] of this.context.projectGraph.entries()) {
|
|
1100
|
+
const absPath = slashify(filePath);
|
|
1101
|
+
const isProtected = protectedFiles.has(absPath);
|
|
1102
|
+
const isManifestEntry = potentialEntryPoints.has(absPath);
|
|
1103
|
+
|
|
1104
|
+
const hasNoIncoming = node.incomingEdges.size === 0;
|
|
1105
|
+
const hasOutgoing = node.outgoingEdges.size > 0 || node.externalPackageUsage.size > 0 || node.dynamicImports.size > 0;
|
|
1106
|
+
|
|
1107
|
+
let isExplicitlyDeclared = isManifestEntry;
|
|
1108
|
+
if (!isExplicitlyDeclared) {
|
|
1109
|
+
const cleanAbsPath = absPath.replace(/\.[^/.]+$/, "");
|
|
1110
|
+
for (const candidate of potentialEntryPoints) {
|
|
1111
|
+
if (candidate.replace(/\.[^/.]+$/, "") === cleanAbsPath) {
|
|
1112
|
+
isExplicitlyDeclared = true;
|
|
1113
|
+
break;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const usesItsImports = node.instantiatedIdentifiers && node.instantiatedIdentifiers.size > 0;
|
|
1119
|
+
const isLikelyBarrel = !isExplicitlyDeclared && (node.isPureBarrel || node.isBarrel || (!usesItsImports && hasOutgoing));
|
|
1120
|
+
|
|
1121
|
+
let finalIsEntry = false;
|
|
1122
|
+
if (isProtected || isExplicitlyDeclared) {
|
|
1123
|
+
finalIsEntry = true;
|
|
1124
|
+
} else if (!hasManifestEntries && hasNoIncoming && hasOutgoing && !isLikelyBarrel) {
|
|
1125
|
+
// UPGRADE: Orphan files with imports are NOT entry points by default.
|
|
1126
|
+
// They must have side effects (detected in AST pass) to be entries.
|
|
1127
|
+
finalIsEntry = node.isEntry;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
node.isEntry = finalIsEntry;
|
|
1131
|
+
if (finalIsEntry && this.context.options.verbose) {
|
|
1132
|
+
console.log(`🎯 [ENTRY POINT CONFIRMED] Root secured: ${path.relative(this.context.cwd, absPath)}`);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
91
1135
|
}
|
|
92
1136
|
|
|
93
1137
|
async auditManifestSupplyChain(packageJsonPath) {
|
|
94
1138
|
try {
|
|
95
1139
|
const text = await fs.readFile(packageJsonPath, 'utf8');
|
|
96
1140
|
const data = JSON.parse(text);
|
|
1141
|
+
// FIX: manifestDependencies must be a Map of Sets for generateSummaryReport to work
|
|
97
1142
|
const depsObj = {
|
|
98
1143
|
dependencies: Object.keys(data.dependencies || {}),
|
|
99
1144
|
devDependencies: Object.keys(data.devDependencies || {}),
|
|
@@ -101,6 +1146,25 @@ export class RefactoringEngine {
|
|
|
101
1146
|
optionalDependencies: Object.keys(data.optionalDependencies || {})
|
|
102
1147
|
};
|
|
103
1148
|
this.context.manifestDependencies.set(packageJsonPath, depsObj);
|
|
1149
|
+
// FIX: Do NOT pre-populate importedUnusedPackages with ALL dependencies here.
|
|
1150
|
+
// This causes false-positive "unused" reports if the analysis phase fails to
|
|
1151
|
+
// correctly mark them as used. EngineContext.generateSummaryReport() is
|
|
1152
|
+
// responsible for identifying unused packages and populating this Map.
|
|
1153
|
+
|
|
1154
|
+
// Also register workspace manifests if not already done
|
|
1155
|
+
if (this.context.isWorkspaceEnabled && this.workspaceGraph) {
|
|
1156
|
+
for (const [dir, manifest] of this.workspaceGraph.packageManifests.entries()) {
|
|
1157
|
+
if (!this.context.manifestDependencies.has(manifest.manifestPath)) {
|
|
1158
|
+
const workspaceDeps = new Set([
|
|
1159
|
+
...Object.keys(manifest.dependencies || {}),
|
|
1160
|
+
...Object.keys(manifest.devDependencies || {}),
|
|
1161
|
+
...Object.keys(manifest.peerDependencies || {}),
|
|
1162
|
+
...Object.keys(manifest.optionalDependencies || {})
|
|
1163
|
+
]);
|
|
1164
|
+
this.context.manifestDependencies.set(manifest.manifestPath, workspaceDeps);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
104
1168
|
} catch (e) {}
|
|
105
1169
|
}
|
|
106
1170
|
|
|
@@ -109,6 +1173,24 @@ export class RefactoringEngine {
|
|
|
109
1173
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
110
1174
|
console.log(`⏱️ Analysis Duration: ${summary.executionDuration}`);
|
|
111
1175
|
console.log(`📂 Total Files Scanned: ${summary.totalFilesProcessed}`);
|
|
112
|
-
console.log(
|
|
1176
|
+
console.log(`💾 Cache Optimization: ${summary.graphCacheOptimization.ratio} hits`);
|
|
1177
|
+
console.log(ansis.dim('\n------------------------------------------------------------\n'));
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
hydrateNodeFromCache(node, cachedRecord) {
|
|
1181
|
+
if (cachedRecord.explicitImports) cachedRecord.explicitImports.forEach(i => node.explicitImports.add(i));
|
|
1182
|
+
if (cachedRecord.dynamicImports) cachedRecord.dynamicImports.forEach(i => node.dynamicImports.add(i));
|
|
1183
|
+
if (cachedRecord.importedSymbols) cachedRecord.importedSymbols.forEach(s => node.importedSymbols.add(s));
|
|
1184
|
+
if (cachedRecord.internalExports) Object.entries(cachedRecord.internalExports).forEach(([k, v]) => node.internalExports.set(k, v));
|
|
1185
|
+
if (cachedRecord.symbolSourceLocations) Object.entries(cachedRecord.symbolSourceLocations).forEach(([k, v]) => node.symbolSourceLocations.set(k, v));
|
|
1186
|
+
if (cachedRecord.externalPackageUsage) cachedRecord.externalPackageUsage.forEach(p => node.externalPackageUsage.add(p));
|
|
1187
|
+
if (cachedRecord.rawStringReferences) cachedRecord.rawStringReferences.forEach(r => node.rawStringReferences.add(r));
|
|
1188
|
+
if (cachedRecord.instantiatedIdentifiers) cachedRecord.instantiatedIdentifiers.forEach(id => node.instantiatedIdentifiers.add(id));
|
|
1189
|
+
if (cachedRecord.propertyAccessChains) cachedRecord.propertyAccessChains.forEach(c => node.propertyAccessChains.add(c));
|
|
1190
|
+
if (cachedRecord.localSuppressedRules) cachedRecord.localSuppressedRules.forEach(r => node.localSuppressedRules.add(r));
|
|
1191
|
+
if (cachedRecord.isLibraryEntry !== undefined) node.isLibraryEntry = cachedRecord.isLibraryEntry;
|
|
1192
|
+
if (cachedRecord.isEntry !== undefined) node.isEntry = cachedRecord.isEntry;
|
|
1193
|
+
if (cachedRecord.calculatedDynamicImports) node.calculatedDynamicImports = cachedRecord.calculatedDynamicImports;
|
|
1194
|
+
if (cachedRecord.globImports) node.globImports = cachedRecord.globImports;
|
|
113
1195
|
}
|
|
114
|
-
}
|
|
1196
|
+
}
|