entkapp 5.0.0 → 5.2.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.
Files changed (34) hide show
  1. package/README.md +63 -20
  2. package/bin/cli.js +2 -2
  3. package/index.js +38 -2241
  4. package/package.json +1 -1
  5. package/src/EngineContext.js +1 -1
  6. package/src/Initializer.js +82 -0
  7. package/src/analyzers/CodeSmellAnalyzer.js +106 -0
  8. package/src/ast/ASTAnalyzer.js +29 -14
  9. package/src/ast/BarrelParser.js +22 -20
  10. package/src/ast/OxcAnalyzer.js +33 -409
  11. package/src/index.js +78 -8
  12. package/src/performance/WorkerTaskRunner.js +7 -7
  13. package/src/plugins/BasePlugin.js +171 -2
  14. package/src/plugins/PluginRegistry.js +193 -81
  15. package/src/plugins/ecosystems/BackendServices.js +168 -32
  16. package/src/plugins/ecosystems/GenericPlugins.js +51 -34
  17. package/src/plugins/ecosystems/ModernFrameworks.js +97 -94
  18. package/src/plugins/ecosystems/MorePlugins.js +429 -51
  19. package/src/plugins/ecosystems/NewPlugins.js +526 -0
  20. package/src/plugins/ecosystems/NextJsPlugin.js +18 -6
  21. package/src/plugins/ecosystems/PluginLoader.js +190 -17
  22. package/src/plugins/ecosystems/TypeScriptPlugin.js +10 -10
  23. package/src/plugins/ecosystems/UltimateBundle.js +168 -0
  24. package/src/resolution/BuildOrchestrator.js +46 -0
  25. package/src/resolution/CircularDetector.js +64 -25
  26. package/src/resolution/ConfigGenerator.js +83 -0
  27. package/src/resolution/DepencyResolver.js +12 -1
  28. package/src/resolution/DependencyFixer.js +88 -0
  29. package/src/resolution/EntryPointDetector.js +4 -4
  30. package/src/resolution/GraphAnalyzer.js +80 -0
  31. package/src/resolution/MigrationAnalyzer.js +60 -0
  32. package/src/resolution/PathMapper.js +47 -3
  33. package/src/resolution/WorkSpaceGraph.js +4 -1
  34. package/docs.zip +0 -0
@@ -64,7 +64,7 @@ async function runTask() {
64
64
  success = await oxcAnalyzer.parseFile(filePath, content, node);
65
65
  }
66
66
  } catch (oxcError) {
67
- success = false; // Fehler abfangen, um den TS-Fallback zu erzwingen
67
+ success = false; // Catch error to force TS fallback
68
68
  }
69
69
 
70
70
  // UPGRADE: Improved fallback logic for CommonJS files in worker threads
@@ -78,8 +78,8 @@ async function runTask() {
78
78
  // 3. OXC found no dependencies but file has CommonJS keywords
79
79
  if (!success || (oxcFailedToFindDependencies && (hasImportExportKeywords || hasCommonJSKeywords))) {
80
80
  try {
81
- // CRITICAL FIX: Scope-Reset für den TS-Parser im isolierten Thread-Kontext
82
- // Verhindert, dass unvollständige Scope-Ketten der vorangegangenen Datei zu 'children of undefined' führen
81
+ // CRITICAL FIX: Scope reset for the TS parser in isolated thread context
82
+ // Prevents incomplete scope chains from the previous file from leading to 'children of undefined'
83
83
  astAnalyzer.currentScope = { symbols: new Map(), parent: null, children: [] };
84
84
  astAnalyzer.scopeStack = [astAnalyzer.currentScope];
85
85
  astAnalyzer.scopeCounter = 0;
@@ -87,14 +87,14 @@ async function runTask() {
87
87
  await astAnalyzer.parseFile(filePath, content, node);
88
88
  } catch (tsError) {
89
89
  if (contextOptions.verbose) {
90
- console.error(`[Worker-Fallback-Error] TS-Parser versagte bei ${filePath}: ${tsError.message}`);
90
+ console.error(`[Worker-Fallback-Error] TS parser failed at ${filePath}: ${tsError.message}`);
91
91
  }
92
92
  results.push(null);
93
93
  continue;
94
94
  }
95
95
  }
96
96
 
97
- // Sichere Serialisierung: Verhindert Abstürze, falls internalExports oder symbolSourceLocations keine Maps mehr sind
97
+ // Safe serialization: Prevents crashes if internalExports or symbolSourceLocations are no longer Maps
98
98
  const serializedExports = node.internalExports instanceof Map
99
99
  ? Object.fromEntries(node.internalExports)
100
100
  : {};
@@ -129,9 +129,9 @@ async function runTask() {
129
129
  });
130
130
  } catch (err) {
131
131
  if (contextOptions.verbose) {
132
- console.error(`[Worker-Loop-Exception] Fehler bei Datei ${filePath}: ${err.message}`);
132
+ console.error(`[Worker-Loop-Exception] Error in file ${filePath}: ${err.message}`);
133
133
  }
134
- results.push(null); // Modul überspringen, Thread am Leben erhalten
134
+ results.push(null); // Skip module, keep thread alive
135
135
  }
136
136
  }
137
137
 
@@ -3,8 +3,9 @@ import path from 'path';
3
3
 
4
4
  /**
5
5
  * Base class for all entkapp plugins.
6
- * Defines the contract for ecosystem detection and entry point mapping.
7
- * Version 3.2.0: Added support for dynamic custom getters.
6
+ * Defines the contract for ecosystem detection, entry point mapping,
7
+ * and missing dependency / devDependency detection.
8
+ * Version 5.0.0: Added getMissingDependencies(), getOrphanedConfigs(), runDependencyDiagnostics().
8
9
  */
9
10
  export class BasePlugin {
10
11
  constructor(context) {
@@ -40,6 +41,174 @@ export class BasePlugin {
40
41
  return ['default'];
41
42
  }
42
43
 
44
+ /**
45
+ * 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
+ */
55
+ getRequiredPackages() {
56
+ return [];
57
+ }
58
+
59
+ /**
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>}
65
+ */
66
+ async getMissingDependencies(baseDir) {
67
+ const diagnostics = [];
68
+ const requiredPackages = this.getRequiredPackages();
69
+ if (requiredPackages.length === 0) return diagnostics;
70
+
71
+ let pkg = null;
72
+ try {
73
+ const raw = await fs.readFile(path.join(baseDir, 'package.json'), 'utf8');
74
+ pkg = JSON.parse(raw);
75
+ } catch {
76
+ return diagnostics;
77
+ }
78
+
79
+ const deps = pkg.dependencies || {};
80
+ const devDeps = pkg.devDependencies || {};
81
+
82
+ for (const entry of requiredPackages) {
83
+ const pkgName = typeof entry === 'string' ? entry : entry.name;
84
+ const isDev = typeof entry === 'object' ? entry.dev : undefined;
85
+ const isOptional = typeof entry === 'object' ? (entry.optional ?? false) : false;
86
+
87
+ const inDeps = pkgName in deps;
88
+ const inDevDeps = pkgName in devDeps;
89
+ const found = inDeps ? 'dependencies' : inDevDeps ? 'devDependencies' : null;
90
+
91
+ if (!found) {
92
+ let triggerFile = null;
93
+ for (const cfgFile of this.getConfigFiles()) {
94
+ try {
95
+ await fs.access(path.join(baseDir, cfgFile));
96
+ triggerFile = cfgFile;
97
+ break;
98
+ } catch { /* not found */ }
99
+ }
100
+
101
+ const expectedIn = isDev === true ? 'devDependencies' : isDev === false ? 'dependencies' : 'either';
102
+ const severity = isOptional ? 'warning' : 'error';
103
+
104
+ let message = `Plugin "${this.name}": package "${pkgName}" is not installed`;
105
+ if (triggerFile) {
106
+ message += ` (config file "${triggerFile}" was found)`;
107
+ }
108
+ if (isDev === true) {
109
+ message += ` — expected in devDependencies`;
110
+ } else if (isDev === false) {
111
+ message += ` — expected in dependencies`;
112
+ }
113
+
114
+ diagnostics.push({
115
+ plugin: this.name,
116
+ package: pkgName,
117
+ expectedIn,
118
+ foundIn: null,
119
+ configFile: triggerFile,
120
+ severity,
121
+ message,
122
+ });
123
+ } else if (isDev === true && found === 'dependencies') {
124
+ diagnostics.push({
125
+ plugin: this.name,
126
+ package: pkgName,
127
+ expectedIn: 'devDependencies',
128
+ foundIn: 'dependencies',
129
+ configFile: null,
130
+ severity: 'warning',
131
+ message: `Plugin "${this.name}": package "${pkgName}" is in "dependencies" but should be in "devDependencies"`,
132
+ });
133
+ } else if (isDev === false && found === 'devDependencies') {
134
+ diagnostics.push({
135
+ plugin: this.name,
136
+ package: pkgName,
137
+ expectedIn: 'dependencies',
138
+ foundIn: 'devDependencies',
139
+ configFile: null,
140
+ severity: 'warning',
141
+ message: `Plugin "${this.name}": package "${pkgName}" is in "devDependencies" but should be in "dependencies"`,
142
+ });
143
+ }
144
+ }
145
+
146
+ return diagnostics;
147
+ }
148
+
149
+ /**
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>}
155
+ */
156
+ async getOrphanedConfigs(baseDir) {
157
+ const diagnostics = [];
158
+ const requiredPackages = this.getRequiredPackages();
159
+ if (requiredPackages.length === 0) return diagnostics;
160
+
161
+ let pkg = null;
162
+ try {
163
+ const raw = await fs.readFile(path.join(baseDir, 'package.json'), 'utf8');
164
+ pkg = JSON.parse(raw);
165
+ } catch {
166
+ return diagnostics;
167
+ }
168
+
169
+ const allDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
170
+
171
+ const anyPackagePresent = requiredPackages.some(entry => {
172
+ const pkgName = typeof entry === 'string' ? entry : entry.name;
173
+ return pkgName in allDeps;
174
+ });
175
+
176
+ if (!anyPackagePresent) {
177
+ for (const cfgFile of this.getConfigFiles()) {
178
+ try {
179
+ await fs.access(path.join(baseDir, cfgFile));
180
+ const missingPkgs = requiredPackages
181
+ .map(e => typeof e === 'string' ? e : e.name)
182
+ .filter(n => !(n in allDeps));
183
+
184
+ diagnostics.push({
185
+ plugin: this.name,
186
+ configFile: cfgFile,
187
+ severity: 'error',
188
+ message: `Plugin "${this.name}": config file "${cfgFile}" exists but required package(s) [${missingPkgs.join(', ')}] are not installed`,
189
+ });
190
+ } catch { /* file not found */ }
191
+ }
192
+ }
193
+
194
+ return diagnostics;
195
+ }
196
+
197
+ /**
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>}
203
+ */
204
+ async runDependencyDiagnostics(baseDir) {
205
+ const [missing, orphaned] = await Promise.all([
206
+ this.getMissingDependencies(baseDir),
207
+ this.getOrphanedConfigs(baseDir),
208
+ ]);
209
+ return [...missing, ...orphaned];
210
+ }
211
+
43
212
  /**
44
213
  * Version 3.2.0: Dynamic getter for custom plugin properties.
45
214
  * @param {string} key - The property key to retrieve
@@ -2,11 +2,13 @@ import { loadAdditionalPlugins } from "./ecosystems/PluginLoader.js";
2
2
  import path from 'path';
3
3
  import fs from 'fs/promises';
4
4
  import { pathToFileURL } from 'url';
5
- import { execa } from 'execa';
6
5
 
7
6
  /**
7
+ * ============================================================================
8
+ * Plugin Registry for entkapp v5.0.0
9
+ * ============================================================================
8
10
  * Advanced Plugin Registry supporting Builtin and Custom plugins.
9
- * Enhanced with TypeScript support and folder-based plugin wrapping.
11
+ * v5.0.0: Added runAllDependencyDiagnostics() for project-wide dependency checks.
10
12
  */
11
13
  export class PluginRegistry {
12
14
  constructor(context) {
@@ -26,69 +28,118 @@ export class PluginRegistry {
26
28
  useCustomPlugins: true,
27
29
  };
28
30
  }
29
-
30
- if (this.config.useBuiltinPlugins) {
31
+ if (this.config.useBuiltinPlugins !== false) {
31
32
  await this.loadBuiltinPlugins();
32
33
  }
33
-
34
- if (this.config.useCustomPlugins) {
34
+ if (this.config.useCustomPlugins !== false) {
35
35
  await this.loadCustomPlugins(projectRoot);
36
36
  }
37
-
38
-
39
37
  }
40
38
 
41
39
  async loadBuiltinPlugins() {
42
- const { NextJsPlugin } = await import('./ecosystems/NextJsPlugin.js');
43
- const { NuxtPlugin, RemixPlugin, SvelteKitPlugin, AstroPlugin, VitepressPlugin } = await import('./ecosystems/GenericPlugins.js');
44
- const { TypeScriptPlugin } = await import('./ecosystems/TypeScriptPlugin.js');
45
- const { ReactPlugin, VuePlugin, SveltePlugin, AngularPlugin } = await import('./ecosystems/ModernFrameworks.js');
46
- const { GraphQLPlugin, DatabasePlugin } = await import('./ecosystems/BackendServices.js');
47
- const {
48
- TailwindPlugin, PostcssPlugin, JestPlugin, VitestPlugin,
49
- PlaywrightPlugin, CypressPlugin, StorybookPlugin,
50
- EslintPlugin, PrettierPlugin, HuskyPlugin, LintStagedPlugin,
51
- CommitlintPlugin, BabelPlugin, RollupPlugin, WebpackPlugin,
52
- GithubActionsPlugin
53
- } = await import('./ecosystems/MorePlugins.js');
54
-
55
- const builtins = [
56
- new NextJsPlugin(this.context),
57
- new NuxtPlugin(this.context),
58
- new RemixPlugin(this.context),
59
- new SvelteKitPlugin(this.context),
60
- new AstroPlugin(this.context),
61
- new VitepressPlugin(this.context),
62
- new TypeScriptPlugin(this.context),
63
- new ReactPlugin(this.context),
64
- new VuePlugin(this.context),
65
- new SveltePlugin(this.context),
66
- new AngularPlugin(this.context),
67
- new GraphQLPlugin(this.context),
68
- new DatabasePlugin(this.context),
69
- new TailwindPlugin(this.context),
70
- new PostcssPlugin(this.context),
71
- new JestPlugin(this.context),
72
- new VitestPlugin(this.context),
73
- new PlaywrightPlugin(this.context),
74
- new CypressPlugin(this.context),
75
- new StorybookPlugin(this.context),
76
- new EslintPlugin(this.context),
77
- new PrettierPlugin(this.context),
78
- new HuskyPlugin(this.context),
79
- new LintStagedPlugin(this.context),
80
- new CommitlintPlugin(this.context),
81
- new BabelPlugin(this.context),
82
- new RollupPlugin(this.context),
83
- new WebpackPlugin(this.context),
84
- new GithubActionsPlugin(this.context)
85
- ];
86
-
87
- builtins.forEach(p => {
88
- if (!this.config.enabledPlugins || this.config.enabledPlugins.includes(p.name)) {
89
- this.register(p);
90
- }
91
- });
40
+ // Delegate to PluginLoader which registers all built-in plugins
41
+ loadAdditionalPlugins(this);
42
+
43
+ // Also load TypeScript and Next.js plugins (primary framework plugins)
44
+ const { ReactPlugin, VuePlugin, SveltePlugin, AngularPlugin, PreactPlugin, SolidPlugin, QwikPlugin, LitPlugin, NuxtPlugin, RemixPlugin, SvelteKitPlugin, AstroPlugin, VitepressPlugin, GatsbyPlugin, RedwoodPlugin, NextJsPlugin, TypeScriptPlugin, ExpressPlugin, FastifyPlugin, NestJsPlugin, HonoPlugin, KoaPlugin, ElysiaPlugin, GraphQLPlugin, ApolloPlugin, TRPCPlugin, DatabasePlugin, PrismaPlugin, DrizzlePlugin, MongoosePlugin, SupabasePlugin, FirebasePlugin, ClerkPlugin, ReduxPlugin, ZustandPlugin, JotaiPlugin, RecoilPlugin, MobXPlugin, PiniaPlugin, TanStackQueryPlugin, ReactRouterPlugin, TanStackRouterPlugin, VueRouterPlugin, AntdPlugin, MuiPlugin, ShadcnPlugin, RadixUIPlugin, ChakraUIPlugin, FramerMotionPlugin, GSAPPlugin, ZodPlugin, YupPlugin, ValibotPlugin, I18nextPlugin, VueI18nPlugin, SentryPlugin, OpenTelemetryPlugin, SocketIoPlugin, TailwindPlugin, PostcssPlugin, UnoCSSPlugin, StylelintPlugin, EslintPlugin, PrettierPlugin, BiomePlugin, OxlintPlugin, HuskyPlugin, LintStagedPlugin, CommitlintPlugin, ChangesetPlugin, BabelPlugin, SWCPlugin, VitePlugin, EsbuildPlugin, RollupPlugin, WebpackPlugin, ParcelPlugin, TurboPlugin, NxPlugin, JestPlugin, VitestPlugin, PlaywrightPlugin, CypressPlugin, StorybookPlugin, MswPlugin, GithubActionsPlugin, DockerPlugin, TerraformPlugin, EditorConfigPlugin, NvmPlugin, VoltaPlugin, DotenvPlugin, PnpmPlugin, YarnPlugin, BunPlugin, SwiperPlugin, QuillPlugin, EnvelopPlugin } = await import('./ecosystems/UltimateBundle.js');
45
+ this.register(new ReactPlugin(this.context));
46
+ this.register(new VuePlugin(this.context));
47
+ this.register(new SveltePlugin(this.context));
48
+ this.register(new AngularPlugin(this.context));
49
+ this.register(new PreactPlugin(this.context));
50
+ this.register(new SolidPlugin(this.context));
51
+ this.register(new QwikPlugin(this.context));
52
+ this.register(new LitPlugin(this.context));
53
+ this.register(new NuxtPlugin(this.context));
54
+ this.register(new RemixPlugin(this.context));
55
+ this.register(new SvelteKitPlugin(this.context));
56
+ this.register(new AstroPlugin(this.context));
57
+ this.register(new VitepressPlugin(this.context));
58
+ this.register(new GatsbyPlugin(this.context));
59
+ this.register(new RedwoodPlugin(this.context));
60
+ this.register(new NextJsPlugin(this.context));
61
+ this.register(new TypeScriptPlugin(this.context));
62
+ this.register(new ExpressPlugin(this.context));
63
+ this.register(new FastifyPlugin(this.context));
64
+ this.register(new NestJsPlugin(this.context));
65
+ this.register(new HonoPlugin(this.context));
66
+ this.register(new KoaPlugin(this.context));
67
+ this.register(new ElysiaPlugin(this.context));
68
+ this.register(new GraphQLPlugin(this.context));
69
+ this.register(new ApolloPlugin(this.context));
70
+ this.register(new TRPCPlugin(this.context));
71
+ this.register(new DatabasePlugin(this.context));
72
+ this.register(new PrismaPlugin(this.context));
73
+ this.register(new DrizzlePlugin(this.context));
74
+ this.register(new MongoosePlugin(this.context));
75
+ this.register(new SupabasePlugin(this.context));
76
+ this.register(new FirebasePlugin(this.context));
77
+ this.register(new ClerkPlugin(this.context));
78
+ this.register(new ReduxPlugin(this.context));
79
+ this.register(new ZustandPlugin(this.context));
80
+ this.register(new JotaiPlugin(this.context));
81
+ this.register(new RecoilPlugin(this.context));
82
+ this.register(new MobXPlugin(this.context));
83
+ this.register(new PiniaPlugin(this.context));
84
+ this.register(new TanStackQueryPlugin(this.context));
85
+ this.register(new ReactRouterPlugin(this.context));
86
+ this.register(new TanStackRouterPlugin(this.context));
87
+ this.register(new VueRouterPlugin(this.context));
88
+ this.register(new AntdPlugin(this.context));
89
+ this.register(new MuiPlugin(this.context));
90
+ this.register(new ShadcnPlugin(this.context));
91
+ this.register(new RadixUIPlugin(this.context));
92
+ this.register(new ChakraUIPlugin(this.context));
93
+ this.register(new FramerMotionPlugin(this.context));
94
+ this.register(new GSAPPlugin(this.context));
95
+ this.register(new ZodPlugin(this.context));
96
+ this.register(new YupPlugin(this.context));
97
+ this.register(new ValibotPlugin(this.context));
98
+ this.register(new I18nextPlugin(this.context));
99
+ this.register(new VueI18nPlugin(this.context));
100
+ this.register(new SentryPlugin(this.context));
101
+ this.register(new OpenTelemetryPlugin(this.context));
102
+ this.register(new SocketIoPlugin(this.context));
103
+ this.register(new TailwindPlugin(this.context));
104
+ this.register(new PostcssPlugin(this.context));
105
+ this.register(new UnoCSSPlugin(this.context));
106
+ this.register(new StylelintPlugin(this.context));
107
+ this.register(new EslintPlugin(this.context));
108
+ this.register(new PrettierPlugin(this.context));
109
+ this.register(new BiomePlugin(this.context));
110
+ this.register(new OxlintPlugin(this.context));
111
+ this.register(new HuskyPlugin(this.context));
112
+ this.register(new LintStagedPlugin(this.context));
113
+ this.register(new CommitlintPlugin(this.context));
114
+ this.register(new ChangesetPlugin(this.context));
115
+ this.register(new BabelPlugin(this.context));
116
+ this.register(new SWCPlugin(this.context));
117
+ this.register(new VitePlugin(this.context));
118
+ this.register(new EsbuildPlugin(this.context));
119
+ this.register(new RollupPlugin(this.context));
120
+ this.register(new WebpackPlugin(this.context));
121
+ this.register(new ParcelPlugin(this.context));
122
+ this.register(new TurboPlugin(this.context));
123
+ this.register(new NxPlugin(this.context));
124
+ this.register(new JestPlugin(this.context));
125
+ this.register(new VitestPlugin(this.context));
126
+ this.register(new PlaywrightPlugin(this.context));
127
+ this.register(new CypressPlugin(this.context));
128
+ this.register(new StorybookPlugin(this.context));
129
+ this.register(new MswPlugin(this.context));
130
+ this.register(new GithubActionsPlugin(this.context));
131
+ this.register(new DockerPlugin(this.context));
132
+ this.register(new TerraformPlugin(this.context));
133
+ this.register(new EditorConfigPlugin(this.context));
134
+ this.register(new NvmPlugin(this.context));
135
+ this.register(new VoltaPlugin(this.context));
136
+ this.register(new DotenvPlugin(this.context));
137
+ this.register(new PnpmPlugin(this.context));
138
+ this.register(new YarnPlugin(this.context));
139
+ this.register(new BunPlugin(this.context));
140
+ this.register(new SwiperPlugin(this.context));
141
+ this.register(new QuillPlugin(this.context));
142
+ this.register(new EnvelopPlugin(this.context));
92
143
  }
93
144
 
94
145
  async loadCustomPlugins(projectRoot) {
@@ -97,20 +148,17 @@ export class PluginRegistry {
97
148
  const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
98
149
  for (const entry of entries) {
99
150
  let pluginPath = path.join(pluginsDir, entry.name);
100
-
101
- // --- NEW: Folder-based plugin support ---
151
+ // Folder-based plugin support
102
152
  if (entry.isDirectory()) {
103
- // Look for index.ts or index.js in the folder
104
153
  const files = await fs.readdir(pluginPath);
105
154
  if (files.includes('index.ts')) {
106
155
  pluginPath = path.join(pluginPath, 'index.ts');
107
156
  } else if (files.includes('index.js')) {
108
157
  pluginPath = path.join(pluginPath, 'index.js');
109
158
  } else {
110
- continue; // No entry point found in folder
159
+ continue;
111
160
  }
112
161
  }
113
-
114
162
  if (pluginPath.endsWith('.ts')) {
115
163
  await this.loadTypeScriptPlugin(pluginPath);
116
164
  } else if (pluginPath.endsWith('.js') || pluginPath.endsWith('.mjs')) {
@@ -134,27 +182,11 @@ export class PluginRegistry {
134
182
  }
135
183
 
136
184
  async loadTypeScriptPlugin(pluginPath) {
137
- // --- TypeScript Plugin Support ---
138
- // Transpile TS to JS on the fly using esbuild or similar if available,
139
- // or use a simple wrapper that uses ts-node/register if we were in that env.
140
- // For this sandbox, we'll simulate a "wrap it" approach by using a temporary JS file.
141
185
  try {
142
- const tempJsPath = pluginPath.replace(/\.ts$/, '.tmp.mjs');
143
- // We use a simple trick: if we have oxc or esbuild, we could transpile.
144
- // For now, let's assume we can use a basic transpilation or just inform the user.
145
- // In a real scenario, we'd use `tsx` or `esbuild` to run this.
146
- if (this.context.verbose) {
186
+ if (this.context?.verbose) {
147
187
  console.log(`[PluginRegistry] Transpiling TS plugin: ${pluginPath}`);
148
188
  }
149
-
150
- // For the sake of "wrapping it", we'll use a dynamic loader if possible.
151
- // In this implementation, we'll just try to import it if the runtime supports it (like node with --loader ts-node/esm)
152
- // But since we want it to "just work", let's implement a more robust loading logic.
153
-
154
- // Implementation detail: we could use `esbuild` to bundle it to a string and then import.
155
- // Since we don't want to add too many dependencies, we'll just log and try a standard import
156
- // which might fail without a loader, but it's the right direction.
157
- await this.loadJavaScriptPlugin(pluginPath);
189
+ await this.loadJavaScriptPlugin(pluginPath);
158
190
  } catch (e) {
159
191
  console.error(`[PluginRegistry] Failed to load TS plugin ${pluginPath}:`, e);
160
192
  }
@@ -183,4 +215,84 @@ export class PluginRegistry {
183
215
  }
184
216
  return active;
185
217
  }
218
+
219
+ /**
220
+ * Version 5.0.0: Run dependency diagnostics across all registered plugins.
221
+ * Returns a consolidated list of missing/misplaced dependencies and orphaned configs.
222
+ *
223
+ * @param {string} baseDir - The project root directory to check
224
+ * @returns {Promise<Array<{
225
+ * plugin: string,
226
+ * severity: 'error' | 'warning',
227
+ * message: string,
228
+ * package?: string,
229
+ * configFile?: string,
230
+ * expectedIn?: string,
231
+ * foundIn?: string | null
232
+ * }>>}
233
+ */
234
+ async runAllDependencyDiagnostics(baseDir) {
235
+ const allDiagnostics = [];
236
+ const seenMessages = new Set();
237
+
238
+ for (const plugin of this.plugins.values()) {
239
+ if (typeof plugin.runDependencyDiagnostics === 'function') {
240
+ try {
241
+ const diagnostics = await plugin.runDependencyDiagnostics(baseDir);
242
+ for (const d of diagnostics) {
243
+ // Deduplicate by message to avoid noise from overlapping plugins
244
+ if (!seenMessages.has(d.message)) {
245
+ seenMessages.add(d.message);
246
+ allDiagnostics.push(d);
247
+ }
248
+ }
249
+ } catch (e) {
250
+ if (this.context?.verbose) {
251
+ console.warn(`[PluginRegistry] Dependency diagnostic failed for plugin "${plugin.name}":`, e.message);
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ // Sort: errors first, then warnings; then alphabetically by plugin name
258
+ return allDiagnostics.sort((a, b) => {
259
+ if (a.severity !== b.severity) return a.severity === 'error' ? -1 : 1;
260
+ return a.plugin.localeCompare(b.plugin);
261
+ });
262
+ }
263
+
264
+ /**
265
+ * Version 5.0.0: Format dependency diagnostics for console output.
266
+ * @param {Array} diagnostics - Result of runAllDependencyDiagnostics()
267
+ * @returns {string} Formatted string ready for console output
268
+ */
269
+ formatDependencyDiagnostics(diagnostics) {
270
+ if (diagnostics.length === 0) {
271
+ return '[entkapp] No missing dependencies detected.';
272
+ }
273
+
274
+ const errors = diagnostics.filter(d => d.severity === 'error');
275
+ const warnings = diagnostics.filter(d => d.severity === 'warning');
276
+
277
+ const lines = [];
278
+ lines.push(`[entkapp] Dependency Diagnostics: ${errors.length} error(s), ${warnings.length} warning(s)`);
279
+ lines.push('');
280
+
281
+ if (errors.length > 0) {
282
+ lines.push(' Errors:');
283
+ for (const d of errors) {
284
+ lines.push(` [ERROR] ${d.message}`);
285
+ }
286
+ lines.push('');
287
+ }
288
+
289
+ if (warnings.length > 0) {
290
+ lines.push(' Warnings:');
291
+ for (const d of warnings) {
292
+ lines.push(` [WARN] ${d.message}`);
293
+ }
294
+ }
295
+
296
+ return lines.join('\n');
297
+ }
186
298
  }