entkapp 5.5.0 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2 -2
  3. package/bin/cli.mjs +175 -0
  4. package/index.cjs +18 -0
  5. package/index.mjs +51 -0
  6. package/package.json +7 -6
  7. package/src/EngineContext.mjs +428 -0
  8. package/src/Initializer.mjs +82 -0
  9. package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
  10. package/src/analyzers/OxcAnalyzer.mjs +11 -0
  11. package/src/api/HeadlessAPI.js +31 -16
  12. package/src/api/HeadlessAPI.mjs +369 -0
  13. package/src/api/PluginSDK.mjs +135 -0
  14. package/src/ast/ASTAnalyzer.js +17 -3
  15. package/src/ast/ASTAnalyzer.mjs +742 -0
  16. package/src/ast/AdvancedAnalysis.mjs +586 -0
  17. package/src/ast/BarrelParser.mjs +230 -0
  18. package/src/ast/DeadCodeDetector.js +7 -5
  19. package/src/ast/DeadCodeDetector.mjs +92 -0
  20. package/src/ast/MagicDetector.mjs +203 -0
  21. package/src/ast/OxcAnalyzer.mjs +188 -0
  22. package/src/ast/SecretScanner.mjs +374 -0
  23. package/src/healing/GitSandbox.mjs +82 -0
  24. package/src/healing/SelfHealer.mjs +48 -0
  25. package/src/index.js +37 -1
  26. package/src/index.mjs +1180 -0
  27. package/src/performance/GraphCache.mjs +108 -0
  28. package/src/performance/SupplyChainGuard.mjs +92 -0
  29. package/src/performance/WorkerPool.mjs +132 -0
  30. package/src/performance/WorkerTaskRunner.mjs +144 -0
  31. package/src/plugins/BasePlugin.mjs +240 -0
  32. package/src/plugins/PluginRegistry.mjs +203 -0
  33. package/src/plugins/ecosystems/BackendServices.mjs +197 -0
  34. package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
  35. package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
  36. package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
  37. package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
  38. package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
  39. package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
  40. package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
  41. package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
  42. package/src/refractor/ImpactAnalyzer.mjs +92 -0
  43. package/src/refractor/SourceRewriter.mjs +86 -0
  44. package/src/refractor/TransactionManager.mjs +5 -0
  45. package/src/refractor/TypeIntegrity.mjs +4 -0
  46. package/src/resolution/BuildOrchestrator.mjs +46 -0
  47. package/src/resolution/CircularDetector.mjs +91 -0
  48. package/src/resolution/ConfigGenerator.mjs +83 -0
  49. package/src/resolution/ConfigLoader.mjs +94 -0
  50. package/src/resolution/DepencyResolver.mjs +66 -0
  51. package/src/resolution/DependencyFixer.mjs +88 -0
  52. package/src/resolution/DependencyProfiler.mjs +286 -0
  53. package/src/resolution/EntryPointDetector.mjs +134 -0
  54. package/src/resolution/FrameworkConfigParser.mjs +119 -0
  55. package/src/resolution/GraphAnalyzer.mjs +80 -0
  56. package/src/resolution/MigrationAnalyzer.mjs +60 -0
  57. package/src/resolution/PathMapper.mjs +82 -0
  58. package/src/resolution/TSConfigLoader.mjs +162 -0
  59. package/src/resolution/WorkSpaceGraph.mjs +183 -0
  60. package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
  61. package/test.js +76 -0
  62. package/wrangler.toml +6 -0
@@ -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
+ }
@@ -0,0 +1,119 @@
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+
4
+ /**
5
+ * Lightweight Static Analysis Parser for Vite / Vitest / Framework Configurations.
6
+ * Extracts 'resolve.alias' and 'build.lib.entry' without executing the file.
7
+ * Version 5.4.0: Added poly-extension path resolution.
8
+ */
9
+ export class FrameworkConfigParser {
10
+ constructor(context) {
11
+ this.context = context;
12
+ }
13
+
14
+ /**
15
+ * Parses a Framework configuration file and extracts structural metadata.
16
+ * @param {string} content - Raw source code of the config file
17
+ * @param {string} filePath - Absolute path to the config file
18
+ * @returns {Object} { aliases: Map<string, string>, entries: Set<string> }
19
+ */
20
+ parse(content, filePath) {
21
+ const results = {
22
+ aliases: new Map(),
23
+ entries: new Set()
24
+ };
25
+
26
+ if (!content) return results;
27
+
28
+ const configDir = path.dirname(filePath);
29
+
30
+ // 1. Extract Aliases from resolve: { alias: { ... } } or alias: [ ... ]
31
+ this._extractAliases(content, configDir, results.aliases);
32
+
33
+ // 2. Extract Entry Points from build: { lib: { entry: '...' } } or rollupOptions: { input: '...' }
34
+ this._extractEntries(content, configDir, results.entries);
35
+
36
+ return results;
37
+ }
38
+
39
+ _extractAliases(content, configDir, aliasMap) {
40
+ const objAliasPatterns = [
41
+ /alias\s*:\s*\{([\s\S]*?)\}/g,
42
+ /resolve\s*:\s*\{[\s\S]*?alias\s*:\s*\{([\s\S]*?)\}/g
43
+ ];
44
+
45
+ for (const pattern of objAliasPatterns) {
46
+ let match;
47
+ while ((match = pattern.exec(content)) !== null) {
48
+ const inner = match[1];
49
+ const pairPattern = /(?:['"]?)(@?[a-zA-Z0-9_\-\/*]+)(?:['"]?)\s*:\s*(?:['"]([^'"]+)['"]|(path\.(?:resolve|join)\([\s\S]*?\)))/g;
50
+ let pair;
51
+ while ((pair = pairPattern.exec(inner)) !== null) {
52
+ const [_, key, stringVal, callVal] = pair;
53
+ aliasMap.set(key, this._resolveValue(stringVal || callVal, configDir));
54
+ }
55
+ }
56
+ }
57
+
58
+ const arrAliasPattern = /alias\s*:\s*\[([\s\S]*?)\]/;
59
+ const arrMatch = arrAliasPattern.exec(content);
60
+ if (arrMatch) {
61
+ const inner = arrMatch[1];
62
+ const findPattern = /\{\s*find\s*:\s*['"]([^'"]+)['"]\s*,\s*replacement\s*:\s*['"]([^'"]+)['"]\s*\}/g;
63
+ let findMatch;
64
+ while ((findMatch = findPattern.exec(inner)) !== null) {
65
+ const [_, key, value] = findMatch;
66
+ aliasMap.set(key, this._resolveValue(value, configDir));
67
+ }
68
+ }
69
+ }
70
+
71
+ _extractEntries(content, configDir, entrySet) {
72
+ const libEntryPattern = /entry\s*:\s*['"]([^'"]+)['"]/;
73
+ const libMatch = libEntryPattern.exec(content);
74
+ if (libMatch) {
75
+ entrySet.add(this._resolveValue(libMatch[1], configDir));
76
+ }
77
+
78
+ const inputPattern = /input\s*:\s*(?:['"]([^'"]+)['"]|\[([\s\S]*?)\])/;
79
+ const inputMatch = inputPattern.exec(content);
80
+ if (inputMatch) {
81
+ if (inputMatch[1]) {
82
+ entrySet.add(this._resolveValue(inputMatch[1], configDir));
83
+ } else if (inputMatch[2]) {
84
+ const paths = inputMatch[2].match(/['"]([^'"]+)['"]/g);
85
+ if (paths) {
86
+ paths.forEach(p => entrySet.add(this._resolveValue(p.replace(/['"]/g, ''), configDir)));
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Resolves a path value and applies poly-extension fallback if the file doesn't exist.
94
+ */
95
+ _resolveValue(val, configDir) {
96
+ const pathCallPattern = /path\.(?:resolve|join)\s*\(\s*(?:__dirname\s*,\s*)?['"]([^'"]+)['"]\s*\)/;
97
+ const pathMatch = pathCallPattern.exec(val);
98
+ if (pathMatch) {
99
+ val = pathMatch[1];
100
+ }
101
+
102
+ if (val.startsWith('.') || val.startsWith('/') || /^[a-zA-Z]:/.test(val)) {
103
+ let resolved = path.resolve(configDir, val).replace(/\\/g, '/');
104
+
105
+ // UPGRADE: Poly-extension fallback
106
+ if (!fs.existsSync(resolved)) {
107
+ const base = resolved.replace(/\.[a-zA-Z0-9]+$/, '');
108
+ const extensions = ['.ts', '.mjs', '.tsx', '.jsx', '.mjs', '.cjs'];
109
+ for (const ext of extensions) {
110
+ if (fs.existsSync(base + ext)) {
111
+ return (base + ext).replace(/\\/g, '/');
112
+ }
113
+ }
114
+ }
115
+ return resolved;
116
+ }
117
+ return val;
118
+ }
119
+ }
@@ -0,0 +1,80 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * ============================================================================
5
+ * Dead Code & Zombie Export Hunter v5.1.0
6
+ * ============================================================================
7
+ * Identifies unused files and exported symbols that are never imported.
8
+ */
9
+ export class GraphAnalyzer {
10
+ constructor(context) {
11
+ this.context = context;
12
+ this.cwd = context.cwd;
13
+ }
14
+
15
+ /**
16
+ * Performs a full reachability analysis from entry points.
17
+ */
18
+ async findDeadCode() {
19
+ const graph = this.context.projectGraph;
20
+ const reachable = new Set();
21
+ const entries = [];
22
+
23
+ // 1. Identify all confirmed entry points
24
+ for (const [filePath, node] of graph.entries()) {
25
+ if (node.isEntry) {
26
+ entries.push(filePath);
27
+ this._walk(filePath, reachable);
28
+ }
29
+ }
30
+
31
+ const deadFiles = [];
32
+ const zombieExports = [];
33
+
34
+ // 2. Find unreachable files
35
+ for (const filePath of graph.keys()) {
36
+ if (!reachable.has(filePath)) {
37
+ deadFiles.push(path.relative(this.cwd, filePath).replace(/\\/g, '/'));
38
+ } else {
39
+ // 3. Find Zombie Exports in reachable files
40
+ const node = graph.get(filePath);
41
+ if (node.isEntry) continue; // Skip entries as their exports are intended for external use
42
+
43
+ for (const [symbol, meta] of node.internalExports.entries()) {
44
+ if (symbol === 'default' || symbol === '*') continue;
45
+
46
+ let isUsed = false;
47
+ // Check if any other node imports this symbol
48
+ for (const [otherPath, otherNode] of graph.entries()) {
49
+ if (otherNode.importedSymbols && otherNode.importedSymbols.has(symbol)) {
50
+ // This is a simplified check; in reality, we'd check if it's imported FROM this file
51
+ isUsed = true;
52
+ break;
53
+ }
54
+ }
55
+
56
+ if (!isUsed) {
57
+ zombieExports.push({
58
+ file: path.relative(this.cwd, filePath).replace(/\\/g, '/'),
59
+ symbol: symbol
60
+ });
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ return { deadFiles, zombieExports };
67
+ }
68
+
69
+ _walk(filePath, reachable) {
70
+ if (reachable.has(filePath)) return;
71
+ reachable.add(filePath);
72
+
73
+ const node = this.context.projectGraph.get(filePath);
74
+ if (!node) return;
75
+
76
+ for (const impPath of node.explicitImports) {
77
+ this._walk(impPath, reachable);
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ============================================================================
3
+ * Migration Path Analyzer v5.1.0
4
+ * ============================================================================
5
+ * Suggests modern alternatives for legacy tools and frameworks.
6
+ */
7
+ export class MigrationAnalyzer {
8
+ constructor(context) {
9
+ this.context = context;
10
+ this.migrations = [
11
+ {
12
+ legacy: 'webpack',
13
+ modern: 'vite',
14
+ reason: 'Vite offers significantly faster HMR and build times using ES modules during development.'
15
+ },
16
+ {
17
+ legacy: 'jest',
18
+ modern: 'vitest',
19
+ reason: 'Vitest is faster, has native ESM support, and shares configuration with Vite.'
20
+ },
21
+ {
22
+ legacy: 'moment',
23
+ modern: 'dayjs',
24
+ reason: 'Day.js is a 2KB alternative to Moment.js with the same modern API.'
25
+ },
26
+ {
27
+ legacy: 'axios',
28
+ modern: 'fetch',
29
+ reason: 'Native fetch is now widely supported and requires no external dependency for simple use cases.'
30
+ },
31
+ {
32
+ legacy: 'ts-node',
33
+ modern: 'tsx',
34
+ reason: 'tsx is faster and has better ESM support for running TypeScript files.'
35
+ },
36
+ {
37
+ legacy: 'eslint-plugin-prettier',
38
+ modern: 'eslint-config-prettier',
39
+ reason: 'Running Prettier as an ESLint rule is slow. It is recommended to run them separately or use a config that turns off conflicting rules.'
40
+ }
41
+ ];
42
+ }
43
+
44
+ async analyze(activePlugins) {
45
+ const suggestions = [];
46
+ const activeNames = activePlugins.map(p => p.name);
47
+
48
+ for (const m of this.migrations) {
49
+ if (activeNames.includes(m.legacy)) {
50
+ suggestions.push({
51
+ from: m.legacy,
52
+ to: m.modern,
53
+ message: `Migration Suggestion: Consider moving from "${m.legacy}" to "${m.modern}". ${m.reason}`
54
+ });
55
+ }
56
+ }
57
+
58
+ return suggestions;
59
+ }
60
+ }