entkapp 5.4.0 β 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/bin/cli.js +117 -58
- package/package.json +1 -1
- package/src/EngineContext.js +0 -6
- package/src/ast/ASTAnalyzer.js +6 -94
- package/src/ast/MagicDetector.js +43 -23
- package/src/ast/OxcAnalyzer.js +27 -190
- package/src/index.js +4 -87
- package/src/performance/GraphCache.js +0 -27
- package/src/performance/WorkerPool.js +4 -22
- package/src/performance/WorkerTaskRunner.js +0 -26
- package/src/plugins/BasePlugin.js +27 -16
- package/src/plugins/PluginRegistry.js +0 -44
- package/src/plugins/ecosystems/UltimateBundle.js +0 -259
- package/src/resolution/ConfigLoader.js +26 -190
- package/src/resolution/GraphAnalyzer.js +1 -65
- package/src/resolution/PathMapper.js +0 -81
- package/src/resolution/TSConfigLoader.js +1 -3
- package/src/resolution/WorkSpaceGraph.js +89 -260
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# πΈοΈ entkapp Ultimate v5.
|
|
1
|
+
# πΈοΈ entkapp Ultimate v5.5.0
|
|
2
2
|
|
|
3
3
|
> **The Ultimate Enterprise Codebase Janitor.** High-speed OXC integration, type-aware analysis, and automated structural healing. Fully standalone architectural orchestrator.
|
|
4
4
|
|
|
@@ -35,6 +35,12 @@ Starts the interactive analysis:
|
|
|
35
35
|
npx entkapp -r
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
### Headless Analysis Mode
|
|
39
|
+
Ideal for CI/CD pipelines. Performs analysis without prompts and outputs JSON:
|
|
40
|
+
```bash
|
|
41
|
+
npx entkapp --analyze
|
|
42
|
+
```
|
|
43
|
+
|
|
38
44
|
### Auto-Fix Mode
|
|
39
45
|
Automatically resolves dependency conflicts and structural issues:
|
|
40
46
|
```bash
|
package/bin/cli.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* ============================================================================
|
|
5
|
-
*
|
|
5
|
+
* π loui CLI Entry Point
|
|
6
6
|
* ============================================================================
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Handles option compilation, environment orchestration, option validation,
|
|
8
|
+
* and initiates the primary operational pipeline loop.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { Command } from 'commander';
|
|
@@ -13,104 +13,163 @@ import ansis from 'ansis';
|
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import fs from 'fs/promises';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
|
-
import
|
|
16
|
+
import readline from 'readline/promises';
|
|
17
17
|
|
|
18
|
-
const
|
|
19
|
-
const
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
20
|
|
|
21
21
|
const program = new Command();
|
|
22
22
|
|
|
23
|
-
async function
|
|
23
|
+
async function bootstrap() {
|
|
24
24
|
try {
|
|
25
|
+
const packageJsonPath = path.resolve(__dirname, '../package.json');
|
|
26
|
+
const packageJsonContent = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
27
|
+
|
|
25
28
|
program
|
|
26
29
|
.name('entkapp')
|
|
27
|
-
.description('
|
|
28
|
-
.version(
|
|
29
|
-
|
|
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.3.1');
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.option('-c, --cwd <path>', 'Specify the execution context root directory', process.cwd())
|
|
35
|
+
.option('-d, --debug', 'Developer`s comprehensive telemetry debug diagnostics', false)
|
|
36
|
+
.option('--fix', 'Enable atomic code updates, structural file pruning, and active type sanitization', false)
|
|
31
37
|
.option('--tsconfig <filename>', 'Specify path to custom layout configurations', 'tsconfig.json')
|
|
32
38
|
.option('--test-command <command>', 'Integrated continuous safety test validation script execution path', 'npm test')
|
|
33
39
|
.option('--workspace', 'Enable high-density workspace workspace/monorepo cluster mesh evaluation parsing', false)
|
|
34
40
|
.option('--verbose', 'Toggle expanded trace telemetry for debug operational diagnostics', false)
|
|
35
41
|
.option('--visualize', 'Generate an interactive execution graph visualization', false)
|
|
36
|
-
.option('-ef, --entry-file <path>', 'Manually specify a primary entry file for analysis')
|
|
37
42
|
.option('-r, --run', 'Execute the primary operational pipeline loop', false)
|
|
38
43
|
.option('-y, --yes', 'Skip confirmation prompts and execute planned structural modifications automatically', false)
|
|
39
44
|
.option('--timeout <ms>', 'Set execution timeout in milliseconds', '30000');
|
|
40
45
|
|
|
41
46
|
program.parse(process.argv);
|
|
42
|
-
|
|
43
47
|
const options = program.opts();
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
|
|
49
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
|
+
|
|
51
|
+
// --- Onboarding Check (Skipped in Non-Interactive Mode) ---
|
|
52
|
+
// FIX: Ensure options.cwd is always a string, never undefined
|
|
53
|
+
const targetCwd = path.resolve(options.cwd || process.cwd());
|
|
54
|
+
const pkgJsonPath = path.join(targetCwd, 'package.json');
|
|
55
|
+
const configDirPath = path.join(targetCwd, 'entkapp');
|
|
56
|
+
|
|
57
|
+
let pkgJson;
|
|
58
|
+
try {
|
|
59
|
+
pkgJson = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
|
|
60
|
+
} catch (e) {}
|
|
61
|
+
|
|
62
|
+
let configInstalled = false;
|
|
63
|
+
if (!options.yes && !options.run) {
|
|
64
|
+
// 1. Ask to install script
|
|
65
|
+
if (pkgJson && !pkgJson.scripts?.['entkapp:run']) {
|
|
66
|
+
const answer = await rl.question(ansis.bold.yellow('β No "entkapp:run" script found in package.json. Install it? (y/n): '));
|
|
67
|
+
if (answer.toLowerCase() === 'y') {
|
|
68
|
+
pkgJson.scripts = pkgJson.scripts || {};
|
|
69
|
+
pkgJson.scripts['entkapp:run'] = 'npx entkapp --fix';
|
|
70
|
+
await fs.writeFile(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
|
|
71
|
+
console.log(ansis.green('β
"entkapp:run" script added to package.json.'));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 2. Ask to install config folder
|
|
76
|
+
try {
|
|
77
|
+
await fs.access(configDirPath);
|
|
78
|
+
configInstalled = true;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
const answer = await rl.question(ansis.bold.yellow('β No "/entkapp" configuration folder found. Create it with defaults? (y/n): '));
|
|
81
|
+
if (answer.toLowerCase() === 'y') {
|
|
82
|
+
await fs.mkdir(configDirPath, { recursive: true });
|
|
83
|
+
await fs.mkdir(path.join(configDirPath, 'plugins'), { recursive: true });
|
|
84
|
+
const defaultConfig = {
|
|
85
|
+
interface: "CLI",
|
|
86
|
+
useBuiltinPlugins: true,
|
|
87
|
+
useCustomPlugins: true,
|
|
88
|
+
|
|
89
|
+
options: { verbose: false, fastMode: true, selfHealing: true },
|
|
90
|
+
enabledPlugins: ["nextjs", "nuxt", "remix", "sveltekit", "astro"]
|
|
91
|
+
};
|
|
92
|
+
await fs.writeFile(path.join(configDirPath, 'config.json'), JSON.stringify(defaultConfig, null, 2));
|
|
93
|
+
console.log(ansis.green('β
"/entkapp" folder? and default config created.'));
|
|
94
|
+
configInstalled = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (pkgJson?.scripts?.['entkapp:run'] || configInstalled) {
|
|
99
|
+
console.log(ansis.bold.cyan('\nπ Setup complete! To start the engine, run:'));
|
|
100
|
+
console.log(ansis.white(` - npx entkapp -r`));
|
|
101
|
+
console.log(ansis.white(` - npm run entkapp:run\n`));
|
|
102
|
+
}
|
|
57
103
|
}
|
|
58
104
|
|
|
105
|
+
rl.close();
|
|
106
|
+
|
|
107
|
+
// Load local config if available
|
|
108
|
+
let localConfig = {};
|
|
109
|
+
try {
|
|
110
|
+
const { ConfigLoader } = await import('../src/resolution/ConfigLoader.js');
|
|
111
|
+
const loader = new ConfigLoader(targetCwd);
|
|
112
|
+
localConfig = await loader.loadConfig();
|
|
113
|
+
} catch (e) {}
|
|
114
|
+
|
|
115
|
+
// Merge options with local config
|
|
116
|
+
const finalInterface = localConfig.interface || 'CLI';
|
|
117
|
+
if (finalInterface === 'GUI') {
|
|
118
|
+
console.log(ansis.bold.magenta('π¨ GUI Mode Detected. Starting Web Interface...'));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Only proceed to execution if -r/--run is provided
|
|
123
|
+
if (!options.run) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- Timeout Handling ---
|
|
128
|
+
const timeoutMs = parseInt(options.timeout);
|
|
59
129
|
const timeoutTimer = setTimeout(() => {
|
|
60
|
-
console.error(ansis.red(`\n
|
|
130
|
+
console.error(ansis.bold.red(`\nπ¨ Execution Timeout: The process exceeded the limit of ${timeoutMs}ms.`));
|
|
61
131
|
process.exit(1);
|
|
62
|
-
},
|
|
132
|
+
}, timeoutMs);
|
|
133
|
+
timeoutTimer.unref(); // Allow process to exit if work finishes
|
|
63
134
|
|
|
64
|
-
console.log(
|
|
135
|
+
console.log(ansis.bold.green(`\nπ¦ entkapp v${packageJsonContent.version || '5.3.1'} Engine Activation`));
|
|
65
136
|
console.log(ansis.dim('------------------------------------------------------------'));
|
|
66
|
-
console.log(`${ansis.bold('Target Workspace Root')}
|
|
67
|
-
console.log(`${ansis.bold('Refactoring Mode')}
|
|
68
|
-
console.log(`${ansis.bold('Validation Sandbox')}
|
|
137
|
+
console.log(`${ansis.bold('Target Workspace Root :')} ${ansis.blue(targetCwd)}`);
|
|
138
|
+
console.log(`${ansis.bold('Refactoring Mode :')} ${options.fix ? ansis.yellow('Active Fixing & Self-Healing Enabled') : ansis.gray('Dry-Run Reporting Only')}`);
|
|
139
|
+
console.log(`${ansis.bold('Validation Sandbox :')} ${ansis.magenta(options.testCommand)}`);
|
|
69
140
|
console.log(ansis.dim('------------------------------------------------------------\n'));
|
|
70
141
|
|
|
71
142
|
const { RefactoringEngine } = await import('../src/index.js');
|
|
72
143
|
|
|
73
|
-
// Prepare entry points - EXTREMELY ROBUST ARRAY HANDLING
|
|
74
|
-
let entryPoints = [];
|
|
75
|
-
if (localConfig.entryPoints && typeof localConfig.entryPoints[Symbol.iterator] === 'function') {
|
|
76
|
-
entryPoints = [...localConfig.entryPoints];
|
|
77
|
-
} else if (localConfig.entryPoints) {
|
|
78
|
-
entryPoints = [localConfig.entryPoints];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (options.entryFile) {
|
|
82
|
-
const absEntry = path.resolve(targetCwd, options.entryFile).replace(/\\/g, '/');
|
|
83
|
-
if (!entryPoints.includes(absEntry)) {
|
|
84
|
-
entryPoints.push(absEntry);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
144
|
const engine = new RefactoringEngine({
|
|
89
145
|
cwd: targetCwd,
|
|
90
146
|
autoFix: options.fix,
|
|
91
147
|
tsconfig: options.tsconfig,
|
|
92
148
|
testCommand: options.testCommand,
|
|
93
|
-
workspace: options.workspace
|
|
149
|
+
workspace: options.workspace,
|
|
94
150
|
verbose: options.verbose,
|
|
95
151
|
skipConfirm: options.yes,
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
152
|
+
// Pass through local config settings
|
|
153
|
+
entryPoints: localConfig.entryPoints,
|
|
154
|
+
exclude: localConfig.exclude,
|
|
155
|
+
rules: localConfig.rules,
|
|
99
156
|
debug: options.debug,
|
|
100
157
|
visualize: options.visualize,
|
|
101
|
-
workspacePackages: localConfig.workspacePackages || [],
|
|
102
|
-
workspaceTsConfigs: localConfig.workspaceTsConfigs || [],
|
|
103
|
-
workspaceConfigFiles: localConfig.workspaceConfigFiles || [],
|
|
104
158
|
});
|
|
105
159
|
|
|
106
160
|
await engine.run();
|
|
107
161
|
|
|
108
162
|
clearTimeout(timeoutTimer);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
163
|
+
console.log(ansis.bold.green('\n⨠Core cycle execution completed successfully. Structural layout is clean.'));
|
|
164
|
+
process.exit(0);
|
|
165
|
+
|
|
166
|
+
} catch (criticalBootError) {
|
|
167
|
+
console.error(ansis.bold.red(`\nπ¨ Critical Lifecycle Boot Instability: ${criticalBootError.message}`));
|
|
168
|
+
if (criticalBootError.stack) {
|
|
169
|
+
console.error(ansis.dim(criticalBootError.stack));
|
|
170
|
+
}
|
|
112
171
|
process.exit(1);
|
|
113
172
|
}
|
|
114
173
|
}
|
|
115
174
|
|
|
116
|
-
|
|
175
|
+
bootstrap();
|
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.
|
|
4
|
+
"version": "5.5.0",
|
|
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
|
@@ -419,12 +419,6 @@ export class EngineContext {
|
|
|
419
419
|
report.importedUnusedPackages = Array.from(this.importedUnusedPackages);
|
|
420
420
|
report.unusedBinaries = Array.from(this.unusedBinaries);
|
|
421
421
|
|
|
422
|
-
// UPGRADE 5.4.3: Integrate enhanced results from GraphAnalyzer
|
|
423
|
-
const analyzer = new (await import('./resolution/GraphAnalyzer.js')).GraphAnalyzer(this);
|
|
424
|
-
const { unusedImports, boundaryViolations } = await analyzer.findDeadCode();
|
|
425
|
-
report.unusedImports = unusedImports;
|
|
426
|
-
report.boundaryViolations = boundaryViolations;
|
|
427
|
-
|
|
428
422
|
return report;
|
|
429
423
|
}
|
|
430
424
|
}
|
package/src/ast/ASTAnalyzer.js
CHANGED
|
@@ -265,16 +265,7 @@ export class ASTAnalyzer {
|
|
|
265
265
|
fileNode.explicitImports.add(specifier);
|
|
266
266
|
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
267
267
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
268
|
-
|
|
269
|
-
const pkgNameReq = this._extractPackageName(specifier);
|
|
270
|
-
let isInternalReq = false;
|
|
271
|
-
if (this.context.pathMapper && typeof this.context.pathMapper.isTsconfigAlias === 'function') {
|
|
272
|
-
if (this.context.pathMapper.isTsconfigAlias(specifier) || this.context.pathMapper.isTsconfigAlias(pkgNameReq)) isInternalReq = true;
|
|
273
|
-
}
|
|
274
|
-
if (!isInternalReq && this.context.workspaceGraph && typeof this.context.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
|
|
275
|
-
if (this.context.workspaceGraph.isLocalWorkspaceSpecifier(specifier) || this.context.workspaceGraph.isLocalWorkspaceSpecifier(pkgNameReq)) isInternalReq = true;
|
|
276
|
-
}
|
|
277
|
-
if (!isInternalReq) fileNode.externalPackageUsage.add(pkgNameReq);
|
|
268
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
278
269
|
}
|
|
279
270
|
} else if (arg && ts.isTemplateExpression(arg)) {
|
|
280
271
|
// Dynamic require with template literal
|
|
@@ -360,40 +351,8 @@ export class ASTAnalyzer {
|
|
|
360
351
|
const specifier = node.moduleSpecifier.text;
|
|
361
352
|
fileNode.explicitImports.add(specifier);
|
|
362
353
|
|
|
363
|
-
// UPGRADE 5.4.2: Distinguish between local aliases and workspace packages
|
|
364
354
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
365
|
-
|
|
366
|
-
const pkgName = this._extractPackageName(specifier);
|
|
367
|
-
|
|
368
|
-
// UPGRADE: First check via isTsconfigAlias() β works even when the target file doesn't exist yet
|
|
369
|
-
if (this.context.pathMapper && typeof this.context.pathMapper.isTsconfigAlias === 'function') {
|
|
370
|
-
if (this.context.pathMapper.isTsconfigAlias(specifier) || this.context.pathMapper.isTsconfigAlias(pkgName)) {
|
|
371
|
-
isInternalAlias = true;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// UPGRADE: Check workspace packages (pnpm-workspace.yaml / package.json workspaces)
|
|
376
|
-
if (!isInternalAlias && this.context.workspaceGraph && typeof this.context.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
|
|
377
|
-
if (this.context.workspaceGraph.isLocalWorkspaceSpecifier(specifier) || this.context.workspaceGraph.isLocalWorkspaceSpecifier(pkgName)) {
|
|
378
|
-
isInternalAlias = true;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Fallback: try resolvePath() β works when the target file exists on disk
|
|
383
|
-
if (!isInternalAlias && this.context.pathMapper && typeof this.context.pathMapper.resolvePath === 'function') {
|
|
384
|
-
const resolved = this.context.pathMapper.resolvePath(specifier);
|
|
385
|
-
if (resolved !== specifier && (resolved.startsWith('/') || resolved.includes(':'))) {
|
|
386
|
-
// Only mark as internal alias if it's NOT a registered workspace package
|
|
387
|
-
const isWorkspacePkg = this.context.workspaceGraph && this.context.workspaceGraph.isLocalWorkspaceSpecifier(pkgName);
|
|
388
|
-
if (!isWorkspacePkg) {
|
|
389
|
-
isInternalAlias = true;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
if (!isInternalAlias) {
|
|
395
|
-
fileNode.externalPackageUsage.add(pkgName);
|
|
396
|
-
}
|
|
355
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
397
356
|
}
|
|
398
357
|
|
|
399
358
|
if (this.context.workspaceGraph && typeof this.context.workspaceGraph.auditImportSpecifier === 'function') {
|
|
@@ -414,29 +373,14 @@ export class ASTAnalyzer {
|
|
|
414
373
|
if (ts.isNamespaceImport(node.importClause.namedBindings)) {
|
|
415
374
|
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
416
375
|
if (targetFile) this.context.importUsageRegistry.add(`${targetFile}:*`);
|
|
417
|
-
|
|
418
|
-
// Track local name for unused import detection
|
|
419
|
-
if (!fileNode.localImportBindings) fileNode.localImportBindings = new Map();
|
|
420
|
-
fileNode.localImportBindings.set(node.importClause.namedBindings.name.text, { specifier, originalName: '*' });
|
|
421
376
|
} else if (ts.isNamedImports(node.importClause.namedBindings)) {
|
|
422
377
|
node.importClause.namedBindings.elements.forEach(element => {
|
|
423
378
|
const importedName = element.propertyName ? element.propertyName.text : element.name.text;
|
|
424
|
-
const localName = element.name.text;
|
|
425
379
|
fileNode.importedSymbols.add(`${specifier}:${importedName}`);
|
|
426
380
|
if (targetFile) this.context.importUsageRegistry.add(`${targetFile}:${importedName}`);
|
|
427
|
-
|
|
428
|
-
// Track local name for unused import detection
|
|
429
|
-
if (!fileNode.localImportBindings) fileNode.localImportBindings = new Map();
|
|
430
|
-
fileNode.localImportBindings.set(localName, { specifier, originalName: importedName });
|
|
431
381
|
});
|
|
432
382
|
}
|
|
433
383
|
}
|
|
434
|
-
|
|
435
|
-
// Handle default import binding
|
|
436
|
-
if (node.importClause.name) {
|
|
437
|
-
if (!fileNode.localImportBindings) fileNode.localImportBindings = new Map();
|
|
438
|
-
fileNode.localImportBindings.set(node.importClause.name.text, { specifier, originalName: 'default' });
|
|
439
|
-
}
|
|
440
384
|
} else {
|
|
441
385
|
// Side-Effect Import (import '...')
|
|
442
386
|
// FIX: Mark the target file as used even if no symbols are imported
|
|
@@ -479,22 +423,7 @@ export class ASTAnalyzer {
|
|
|
479
423
|
if (specifier) {
|
|
480
424
|
fileNode.explicitImports.add(specifier);
|
|
481
425
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
482
|
-
|
|
483
|
-
const pkgNameExport = this._extractPackageName(specifier);
|
|
484
|
-
let isInternalAliasExport = false;
|
|
485
|
-
if (this.context.pathMapper && typeof this.context.pathMapper.isTsconfigAlias === 'function') {
|
|
486
|
-
if (this.context.pathMapper.isTsconfigAlias(specifier) || this.context.pathMapper.isTsconfigAlias(pkgNameExport)) {
|
|
487
|
-
isInternalAliasExport = true;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (!isInternalAliasExport && this.context.workspaceGraph && typeof this.context.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
|
|
491
|
-
if (this.context.workspaceGraph.isLocalWorkspaceSpecifier(specifier) || this.context.workspaceGraph.isLocalWorkspaceSpecifier(pkgNameExport)) {
|
|
492
|
-
isInternalAliasExport = true;
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
if (!isInternalAliasExport) {
|
|
496
|
-
fileNode.externalPackageUsage.add(pkgNameExport);
|
|
497
|
-
}
|
|
426
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
498
427
|
}
|
|
499
428
|
|
|
500
429
|
if (!node.exportClause) {
|
|
@@ -658,16 +587,7 @@ export class ASTAnalyzer {
|
|
|
658
587
|
fileNode.dynamicImports.add(specifier);
|
|
659
588
|
fileNode.importedSymbols.add(`${specifier}:*`);
|
|
660
589
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
661
|
-
|
|
662
|
-
const pkgNameDyn = this._extractPackageName(specifier);
|
|
663
|
-
let isInternalDyn = false;
|
|
664
|
-
if (this.context.pathMapper && typeof this.context.pathMapper.isTsconfigAlias === 'function') {
|
|
665
|
-
if (this.context.pathMapper.isTsconfigAlias(specifier) || this.context.pathMapper.isTsconfigAlias(pkgNameDyn)) isInternalDyn = true;
|
|
666
|
-
}
|
|
667
|
-
if (!isInternalDyn && this.context.workspaceGraph && typeof this.context.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
|
|
668
|
-
if (this.context.workspaceGraph.isLocalWorkspaceSpecifier(specifier) || this.context.workspaceGraph.isLocalWorkspaceSpecifier(pkgNameDyn)) isInternalDyn = true;
|
|
669
|
-
}
|
|
670
|
-
if (!isInternalDyn) fileNode.externalPackageUsage.add(pkgNameDyn);
|
|
590
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
671
591
|
}
|
|
672
592
|
} else {
|
|
673
593
|
// Non-literal dynamic import
|
|
@@ -688,17 +608,9 @@ export class ASTAnalyzer {
|
|
|
688
608
|
if (this.context.verbose) console.log(`[AST-DEBUG] Added import: ${specifier}`);
|
|
689
609
|
fileNode.explicitImports.add(specifier);
|
|
690
610
|
|
|
691
|
-
// UPGRADE:
|
|
611
|
+
// UPGRADE: Also track package usage for unlisted dependency detection
|
|
692
612
|
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
693
|
-
|
|
694
|
-
let isInternalCE = false;
|
|
695
|
-
if (this.context.pathMapper && typeof this.context.pathMapper.isTsconfigAlias === 'function') {
|
|
696
|
-
if (this.context.pathMapper.isTsconfigAlias(specifier) || this.context.pathMapper.isTsconfigAlias(pkgNameCE)) isInternalCE = true;
|
|
697
|
-
}
|
|
698
|
-
if (!isInternalCE && this.context.workspaceGraph && typeof this.context.workspaceGraph.isLocalWorkspaceSpecifier === 'function') {
|
|
699
|
-
if (this.context.workspaceGraph.isLocalWorkspaceSpecifier(specifier) || this.context.workspaceGraph.isLocalWorkspaceSpecifier(pkgNameCE)) isInternalCE = true;
|
|
700
|
-
}
|
|
701
|
-
if (!isInternalCE) fileNode.externalPackageUsage.add(pkgNameCE);
|
|
613
|
+
fileNode.externalPackageUsage.add(this._extractPackageName(specifier));
|
|
702
614
|
}
|
|
703
615
|
}
|
|
704
616
|
}
|
package/src/ast/MagicDetector.js
CHANGED
|
@@ -6,7 +6,15 @@ import { PluginRegistry } from '../plugins/PluginRegistry.js';
|
|
|
6
6
|
* Ecosystem Entry Point Manifest & Dynamic Framework Router Heuristic Validator
|
|
7
7
|
* Intercepts implicit conventions to handle cases where direct import statements are absent.
|
|
8
8
|
* Now refactored to use a pluggable architecture.
|
|
9
|
-
*
|
|
9
|
+
*
|
|
10
|
+
* Improvements over v1:
|
|
11
|
+
* - Extended config-file detection list (Biome, Oxlint, tsup, unbuild, etc.)
|
|
12
|
+
* - Next.js App Router conventions (page.tsx, layout.tsx, loading.tsx, error.tsx, etc.)
|
|
13
|
+
* - Remix conventions (route files under app/routes/)
|
|
14
|
+
* - SvelteKit conventions (+page.svelte, +layout.svelte, etc.)
|
|
15
|
+
* - Astro page/layout conventions
|
|
16
|
+
* - Common entry-point patterns (bin/, cli.ts, server.ts, main.ts, app.ts)
|
|
17
|
+
* - Test file patterns extended to cover Vitest workspace files
|
|
10
18
|
*/
|
|
11
19
|
export class MagicDetector {
|
|
12
20
|
constructor(context) {
|
|
@@ -23,26 +31,18 @@ export class MagicDetector {
|
|
|
23
31
|
|
|
24
32
|
/**
|
|
25
33
|
* Audits the project context to map active micro-framework ecosystems.
|
|
34
|
+
* @param {string} baseContextDirectory - Root file workspace context execution vector path
|
|
26
35
|
*/
|
|
27
36
|
async identifyActiveProjectEcosystems(baseContextDirectory) {
|
|
28
37
|
await this.ensureInitialized(baseContextDirectory);
|
|
29
38
|
const activePlugins = await this.registry.getActivePlugins(baseContextDirectory);
|
|
30
39
|
const activeFrameworkFlags = activePlugins.map(p => p.name);
|
|
40
|
+
|
|
41
|
+
// Universal infrastructure overrides (testing platforms and common bundlers)
|
|
31
42
|
activeFrameworkFlags.push('universal-tooling-vectors');
|
|
32
43
|
return activeFrameworkFlags;
|
|
33
44
|
}
|
|
34
45
|
|
|
35
|
-
/**
|
|
36
|
-
* Version 5.4.0: Delegates entry point detection to the plugin registry.
|
|
37
|
-
* @param {string} content - The file content
|
|
38
|
-
* @param {string} filePath - The absolute path to the file
|
|
39
|
-
* @returns {Array<string>} List of detected entry points
|
|
40
|
-
*/
|
|
41
|
-
async detectEntryPointsFromPlugins(content, filePath) {
|
|
42
|
-
await this.ensureInitialized();
|
|
43
|
-
return this.registry.detectEntryPointsFromContent(content, filePath);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
46
|
/**
|
|
47
47
|
* Assesses if a file path acts as an implicit route entry point.
|
|
48
48
|
*/
|
|
@@ -60,6 +60,7 @@ export class MagicDetector {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// Apply baseline platform rules (Test suites, lint parameters, continuous integration files)
|
|
63
64
|
if (this.isCoreToolingSuiteElement(normalizedSystemPath)) {
|
|
64
65
|
return true;
|
|
65
66
|
}
|
|
@@ -67,46 +68,49 @@ export class MagicDetector {
|
|
|
67
68
|
return false;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
/**
|
|
71
|
-
* Version 5.3.0: Delegates content analysis to the plugin registry.
|
|
72
|
-
*/
|
|
73
|
-
async runPluginContentAnalysis(node, absolutePath) {
|
|
74
|
-
await this.ensureInitialized();
|
|
75
|
-
await this.registry.runPluginAnalysis(node, absolutePath);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
71
|
isCoreToolingSuiteElement(normalizedPath) {
|
|
72
|
+
// ββ Test / spec files ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
79
73
|
if (/\.(test|spec|e2e|cy)\.(js|ts|tsx|jsx)$/i.test(normalizedPath)) return true;
|
|
80
74
|
if (/\.stories\.(js|ts|tsx|jsx)$/i.test(normalizedPath)) return true;
|
|
81
75
|
|
|
76
|
+
// ββ Build / bundler config files βββββββββββββββββββββββββββββββββββββββββββ
|
|
82
77
|
const configFragments = [
|
|
78
|
+
// Test runners
|
|
83
79
|
'jest.config.', 'vitest.config.', 'vitest.workspace.',
|
|
84
80
|
'playwright.config.', 'cypress.config.',
|
|
81
|
+
// Bundlers
|
|
85
82
|
'webpack.config.', 'vite.config.', 'rollup.config.',
|
|
86
83
|
'esbuild.config.', 'parcel.config.',
|
|
87
84
|
'tsup.config.', 'unbuild.config.', 'pkgroll.config.',
|
|
85
|
+
// CSS / styling
|
|
88
86
|
'tailwind.config.', 'postcss.config.', '.postcssrc.',
|
|
87
|
+
// Linters / formatters
|
|
89
88
|
'.eslintrc.', 'eslint.config.', 'prettier.config.', '.prettierrc.',
|
|
90
89
|
'.stylelintrc.', 'stylelint.config.',
|
|
91
90
|
'biome.json', '.oxlintrc.',
|
|
91
|
+
// Babel / transpilation
|
|
92
92
|
'.babelrc.', 'babel.config.',
|
|
93
|
+
// Commit / git hooks
|
|
93
94
|
'.commitlintrc.', 'commitlint.config.',
|
|
94
95
|
'.lintstagedrc.', 'lint-staged.config.',
|
|
96
|
+
// Documentation
|
|
95
97
|
'typedoc.config.', 'typedoc.json',
|
|
98
|
+
// Monorepo tools
|
|
96
99
|
'turbo.json', 'nx.json', 'lerna.json',
|
|
100
|
+
// Misc tooling
|
|
97
101
|
'knip.config.', 'knip.json',
|
|
98
102
|
'syncpack.config.',
|
|
103
|
+
// Internal worker
|
|
99
104
|
'WorkerTaskRunner.js'
|
|
100
105
|
];
|
|
101
106
|
if (configFragments.some(f => normalizedPath.includes(f))) return true;
|
|
102
107
|
|
|
108
|
+
// ββ Common application entry points βββββββββββββββββββββββββββββββββββββββ
|
|
103
109
|
const entryPatterns = [
|
|
104
110
|
// CLI binaries
|
|
105
111
|
'/bin/cli.js', '/bin/cli.ts', '/bin/cli.mjs',
|
|
106
112
|
'/bin/index.js', '/bin/index.ts',
|
|
107
|
-
//
|
|
108
|
-
'/index.js', '/index.ts', '/index.jsx', '/index.tsx', '/index.mjs', '/index.cjs',
|
|
109
|
-
// Server / app entry points
|
|
113
|
+
// Server / app entry points (Reduced in v3.3.8 to avoid false positives in libraries)
|
|
110
114
|
'/src/main.ts', '/src/main.js',
|
|
111
115
|
'/src/app.ts', '/src/app.js',
|
|
112
116
|
'/src/api/HeadlessAPI.js', '/src/api/PluginSDK.js',
|
|
@@ -115,37 +119,50 @@ export class MagicDetector {
|
|
|
115
119
|
];
|
|
116
120
|
if (entryPatterns.some(p => normalizedPath.endsWith(p))) return true;
|
|
117
121
|
|
|
122
|
+
// ββ Next.js App Router conventions ββββββββββββββββββββββββββββββββββββββββ
|
|
123
|
+
// Files under app/ directory with Next.js special names
|
|
118
124
|
if (/\/app\/(page|layout|loading|error|not-found|template|default|route|middleware)\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
125
|
+
// Next.js Pages Router
|
|
119
126
|
if (/\/pages\/.*\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
127
|
+
// Next.js API routes
|
|
120
128
|
if (/\/pages\/api\/.*\.(js|ts)$/.test(normalizedPath)) return true;
|
|
129
|
+
// Next.js middleware
|
|
121
130
|
if (/\/middleware\.(js|ts)$/.test(normalizedPath)) return true;
|
|
131
|
+
// Next.js config
|
|
122
132
|
if (/\/next\.config\.(js|ts|mjs|cjs)$/.test(normalizedPath)) return true;
|
|
123
133
|
|
|
134
|
+
// ββ Remix conventions βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
124
135
|
if (/\/app\/routes\/.*\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
125
136
|
if (/\/app\/root\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
126
137
|
if (/\/app\/entry\.(client|server)\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
127
138
|
|
|
139
|
+
// ββ SvelteKit conventions βββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
128
140
|
if (/\/\+page(\.(server|client))?\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
129
141
|
if (/\/\+layout(\.(server|client))?\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
130
142
|
if (/\/\+error\.(svelte|js|ts)$/.test(normalizedPath)) return true;
|
|
131
143
|
if (/\/\+server\.(js|ts)$/.test(normalizedPath)) return true;
|
|
132
144
|
if (/\/svelte\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
133
145
|
|
|
146
|
+
// ββ Astro conventions βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
134
147
|
if (/\/src\/pages\/.*\.astro$/.test(normalizedPath)) return true;
|
|
135
148
|
if (/\/src\/layouts\/.*\.astro$/.test(normalizedPath)) return true;
|
|
136
149
|
if (/\/astro\.config\.(mjs|js|ts)$/.test(normalizedPath)) return true;
|
|
137
150
|
|
|
151
|
+
// ββ Nuxt conventions ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
138
152
|
if (/\/pages\/.*\.vue$/.test(normalizedPath)) return true;
|
|
139
153
|
if (/\/layouts\/.*\.vue$/.test(normalizedPath)) return true;
|
|
140
154
|
if (/\/server\/api\/.*\.(js|ts)$/.test(normalizedPath)) return true;
|
|
141
155
|
if (/\/nuxt\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
142
156
|
|
|
157
|
+
// ββ React / Vite entry points βββββββββββββββββββββββββββββββββββββββββββββ
|
|
143
158
|
if (/\/vite\.config\.(js|ts|mjs)$/.test(normalizedPath)) return true;
|
|
144
159
|
|
|
160
|
+
// ββ Angular conventions βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
145
161
|
if (/\/main\.(ts|js)$/.test(normalizedPath)) return true;
|
|
146
162
|
if (/\/app\.module\.(ts|js)$/.test(normalizedPath)) return true;
|
|
147
163
|
if (/\/angular\.json$/.test(normalizedPath)) return true;
|
|
148
164
|
|
|
165
|
+
// ββ Expo / React Native βββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
149
166
|
if (/\/app\/_layout\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
150
167
|
if (/\/app\/index\.(js|ts|tsx|jsx)$/.test(normalizedPath)) return true;
|
|
151
168
|
|
|
@@ -159,8 +176,10 @@ export class MagicDetector {
|
|
|
159
176
|
await this.ensureInitialized();
|
|
160
177
|
if (!await this.isImplicitlyRequiredByEcosystem(filePath, activeFrameworks)) return;
|
|
161
178
|
|
|
179
|
+
// Retain entry point elements within memory to keep verification safe
|
|
162
180
|
fileNode.isEntry = true;
|
|
163
181
|
|
|
182
|
+
// Apply dynamic exports coverage metrics based on active platform contracts
|
|
164
183
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
165
184
|
const plugins = this.registry.getPlugins();
|
|
166
185
|
|
|
@@ -173,6 +192,7 @@ export class MagicDetector {
|
|
|
173
192
|
const contracts = plugin.getRequiredSystemContracts();
|
|
174
193
|
contracts.forEach(contractMethodToken => {
|
|
175
194
|
if (fileNode.internalExports.has(contractMethodToken)) {
|
|
195
|
+
// Emulate active local reference linkages to protect the export
|
|
176
196
|
fileNode.instantiatedIdentifiers.add(contractMethodToken);
|
|
177
197
|
}
|
|
178
198
|
});
|