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.
- package/README.md +1 -1
- package/bin/cli.js +2 -2
- package/bin/cli.mjs +175 -0
- package/index.cjs +18 -0
- package/index.mjs +51 -0
- package/package.json +7 -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 +17 -3
- 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.mjs +203 -0
- 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 +37 -1
- package/src/index.mjs +1180 -0
- package/src/performance/GraphCache.mjs +108 -0
- package/src/performance/SupplyChainGuard.mjs +92 -0
- package/src/performance/WorkerPool.mjs +132 -0
- package/src/performance/WorkerTaskRunner.mjs +144 -0
- package/src/plugins/BasePlugin.mjs +240 -0
- 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.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.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.mjs +80 -0
- package/src/resolution/MigrationAnalyzer.mjs +60 -0
- package/src/resolution/PathMapper.mjs +82 -0
- package/src/resolution/TSConfigLoader.mjs +162 -0
- package/src/resolution/WorkSpaceGraph.mjs +183 -0
- package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
- package/test.js +76 -0
- package/wrangler.toml +6 -0
|
@@ -0,0 +1,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
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ============================================================================
|
|
3
|
+
* Backend Services Plugins for entkapp v5.0.0
|
|
4
|
+
* ============================================================================
|
|
5
|
+
* Built-in support for GraphQL, REST APIs, Databases, and BaaS providers.
|
|
6
|
+
* v5.0.0: All plugins implement getRequiredPackages() for dependency detection.
|
|
7
|
+
*/
|
|
8
|
+
import { BasePlugin } from '../BasePlugin.mjs';
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
export class GraphQLPlugin extends BasePlugin {
|
|
13
|
+
get name() { return 'graphql'; }
|
|
14
|
+
getConfigFiles() { return ['package.json', 'graphql.config.mjs', 'graphql.config.ts', '.graphqlconfig', 'graphql.config.yml']; }
|
|
15
|
+
getRequiredPackages() {
|
|
16
|
+
return [{ name: 'graphql', dev: false }];
|
|
17
|
+
}
|
|
18
|
+
async isActive(baseDir) {
|
|
19
|
+
try {
|
|
20
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
21
|
+
return !!(pkgJson.dependencies?.graphql || pkgJson.devDependencies?.graphql || pkgJson.dependencies?.['@apollo/client']);
|
|
22
|
+
} catch { return false; }
|
|
23
|
+
}
|
|
24
|
+
async analyze(node, filePath) {
|
|
25
|
+
const gqlPattern = /gql\s*`([\s\S]*?)`/g;
|
|
26
|
+
const matches = node.rawCode?.match(gqlPattern) || [];
|
|
27
|
+
if (matches.length > 0) node.graphqlQueries = matches;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class ApolloPlugin extends BasePlugin {
|
|
32
|
+
get name() { return 'apollo'; }
|
|
33
|
+
getConfigFiles() { return ['package.json', 'apollo.config.mjs', 'apollo.config.ts']; }
|
|
34
|
+
getRequiredPackages() {
|
|
35
|
+
return [
|
|
36
|
+
{ name: '@apollo/client', dev: false },
|
|
37
|
+
{ name: '@apollo/server', dev: false, optional: true },
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
async isActive(baseDir) {
|
|
41
|
+
try {
|
|
42
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
43
|
+
return !!(pkgJson.dependencies?.['@apollo/client'] || pkgJson.dependencies?.['@apollo/server'] ||
|
|
44
|
+
pkgJson.devDependencies?.['@apollo/client'] || pkgJson.devDependencies?.['@apollo/server']);
|
|
45
|
+
} catch { return false; }
|
|
46
|
+
}
|
|
47
|
+
async analyze(node) {
|
|
48
|
+
if (node.rawCode?.includes('ApolloClient') || node.rawCode?.includes('ApolloServer')) node.isEntry = true;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export class DatabasePlugin extends BasePlugin {
|
|
53
|
+
get name() { return 'database'; }
|
|
54
|
+
getConfigFiles() { return ['package.json', 'prisma/schema.prisma', 'drizzle.config.ts', 'drizzle.config.mjs', 'ormconfig.json']; }
|
|
55
|
+
getRequiredPackages() {
|
|
56
|
+
return [
|
|
57
|
+
{ name: '@prisma/client', dev: false, optional: true },
|
|
58
|
+
{ name: 'drizzle-orm', dev: false, optional: true },
|
|
59
|
+
{ name: 'typeorm', dev: false, optional: true },
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
async analyze(node, filePath) {
|
|
63
|
+
if (node.explicitImports?.has('@prisma/client') || node.explicitImports?.has('drizzle-orm'))
|
|
64
|
+
node.usesDatabase = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class PrismaPlugin extends BasePlugin {
|
|
69
|
+
get name() { return 'prisma'; }
|
|
70
|
+
getConfigFiles() { return ['prisma/schema.prisma', 'schema.prisma']; }
|
|
71
|
+
getRequiredPackages() {
|
|
72
|
+
return [
|
|
73
|
+
{ name: '@prisma/client', dev: false },
|
|
74
|
+
{ name: 'prisma', dev: true },
|
|
75
|
+
];
|
|
76
|
+
}
|
|
77
|
+
async isActive(baseDir) {
|
|
78
|
+
for (const f of this.getConfigFiles()) {
|
|
79
|
+
try { await fs.access(path.join(baseDir, f)); return true; } catch {}
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
83
|
+
return !!(pkgJson.dependencies?.['@prisma/client'] || pkgJson.devDependencies?.prisma);
|
|
84
|
+
} catch { return false; }
|
|
85
|
+
}
|
|
86
|
+
async analyze(node) {
|
|
87
|
+
if (node.rawCode?.includes('new PrismaClient()')) node.isEntry = true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class DrizzlePlugin extends BasePlugin {
|
|
92
|
+
get name() { return 'drizzle'; }
|
|
93
|
+
getConfigFiles() { return ['drizzle.config.ts', 'drizzle.config.mjs', 'drizzle.config.mjs']; }
|
|
94
|
+
getRequiredPackages() {
|
|
95
|
+
return [
|
|
96
|
+
{ name: 'drizzle-orm', dev: false },
|
|
97
|
+
{ name: 'drizzle-kit', dev: true },
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
async isActive(baseDir) {
|
|
101
|
+
for (const f of this.getConfigFiles()) {
|
|
102
|
+
try { await fs.access(path.join(baseDir, f)); return true; } catch {}
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
106
|
+
return !!(pkgJson.dependencies?.['drizzle-orm'] || pkgJson.devDependencies?.['drizzle-orm']);
|
|
107
|
+
} catch { return false; }
|
|
108
|
+
}
|
|
109
|
+
async analyze(node) {
|
|
110
|
+
if (node.rawCode?.includes('drizzle(')) node.isEntry = true;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class MongoosePlugin extends BasePlugin {
|
|
115
|
+
get name() { return 'mongoose'; }
|
|
116
|
+
getConfigFiles() { return ['package.json']; }
|
|
117
|
+
getRequiredPackages() {
|
|
118
|
+
return [{ name: 'mongoose', dev: false }];
|
|
119
|
+
}
|
|
120
|
+
async isActive(baseDir) {
|
|
121
|
+
try {
|
|
122
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
123
|
+
return !!(pkgJson.dependencies?.mongoose || pkgJson.devDependencies?.mongoose);
|
|
124
|
+
} catch { return false; }
|
|
125
|
+
}
|
|
126
|
+
async analyze(node) {
|
|
127
|
+
if (node.rawCode?.includes('mongoose.model(')) node.isEntry = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export class SupabasePlugin extends BasePlugin {
|
|
132
|
+
get name() { return 'supabase'; }
|
|
133
|
+
getConfigFiles() { return ['supabase/config.toml', '.env', '.env.local', 'package.json']; }
|
|
134
|
+
getRequiredPackages() {
|
|
135
|
+
return [{ name: '@supabase/supabase-js', dev: false }];
|
|
136
|
+
}
|
|
137
|
+
async isActive(baseDir) {
|
|
138
|
+
try {
|
|
139
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
140
|
+
return !!(pkgJson.dependencies?.['@supabase/supabase-js'] || pkgJson.devDependencies?.['@supabase/supabase-js']);
|
|
141
|
+
} catch { return false; }
|
|
142
|
+
}
|
|
143
|
+
async analyze(node) {
|
|
144
|
+
if (node.rawCode?.includes('createClient') && node.rawCode?.includes('supabase')) node.isEntry = true;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export class FirebasePlugin extends BasePlugin {
|
|
149
|
+
get name() { return 'firebase'; }
|
|
150
|
+
getConfigFiles() { return ['firebase.json', '.firebaserc', 'package.json']; }
|
|
151
|
+
getRequiredPackages() {
|
|
152
|
+
return [{ name: 'firebase', dev: false }];
|
|
153
|
+
}
|
|
154
|
+
async isActive(baseDir) {
|
|
155
|
+
for (const f of ['firebase.json', '.firebaserc']) {
|
|
156
|
+
try { await fs.access(path.join(baseDir, f)); return true; } catch {}
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
160
|
+
return !!(pkgJson.dependencies?.firebase || pkgJson.devDependencies?.firebase);
|
|
161
|
+
} catch { return false; }
|
|
162
|
+
}
|
|
163
|
+
async analyze(node) {
|
|
164
|
+
if (node.rawCode?.includes('initializeApp') && node.rawCode?.includes('firebase')) node.isEntry = true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export class ClerkPlugin extends BasePlugin {
|
|
169
|
+
get name() { return 'clerk'; }
|
|
170
|
+
getConfigFiles() { return ['package.json', '.env', '.env.local']; }
|
|
171
|
+
getRequiredPackages() {
|
|
172
|
+
return [{ name: '@clerk/nextjs', dev: false, optional: true }, { name: '@clerk/clerk-react', dev: false, optional: true }];
|
|
173
|
+
}
|
|
174
|
+
async isActive(baseDir) {
|
|
175
|
+
try {
|
|
176
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
177
|
+
const allDeps = { ...(pkgJson.dependencies || {}), ...(pkgJson.devDependencies || {}) };
|
|
178
|
+
return Object.keys(allDeps).some(k => k.startsWith('@clerk/'));
|
|
179
|
+
} catch { return false; }
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export class TRPCPlugin extends BasePlugin {
|
|
184
|
+
get name() { return 'trpc'; }
|
|
185
|
+
getConfigFiles() { return ['package.json']; }
|
|
186
|
+
getRequiredPackages() {
|
|
187
|
+
return [{ name: '@trpc/server', dev: false }, { name: '@trpc/client', dev: false }];
|
|
188
|
+
}
|
|
189
|
+
async isActive(baseDir) {
|
|
190
|
+
try {
|
|
191
|
+
const pkgJson = JSON.parse(await fs.readFile(path.join(baseDir, 'package.json'), 'utf8'));
|
|
192
|
+
const allDeps = { ...(pkgJson.dependencies || {}), ...(pkgJson.devDependencies || {}) };
|
|
193
|
+
return Object.keys(allDeps).some(k => k.startsWith('@trpc/'));
|
|
194
|
+
} catch { return false; }
|
|
195
|
+
}
|
|
196
|
+
getRequiredSystemContracts() { return ['router', 'procedure', 'createTRPCRouter']; }
|
|
197
|
+
}
|