entkapp 5.3.0 → 5.4.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.
@@ -47,10 +47,11 @@ export class WorkerPool {
47
47
 
48
48
  try {
49
49
  const analyticalResultsSubsets = await Promise.all(threadTaskExecutionsList);
50
+ const flatResults = analyticalResultsSubsets.flat();
50
51
 
51
52
  // Merge thread structural subsets back into the primary context graph nodes
52
- analyticalResultsSubsets.flat().forEach(result => {
53
- if (!result) return;
53
+ for (const result of flatResults) {
54
+ if (!result) continue;
54
55
  // UPGRADE: Normalize filePath before merging to prevent duplicate nodes in the graph
55
56
  const normalizedPath = path.resolve(this.context.cwd, result.filePath).replace(/\\/g, '/');
56
57
  const node = masterEngineInstanceReference.context.getOrCreateNode(normalizedPath);
@@ -95,7 +96,13 @@ export class WorkerPool {
95
96
  if (!node.globImports) node.globImports = [];
96
97
  result.globImports.forEach(i => node.globImports.push(i));
97
98
  }
98
- });
99
+
100
+ // UPGRADE 5.3.0: Run plugin-specific content analysis for worker-parsed files
101
+ if (result.rawCode) {
102
+ node.rawCode = result.rawCode;
103
+ await masterEngineInstanceReference.magicDetector.runPluginContentAnalysis(node, normalizedPath);
104
+ }
105
+ }
99
106
 
100
107
  return true;
101
108
  } catch (poolThreadFault) {
@@ -107,13 +114,24 @@ export class WorkerPool {
107
114
  }
108
115
 
109
116
  executeChunkInsideThread(fileChunkSubset) {
117
+ // UPGRADE: Serialize alias patterns and workspace package names so workers can filter false positives
118
+ const serializedAliasPatterns = (this.context.pathMapper && this.context.pathMapper._aliasPatterns)
119
+ ? this.context.pathMapper._aliasPatterns.map(p => p.regex.source)
120
+ : [];
121
+ const workspacePackageNames = (this.context.workspaceGraph && this.context.workspaceGraph.workspacePackages)
122
+ ? Array.from(this.context.workspaceGraph.workspacePackages.keys())
123
+ : [];
124
+
110
125
  return new Promise((resolve, reject) => {
111
126
  const workerInstance = new Worker(this.workerScriptPath, { type: 'module',
112
127
  workerData: {
113
128
  files: fileChunkSubset,
114
129
  contextOptions: {
115
130
  verbose: this.context.verbose,
116
- cwd: this.context.cwd
131
+ cwd: this.context.cwd,
132
+ // UPGRADE: Pass alias/workspace data so workers can filter false positives
133
+ aliasPatternSources: serializedAliasPatterns,
134
+ workspacePackageNames
117
135
  }
118
136
  }
119
137
  });
@@ -12,11 +12,35 @@ async function runTask() {
12
12
  const { files, contextOptions } = workerData;
13
13
  const results = [];
14
14
 
15
+ // UPGRADE: Reconstruct alias/workspace filter helpers from serialized contextOptions
16
+ // These are passed from WorkerPool.executeChunkInsideThread so workers can skip false positives
17
+ const aliasRegexes = (contextOptions.aliasPatternSources || []).map(src => {
18
+ try { return new RegExp(src); } catch { return null; }
19
+ }).filter(Boolean);
20
+ const workspacePackageSet = new Set(contextOptions.workspacePackageNames || []);
21
+
22
+ // Lightweight helpers mirroring PathMapper.isTsconfigAlias and WorkspaceGraph.isLocalWorkspaceSpecifier
23
+ const isTsconfigAlias = (spec) => aliasRegexes.some(r => r.test(spec));
24
+ const isWorkspacePkg = (spec) => {
25
+ if (workspacePackageSet.has(spec)) return true;
26
+ for (const name of workspacePackageSet) { if (spec.startsWith(name + '/')) return true; }
27
+ return false;
28
+ };
29
+
15
30
  // Create a minimal context for analyzers
16
31
  const mockContext = {
17
32
  verbose: contextOptions.verbose,
18
33
  cwd: contextOptions.cwd || process.cwd(),
19
34
  projectGraph: new Map(),
35
+ // UPGRADE: Expose lightweight alias/workspace helpers so ASTAnalyzer can filter imports
36
+ pathMapper: {
37
+ isTsconfigAlias,
38
+ _aliasPatterns: aliasRegexes.map(r => ({ regex: r }))
39
+ },
40
+ workspaceGraph: {
41
+ isLocalWorkspaceSpecifier: isWorkspacePkg,
42
+ workspacePackages: workspacePackageSet
43
+ },
20
44
  getOrCreateNode: (path) => ({
21
45
  filePath: path,
22
46
  explicitImports: new Set(),
@@ -50,6 +74,7 @@ async function runTask() {
50
74
  try {
51
75
  const content = await fs.readFile(filePath, 'utf8');
52
76
  const node = mockContext.getOrCreateNode(filePath);
77
+ node.rawCode = content; // UPGRADE: Add raw code to node for plugin analysis in main thread
53
78
 
54
79
  // Defensiver Check: Sicherstellen, dass die Map/Set-Instanzen sauber existieren
55
80
  if (!(node.internalExports instanceof Map)) node.internalExports = new Map();
@@ -106,6 +131,7 @@ async function runTask() {
106
131
  // Serialize the node data for transfer back to main thread
107
132
  results.push({
108
133
  filePath: node.filePath,
134
+ rawCode: node.rawCode, // UPGRADE: Pass raw code back to main thread for plugin analysis
109
135
  explicitImports: Array.from(node.explicitImports || []),
110
136
  dynamicImports: Array.from(node.dynamicImports || []),
111
137
  importedSymbols: Array.from(node.importedSymbols || []),
@@ -5,7 +5,7 @@ import path from 'path';
5
5
  * Base class for all entkapp plugins.
6
6
  * Defines the contract for ecosystem detection, entry point mapping,
7
7
  * and missing dependency / devDependency detection.
8
- * Version 5.0.0: Added getMissingDependencies(), getOrphanedConfigs(), runDependencyDiagnostics().
8
+ * Version 5.4.0: Added detectEntryPoints() for dynamic framework entry detection.
9
9
  */
10
10
  export class BasePlugin {
11
11
  constructor(context) {
@@ -34,6 +34,17 @@ export class BasePlugin {
34
34
  return [];
35
35
  }
36
36
 
37
+ /**
38
+ * Version 5.4.0: Dynamically detect entry points from a file's content.
39
+ * Useful for parsing vite.config.ts, webpack.config.js, etc.
40
+ * @param {string} content - The file content
41
+ * @param {string} filePath - The absolute path to the file
42
+ * @returns {Array<string>} List of relative or absolute entry point paths
43
+ */
44
+ detectEntryPoints(content, filePath) {
45
+ return [];
46
+ }
47
+
37
48
  /**
38
49
  * Returns symbols that are implicitly required/exported by the framework.
39
50
  */
@@ -43,25 +54,13 @@ export class BasePlugin {
43
54
 
44
55
  /**
45
56
  * Returns the npm package names required for this plugin to work.
46
- * Each entry is either a string or an object:
47
- * { name: string, dev?: boolean, optional?: boolean }
48
- * - dev: true => expected in devDependencies
49
- * - dev: false => expected in dependencies
50
- * - optional => only warn, not error
51
- *
52
- * Override in subclasses to enable automatic dependency checking.
53
- * @returns {Array<string | {name: string, dev?: boolean, optional?: boolean}>}
54
57
  */
55
58
  getRequiredPackages() {
56
59
  return [];
57
60
  }
58
61
 
59
62
  /**
60
- * Version 5.0.0: Checks whether all required packages are present in package.json.
61
- * Returns an array of diagnostic objects describing missing or misplaced dependencies.
62
- *
63
- * @param {string} baseDir - The project root directory
64
- * @returns {Promise<Array>}
63
+ * Checks whether all required packages are present in package.json.
65
64
  */
66
65
  async getMissingDependencies(baseDir) {
67
66
  const diagnostics = [];
@@ -147,11 +146,7 @@ export class BasePlugin {
147
146
  }
148
147
 
149
148
  /**
150
- * Version 5.0.0: Checks for config files that exist without the corresponding package.
151
- * Detects "orphaned" configs – e.g. .prettierrc exists but prettier is not installed.
152
- *
153
- * @param {string} baseDir
154
- * @returns {Promise<Array>}
149
+ * Checks for config files that exist without the corresponding package.
155
150
  */
156
151
  async getOrphanedConfigs(baseDir) {
157
152
  const diagnostics = [];
@@ -195,11 +190,7 @@ export class BasePlugin {
195
190
  }
196
191
 
197
192
  /**
198
- * Version 5.0.0: Run all dependency diagnostics (missing + orphaned).
199
- * Convenience method combining getMissingDependencies and getOrphanedConfigs.
200
- *
201
- * @param {string} baseDir
202
- * @returns {Promise<Array>}
193
+ * Run all dependency diagnostics (missing + orphaned).
203
194
  */
204
195
  async runDependencyDiagnostics(baseDir) {
205
196
  const [missing, orphaned] = await Promise.all([
@@ -210,9 +201,7 @@ export class BasePlugin {
210
201
  }
211
202
 
212
203
  /**
213
- * Version 3.2.0: Dynamic getter for custom plugin properties.
214
- * @param {string} key - The property key to retrieve
215
- * @returns {any} The value of the custom property
204
+ * Dynamic getter for custom plugin properties.
216
205
  */
217
206
  get(key) {
218
207
  const methodName = `get${key.charAt(0).toUpperCase() + key.slice(1)}`;
@@ -111,6 +111,31 @@ export class PluginRegistry {
111
111
  return this.plugins.get(name);
112
112
  }
113
113
 
114
+ /**
115
+ * Version 5.4.0: Collect entry points from all plugins for a given file.
116
+ * @param {string} content - The file content
117
+ * @param {string} filePath - The absolute path to the file
118
+ * @returns {Array<string>} List of detected entry points
119
+ */
120
+ detectEntryPointsFromContent(content, filePath) {
121
+ const entryPoints = [];
122
+ for (const plugin of this.plugins.values()) {
123
+ if (typeof plugin.detectEntryPoints === 'function') {
124
+ try {
125
+ const detected = plugin.detectEntryPoints(content, filePath);
126
+ if (Array.isArray(detected)) {
127
+ entryPoints.push(...detected);
128
+ }
129
+ } catch (e) {
130
+ if (this.context?.verbose) {
131
+ console.warn(`[PluginRegistry] Entry detection failed for plugin "${plugin.name}" on ${filePath}:`, e.message);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ return entryPoints;
137
+ }
138
+
114
139
  async getActivePlugins(baseDir) {
115
140
  const active = [];
116
141
  for (const plugin of this.plugins.values()) {
@@ -121,6 +146,25 @@ export class PluginRegistry {
121
146
  return active;
122
147
  }
123
148
 
149
+ /**
150
+ * Version 5.3.0: Run content analysis on a node using all active plugins.
151
+ * @param {GraphNode} node - The node to analyze
152
+ * @param {string} filePath - The absolute path to the file
153
+ */
154
+ async runPluginAnalysis(node, filePath) {
155
+ for (const plugin of this.plugins.values()) {
156
+ if (typeof plugin.analyze === 'function') {
157
+ try {
158
+ await plugin.analyze(node, filePath);
159
+ } catch (e) {
160
+ if (this.context?.verbose) {
161
+ console.warn(`[PluginRegistry] Analysis failed for plugin "${plugin.name}" on ${filePath}:`, e.message);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+
124
168
  /**
125
169
  * Version 5.0.0: Run dependency diagnostics across all registered plugins.
126
170
  * Returns a consolidated list of missing/misplaced dependencies and orphaned configs.
@@ -8,6 +8,7 @@
8
8
  import path from 'path';
9
9
  import fs from 'fs/promises';
10
10
  import { BasePlugin } from '../BasePlugin.js';
11
+ import { FrameworkConfigParser } from '../../resolution/FrameworkConfigParser.js';
11
12
 
12
13
 
13
14
  export class GraphQLPlugin extends BasePlugin {
@@ -211,6 +212,27 @@ export class NuxtPlugin extends BasePlugin {
211
212
  }
212
213
  return false;
213
214
  }
215
+ detectEntryPoints(content, filePath) {
216
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
217
+ const parser = new FrameworkConfigParser(this.context);
218
+ const { entries } = parser.parse(content, filePath);
219
+ return Array.from(entries);
220
+ }
221
+ return [];
222
+ }
223
+ async analyze(node, filePath) {
224
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
225
+ node.isEntry = true;
226
+ if (node.rawCode) {
227
+ const parser = new FrameworkConfigParser(this.context);
228
+ const { aliases, entries } = parser.parse(node.rawCode, filePath);
229
+ if (aliases.size > 0) node.frameworkAliases = aliases;
230
+ if (entries.size > 0) node.frameworkEntries = entries;
231
+ if (node.rawCode.includes('modules:')) node.hasNuxtModules = true;
232
+ if (node.rawCode.includes('components:')) node.hasNuxtComponentsConfig = true;
233
+ }
234
+ }
235
+ }
214
236
  }
215
237
 
216
238
  export class RemixPlugin extends BasePlugin {
@@ -233,6 +255,21 @@ export class RemixPlugin extends BasePlugin {
233
255
  }
234
256
  return false;
235
257
  }
258
+ detectEntryPoints(content, filePath) {
259
+ if (filePath.endsWith('remix.config.js') || filePath.includes('vite.config.')) {
260
+ const parser = new FrameworkConfigParser(this.context);
261
+ const { entries } = parser.parse(content, filePath);
262
+ return Array.from(entries);
263
+ }
264
+ return [];
265
+ }
266
+ async analyze(node, filePath) {
267
+ if (filePath.endsWith('remix.config.js')) {
268
+ node.isEntry = true;
269
+ if (node.rawCode?.includes('ignoredRouteFiles:')) node.hasRemixIgnoredRoutes = true;
270
+ if (node.rawCode?.includes('serverBuildPath:')) node.hasRemixServerBuildPath = true;
271
+ }
272
+ }
236
273
  }
237
274
 
238
275
  export class SvelteKitPlugin extends BasePlugin {
@@ -251,6 +288,29 @@ export class SvelteKitPlugin extends BasePlugin {
251
288
  async isActive(baseDir) {
252
289
  try { await fs.access(path.join(baseDir, 'svelte.config.js')); return true; } catch { return false; }
253
290
  }
291
+ detectEntryPoints(content, filePath) {
292
+ if (filePath.includes('svelte.config.')) {
293
+ const parser = new FrameworkConfigParser(this.context);
294
+ const { entries } = parser.parse(content, filePath);
295
+ return Array.from(entries);
296
+ }
297
+ return [];
298
+ }
299
+ async analyze(node, filePath) {
300
+ if (filePath.includes('svelte.config.')) {
301
+ node.isEntry = true;
302
+ if (node.rawCode) {
303
+ const parser = new FrameworkConfigParser(this.context);
304
+ const { aliases, entries } = parser.parse(node.rawCode, filePath);
305
+ if (aliases.size > 0) {
306
+ node.frameworkAliases = aliases;
307
+ node.hasSvelteAliases = true;
308
+ }
309
+ if (entries.size > 0) node.frameworkEntries = entries;
310
+ if (node.rawCode.includes('adapter:')) node.hasSvelteAdapter = true;
311
+ }
312
+ }
313
+ }
254
314
  }
255
315
 
256
316
  export class AstroPlugin extends BasePlugin {
@@ -265,12 +325,39 @@ export class AstroPlugin extends BasePlugin {
265
325
  }
266
326
  return false;
267
327
  }
328
+ detectEntryPoints(content, filePath) {
329
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
330
+ const parser = new FrameworkConfigParser(this.context);
331
+ const { entries } = parser.parse(content, filePath);
332
+ return Array.from(entries);
333
+ }
334
+ return [];
335
+ }
336
+ async analyze(node, filePath) {
337
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
338
+ node.isEntry = true;
339
+ if (node.rawCode) {
340
+ const parser = new FrameworkConfigParser(this.context);
341
+ const { aliases, entries } = parser.parse(node.rawCode, filePath);
342
+ if (aliases.size > 0) node.frameworkAliases = aliases;
343
+ if (entries.size > 0) node.frameworkEntries = entries;
344
+ if (node.rawCode.includes('integrations:')) node.hasAstroIntegrations = true;
345
+ if (node.rawCode.includes('output:')) node.hasAstroOutputConfig = true;
346
+ }
347
+ }
348
+ }
268
349
  }
269
350
 
270
351
  export class VitepressPlugin extends BasePlugin {
271
352
  get name() { return 'vitepress'; }
272
353
  getConfigFiles() { return ['package.json']; }
273
354
  getRequiredPackages() { return [{ name: 'vitepress', dev: true }]; }
355
+
356
+ getRoutePatterns() {
357
+ // Protection: Every file inside a .vitepress folder is treated as an entry point
358
+ return [/\/\.vitepress\//];
359
+ }
360
+
274
361
  async isActive(baseDir) {
275
362
  try {
276
363
  const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
@@ -282,6 +369,62 @@ export class VitepressPlugin extends BasePlugin {
282
369
  return hasDep;
283
370
  } catch { return false; }
284
371
  }
372
+
373
+ async runDependencyDiagnostics(baseDir) {
374
+ const diagnostics = await super.runDependencyDiagnostics(baseDir);
375
+
376
+ let pkg = null;
377
+ try {
378
+ const raw = await fs.readFile(path.join(baseDir, 'package.json'), 'utf8');
379
+ pkg = JSON.parse(raw);
380
+ } catch { return diagnostics; }
381
+
382
+ const scripts = pkg.scripts || {};
383
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
384
+ const hasVitepressDep = 'vitepress' in allDeps;
385
+
386
+ // Check if any script uses vitepress
387
+ const hasVitepressCommand = Object.values(scripts).some(s => s.includes('vitepress'));
388
+
389
+ // Logic for .vitepress folder existence
390
+ let hasVitepressFolder = false;
391
+ const possibleDirs = ['.vitepress', 'docs/.vitepress', 'docs/docs/.vitepress'];
392
+ for (const d of possibleDirs) {
393
+ try {
394
+ await fs.access(path.join(baseDir, d));
395
+ hasVitepressFolder = true;
396
+ break;
397
+ } catch {}
398
+ }
399
+
400
+ // Scenario 1: build cmd/dependency exists but no folder -> unused dependency
401
+ if ((hasVitepressCommand || hasVitepressDep) && !hasVitepressFolder) {
402
+ diagnostics.push({
403
+ plugin: this.name,
404
+ severity: 'warning',
405
+ message: `Plugin "${this.name}": "vitepress" is defined in package.json or scripts, but no ".vitepress" folder was found. This might be an unused dependency.`
406
+ });
407
+ }
408
+
409
+ // Scenario 2: folder exists but no dependency -> missing dependency
410
+ if (hasVitepressFolder && !hasVitepressDep) {
411
+ diagnostics.push({
412
+ plugin: this.name,
413
+ severity: 'error',
414
+ message: `Plugin "${this.name}": ".vitepress" folder exists, but "vitepress" package is not installed in package.json.`
415
+ });
416
+ }
417
+
418
+ return diagnostics;
419
+ }
420
+
421
+ async analyze(node, filePath) {
422
+ // If it's a config file inside .vitepress, mark it
423
+ if (filePath.includes('/.vitepress/config.') || filePath.includes('/.vitepress/theme/')) {
424
+ node.isEntry = true;
425
+ node.isVitepressInternal = true;
426
+ }
427
+ }
285
428
  }
286
429
 
287
430
  export class GatsbyPlugin extends BasePlugin {
@@ -321,6 +464,14 @@ export class NextJsPlugin extends BasePlugin {
321
464
  }
322
465
  return false;
323
466
  }
467
+ async analyze(node, filePath) {
468
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
469
+ node.isEntry = true;
470
+ if (node.rawCode?.includes('rewrites')) node.hasNextRewrites = true;
471
+ if (node.rawCode?.includes('redirects')) node.hasNextRedirects = true;
472
+ if (node.rawCode?.includes('images:')) node.hasNextImageConfig = true;
473
+ }
474
+ }
324
475
  }
325
476
 
326
477
  export class ReactPlugin extends BasePlugin {
@@ -443,6 +594,42 @@ export class VitePlugin extends BasePlugin {
443
594
  }
444
595
  return false;
445
596
  }
597
+ detectEntryPoints(content, filePath) {
598
+ if (filePath.includes('vite.config.')) {
599
+ const parser = new FrameworkConfigParser(this.context);
600
+ const { entries } = parser.parse(content, filePath);
601
+ return Array.from(entries);
602
+ }
603
+ return [];
604
+ }
605
+ async analyze(node, filePath) {
606
+ if (filePath.includes('vite.config.')) {
607
+ // Mark config as an entry so its own dependencies are tracked
608
+ node.isEntry = true;
609
+
610
+ // UPGRADE: Use FrameworkConfigParser for deep structural extraction
611
+ if (node.rawCode) {
612
+ const parser = new FrameworkConfigParser(this.context);
613
+ const { aliases, entries } = parser.parse(node.rawCode, filePath);
614
+
615
+ if (aliases.size > 0) {
616
+ node.frameworkAliases = aliases;
617
+ node.viteAliasesDetected = true;
618
+ }
619
+
620
+ if (entries.size > 0) {
621
+ node.frameworkEntries = entries;
622
+ node.hasCustomViteInputs = true;
623
+ }
624
+
625
+ // Detect Plugins (Keep existing simple check)
626
+ const pluginsPattern = /plugins\s*:\s*\[([\s\S]*?)\]/g;
627
+ if (pluginsPattern.test(node.rawCode)) {
628
+ node.hasVitePlugins = true;
629
+ }
630
+ }
631
+ }
632
+ }
446
633
  }
447
634
 
448
635
  export class EsbuildPlugin extends BasePlugin {
@@ -479,6 +666,30 @@ export class WebpackPlugin extends BasePlugin {
479
666
  }
480
667
  return false;
481
668
  }
669
+ detectEntryPoints(content, filePath) {
670
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
671
+ const parser = new FrameworkConfigParser(this.context);
672
+ const { entries } = parser.parse(content, filePath);
673
+ return Array.from(entries);
674
+ }
675
+ return [];
676
+ }
677
+ detectEntryPoints(content, filePath) {
678
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
679
+ const parser = new FrameworkConfigParser(this.context);
680
+ const { entries } = parser.parse(content, filePath);
681
+ return Array.from(entries);
682
+ }
683
+ return [];
684
+ }
685
+ async analyze(node, filePath) {
686
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
687
+ node.isEntry = true;
688
+ if (node.rawCode?.includes('entry:')) node.hasWebpackEntries = true;
689
+ if (node.rawCode?.includes('resolve:')) node.hasWebpackResolve = true;
690
+ if (node.rawCode?.includes('plugins:')) node.hasWebpackPlugins = true;
691
+ }
692
+ }
482
693
  }
483
694
 
484
695
  export class ParcelPlugin extends BasePlugin {
@@ -521,6 +732,14 @@ export class TailwindPlugin extends BasePlugin {
521
732
  }
522
733
  return false;
523
734
  }
735
+ async analyze(node, filePath) {
736
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
737
+ node.isEntry = true;
738
+ if (node.rawCode?.includes('content:')) node.hasTailwindContent = true;
739
+ if (node.rawCode?.includes('theme:')) node.hasTailwindTheme = true;
740
+ if (node.rawCode?.includes('plugins:')) node.hasTailwindPlugins = true;
741
+ }
742
+ }
524
743
  }
525
744
 
526
745
  export class PostcssPlugin extends BasePlugin {
@@ -665,6 +884,13 @@ export class JestPlugin extends BasePlugin {
665
884
  async isActive(baseDir) {
666
885
  try { await fs.access(path.join(baseDir, 'jest.config.js')); return true; } catch { return false; }
667
886
  }
887
+ async analyze(node, filePath) {
888
+ if (filePath.endsWith('jest.config.js')) {
889
+ node.isEntry = true;
890
+ if (node.rawCode?.includes('testEnvironment:')) node.hasJestTestEnv = true;
891
+ if (node.rawCode?.includes('setupFiles:')) node.hasJestSetupFiles = true;
892
+ }
893
+ }
668
894
  }
669
895
 
670
896
  export class VitestPlugin extends BasePlugin {
@@ -677,6 +903,39 @@ export class VitestPlugin extends BasePlugin {
677
903
  }
678
904
  return false;
679
905
  }
906
+ detectEntryPoints(content, filePath) {
907
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
908
+ const parser = new FrameworkConfigParser(this.context);
909
+ const { entries } = parser.parse(content, filePath);
910
+ return Array.from(entries);
911
+ }
912
+ return [];
913
+ }
914
+ detectEntryPoints(content, filePath) {
915
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
916
+ const parser = new FrameworkConfigParser(this.context);
917
+ const { entries } = parser.parse(content, filePath);
918
+ return Array.from(entries);
919
+ }
920
+ return [];
921
+ }
922
+ async analyze(node, filePath) {
923
+ if (this.getConfigFiles().some(f => filePath.endsWith(f))) {
924
+ node.isEntry = true;
925
+ if (node.rawCode) {
926
+ const parser = new FrameworkConfigParser(this.context);
927
+ const { aliases, entries } = parser.parse(node.rawCode, filePath);
928
+ if (aliases.size > 0) {
929
+ node.frameworkAliases = aliases;
930
+ node.hasWebpackAliases = true;
931
+ }
932
+ if (entries.size > 0) {
933
+ node.frameworkEntries = entries;
934
+ node.hasWebpackEntries = true;
935
+ }
936
+ }
937
+ }
938
+ }
680
939
  }
681
940
 
682
941
  export class PlaywrightPlugin extends BasePlugin {