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
|
@@ -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.
|
|
8
|
+
* Version 5.0.0: Added getMissingDependencies(), getOrphanedConfigs(), runDependencyDiagnostics().
|
|
9
9
|
*/
|
|
10
10
|
export class BasePlugin {
|
|
11
11
|
constructor(context) {
|
|
@@ -34,17 +34,6 @@ 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
|
-
|
|
48
37
|
/**
|
|
49
38
|
* Returns symbols that are implicitly required/exported by the framework.
|
|
50
39
|
*/
|
|
@@ -54,13 +43,25 @@ export class BasePlugin {
|
|
|
54
43
|
|
|
55
44
|
/**
|
|
56
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}>}
|
|
57
54
|
*/
|
|
58
55
|
getRequiredPackages() {
|
|
59
56
|
return [];
|
|
60
57
|
}
|
|
61
58
|
|
|
62
59
|
/**
|
|
63
|
-
* Checks whether all required packages are present in package.json.
|
|
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>}
|
|
64
65
|
*/
|
|
65
66
|
async getMissingDependencies(baseDir) {
|
|
66
67
|
const diagnostics = [];
|
|
@@ -146,7 +147,11 @@ export class BasePlugin {
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
/**
|
|
149
|
-
* Checks for config files that exist without the corresponding package.
|
|
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>}
|
|
150
155
|
*/
|
|
151
156
|
async getOrphanedConfigs(baseDir) {
|
|
152
157
|
const diagnostics = [];
|
|
@@ -190,7 +195,11 @@ export class BasePlugin {
|
|
|
190
195
|
}
|
|
191
196
|
|
|
192
197
|
/**
|
|
193
|
-
* Run all dependency diagnostics (missing + orphaned).
|
|
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>}
|
|
194
203
|
*/
|
|
195
204
|
async runDependencyDiagnostics(baseDir) {
|
|
196
205
|
const [missing, orphaned] = await Promise.all([
|
|
@@ -201,7 +210,9 @@ export class BasePlugin {
|
|
|
201
210
|
}
|
|
202
211
|
|
|
203
212
|
/**
|
|
204
|
-
* Dynamic getter for custom plugin properties.
|
|
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
|
|
205
216
|
*/
|
|
206
217
|
get(key) {
|
|
207
218
|
const methodName = `get${key.charAt(0).toUpperCase() + key.slice(1)}`;
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Base class for all entkapp plugins.
|
|
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().
|
|
9
|
+
*/
|
|
10
|
+
export class BasePlugin {
|
|
11
|
+
constructor(context) {
|
|
12
|
+
this.context = context;
|
|
13
|
+
this.customGetters = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Unique identifier for the plugin (e.g., 'nextjs').
|
|
18
|
+
*/
|
|
19
|
+
get name() {
|
|
20
|
+
throw new Error('Plugin must implement name getter');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns a list of configuration files that indicate this ecosystem is active.
|
|
25
|
+
*/
|
|
26
|
+
getConfigFiles() {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Returns regex patterns for files that should be treated as entry points.
|
|
32
|
+
*/
|
|
33
|
+
getRoutePatterns() {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Returns symbols that are implicitly required/exported by the framework.
|
|
39
|
+
*/
|
|
40
|
+
getRequiredSystemContracts() {
|
|
41
|
+
return ['default'];
|
|
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
|
+
|
|
212
|
+
/**
|
|
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
|
|
216
|
+
*/
|
|
217
|
+
get(key) {
|
|
218
|
+
const methodName = `get${key.charAt(0).toUpperCase() + key.slice(1)}`;
|
|
219
|
+
if (typeof this[methodName] === 'function') {
|
|
220
|
+
return this[methodName]();
|
|
221
|
+
}
|
|
222
|
+
return this.customGetters.get(key);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Optional: Logic to detect if the plugin should be active in the given directory.
|
|
227
|
+
*/
|
|
228
|
+
async isActive(baseDir) {
|
|
229
|
+
const configFiles = this.getConfigFiles();
|
|
230
|
+
for (const file of configFiles) {
|
|
231
|
+
try {
|
|
232
|
+
await fs.access(path.join(baseDir, file));
|
|
233
|
+
return true;
|
|
234
|
+
} catch {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -111,31 +111,6 @@ 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
|
-
|
|
139
114
|
async getActivePlugins(baseDir) {
|
|
140
115
|
const active = [];
|
|
141
116
|
for (const plugin of this.plugins.values()) {
|
|
@@ -146,25 +121,6 @@ export class PluginRegistry {
|
|
|
146
121
|
return active;
|
|
147
122
|
}
|
|
148
123
|
|
|
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
|
-
|
|
168
124
|
/**
|
|
169
125
|
* Version 5.0.0: Run dependency diagnostics across all registered plugins.
|
|
170
126
|
* Returns a consolidated list of missing/misplaced dependencies and orphaned configs.
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { loadAdditionalPlugins } from "./ecosystems/PluginLoader.mjs";
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs/promises';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { AllPluginClasses } from './ecosystems/UltimateBundle.mjs';
|
|
6
|
+
/**
|
|
7
|
+
* ============================================================================
|
|
8
|
+
* Plugin Registry for entkapp v5.0.0
|
|
9
|
+
* ============================================================================
|
|
10
|
+
* Advanced Plugin Registry supporting Builtin and Custom plugins.
|
|
11
|
+
* v5.0.0: Added runAllDependencyDiagnostics() for project-wide dependency checks.
|
|
12
|
+
*/
|
|
13
|
+
export class PluginRegistry {
|
|
14
|
+
constructor(context) {
|
|
15
|
+
this.context = context;
|
|
16
|
+
this.plugins = new Map();
|
|
17
|
+
this.config = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async init(projectRoot) {
|
|
21
|
+
const configPath = path.join(projectRoot, 'entkapp', 'config.json');
|
|
22
|
+
try {
|
|
23
|
+
const configRaw = await fs.readFile(configPath, 'utf8');
|
|
24
|
+
this.config = JSON.parse(configRaw);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
this.config = {
|
|
27
|
+
useBuiltinPlugins: true,
|
|
28
|
+
useCustomPlugins: true,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (this.config.useBuiltinPlugins !== false) {
|
|
32
|
+
await this.loadBuiltinPlugins();
|
|
33
|
+
}
|
|
34
|
+
if (this.config.useCustomPlugins !== false) {
|
|
35
|
+
await this.loadCustomPlugins(projectRoot);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async loadBuiltinPlugins() {
|
|
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
|
+
|
|
45
|
+
|
|
46
|
+
AllPluginClasses.forEach(PluginClass => {
|
|
47
|
+
this.register(new PluginClass(this.context));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async loadCustomPlugins(projectRoot) {
|
|
51
|
+
const pluginsDir = path.join(projectRoot, 'entkapp', 'plugins');
|
|
52
|
+
try {
|
|
53
|
+
const entries = await fs.readdir(pluginsDir, { withFileTypes: true });
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
let pluginPath = path.join(pluginsDir, entry.name);
|
|
56
|
+
// Folder-based plugin support
|
|
57
|
+
if (entry.isDirectory()) {
|
|
58
|
+
const files = await fs.readdir(pluginPath);
|
|
59
|
+
if (files.includes('index.ts')) {
|
|
60
|
+
pluginPath = path.join(pluginPath, 'index.ts');
|
|
61
|
+
} else if (files.includes('index.mjs')) {
|
|
62
|
+
pluginPath = path.join(pluginPath, 'index.mjs');
|
|
63
|
+
} else {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (pluginPath.endsWith('.ts')) {
|
|
68
|
+
await this.loadTypeScriptPlugin(pluginPath);
|
|
69
|
+
} else if (pluginPath.endsWith('.mjs') || pluginPath.endsWith('.mjs')) {
|
|
70
|
+
await this.loadJavaScriptPlugin(pluginPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (e) {
|
|
74
|
+
// No custom plugins or dir missing
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async loadJavaScriptPlugin(pluginPath) {
|
|
79
|
+
try {
|
|
80
|
+
const pluginModule = await import(pathToFileURL(pluginPath).href);
|
|
81
|
+
const PluginClass = pluginModule.default || pluginModule;
|
|
82
|
+
const pluginInstance = new PluginClass(this.context);
|
|
83
|
+
this.register(pluginInstance);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.error(`[PluginRegistry] Failed to load JS plugin ${pluginPath}:`, e);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async loadTypeScriptPlugin(pluginPath) {
|
|
90
|
+
try {
|
|
91
|
+
if (this.context?.verbose) {
|
|
92
|
+
console.log(`[PluginRegistry] Transpiling TS plugin: ${pluginPath}`);
|
|
93
|
+
}
|
|
94
|
+
await this.loadJavaScriptPlugin(pluginPath);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.error(`[PluginRegistry] Failed to load TS plugin ${pluginPath}:`, e);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
register(plugin) {
|
|
101
|
+
if (plugin && plugin.name) {
|
|
102
|
+
this.plugins.set(plugin.name, plugin);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
getPlugins() {
|
|
107
|
+
return Array.from(this.plugins.values());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
getPlugin(name) {
|
|
111
|
+
return this.plugins.get(name);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async getActivePlugins(baseDir) {
|
|
115
|
+
const active = [];
|
|
116
|
+
for (const plugin of this.plugins.values()) {
|
|
117
|
+
if (await plugin.isActive(baseDir)) {
|
|
118
|
+
active.push(plugin);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return active;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Version 5.0.0: Run dependency diagnostics across all registered plugins.
|
|
126
|
+
* Returns a consolidated list of missing/misplaced dependencies and orphaned configs.
|
|
127
|
+
*
|
|
128
|
+
* @param {string} baseDir - The project root directory to check
|
|
129
|
+
* @returns {Promise<Array<{
|
|
130
|
+
* plugin: string,
|
|
131
|
+
* severity: 'error' | 'warning',
|
|
132
|
+
* message: string,
|
|
133
|
+
* package?: string,
|
|
134
|
+
* configFile?: string,
|
|
135
|
+
* expectedIn?: string,
|
|
136
|
+
* foundIn?: string | null
|
|
137
|
+
* }>>}
|
|
138
|
+
*/
|
|
139
|
+
async runAllDependencyDiagnostics(baseDir) {
|
|
140
|
+
const allDiagnostics = [];
|
|
141
|
+
const seenMessages = new Set();
|
|
142
|
+
|
|
143
|
+
for (const plugin of this.plugins.values()) {
|
|
144
|
+
if (typeof plugin.runDependencyDiagnostics === 'function') {
|
|
145
|
+
try {
|
|
146
|
+
const diagnostics = await plugin.runDependencyDiagnostics(baseDir);
|
|
147
|
+
for (const d of diagnostics) {
|
|
148
|
+
// Deduplicate by message to avoid noise from overlapping plugins
|
|
149
|
+
if (!seenMessages.has(d.message)) {
|
|
150
|
+
seenMessages.add(d.message);
|
|
151
|
+
allDiagnostics.push(d);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
if (this.context?.verbose) {
|
|
156
|
+
console.warn(`[PluginRegistry] Dependency diagnostic failed for plugin "${plugin.name}":`, e.message);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Sort: errors first, then warnings; then alphabetically by plugin name
|
|
163
|
+
return allDiagnostics.sort((a, b) => {
|
|
164
|
+
if (a.severity !== b.severity) return a.severity === 'error' ? -1 : 1;
|
|
165
|
+
return a.plugin.localeCompare(b.plugin);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Version 5.0.0: Format dependency diagnostics for console output.
|
|
171
|
+
* @param {Array} diagnostics - Result of runAllDependencyDiagnostics()
|
|
172
|
+
* @returns {string} Formatted string ready for console output
|
|
173
|
+
*/
|
|
174
|
+
formatDependencyDiagnostics(diagnostics) {
|
|
175
|
+
if (diagnostics.length === 0) {
|
|
176
|
+
return '[entkapp] No missing dependencies detected.';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const errors = diagnostics.filter(d => d.severity === 'error');
|
|
180
|
+
const warnings = diagnostics.filter(d => d.severity === 'warning');
|
|
181
|
+
|
|
182
|
+
const lines = [];
|
|
183
|
+
lines.push(`[entkapp] Dependency Diagnostics: ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
184
|
+
lines.push('');
|
|
185
|
+
|
|
186
|
+
if (errors.length > 0) {
|
|
187
|
+
lines.push(' Errors:');
|
|
188
|
+
for (const d of errors) {
|
|
189
|
+
lines.push(` [ERROR] ${d.message}`);
|
|
190
|
+
}
|
|
191
|
+
lines.push('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (warnings.length > 0) {
|
|
195
|
+
lines.push(' Warnings:');
|
|
196
|
+
for (const d of warnings) {
|
|
197
|
+
lines.push(` [WARN] ${d.message}`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return lines.join('\n');
|
|
202
|
+
}
|
|
203
|
+
}
|