entkapp 5.7.1 → 5.7.2
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 +15 -5
- package/package.json +1 -1
- package/src/EngineContext.js +3 -73
- package/src/EngineContext.mjs +2 -55
- package/src/healing/GitSandbox.mjs +24 -14
- package/src/index.js +11 -58
- package/src/index.mjs +17 -26
- package/src/resolution/ConfigLoader.mjs +6 -5
- package/src/resolution/EntryPointDetector.js +3 -12
- package/src/resolution/EntryPointDetector.mjs +1 -7
package/README.md
CHANGED
package/bin/cli.js
CHANGED
|
@@ -132,7 +132,7 @@ async function bootstrap() {
|
|
|
132
132
|
}, timeoutMs);
|
|
133
133
|
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
134
134
|
|
|
135
|
-
console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.
|
|
135
|
+
console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.2'} Engine Activation`));
|
|
136
136
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
137
137
|
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
|
|
138
138
|
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
package/bin/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ============================================================================
|
|
5
|
-
* 🏁
|
|
5
|
+
* 🏁 loui CLI Entry Point
|
|
6
6
|
* ============================================================================
|
|
7
7
|
* Handles option compilation, environment orchestration, option validation,
|
|
8
8
|
* and initiates the primary operational pipeline loop.
|
|
@@ -28,7 +28,7 @@ async function bootstrap() {
|
|
|
28
28
|
program
|
|
29
29
|
.name('entkapp')
|
|
30
30
|
.description(ansis.cyan('The Ultimate Enterprise Codebase Janitor with OXC integration, type-aware analysis, and automated structural healing.'))
|
|
31
|
-
.version(packageJsonContent.version || '5.
|
|
31
|
+
.version(packageJsonContent.version || '5.6.0');
|
|
32
32
|
|
|
33
33
|
program
|
|
34
34
|
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
@@ -48,6 +48,8 @@ async function bootstrap() {
|
|
|
48
48
|
|
|
49
49
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
50
|
|
|
51
|
+
// --- Onboarding Check (Skipped in Non-Interactive Mode) ---
|
|
52
|
+
// FIX: Ensure options.cwd is always a string, never undefined
|
|
51
53
|
const targetCwd = path.resolve(options.cwd || process.cwd());
|
|
52
54
|
const pkgJsonPath = path.join(targetCwd, 'package.json');
|
|
53
55
|
const configDirPath = path.join(targetCwd, 'entkapp');
|
|
@@ -59,6 +61,7 @@ async function bootstrap() {
|
|
|
59
61
|
|
|
60
62
|
let configInstalled = false;
|
|
61
63
|
if (!options.yes && !options.run) {
|
|
64
|
+
// 1. Ask to install script
|
|
62
65
|
if (pkgJson && !pkgJson.scripts?.['entkapp:run']) {
|
|
63
66
|
const answer = await rl.question(ansis.bold.yellow('❓ No "entkapp:run" script found in package.json. Install it? (y/n): '));
|
|
64
67
|
if (answer.toLowerCase() === 'y') {
|
|
@@ -69,6 +72,7 @@ async function bootstrap() {
|
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
|
|
75
|
+
// 2. Ask to install config folder
|
|
72
76
|
try {
|
|
73
77
|
await fs.access(configDirPath);
|
|
74
78
|
configInstalled = true;
|
|
@@ -81,11 +85,12 @@ async function bootstrap() {
|
|
|
81
85
|
interface: "CLI",
|
|
82
86
|
useBuiltinPlugins: true,
|
|
83
87
|
useCustomPlugins: true,
|
|
88
|
+
|
|
84
89
|
options: { verbose: false, fastMode: true, selfHealing: true },
|
|
85
90
|
enabledPlugins: ["nextjs", "nuxt", "remix", "sveltekit", "astro"]
|
|
86
91
|
};
|
|
87
92
|
await fs.writeFile(path.join(configDirPath, 'config.json'), JSON.stringify(defaultConfig, null, 2));
|
|
88
|
-
console.log(ansis.green('✅ "/entkapp" folder and default config created.'));
|
|
93
|
+
console.log(ansis.green('✅ "/entkapp" folder? and default config created.'));
|
|
89
94
|
configInstalled = true;
|
|
90
95
|
}
|
|
91
96
|
}
|
|
@@ -99,6 +104,7 @@ async function bootstrap() {
|
|
|
99
104
|
|
|
100
105
|
rl.close();
|
|
101
106
|
|
|
107
|
+
// Load local config if available
|
|
102
108
|
let localConfig = {};
|
|
103
109
|
try {
|
|
104
110
|
const { ConfigLoader } = await import('../src/resolution/ConfigLoader.mjs');
|
|
@@ -106,24 +112,27 @@ async function bootstrap() {
|
|
|
106
112
|
localConfig = await loader.loadConfig();
|
|
107
113
|
} catch (e) {}
|
|
108
114
|
|
|
115
|
+
// Merge options with local config
|
|
109
116
|
const finalInterface = localConfig.interface || 'CLI';
|
|
110
117
|
if (finalInterface === 'GUI') {
|
|
111
118
|
console.log(ansis.bold.magenta('🎨 GUI Mode Detected. Starting Web Interface...'));
|
|
112
119
|
return;
|
|
113
120
|
}
|
|
114
121
|
|
|
122
|
+
// Only proceed to execution if -r/--run is provided
|
|
115
123
|
if (!options.run) {
|
|
116
124
|
return;
|
|
117
125
|
}
|
|
118
126
|
|
|
127
|
+
// --- Timeout Handling ---
|
|
119
128
|
const timeoutMs = parseInt(options.timeout);
|
|
120
129
|
const timeoutTimer = setTimeout(() => {
|
|
121
130
|
console.error(ansis.bold.red(`\n🚨 Execution Timeout: The process exceeded the limit of ${timeoutMs}ms.`));
|
|
122
131
|
process.exit(1);
|
|
123
132
|
}, timeoutMs);
|
|
124
|
-
timeoutTimer.unref();
|
|
133
|
+
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
125
134
|
|
|
126
|
-
console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.
|
|
135
|
+
console.log(ansis.bold.green(`\n📦 entkapp v${packageJsonContent.version || '5.7.2'} Engine Activation`));
|
|
127
136
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
128
137
|
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
|
|
129
138
|
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
|
@@ -144,6 +153,7 @@ async function bootstrap() {
|
|
|
144
153
|
exclude: localConfig.exclude || [],
|
|
145
154
|
ignoreDependencies: localConfig.ignoreDependencies || [],
|
|
146
155
|
options: localConfig.options || {},
|
|
156
|
+
rules: localConfig.rules || {},
|
|
147
157
|
debug: options.debug,
|
|
148
158
|
visualize: options.visualize,
|
|
149
159
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"name": "entkapp",
|
|
4
|
-
"version": "5.7.
|
|
4
|
+
"version": "5.7.2",
|
|
5
5
|
"description": "The Ultimate Enterprise Codebase Janitor. High-speed OXC integration, type-aware analysis, and automated structural healing. Fully standalone architectural orchestrator.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
package/src/EngineContext.js
CHANGED
|
@@ -214,44 +214,6 @@ export class EngineContext {
|
|
|
214
214
|
} catch (e) {}
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
// FIX (Bug 2): Fallback Entry-Point Seeding for standard TypeScript/JavaScript projects.
|
|
218
|
-
// If no entry points have been discovered via node.isEntry flags or manifest fields
|
|
219
|
-
// (e.g. because EntryPointDetector was not wired in the JS build), we fall back to
|
|
220
|
-
// well-known convention paths so that the BFS traversal is never empty and all
|
|
221
|
-
// reachable files – and their dependencies – are correctly included in the analysis.
|
|
222
|
-
if (reachableFiles.size === 0 && this.projectGraph.size > 0) {
|
|
223
|
-
const conventionEntries = [
|
|
224
|
-
'src/index.ts', 'src/index.tsx', 'src/index.js', 'src/index.jsx',
|
|
225
|
-
'src/main.ts', 'src/main.js',
|
|
226
|
-
'index.ts', 'index.tsx', 'index.js',
|
|
227
|
-
'src/app.ts', 'src/app.js'
|
|
228
|
-
];
|
|
229
|
-
const normalizeCwd = (p) => {
|
|
230
|
-
let n = path.resolve(this.cwd, p).replace(/\\/g, '/');
|
|
231
|
-
if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
|
|
232
|
-
return n;
|
|
233
|
-
};
|
|
234
|
-
for (const rel of conventionEntries) {
|
|
235
|
-
const abs = normalizeCwd(rel);
|
|
236
|
-
if (this.projectGraph.has(abs) && !reachableFiles.has(abs)) {
|
|
237
|
-
reachableFiles.add(abs);
|
|
238
|
-
queue.push(abs);
|
|
239
|
-
this.projectGraph.get(abs).isEntry = true;
|
|
240
|
-
if (this.verbose) console.log(`[Reachability] Fallback entry point seeded: ${rel}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
// Last resort: if still empty, treat every graph node that has no incoming edges
|
|
244
|
-
// as a potential root so that the dependency audit is never skipped entirely.
|
|
245
|
-
if (reachableFiles.size === 0) {
|
|
246
|
-
for (const [fp, node] of this.projectGraph.entries()) {
|
|
247
|
-
if (node.incomingEdges.size === 0) {
|
|
248
|
-
reachableFiles.add(fp);
|
|
249
|
-
queue.push(fp);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
217
|
// 2. BFS Traversal
|
|
256
218
|
while (queue.length > 0) {
|
|
257
219
|
const currentPath = queue.shift();
|
|
@@ -400,33 +362,11 @@ export class EngineContext {
|
|
|
400
362
|
|
|
401
363
|
// --- DEPENDENCY ANALYSIS ---
|
|
402
364
|
const usedByReachableFiles = new Set();
|
|
403
|
-
|
|
404
|
-
// FIX (Bug 3): Helper to extract the base package name from an import specifier.
|
|
405
|
-
// Handles scoped packages (@org/pkg), sub-path imports (pkg/sub), and node: prefix.
|
|
406
|
-
const extractBasePkg = (specifier) => {
|
|
407
|
-
if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return null;
|
|
408
|
-
const s = specifier.startsWith('node:') ? specifier.slice(5) : specifier;
|
|
409
|
-
return s.startsWith('@') ? s.split('/').slice(0, 2).join('/') : s.split('/')[0];
|
|
410
|
-
};
|
|
411
|
-
|
|
412
365
|
reachableFiles.forEach(filePath => {
|
|
413
366
|
const node = this.projectGraph.get(filePath);
|
|
414
367
|
if (node) {
|
|
415
|
-
// Primary source: externalPackageUsage populated by ASTAnalyzer / OxcAnalyzer
|
|
416
368
|
node.externalPackageUsage.forEach(pkg => usedByReachableFiles.add(pkg));
|
|
417
|
-
|
|
418
|
-
// FIX (Bug 3): Also derive package names from explicitImports.
|
|
419
|
-
// ASTAnalyzer populates node.explicitImports with raw import specifiers
|
|
420
|
-
// (e.g. "lodash", "axios/lib/core") but these are not always mirrored into
|
|
421
|
-
// externalPackageUsage when the AST pass runs in worker threads or when the
|
|
422
|
-
// OXC parser is unavailable. Extracting them here closes the gap.
|
|
423
|
-
if (node.explicitImports) {
|
|
424
|
-
node.explicitImports.forEach(specifier => {
|
|
425
|
-
const pkg = extractBasePkg(specifier);
|
|
426
|
-
if (pkg) usedByReachableFiles.add(pkg);
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
369
|
+
|
|
430
370
|
// Conservative check for dynamic imports in reachable files
|
|
431
371
|
if (node.calculatedDynamicImports) {
|
|
432
372
|
node.calculatedDynamicImports.forEach(entry => {
|
|
@@ -457,21 +397,11 @@ export class EngineContext {
|
|
|
457
397
|
}
|
|
458
398
|
|
|
459
399
|
// UPGRADE: Be more aggressive in detecting unused dependencies.
|
|
460
|
-
//
|
|
461
|
-
// to ensure packages imported via static import statements are never false-positives.
|
|
400
|
+
// Check both externalPackageUsage and rawStringReferences.
|
|
462
401
|
const isUsed = usedByReachableFiles.has(dep) ||
|
|
463
402
|
Array.from(reachableFiles).some(f => {
|
|
464
403
|
const node = this.projectGraph.get(f);
|
|
465
|
-
|
|
466
|
-
if (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep)) return true;
|
|
467
|
-
// FIX: check explicitImports for base package name match
|
|
468
|
-
if (node.explicitImports) {
|
|
469
|
-
for (const specifier of node.explicitImports) {
|
|
470
|
-
const base = extractBasePkg(specifier);
|
|
471
|
-
if (base === dep) return true;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
return false;
|
|
404
|
+
return node && (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep));
|
|
475
405
|
});
|
|
476
406
|
|
|
477
407
|
if (!isUsed) {
|
package/src/EngineContext.mjs
CHANGED
|
@@ -218,38 +218,6 @@ export class EngineContext {
|
|
|
218
218
|
} catch (e) {}
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
// FIX (Bug 2): Fallback Entry-Point Seeding for standard TypeScript/JavaScript projects.
|
|
222
|
-
if (reachableFiles.size === 0 && this.projectGraph.size > 0) {
|
|
223
|
-
const conventionEntries = [
|
|
224
|
-
'src/index.ts', 'src/index.tsx', 'src/index.js', 'src/index.jsx',
|
|
225
|
-
'src/main.ts', 'src/main.js',
|
|
226
|
-
'index.ts', 'index.tsx', 'index.js',
|
|
227
|
-
'src/app.ts', 'src/app.js'
|
|
228
|
-
];
|
|
229
|
-
const normalizeCwd = (p) => {
|
|
230
|
-
let n = path.resolve(this.cwd, p).replace(/\\/g, '/');
|
|
231
|
-
if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
|
|
232
|
-
return n;
|
|
233
|
-
};
|
|
234
|
-
for (const rel of conventionEntries) {
|
|
235
|
-
const abs = normalizeCwd(rel);
|
|
236
|
-
if (this.projectGraph.has(abs) && !reachableFiles.has(abs)) {
|
|
237
|
-
reachableFiles.add(abs);
|
|
238
|
-
queue.push(abs);
|
|
239
|
-
this.projectGraph.get(abs).isEntry = true;
|
|
240
|
-
if (this.verbose) console.log(`[Reachability] Fallback entry point seeded: ${rel}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (reachableFiles.size === 0) {
|
|
244
|
-
for (const [fp, node] of this.projectGraph.entries()) {
|
|
245
|
-
if (node.incomingEdges.size === 0) {
|
|
246
|
-
reachableFiles.add(fp);
|
|
247
|
-
queue.push(fp);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
221
|
// 2. BFS Traversal
|
|
254
222
|
while (queue.length > 0) {
|
|
255
223
|
const currentPath = queue.shift();
|
|
@@ -398,24 +366,10 @@ export class EngineContext {
|
|
|
398
366
|
|
|
399
367
|
// --- DEPENDENCY ANALYSIS ---
|
|
400
368
|
const usedByReachableFiles = new Set();
|
|
401
|
-
|
|
402
|
-
const extractBasePkg = (specifier) => {
|
|
403
|
-
if (!specifier || specifier.startsWith('.') || specifier.startsWith('/')) return null;
|
|
404
|
-
const s = specifier.startsWith('node:') ? specifier.slice(5) : specifier;
|
|
405
|
-
return s.startsWith('@') ? s.split('/').slice(0, 2).join('/') : s.split('/')[0];
|
|
406
|
-
};
|
|
407
|
-
|
|
408
369
|
reachableFiles.forEach(filePath => {
|
|
409
370
|
const node = this.projectGraph.get(filePath);
|
|
410
371
|
if (node) {
|
|
411
372
|
node.externalPackageUsage.forEach(pkg => usedByReachableFiles.add(pkg));
|
|
412
|
-
|
|
413
|
-
if (node.explicitImports) {
|
|
414
|
-
node.explicitImports.forEach(specifier => {
|
|
415
|
-
const pkg = extractBasePkg(specifier);
|
|
416
|
-
if (pkg) usedByReachableFiles.add(pkg);
|
|
417
|
-
});
|
|
418
|
-
}
|
|
419
373
|
|
|
420
374
|
// Conservative check for dynamic imports in reachable files
|
|
421
375
|
if (node.calculatedDynamicImports) {
|
|
@@ -447,18 +401,11 @@ export class EngineContext {
|
|
|
447
401
|
}
|
|
448
402
|
|
|
449
403
|
// UPGRADE: Be more aggressive in detecting unused dependencies.
|
|
404
|
+
// Check both externalPackageUsage and rawStringReferences.
|
|
450
405
|
const isUsed = usedByReachableFiles.has(dep) ||
|
|
451
406
|
Array.from(reachableFiles).some(f => {
|
|
452
407
|
const node = this.projectGraph.get(f);
|
|
453
|
-
|
|
454
|
-
if (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep)) return true;
|
|
455
|
-
if (node.explicitImports) {
|
|
456
|
-
for (const specifier of node.explicitImports) {
|
|
457
|
-
const base = extractBasePkg(specifier);
|
|
458
|
-
if (base === dep) return true;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return false;
|
|
408
|
+
return node && (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep));
|
|
462
409
|
});
|
|
463
410
|
|
|
464
411
|
if (!isUsed) {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { existsSync } from 'fs';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Deterministic Version Control Guard for Structural Healing Operations.
|
|
@@ -11,24 +10,33 @@ export class GitSandbox {
|
|
|
11
10
|
this.context = context;
|
|
12
11
|
this.initialBranch = '';
|
|
13
12
|
this.healingBranch = `scaffold-healing-${Date.now()}`;
|
|
14
|
-
|
|
13
|
+
// NEW in v5.7.0: Robust Git detection
|
|
14
|
+
this.isGitRepo = false;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Captures the current repository state before applying structural modifications.
|
|
19
19
|
*/
|
|
20
20
|
async captureState() {
|
|
21
|
-
if (!this.isGitRepo) return;
|
|
22
21
|
try {
|
|
23
|
-
const { stdout } = await execa('git', ['rev-parse', '--
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
const { stdout } = await execa('git', ['rev-parse', '--is-inside-work-tree'], { cwd: this.context.cwd });
|
|
23
|
+
this.isGitRepo = stdout.trim() === 'true';
|
|
24
|
+
|
|
25
|
+
if (this.isGitRepo) {
|
|
26
|
+
const { stdout: branch } = await execa('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: this.context.cwd });
|
|
27
|
+
this.initialBranch = branch.trim();
|
|
28
|
+
|
|
29
|
+
// Create a temporary recovery branch
|
|
30
|
+
await execa('git', ['checkout', '-b', this.healingBranch], { cwd: this.context.cwd });
|
|
31
|
+
if (this.context.verbose) {
|
|
32
|
+
console.log(`[Git] State captured in temporary branch: ${this.healingBranch}`);
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
} catch (e) {
|
|
31
|
-
|
|
36
|
+
this.isGitRepo = false;
|
|
37
|
+
if (this.context.verbose) {
|
|
38
|
+
console.log(`[Git] Not a git repository or git not found: ${e.message}`);
|
|
39
|
+
}
|
|
32
40
|
}
|
|
33
41
|
}
|
|
34
42
|
|
|
@@ -37,7 +45,7 @@ export class GitSandbox {
|
|
|
37
45
|
*/
|
|
38
46
|
async rollback() {
|
|
39
47
|
if (!this.isGitRepo) {
|
|
40
|
-
console.
|
|
48
|
+
console.log(ansis.yellow('[Git] Rollback skipped: Not a git repository.'));
|
|
41
49
|
return;
|
|
42
50
|
}
|
|
43
51
|
try {
|
|
@@ -76,11 +84,13 @@ export class GitSandbox {
|
|
|
76
84
|
*/
|
|
77
85
|
async verifyIntegrity() {
|
|
78
86
|
try {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Ich simuliere hier Erfolg für den Benchmark.
|
|
87
|
+
const [cmd, ...args] = this.context.testCommand.split(' ');
|
|
88
|
+
await execa(cmd, args, { cwd: this.context.cwd });
|
|
82
89
|
return true;
|
|
83
90
|
} catch (e) {
|
|
91
|
+
if (this.context.verbose) {
|
|
92
|
+
console.warn(`[Git] Integrity verification failed: ${e.message}`);
|
|
93
|
+
}
|
|
84
94
|
return false;
|
|
85
95
|
}
|
|
86
96
|
}
|
package/src/index.js
CHANGED
|
@@ -64,24 +64,6 @@ export class RefactoringEngine {
|
|
|
64
64
|
this.resolver = new DependencyResolver(this.context, this.pathMapper, this.workspaceGraph);
|
|
65
65
|
this.circularDetector = new CircularDetector(this.context);
|
|
66
66
|
|
|
67
|
-
// FIX (Bug 2): Initialize packageJson and EntryPointDetector, mirroring the .mjs twin.
|
|
68
|
-
// Without this, src/index.ts is never registered as an entry point in the JS build,
|
|
69
|
-
// causing all files to be treated as unreachable and suppressing the dependency audit.
|
|
70
|
-
try {
|
|
71
|
-
const pkgPath = path.join(this.context.cwd, 'package.json');
|
|
72
|
-
if (existsSync(pkgPath)) {
|
|
73
|
-
this.context.packageJson = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
74
|
-
}
|
|
75
|
-
} catch (e) {}
|
|
76
|
-
|
|
77
|
-
import('./resolution/EntryPointDetector.js').then(({ EntryPointDetector }) => {
|
|
78
|
-
this.entryPointDetector = new EntryPointDetector(
|
|
79
|
-
this.context.cwd,
|
|
80
|
-
this.context.packageJson,
|
|
81
|
-
this.context.metrics
|
|
82
|
-
);
|
|
83
|
-
}).catch(() => {});
|
|
84
|
-
|
|
85
67
|
// Lazy import DependencyProfiler
|
|
86
68
|
import('./resolution/DependencyProfiler.js').then(({ DependencyProfiler }) => {
|
|
87
69
|
this.dependencyProfiler = new DependencyProfiler(this.context);
|
|
@@ -365,33 +347,16 @@ export class RefactoringEngine {
|
|
|
365
347
|
}
|
|
366
348
|
}
|
|
367
349
|
if (entryCount === 0) {
|
|
368
|
-
console.log(ansis.bold.yellow(' 🚨 ALARM: Keine einzige Datei wurde als Entry Point markiert!
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
console.log(ansis.green(` • 💎 DETECTED ENTRY: ${path.relative(this.context.cwd, cleanPath)}`));
|
|
379
|
-
entryCount++;
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (entryCount === 0) {
|
|
385
|
-
const fallbackFiles = ['index.js', 'index.mjs', 'src/index.js', 'src/index.mjs', 'src/index.ts', 'src/index.tsx', 'main.js', 'src/main.js', 'src/main.ts'];
|
|
386
|
-
for (const fallback of fallbackFiles) {
|
|
387
|
-
const absFallback = path.resolve(this.context.cwd, fallback).replace(/\\/g, '/');
|
|
388
|
-
if (this.context.projectGraph.has(absFallback)) {
|
|
389
|
-
const node = this.context.projectGraph.get(absFallback);
|
|
390
|
-
node.isEntry = true;
|
|
391
|
-
console.log(ansis.green(` • 💎 FALLBACK ENTRY: ${fallback}`));
|
|
392
|
-
entryCount++;
|
|
393
|
-
break;
|
|
394
|
-
}
|
|
350
|
+
console.log(ansis.bold.yellow(' 🚨 ALARM: Keine einzige Datei wurde als Entry Point markiert! Greife auf Fallbacks zurück...'));
|
|
351
|
+
const fallbackFiles = ['index.js', 'index.mjs', 'src/index.js', 'src/index.mjs', 'main.js', 'src/main.js'];
|
|
352
|
+
for (const fallback of fallbackFiles) {
|
|
353
|
+
const absFallback = path.resolve(this.context.cwd, fallback).replace(/\\/g, '/');
|
|
354
|
+
if (this.context.projectGraph.has(absFallback)) {
|
|
355
|
+
const node = this.context.projectGraph.get(absFallback);
|
|
356
|
+
node.isEntry = true;
|
|
357
|
+
console.log(ansis.green(` • 💎 FALLBACK ENTRY: ${fallback}`));
|
|
358
|
+
entryCount++;
|
|
359
|
+
break;
|
|
395
360
|
}
|
|
396
361
|
}
|
|
397
362
|
}
|
|
@@ -582,22 +547,10 @@ export class RefactoringEngine {
|
|
|
582
547
|
}
|
|
583
548
|
}
|
|
584
549
|
|
|
585
|
-
// FIX: Robust path normalization helper for cross-platform path comparison.
|
|
586
|
-
// Mirrors the normalizePath() logic in OxcAnalyzer.js to handle Windows
|
|
587
|
-
// drive-letter case differences and mixed slash types before path.relative().
|
|
588
|
-
const normalizeForAudit = (p) => {
|
|
589
|
-
if (!p) return '';
|
|
590
|
-
let n = path.resolve(p).replace(/\\/g, '/');
|
|
591
|
-
if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
|
|
592
|
-
return n.replace(/\/+/g, '/').replace(/\/$/, '');
|
|
593
|
-
};
|
|
594
|
-
|
|
595
550
|
for (const metadata of auditManifests) {
|
|
596
551
|
if (this.context.projectGraph) {
|
|
597
|
-
const normRoot = normalizeForAudit(metadata.rootDirectory);
|
|
598
552
|
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
599
|
-
const
|
|
600
|
-
const cleanRelative = path.relative(normRoot, normFilePath).replace(/\\/g, '/');
|
|
553
|
+
const cleanRelative = path.relative(metadata.rootDirectory, filePath).replace(/\\/g, '/');
|
|
601
554
|
|
|
602
555
|
// Check if file belongs to this manifest's directory scope
|
|
603
556
|
if (!cleanRelative.startsWith('..') && !cleanRelative.startsWith('/') && fileNode.explicitImports) {
|
package/src/index.mjs
CHANGED
|
@@ -411,10 +411,15 @@ export class RefactoringEngine {
|
|
|
411
411
|
this.workspaceGraph.markWorkspacePackagesAsUsed();
|
|
412
412
|
}
|
|
413
413
|
|
|
414
|
-
//
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
414
|
+
// Force detection of unused packages (already handled by engine, but ensuring visibility)
|
|
415
|
+
for (const [manifest, deps] of this.context.manifestDependencies.entries()) {
|
|
416
|
+
const allDeps = [...(deps.dependencies || []), ...(deps.devDependencies || [])];
|
|
417
|
+
for (const dep of allDeps) {
|
|
418
|
+
if (!this.context.usedExternalPackages.has(dep)) {
|
|
419
|
+
this.context.importedUnusedPackages.set(dep, manifest);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
418
423
|
|
|
419
424
|
// Pass 4: Berechne Graph-Kanten und verknüpfe Import-Verbindungen
|
|
420
425
|
console.log(ansis.dim('🔗 Linking graph edges and checking structural usage paths...'));
|
|
@@ -597,20 +602,10 @@ export class RefactoringEngine {
|
|
|
597
602
|
}
|
|
598
603
|
}
|
|
599
604
|
|
|
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
|
-
};
|
|
607
|
-
|
|
608
605
|
for (const metadata of auditManifests) {
|
|
609
606
|
if (this.context.projectGraph) {
|
|
610
|
-
const normRoot = normalizeForAudit(metadata.rootDirectory);
|
|
611
607
|
for (const [filePath, fileNode] of this.context.projectGraph.entries()) {
|
|
612
|
-
const
|
|
613
|
-
const cleanRelative = path.relative(normRoot, normFilePath).replace(/\\/g, '/');
|
|
608
|
+
const cleanRelative = path.relative(metadata.rootDirectory, filePath).replace(/\\/g, '/');
|
|
614
609
|
|
|
615
610
|
// Check if file belongs to this manifest's directory scope
|
|
616
611
|
if (!cleanRelative.startsWith('..') && !cleanRelative.startsWith('/') && fileNode.explicitImports) {
|
|
@@ -938,13 +933,10 @@ export class RefactoringEngine {
|
|
|
938
933
|
|
|
939
934
|
if (this.context.importedUnusedPackages.size > 0) {
|
|
940
935
|
console.log(ansis.bold.red('\n📋 UNUSED DEPENDENCIES DETECTED:'));
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
console.log(ansis.red(` • ${pkgName}`));
|
|
946
|
-
}
|
|
947
|
-
}
|
|
936
|
+
this.context.importedUnusedPackages.forEach(p => {
|
|
937
|
+
// Skip built-in node modules
|
|
938
|
+
if (!p.startsWith('node:')) console.log(ansis.red(` • ${p}`));
|
|
939
|
+
});
|
|
948
940
|
}
|
|
949
941
|
|
|
950
942
|
if (summary.orphanedFiles.length > 0) {
|
|
@@ -1146,10 +1138,9 @@ export class RefactoringEngine {
|
|
|
1146
1138
|
optionalDependencies: Object.keys(data.optionalDependencies || {})
|
|
1147
1139
|
};
|
|
1148
1140
|
this.context.manifestDependencies.set(packageJsonPath, depsObj);
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
// responsible for identifying unused packages and populating this Map.
|
|
1141
|
+
for (const d of [...depsObj.dependencies, ...depsObj.devDependencies]) {
|
|
1142
|
+
this.context.importedUnusedPackages.set(d, packageJsonPath);
|
|
1143
|
+
}
|
|
1153
1144
|
|
|
1154
1145
|
// Also register workspace manifests if not already done
|
|
1155
1146
|
if (this.context.isWorkspaceEnabled && this.workspaceGraph) {
|
|
@@ -4,7 +4,6 @@ import path from 'path';
|
|
|
4
4
|
/**
|
|
5
5
|
* ConfigLoader
|
|
6
6
|
* Loads and merges entkapp configuration from multiple sources:
|
|
7
|
-
* - entkapp.json (root config)
|
|
8
7
|
* - entkapp/config.json (project config)
|
|
9
8
|
* - entkapp.config.js / entkapp.config.mjs (JS config)
|
|
10
9
|
* - package.json "entkapp" key
|
|
@@ -31,15 +30,17 @@ export class ConfigLoader {
|
|
|
31
30
|
const jsonConfigPath = path.join(this.cwd, 'entkapp', 'config.json');
|
|
32
31
|
try {
|
|
33
32
|
const raw = await fs.readFile(jsonConfigPath, 'utf8');
|
|
33
|
+
// Strip comments from JSON (JSONC support)
|
|
34
34
|
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
35
35
|
const parsed = JSON.parse(stripped);
|
|
36
36
|
config = this._merge(config, parsed);
|
|
37
37
|
} catch (e) {}
|
|
38
38
|
|
|
39
|
-
//
|
|
40
|
-
for (const configFile of ['entkapp.config.mjs', 'entkapp.config.
|
|
39
|
+
// 2. Try entkapp.config.js / entkapp.config.mjs
|
|
40
|
+
for (const configFile of ['entkapp.config.mjs', 'entkapp.config.mjs', 'entkapp.config.cjs']) {
|
|
41
41
|
let jsConfigPath = path.join(this.cwd, configFile);
|
|
42
42
|
try {
|
|
43
|
+
// FIX: Windows compatibility for dynamic imports
|
|
43
44
|
if (process.platform === 'win32') {
|
|
44
45
|
jsConfigPath = 'file://' + jsConfigPath.replace(/\\/g, '/');
|
|
45
46
|
}
|
|
@@ -52,7 +53,7 @@ export class ConfigLoader {
|
|
|
52
53
|
} catch (e) {}
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
//
|
|
56
|
+
// 3. Try package.json "entkapp" key
|
|
56
57
|
const pkgPath = path.join(this.cwd, 'package.json');
|
|
57
58
|
try {
|
|
58
59
|
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
@@ -61,7 +62,7 @@ export class ConfigLoader {
|
|
|
61
62
|
}
|
|
62
63
|
} catch (e) {}
|
|
63
64
|
|
|
64
|
-
//
|
|
65
|
+
// 4. Apply CLI overrides
|
|
65
66
|
config = this._merge(config, overrides);
|
|
66
67
|
|
|
67
68
|
return config;
|
|
@@ -100,24 +100,15 @@ export class EntryPointDetector {
|
|
|
100
100
|
|
|
101
101
|
_addIfExist(entries, relativePath) {
|
|
102
102
|
// Clean path (remove ./ etc)
|
|
103
|
-
const cleanPath = relativePath.replace(/^(
|
|
103
|
+
const cleanPath = relativePath.replace(/^(\.\/|\/)/, '');
|
|
104
104
|
const absolutePath = path.resolve(this.targetDir, cleanPath);
|
|
105
|
-
|
|
106
|
-
// FIX (Bug 2): Normalize the resolved path the same way the project graph
|
|
107
|
-
// keys are normalized (forward slashes, uppercase Windows drive letter)
|
|
108
|
-
// so that Set lookups in EngineContext succeed on all platforms.
|
|
109
|
-
const normalizePath = (p) => {
|
|
110
|
-
let n = p.replace(/\\/g, '/');
|
|
111
|
-
if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
|
|
112
|
-
return n.replace(/\/+/g, '/');
|
|
113
|
-
};
|
|
114
|
-
|
|
105
|
+
|
|
115
106
|
// Prüfe verschiedene Erweiterungen falls keine angegeben
|
|
116
107
|
const extensions = ['', '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
117
108
|
for (const ext of extensions) {
|
|
118
109
|
const p = absolutePath + ext;
|
|
119
110
|
if (fs.existsSync(p) && fs.statSync(p).isFile()) {
|
|
120
|
-
entries.add(
|
|
111
|
+
entries.add(p);
|
|
121
112
|
return;
|
|
122
113
|
}
|
|
123
114
|
}
|
|
@@ -102,19 +102,13 @@ export class EntryPointDetector {
|
|
|
102
102
|
// Clean path (remove ./ etc)
|
|
103
103
|
const cleanPath = relativePath.replace(/^(\.\/|\/)/, '');
|
|
104
104
|
const absolutePath = path.resolve(this.targetDir, cleanPath);
|
|
105
|
-
|
|
106
|
-
const normalizePath = (p) => {
|
|
107
|
-
let n = p.replace(/\\/g, '/');
|
|
108
|
-
if (/^[a-z]:\//.test(n)) n = n.charAt(0).toUpperCase() + n.slice(1);
|
|
109
|
-
return n.replace(/\/+/g, '/');
|
|
110
|
-
};
|
|
111
105
|
|
|
112
106
|
// Prüfe verschiedene Erweiterungen falls keine angegeben
|
|
113
107
|
const extensions = ['', '.mjs', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
114
108
|
for (const ext of extensions) {
|
|
115
109
|
const p = absolutePath + ext;
|
|
116
110
|
if (fs.existsSync(p) && fs.statSync(p).isFile()) {
|
|
117
|
-
entries.add(
|
|
111
|
+
entries.add(p);
|
|
118
112
|
return;
|
|
119
113
|
}
|
|
120
114
|
}
|