entkapp 5.4.0 → 5.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/bin/cli.js +117 -58
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -6
- package/src/EngineContext.js +0 -6
- package/src/EngineContext.mjs +428 -0
- package/src/Initializer.mjs +82 -0
- package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
- package/src/analyzers/OxcAnalyzer.mjs +11 -0
- package/src/api/HeadlessAPI.js +31 -16
- package/src/api/HeadlessAPI.mjs +369 -0
- package/src/api/PluginSDK.mjs +135 -0
- package/src/ast/ASTAnalyzer.js +23 -97
- package/src/ast/ASTAnalyzer.mjs +742 -0
- package/src/ast/AdvancedAnalysis.mjs +586 -0
- package/src/ast/BarrelParser.mjs +230 -0
- package/src/ast/DeadCodeDetector.js +7 -5
- package/src/ast/DeadCodeDetector.mjs +92 -0
- package/src/ast/MagicDetector.js +43 -23
- package/src/ast/MagicDetector.mjs +203 -0
- package/src/ast/OxcAnalyzer.js +27 -190
- package/src/ast/OxcAnalyzer.mjs +188 -0
- package/src/ast/SecretScanner.mjs +374 -0
- package/src/healing/GitSandbox.mjs +82 -0
- package/src/healing/SelfHealer.mjs +48 -0
- package/src/index.js +41 -88
- package/src/index.mjs +1176 -0
- package/src/performance/GraphCache.js +0 -27
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.js +4 -22
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.js +0 -26
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.js +27 -16
- package/src/plugins/BasePlugin.mjs +240 -0
- package/src/plugins/PluginRegistry.js +0 -44
- package/src/plugins/PluginRegistry.mjs +203 -0
- package/src/plugins/ecosystems/BackendServices.mjs +197 -0
- package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
- package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
- package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
- package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
- package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
- package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
- package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
- package/src/plugins/ecosystems/UltimateBundle.js +0 -259
- package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
- package/src/refractor/ImpactAnalyzer.mjs +92 -0
- package/src/refractor/SourceRewriter.mjs +86 -0
- package/src/refractor/TransactionManager.mjs +5 -0
- package/src/refractor/TypeIntegrity.mjs +4 -0
- package/src/resolution/BuildOrchestrator.mjs +46 -0
- package/src/resolution/CircularDetector.mjs +91 -0
- package/src/resolution/ConfigGenerator.mjs +83 -0
- package/src/resolution/ConfigLoader.js +26 -190
- package/src/resolution/ConfigLoader.mjs +94 -0
- package/src/resolution/DepencyResolver.mjs +66 -0
- package/src/resolution/DependencyFixer.mjs +88 -0
- package/src/resolution/DependencyProfiler.mjs +286 -0
- package/src/resolution/EntryPointDetector.mjs +134 -0
- package/src/resolution/FrameworkConfigParser.mjs +119 -0
- package/src/resolution/GraphAnalyzer.js +1 -65
- package/src/resolution/GraphAnalyzer.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.js +0 -81
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.js +1 -3
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.js +89 -260
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* ConfigLoader
|
|
6
|
+
* Loads and merges entkapp configuration from multiple sources:
|
|
7
|
+
* - entkapp/config.json (project config)
|
|
8
|
+
* - entkapp.config.js / entkapp.config.mjs (JS config)
|
|
9
|
+
* - package.json "entkapp" key
|
|
10
|
+
* - CLI flags (passed as overrides)
|
|
11
|
+
*/
|
|
12
|
+
export class ConfigLoader {
|
|
13
|
+
constructor(cwd) {
|
|
14
|
+
this.cwd = cwd;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async loadConfig(overrides = {}) {
|
|
18
|
+
let config = this._defaultConfig();
|
|
19
|
+
|
|
20
|
+
// 1. Try entkapp/config.json
|
|
21
|
+
const jsonConfigPath = path.join(this.cwd, 'entkapp', 'config.json');
|
|
22
|
+
try {
|
|
23
|
+
const raw = await fs.readFile(jsonConfigPath, 'utf8');
|
|
24
|
+
// Strip comments from JSON (JSONC support)
|
|
25
|
+
const stripped = raw.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, '');
|
|
26
|
+
const parsed = JSON.parse(stripped);
|
|
27
|
+
config = this._merge(config, parsed);
|
|
28
|
+
} catch (e) {}
|
|
29
|
+
|
|
30
|
+
// 2. Try entkapp.config.js / entkapp.config.mjs
|
|
31
|
+
for (const configFile of ['entkapp.config.mjs', 'entkapp.config.mjs', 'entkapp.config.cjs']) {
|
|
32
|
+
let jsConfigPath = path.join(this.cwd, configFile);
|
|
33
|
+
try {
|
|
34
|
+
// FIX: Windows compatibility for dynamic imports
|
|
35
|
+
if (process.platform === 'win32') {
|
|
36
|
+
jsConfigPath = 'file://' + jsConfigPath.replace(/\\/g, '/');
|
|
37
|
+
}
|
|
38
|
+
const mod = await import(jsConfigPath);
|
|
39
|
+
const jsConfig = mod.default || mod;
|
|
40
|
+
if (typeof jsConfig === 'object') {
|
|
41
|
+
config = this._merge(config, jsConfig);
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
} catch (e) {}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 3. Try package.json "entkapp" key
|
|
48
|
+
const pkgPath = path.join(this.cwd, 'package.json');
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
|
|
51
|
+
if (pkg.entkapp && typeof pkg.entkapp === 'object') {
|
|
52
|
+
config = this._merge(config, pkg.entkapp);
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {}
|
|
55
|
+
|
|
56
|
+
// 4. Apply CLI overrides
|
|
57
|
+
config = this._merge(config, overrides);
|
|
58
|
+
|
|
59
|
+
return config;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_defaultConfig() {
|
|
63
|
+
return {
|
|
64
|
+
interface: 'CLI',
|
|
65
|
+
useBuiltinPlugins: true,
|
|
66
|
+
useCustomPlugins: true,
|
|
67
|
+
options: {
|
|
68
|
+
verbose: false,
|
|
69
|
+
fastMode: true,
|
|
70
|
+
selfHealing: true
|
|
71
|
+
},
|
|
72
|
+
enabledPlugins: [],
|
|
73
|
+
ignoreDependencies: ['entkapp', '@types/*'],
|
|
74
|
+
exclude: ['node_modules', '.git', 'dist', 'build', 'coverage'],
|
|
75
|
+
entryPoints: [],
|
|
76
|
+
workspace: false
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
_merge(base, override) {
|
|
81
|
+
if (!override || typeof override !== 'object') return base;
|
|
82
|
+
const result = { ...base };
|
|
83
|
+
for (const key of Object.keys(override)) {
|
|
84
|
+
if (key === 'options' && typeof override[key] === 'object') {
|
|
85
|
+
result.options = { ...(base.options || {}), ...override[key] };
|
|
86
|
+
} else if (Array.isArray(override[key])) {
|
|
87
|
+
result[key] = override[key];
|
|
88
|
+
} else {
|
|
89
|
+
result[key] = override[key];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
|
|
4
|
+
export class DependencyResolver {
|
|
5
|
+
constructor(context, pathMapper, workspaceGraph) {
|
|
6
|
+
this.context = context;
|
|
7
|
+
this.pathMapper = pathMapper;
|
|
8
|
+
this.workspaceGraph = workspaceGraph;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
normalizePath(p) {
|
|
12
|
+
if (!p) return p;
|
|
13
|
+
const original = p;
|
|
14
|
+
// Handle Windows drive letters and backslashes
|
|
15
|
+
let normalized = p.replace(/\\/g, '/');
|
|
16
|
+
if (/^[a-z]:\//i.test(normalized)) {
|
|
17
|
+
normalized = normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
18
|
+
}
|
|
19
|
+
if (this.context.verbose && original !== normalized) {
|
|
20
|
+
// console.log(`[Path-DEBUG] Normalized: ${original} -> ${normalized}`);
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolveModulePath(sourceFile, specifier) {
|
|
26
|
+
const cleanSource = this.normalizePath(sourceFile);
|
|
27
|
+
|
|
28
|
+
// Check if it's a workspace package reference
|
|
29
|
+
if (this.context.isWorkspaceEnabled && this.workspaceGraph && this.workspaceGraph.isLocalWorkspaceSpecifier(specifier)) {
|
|
30
|
+
const match = this.workspaceGraph.getWorkspacePackageMatch(specifier);
|
|
31
|
+
if (match && match.entryPoints && match.entryPoints.length > 0) {
|
|
32
|
+
// Return the first entry point for the workspace package
|
|
33
|
+
return this.normalizePath(match.entryPoints[0]);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// UPGRADE: Use PathMapper for sophisticated resolution (TS-to-JS, aliases, etc.)
|
|
38
|
+
if (this.pathMapper) {
|
|
39
|
+
// Allow pathMapper to resolve aliases directly from specifier
|
|
40
|
+
const aliasResolved = this.pathMapper.resolvePath(specifier);
|
|
41
|
+
if (aliasResolved && aliasResolved !== specifier && existsSync(aliasResolved)) {
|
|
42
|
+
return this.normalizePath(aliasResolved);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const dir = path.dirname(cleanSource);
|
|
46
|
+
const target = path.resolve(dir, specifier);
|
|
47
|
+
const resolved = this.pathMapper.resolvePath(target);
|
|
48
|
+
if (resolved && existsSync(resolved)) {
|
|
49
|
+
return this.normalizePath(resolved);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (specifier.startsWith('.')) {
|
|
54
|
+
const dir = path.dirname(cleanSource);
|
|
55
|
+
const target = path.resolve(dir, specifier);
|
|
56
|
+
const normalizedTarget = this.normalizePath(target);
|
|
57
|
+
|
|
58
|
+
const extensions = ['', '.mjs', '.ts', '.tsx', '.jsx', '/index.mjs', '/index.ts', '/index.tsx'];
|
|
59
|
+
for (const ext of extensions) {
|
|
60
|
+
const p = normalizedTarget + ext;
|
|
61
|
+
if (existsSync(p)) return this.normalizePath(p);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ============================================================================
|
|
7
|
+
* Dependency Auto-Fixer v5.1.0
|
|
8
|
+
* ============================================================================
|
|
9
|
+
* Automatically installs missing dependencies using the detected package manager.
|
|
10
|
+
*/
|
|
11
|
+
export class DependencyFixer {
|
|
12
|
+
constructor(context) {
|
|
13
|
+
this.context = context;
|
|
14
|
+
this.cwd = context.cwd;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async detectPackageManager() {
|
|
18
|
+
const files = await fs.readdir(this.cwd);
|
|
19
|
+
if (files.includes('pnpm-lock.yaml')) return 'pnpm';
|
|
20
|
+
if (files.includes('yarn.lock')) return 'yarn';
|
|
21
|
+
if (files.includes('bun.lockb')) return 'bun';
|
|
22
|
+
return 'npm';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async fix(diagnostics) {
|
|
26
|
+
const pkgManager = await this.detectPackageManager();
|
|
27
|
+
const toInstall = {
|
|
28
|
+
dependencies: new Set(),
|
|
29
|
+
devDependencies: new Set()
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
for (const d of diagnostics) {
|
|
33
|
+
if (d.package && d.severity === 'error') {
|
|
34
|
+
if (d.expectedIn === 'devDependencies') {
|
|
35
|
+
toInstall.devDependencies.add(d.package);
|
|
36
|
+
} else {
|
|
37
|
+
toInstall.dependencies.add(d.package);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const results = [];
|
|
43
|
+
|
|
44
|
+
if (toInstall.dependencies.size > 0) {
|
|
45
|
+
const deps = Array.from(toInstall.dependencies);
|
|
46
|
+
results.push(await this._runInstall(pkgManager, deps, false));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (toInstall.devDependencies.size > 0) {
|
|
50
|
+
const devDeps = Array.from(toInstall.devDependencies);
|
|
51
|
+
results.push(await this._runInstall(pkgManager, devDeps, true));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return results;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _runInstall(pm, packages, isDev) {
|
|
58
|
+
const args = [];
|
|
59
|
+
if (pm === 'npm') {
|
|
60
|
+
args.push('install', isDev ? '--save-dev' : '--save');
|
|
61
|
+
} else if (pm === 'yarn') {
|
|
62
|
+
args.push('add', isDev ? '--dev' : '');
|
|
63
|
+
} else if (pm === 'pnpm') {
|
|
64
|
+
args.push('add', isDev ? '--save-dev' : '');
|
|
65
|
+
} else if (pm === 'bun') {
|
|
66
|
+
args.push('add', isDev ? '--dev' : '');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const finalArgs = [...args.filter(Boolean), ...packages];
|
|
70
|
+
console.log(`[DependencyFixer] Running: ${pm} ${finalArgs.join(' ')}`);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
// In a real scenario, we would use execa here.
|
|
74
|
+
// For the SDK, we return the command that should be run.
|
|
75
|
+
return {
|
|
76
|
+
success: true,
|
|
77
|
+
command: `${pm} ${finalArgs.join(' ')}`,
|
|
78
|
+
packages
|
|
79
|
+
};
|
|
80
|
+
} catch (e) {
|
|
81
|
+
return {
|
|
82
|
+
success: false,
|
|
83
|
+
error: e.message,
|
|
84
|
+
packages
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Advanced Dependency Profiling Engine.
|
|
6
|
+
* Traces Peer Dependencies and Implicit Tooling Invocations.
|
|
7
|
+
* Now supports "Unused Binaries" detection.
|
|
8
|
+
*/
|
|
9
|
+
export class DependencyProfiler {
|
|
10
|
+
constructor(context) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
|
|
13
|
+
this.binaryToPackageMap = {
|
|
14
|
+
'tsc': 'typescript',
|
|
15
|
+
'ts-node': 'ts-node',
|
|
16
|
+
'tsx': 'tsx',
|
|
17
|
+
'node': 'node',
|
|
18
|
+
'bun': 'bun',
|
|
19
|
+
'deno': 'deno',
|
|
20
|
+
'jest': 'jest',
|
|
21
|
+
'vitest': 'vitest',
|
|
22
|
+
'mocha': 'mocha',
|
|
23
|
+
'jasmine': 'jasmine',
|
|
24
|
+
'ava': 'ava',
|
|
25
|
+
'tap': 'tap',
|
|
26
|
+
'uvu': 'uvu',
|
|
27
|
+
'c8': 'c8',
|
|
28
|
+
'nyc': 'nyc',
|
|
29
|
+
'playwright': '@playwright/test',
|
|
30
|
+
'cypress': 'cypress',
|
|
31
|
+
'puppeteer': 'puppeteer',
|
|
32
|
+
'webdriverio': 'webdriverio',
|
|
33
|
+
'wdio': '@wdio/cli',
|
|
34
|
+
'eslint': 'eslint',
|
|
35
|
+
'prettier': 'prettier',
|
|
36
|
+
'tslint': 'tslint',
|
|
37
|
+
'biome': '@biomejs/biome',
|
|
38
|
+
'oxlint': 'oxlint',
|
|
39
|
+
'stylelint': 'stylelint',
|
|
40
|
+
'markdownlint': 'markdownlint-cli',
|
|
41
|
+
'commitlint': '@commitlint/cli',
|
|
42
|
+
'lint-staged': 'lint-staged',
|
|
43
|
+
'vite': 'vite',
|
|
44
|
+
'webpack': 'webpack',
|
|
45
|
+
'rollup': 'rollup',
|
|
46
|
+
'esbuild': 'esbuild',
|
|
47
|
+
'parcel': 'parcel',
|
|
48
|
+
'turbo': 'turbo',
|
|
49
|
+
'nx': 'nx',
|
|
50
|
+
'lerna': 'lerna',
|
|
51
|
+
'changesets': '@changesets/cli',
|
|
52
|
+
'changeset': '@changesets/cli',
|
|
53
|
+
'tsup': 'tsup',
|
|
54
|
+
'unbuild': 'unbuild',
|
|
55
|
+
'pkgroll': 'pkgroll',
|
|
56
|
+
'microbundle': 'microbundle',
|
|
57
|
+
'ncc': '@vercel/ncc',
|
|
58
|
+
'swc': '@swc/cli',
|
|
59
|
+
'tailwind': 'tailwindcss',
|
|
60
|
+
'tailwindcss': 'tailwindcss',
|
|
61
|
+
'postcss': 'postcss',
|
|
62
|
+
'sass': 'sass',
|
|
63
|
+
'less': 'less',
|
|
64
|
+
'next': 'next',
|
|
65
|
+
'nuxt': 'nuxt',
|
|
66
|
+
'astro': 'astro',
|
|
67
|
+
'remix': '@remix-run/dev',
|
|
68
|
+
'svelte-kit': '@sveltejs/kit',
|
|
69
|
+
'expo': 'expo',
|
|
70
|
+
'react-scripts': 'react-scripts',
|
|
71
|
+
'ng': '@angular/cli',
|
|
72
|
+
'vue': '@vue/cli-service',
|
|
73
|
+
'gatsby': 'gatsby',
|
|
74
|
+
'nodemon': 'nodemon',
|
|
75
|
+
'ts-node-dev': 'ts-node-dev',
|
|
76
|
+
'concurrently': 'concurrently',
|
|
77
|
+
'cross-env': 'cross-env',
|
|
78
|
+
'dotenv': 'dotenv-cli',
|
|
79
|
+
'dotenv-cli': 'dotenv-cli',
|
|
80
|
+
'rimraf': 'rimraf',
|
|
81
|
+
'del-cli': 'del-cli',
|
|
82
|
+
'copyfiles': 'copyfiles',
|
|
83
|
+
'cpy-cli': 'cpy-cli',
|
|
84
|
+
'mkdirp': 'mkdirp',
|
|
85
|
+
'shx': 'shx',
|
|
86
|
+
'npm-run-all': 'npm-run-all',
|
|
87
|
+
'run-p': 'npm-run-all',
|
|
88
|
+
'run-s': 'npm-run-all',
|
|
89
|
+
'typedoc': 'typedoc',
|
|
90
|
+
'jsdoc': 'jsdoc',
|
|
91
|
+
'storybook': 'storybook',
|
|
92
|
+
'sb': 'storybook',
|
|
93
|
+
'husky': 'husky',
|
|
94
|
+
'simple-git-hooks': 'simple-git-hooks',
|
|
95
|
+
'lefthook': 'lefthook',
|
|
96
|
+
'pnpm': 'pnpm',
|
|
97
|
+
'yarn': 'yarn',
|
|
98
|
+
'npm': 'npm',
|
|
99
|
+
'patch-package': 'patch-package',
|
|
100
|
+
'syncpack': 'syncpack',
|
|
101
|
+
'publint': 'publint',
|
|
102
|
+
'attw': '@arethetypeswrong/cli',
|
|
103
|
+
'size-limit': 'size-limit',
|
|
104
|
+
'bundlesize': 'bundlesize',
|
|
105
|
+
'depcheck': 'depcheck',
|
|
106
|
+
'knip': 'knip',
|
|
107
|
+
'entkapp': 'entkapp'
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
this.configFileToPackageMap = {
|
|
111
|
+
'jest.config': 'jest',
|
|
112
|
+
'vitest.config': 'vitest',
|
|
113
|
+
'playwright.config': '@playwright/test',
|
|
114
|
+
'cypress.config': 'cypress',
|
|
115
|
+
'webpack.config': 'webpack',
|
|
116
|
+
'vite.config': 'vite',
|
|
117
|
+
'rollup.config': 'rollup',
|
|
118
|
+
'tailwind.config': 'tailwindcss',
|
|
119
|
+
'postcss.config': 'postcss',
|
|
120
|
+
'.eslintrc': 'eslint',
|
|
121
|
+
'eslint.config': 'eslint',
|
|
122
|
+
'.prettierrc': 'prettier',
|
|
123
|
+
'prettier.config': 'prettier',
|
|
124
|
+
'.babelrc': '@babel/core',
|
|
125
|
+
'babel.config': '@babel/core',
|
|
126
|
+
'.stylelintrc': 'stylelint',
|
|
127
|
+
'stylelint.config': 'stylelint',
|
|
128
|
+
'svelte.config': '@sveltejs/kit',
|
|
129
|
+
'astro.config': 'astro',
|
|
130
|
+
'nuxt.config': 'nuxt',
|
|
131
|
+
'next.config': 'next',
|
|
132
|
+
'remix.config': '@remix-run/dev',
|
|
133
|
+
'.commitlintrc': '@commitlint/cli',
|
|
134
|
+
'commitlint.config': '@commitlint/cli',
|
|
135
|
+
'tsup.config': 'tsup',
|
|
136
|
+
'typedoc': 'typedoc',
|
|
137
|
+
'.lintstagedrc': 'lint-staged',
|
|
138
|
+
'lint-staged.config': 'lint-staged',
|
|
139
|
+
'lefthook': 'lefthook',
|
|
140
|
+
'knip.config': 'knip',
|
|
141
|
+
'knip.json': 'knip'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async traceImplicitInvocations(projectRoot) {
|
|
146
|
+
const usedPackages = new Set();
|
|
147
|
+
const usedBinaries = new Set(); // NEW: Track which binaries are actually used
|
|
148
|
+
|
|
149
|
+
// 1. Scan package.json scripts (Root)
|
|
150
|
+
await this._scanDirectoryForConfigs(projectRoot, usedPackages, usedBinaries);
|
|
151
|
+
|
|
152
|
+
// 1.5 Scan Workspace package.json scripts and configs
|
|
153
|
+
if (this.context.isWorkspaceEnabled && this.context.monorepoPackageRoots) {
|
|
154
|
+
for (const workspaceRoot of this.context.monorepoPackageRoots) {
|
|
155
|
+
await this._scanDirectoryForConfigs(workspaceRoot, usedPackages, usedBinaries);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 2. Scan CI workflows
|
|
160
|
+
try {
|
|
161
|
+
const githubWorkflows = path.join(projectRoot, '.github/workflows');
|
|
162
|
+
const files = await fs.readdir(githubWorkflows).catch(() => []);
|
|
163
|
+
for (const file of files) {
|
|
164
|
+
if (file.endsWith('.yml') || file.endsWith('.yaml')) {
|
|
165
|
+
const content = await fs.readFile(path.join(githubWorkflows, file), 'utf8');
|
|
166
|
+
this.extractPackagesFromScript(content, usedPackages, usedBinaries);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch (e) {}
|
|
170
|
+
|
|
171
|
+
// 4. Identify Unused Binaries (Root)
|
|
172
|
+
await this._identifyUnusedBinaries(projectRoot, usedBinaries);
|
|
173
|
+
|
|
174
|
+
// 4.5 Identify Unused Binaries (Workspaces)
|
|
175
|
+
if (this.context.isWorkspaceEnabled && this.context.monorepoPackageRoots) {
|
|
176
|
+
for (const workspaceRoot of this.context.monorepoPackageRoots) {
|
|
177
|
+
await this._identifyUnusedBinaries(workspaceRoot, usedBinaries);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return usedPackages;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async _scanDirectoryForConfigs(dir, usedPackages, usedBinaries) {
|
|
185
|
+
try {
|
|
186
|
+
const pkgJsonPath = path.join(dir, 'package.json');
|
|
187
|
+
const pkg = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
|
|
188
|
+
|
|
189
|
+
if (pkg.scripts) {
|
|
190
|
+
for (const script of Object.values(pkg.scripts)) {
|
|
191
|
+
this.extractPackagesFromScript(script, usedPackages, usedBinaries);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (pkg.dependencies || pkg.devDependencies) {
|
|
196
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
197
|
+
for (const depName of Object.keys(allDeps)) {
|
|
198
|
+
if (depName.startsWith('@types/')) {
|
|
199
|
+
usedPackages.add(depName);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} catch (e) {}
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
const dirEntries = await fs.readdir(dir, { withFileTypes: true });
|
|
207
|
+
for (const entry of dirEntries) {
|
|
208
|
+
if (!entry.isFile()) continue;
|
|
209
|
+
const fileName = entry.name;
|
|
210
|
+
for (const [fragment, pkgName] of Object.entries(this.configFileToPackageMap)) {
|
|
211
|
+
if (fileName.startsWith(fragment) || fileName === fragment) {
|
|
212
|
+
usedPackages.add(pkgName);
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (e) {}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async _identifyUnusedBinaries(dir, usedBinaries) {
|
|
221
|
+
try {
|
|
222
|
+
const binDir = path.join(dir, 'node_modules', '.bin');
|
|
223
|
+
const availableBinaries = await fs.readdir(binDir).catch(() => []);
|
|
224
|
+
|
|
225
|
+
for (const bin of availableBinaries) {
|
|
226
|
+
if (bin.startsWith('.') || ['npm', 'pnpm', 'yarn', 'bun'].includes(bin)) continue;
|
|
227
|
+
|
|
228
|
+
if (!usedBinaries.has(bin)) {
|
|
229
|
+
this.context.unusedBinaries.add(bin);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
extractPackagesFromScript(script, packageCollector, binaryCollector) {
|
|
236
|
+
const words = script.split(/[\s&|;()\n\r\t]+/);
|
|
237
|
+
for (let i = 0; i < words.length; i++) {
|
|
238
|
+
const rawWord = words[i];
|
|
239
|
+
const cleanWord = rawWord.replace(/['"]/g, '').replace(/^(npx|pnpx|bunx|yarn|pnpm)\s+/, '');
|
|
240
|
+
|
|
241
|
+
if (this.binaryToPackageMap[cleanWord]) {
|
|
242
|
+
packageCollector.add(this.binaryToPackageMap[cleanWord]);
|
|
243
|
+
if (binaryCollector) binaryCollector.add(cleanWord);
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if ((rawWord === 'npx' || rawWord === 'pnpx' || rawWord === 'bunx') && i + 1 < words.length) {
|
|
248
|
+
const nextWord = words[i + 1].replace(/['"]/g, '');
|
|
249
|
+
if (this.binaryToPackageMap[nextWord]) {
|
|
250
|
+
packageCollector.add(this.binaryToPackageMap[nextWord]);
|
|
251
|
+
if (binaryCollector) binaryCollector.add(nextWord);
|
|
252
|
+
}
|
|
253
|
+
if (nextWord.startsWith('@') || nextWord.includes('/')) {
|
|
254
|
+
const pkgName = nextWord.split('/').slice(0, nextWord.startsWith('@') ? 2 : 1).join('/');
|
|
255
|
+
if (pkgName) packageCollector.add(pkgName);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async resolvePeerDependencies(usedPackages, projectRoot) {
|
|
262
|
+
const peerDeps = new Set();
|
|
263
|
+
const nodeModules = path.join(projectRoot, 'node_modules');
|
|
264
|
+
|
|
265
|
+
for (const pkgName of usedPackages) {
|
|
266
|
+
try {
|
|
267
|
+
const pkgJsonPath = path.join(nodeModules, pkgName, 'package.json');
|
|
268
|
+
const pkg = JSON.parse(await fs.readFile(pkgJsonPath, 'utf8'));
|
|
269
|
+
if (pkg.peerDependencies) {
|
|
270
|
+
Object.keys(pkg.peerDependencies).forEach(dep => peerDeps.add(dep));
|
|
271
|
+
}
|
|
272
|
+
} catch (e) {}
|
|
273
|
+
}
|
|
274
|
+
return peerDeps;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
shouldExcludeFromUnusedCheck(packageName, depType) {
|
|
278
|
+
if (depType === 'peerDependency' || depType === 'optionalDependency') {
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
if (packageName.startsWith('@types/')) {
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export class EntryPointDetector {
|
|
5
|
+
constructor(targetDir, packageJson, stats) {
|
|
6
|
+
this.targetDir = targetDir;
|
|
7
|
+
this.packageJson = packageJson || {};
|
|
8
|
+
this.stats = stats;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detects all potential entry points of a project.
|
|
13
|
+
* @returns {Set<string>} Set of absolute paths.
|
|
14
|
+
*/
|
|
15
|
+
detect() {
|
|
16
|
+
const entries = new Set();
|
|
17
|
+
|
|
18
|
+
// 1. package.json Standards
|
|
19
|
+
this._addFromPackageJson(entries);
|
|
20
|
+
|
|
21
|
+
// 2. Framework-specific paths
|
|
22
|
+
this._addFromFrameworks(entries);
|
|
23
|
+
|
|
24
|
+
// 3. Fallbacks / Konventionen
|
|
25
|
+
this._addFromConventions(entries);
|
|
26
|
+
|
|
27
|
+
return entries;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_addFromPackageJson(entries) {
|
|
31
|
+
const fields = ['main', 'module', 'browser', 'bin'];
|
|
32
|
+
fields.forEach(field => {
|
|
33
|
+
const val = this.packageJson[field];
|
|
34
|
+
if (typeof val === 'string') {
|
|
35
|
+
this._addIfExist(entries, val);
|
|
36
|
+
} else if (typeof val === 'object' && val !== null) {
|
|
37
|
+
Object.values(val).forEach(v => {
|
|
38
|
+
if (typeof v === 'string') this._addIfExist(entries, v);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Exports field (modern Node.js)
|
|
44
|
+
if (this.packageJson.exports) {
|
|
45
|
+
this._parseExports(this.packageJson.exports, entries);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_parseExports(exports, entries) {
|
|
50
|
+
if (typeof exports === 'string') {
|
|
51
|
+
this._addIfExist(entries, exports);
|
|
52
|
+
} else if (typeof exports === 'object' && exports !== null) {
|
|
53
|
+
for (const key in exports) {
|
|
54
|
+
const val = exports[key];
|
|
55
|
+
if (typeof val === 'string') {
|
|
56
|
+
this._addIfExist(entries, val);
|
|
57
|
+
} else if (typeof val === 'object') {
|
|
58
|
+
this._parseExports(val, entries);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_addFromFrameworks(entries) {
|
|
65
|
+
if (!this.stats || !this.stats.detectedFrameworks) return;
|
|
66
|
+
|
|
67
|
+
const frameworks = this.stats.detectedFrameworks;
|
|
68
|
+
|
|
69
|
+
if (frameworks.includes('next')) {
|
|
70
|
+
this._scanDir(path.join(this.targetDir, 'pages'), entries);
|
|
71
|
+
this._scanDir(path.join(this.targetDir, 'app'), entries, ['page.tsx', 'page.mjs', 'route.ts', 'route.mjs']);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (frameworks.includes('nuxt')) {
|
|
75
|
+
this._scanDir(path.join(this.targetDir, 'pages'), entries);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (frameworks.includes('svelte')) {
|
|
79
|
+
this._scanDir(path.join(this.targetDir, 'src/routes'), entries);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_addFromConventions(entries) {
|
|
84
|
+
const fallbacks = [
|
|
85
|
+
'index.mjs', 'index.ts', 'index.jsx', 'index.tsx',
|
|
86
|
+
'src/index.mjs', 'src/index.ts', 'src/index.jsx', 'src/index.tsx',
|
|
87
|
+
'main.mjs', 'main.ts', 'src/main.mjs', 'src/main.ts',
|
|
88
|
+
'app.mjs', 'app.ts', 'src/app.mjs', 'src/app.ts',
|
|
89
|
+
'server.mjs', 'server.ts', 'src/server.mjs', 'src/server.ts'
|
|
90
|
+
];
|
|
91
|
+
fallbacks.forEach(f => this._addIfExist(entries, f));
|
|
92
|
+
|
|
93
|
+
// If no entries found yet, and we are in a simple src structure,
|
|
94
|
+
// protect common top-level files in src/
|
|
95
|
+
if (entries.size === 0) {
|
|
96
|
+
const commonSrcFiles = ['src/app.ts', 'src/main.ts', 'src/index.ts', 'src/app.mjs', 'src/main.mjs', 'src/index.mjs'];
|
|
97
|
+
commonSrcFiles.forEach(f => this._addIfExist(entries, f));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
_addIfExist(entries, relativePath) {
|
|
102
|
+
// Clean path (remove ./ etc)
|
|
103
|
+
const cleanPath = relativePath.replace(/^(\.\/|\/)/, '');
|
|
104
|
+
const absolutePath = path.resolve(this.targetDir, cleanPath);
|
|
105
|
+
|
|
106
|
+
// Prüfe verschiedene Erweiterungen falls keine angegeben
|
|
107
|
+
const extensions = ['', '.mjs', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
108
|
+
for (const ext of extensions) {
|
|
109
|
+
const p = absolutePath + ext;
|
|
110
|
+
if (fs.existsSync(p) && fs.statSync(p).isFile()) {
|
|
111
|
+
entries.add(p);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
_scanDir(dir, entries, specificFiles = null) {
|
|
118
|
+
if (!fs.existsSync(dir)) return;
|
|
119
|
+
|
|
120
|
+
const files = fs.readdirSync(dir, { recursive: true });
|
|
121
|
+
files.forEach(file => {
|
|
122
|
+
const fullPath = path.join(dir, file);
|
|
123
|
+
if (fs.statSync(fullPath).isFile()) {
|
|
124
|
+
if (specificFiles) {
|
|
125
|
+
if (specificFiles.includes(path.basename(file))) {
|
|
126
|
+
entries.add(fullPath);
|
|
127
|
+
}
|
|
128
|
+
} else if (/\.(js|ts|jsx|tsx|vue|svelte)$/.test(file)) {
|
|
129
|
+
entries.add(fullPath);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|