entkapp 5.5.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.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2 -2
  3. package/bin/cli.mjs +175 -0
  4. package/index.cjs +18 -0
  5. package/index.mjs +51 -0
  6. package/package.json +7 -6
  7. package/src/EngineContext.mjs +428 -0
  8. package/src/Initializer.mjs +82 -0
  9. package/src/analyzers/CodeSmellAnalyzer.mjs +106 -0
  10. package/src/analyzers/OxcAnalyzer.mjs +11 -0
  11. package/src/api/HeadlessAPI.js +31 -16
  12. package/src/api/HeadlessAPI.mjs +369 -0
  13. package/src/api/PluginSDK.mjs +135 -0
  14. package/src/ast/ASTAnalyzer.js +17 -3
  15. package/src/ast/ASTAnalyzer.mjs +742 -0
  16. package/src/ast/AdvancedAnalysis.mjs +586 -0
  17. package/src/ast/BarrelParser.mjs +230 -0
  18. package/src/ast/DeadCodeDetector.js +7 -5
  19. package/src/ast/DeadCodeDetector.mjs +92 -0
  20. package/src/ast/MagicDetector.mjs +203 -0
  21. package/src/ast/OxcAnalyzer.mjs +188 -0
  22. package/src/ast/SecretScanner.mjs +374 -0
  23. package/src/healing/GitSandbox.mjs +82 -0
  24. package/src/healing/SelfHealer.mjs +48 -0
  25. package/src/index.js +37 -1
  26. package/src/index.mjs +1176 -0
  27. package/src/performance/GraphCache.mjs +108 -0
  28. package/src/performance/SupplyChainGuard.mjs +92 -0
  29. package/src/performance/WorkerPool.mjs +132 -0
  30. package/src/performance/WorkerTaskRunner.mjs +144 -0
  31. package/src/plugins/BasePlugin.mjs +240 -0
  32. package/src/plugins/PluginRegistry.mjs +203 -0
  33. package/src/plugins/ecosystems/BackendServices.mjs +197 -0
  34. package/src/plugins/ecosystems/GenericPlugins.mjs +142 -0
  35. package/src/plugins/ecosystems/ModernFrameworks.mjs +162 -0
  36. package/src/plugins/ecosystems/MorePlugins.mjs +562 -0
  37. package/src/plugins/ecosystems/NewPlugins.mjs +526 -0
  38. package/src/plugins/ecosystems/NextJsPlugin.mjs +45 -0
  39. package/src/plugins/ecosystems/PluginLoader.mjs +193 -0
  40. package/src/plugins/ecosystems/TypeScriptPlugin.mjs +56 -0
  41. package/src/plugins/ecosystems/UltimateBundle.mjs +1182 -0
  42. package/src/refractor/ImpactAnalyzer.mjs +92 -0
  43. package/src/refractor/SourceRewriter.mjs +86 -0
  44. package/src/refractor/TransactionManager.mjs +5 -0
  45. package/src/refractor/TypeIntegrity.mjs +4 -0
  46. package/src/resolution/BuildOrchestrator.mjs +46 -0
  47. package/src/resolution/CircularDetector.mjs +91 -0
  48. package/src/resolution/ConfigGenerator.mjs +83 -0
  49. package/src/resolution/ConfigLoader.mjs +94 -0
  50. package/src/resolution/DepencyResolver.mjs +66 -0
  51. package/src/resolution/DependencyFixer.mjs +88 -0
  52. package/src/resolution/DependencyProfiler.mjs +286 -0
  53. package/src/resolution/EntryPointDetector.mjs +134 -0
  54. package/src/resolution/FrameworkConfigParser.mjs +119 -0
  55. package/src/resolution/GraphAnalyzer.mjs +80 -0
  56. package/src/resolution/MigrationAnalyzer.mjs +60 -0
  57. package/src/resolution/PathMapper.mjs +82 -0
  58. package/src/resolution/TSConfigLoader.mjs +162 -0
  59. package/src/resolution/WorkSpaceGraph.mjs +183 -0
  60. package/src/resolution/WorkspaceDiagnostic.mjs +139 -0
  61. package/test.js +76 -0
  62. package/wrangler.toml +6 -0
@@ -0,0 +1,428 @@
1
+ import fsSync from 'fs';
2
+ import path from 'path';
3
+ import ansis from 'ansis';
4
+
5
+ /**
6
+ * Core In-Memory Graph Node Representation.
7
+ * Tracks structural metadata, dependencies, and usage metrics for a single file.
8
+ */
9
+ export class GraphNode {
10
+ constructor(filePath) {
11
+ this.filePath = filePath;
12
+ this.incomingEdges = new Set(); // Files that import THIS file
13
+ this.outgoingEdges = new Set(); // Files that THIS file imports
14
+
15
+ this.explicitImports = new Set(); // Raw import strings
16
+ this.importedSymbols = new Set(); // "file:symbol" or "file:*"
17
+ this.internalExports = new Map(); // symbol -> { type, start, end, ... }
18
+
19
+ this.externalPackageUsage = new Set();
20
+ this.instantiatedIdentifiers = new Set();
21
+ this.propertyAccessChains = new Set();
22
+ this.rawStringReferences = new Set();
23
+ this.jsxComponents = new Set();
24
+ this.jsxProps = new Set();
25
+ this.decorators = new Set();
26
+ this.dynamicImports = new Set();
27
+ this.calculatedDynamicImports = [];
28
+
29
+ this.isEntry = false;
30
+ this.isLibraryEntry = false;
31
+ this.isFrameworkComponent = false;
32
+ this.symbolSourceLocations = new Map(); // symbol -> { line, column }
33
+ this.localSuppressedRules = new Set();
34
+ }
35
+
36
+ isSymbolReferencedExternally(symbolName, projectGraph, visited = new Set()) {
37
+ const visitKey = `${this.filePath}:${symbolName}`;
38
+ if (visited.has(visitKey)) return false;
39
+ visited.add(visitKey);
40
+
41
+ // Self-reference check
42
+ if (this.instantiatedIdentifiers.has(symbolName)) return true;
43
+ for (const accessChain of this.propertyAccessChains) {
44
+ if (accessChain.endsWith(`.${symbolName}`) || accessChain.includes(`.${symbolName}.`)) return true;
45
+ }
46
+
47
+ for (const parentPath of this.incomingEdges) {
48
+ const parentNode = projectGraph.get(parentPath);
49
+ if (!parentNode) continue;
50
+
51
+ // Direct Import Tokens
52
+ const absoluteImportKey = `${this.filePath}:${symbolName}`;
53
+ const absoluteStarKey = `${this.filePath}:*`;
54
+ if (parentNode.importedSymbols.has(absoluteImportKey) || parentNode.importedSymbols.has(absoluteStarKey)) return true;
55
+
56
+ // Advanced Member Usage Detection
57
+ if (parentNode.propertyAccessChains.has(symbolName)) return true;
58
+
59
+ // If it's a member access like 'Logger.error' or 'app.start', we check for the leaf name usage.
60
+ if (symbolName.includes('.')) {
61
+ const parts = symbolName.split('.');
62
+ const leafName = parts[parts.length - 1];
63
+ const parentName = parts[0];
64
+
65
+ if ((parentNode.instantiatedIdentifiers.has(parentName) ||
66
+ parentNode.importedSymbols.has(`${this.filePath}:${parentName}`)) &&
67
+ parentNode.instantiatedIdentifiers.has(leafName)) {
68
+ return true;
69
+ }
70
+ }
71
+
72
+ // Dynamic Import Check
73
+ if (parentNode.dynamicImports.has(this.filePath) || parentNode.explicitImports.has(this.filePath)) {
74
+ if (parentNode.dynamicImports.has(this.filePath)) return true;
75
+ }
76
+
77
+ // Recursive Re-export Resolution
78
+ for (const [exportedName, exportMeta] of parentNode.internalExports.entries()) {
79
+ const isMatch = (exportMeta.type === 're-export' || exportMeta.type === 're-export-namespace' || exportMeta.type === 're-export-all') &&
80
+ (exportMeta.originalName === symbolName || exportMeta.originalName === '*' || exportMeta.type === 're-export-all') &&
81
+ (exportMeta.source === this.filePath || (exportMeta.source && path.resolve(path.dirname(parentPath), exportMeta.source) === this.filePath));
82
+
83
+ if (isMatch) {
84
+ if (parentNode.isSymbolReferencedExternally(exportedName, projectGraph, visited)) return true;
85
+ }
86
+ }
87
+
88
+ // Fuzzy / Dynamic usage (Identifiers, Strings, JSX, Decorators)
89
+ // UPGRADE: Only check for fuzzy matches if we have a reason to believe it might be used.
90
+ // We skip global names like 'toLowerCase' unless they are explicitly imported.
91
+ const isGlobalName = ['toLowerCase', 'toUpperCase', 'toString', 'valueOf', 'hasOwnProperty', 'constructor'].includes(symbolName);
92
+
93
+ if (!isGlobalName) {
94
+ if (parentNode.instantiatedIdentifiers.has(symbolName)) return true;
95
+ if (parentNode.rawStringReferences.has(symbolName)) return true;
96
+ if (parentNode.jsxComponents.has(symbolName)) return true;
97
+ if (parentNode.decorators.has(symbolName)) return true;
98
+ }
99
+
100
+ for (const accessChain of parentNode.propertyAccessChains) {
101
+ if (accessChain.endsWith(`.${symbolName}`) || accessChain.includes(`.${symbolName}.`)) return true;
102
+ }
103
+ }
104
+
105
+ return false;
106
+ }
107
+ }
108
+
109
+ export class EngineContext {
110
+ constructor(cwd) {
111
+ this.cwd = cwd || process.cwd();
112
+ this.projectGraph = new Map(); // Path -> GraphNode
113
+ this.usedExternalPackages = new Set();
114
+ this.unimportedUsedPackages = new Set();
115
+ this.importedUnusedPackages = new Set();
116
+ this.unusedBinaries = new Set();
117
+ this.manifestDependencies = new Map();
118
+
119
+ this.isWorkspaceEnabled = false;
120
+ this.monorepoPackageRoots = new Set();
121
+ this.verbose = false;
122
+ this.metrics = { totalFilesScanned: 0, cacheHits: 0, cacheMisses: 0 };
123
+ this.allSecretFindings = [];
124
+ }
125
+
126
+ getOrCreateNode(filePath) {
127
+ if (!this.projectGraph.has(filePath)) {
128
+ this.projectGraph.set(filePath, new GraphNode(filePath));
129
+ // NEW: Automatically track package.json files in manifestDependencies
130
+ if (filePath.endsWith('package.json')) {
131
+ this.manifestDependencies.set(filePath, new Set());
132
+ }
133
+ }
134
+ return this.projectGraph.get(filePath);
135
+ }
136
+
137
+ async generateSummaryReport() {
138
+ const report = {
139
+ orphanedFiles: [],
140
+ deadExports: [],
141
+ unusedDependencies: [],
142
+ unimportedUsedPackages: [],
143
+ importedUnusedPackages: [],
144
+ unusedBinaries: []
145
+ };
146
+
147
+ // --- DEEP REACHABILITY ANALYSIS (BFS) ---
148
+ const reachableFiles = new Set();
149
+ const queue = [];
150
+
151
+ // 1. Initialize BFS with Entry Points
152
+ for (const [filePath, node] of this.projectGraph.entries()) {
153
+ // FIX: Also check if the file is explicitly mentioned in package.json bin or main
154
+ if (node.isEntry || node.isLibraryEntry || node.isFrameworkComponent) {
155
+ reachableFiles.add(filePath);
156
+ queue.push(filePath);
157
+ }
158
+ }
159
+
160
+ // UPGRADE: Ensure package.json main and bin are always entry points
161
+ // UPGRADE: Comprehensive Entry Point Discovery (Manifests + Tooling)
162
+ for (const [manifestPath, deps] of this.manifestDependencies.entries()) {
163
+ try {
164
+ const data = JSON.parse(fsSync.readFileSync(manifestPath, 'utf8'));
165
+ const pkgDir = path.dirname(manifestPath);
166
+ const entries = [];
167
+
168
+ // 1. Standard Manifest Entries
169
+ if (data.main) entries.push(path.resolve(pkgDir, data.main));
170
+ if (data.module) entries.push(path.resolve(pkgDir, data.module));
171
+ if (data.exports) {
172
+ const unwind = (val) => {
173
+ if (typeof val === 'string') entries.push(path.resolve(pkgDir, val));
174
+ else if (typeof val === 'object' && val !== null) Object.values(val).forEach(unwind);
175
+ };
176
+ unwind(data.exports);
177
+ }
178
+ if (data.bin) {
179
+ if (typeof data.bin === 'string') entries.push(path.resolve(pkgDir, data.bin));
180
+ else Object.values(data.bin).forEach(b => entries.push(path.resolve(pkgDir, b)));
181
+ }
182
+
183
+ // 2. Protect Documentation & Configs
184
+ const possibleConfigs = ['vite.config.mjs', 'vite.config.ts', 'vitest.config.ts', 'tsconfig.json'];
185
+ possibleConfigs.forEach(c => entries.push(path.resolve(pkgDir, c)));
186
+
187
+ entries.forEach(e => {
188
+ const normalized = e.replace(/\\/g, '/');
189
+ // Check with common extensions if not found
190
+ const candidates = [normalized, normalized + '.mjs', normalized + '.ts', normalized + '.tsx', normalized + '/index.mjs', normalized + '/index.ts'];
191
+ for (const cand of candidates) {
192
+ if (this.projectGraph.has(cand) && !reachableFiles.has(cand)) {
193
+ reachableFiles.add(cand);
194
+ queue.push(cand);
195
+ this.projectGraph.get(cand).isEntry = true;
196
+ break;
197
+ }
198
+ }
199
+ });
200
+
201
+ // --- PLUGIN-BASED ECOSYSTEM DETECTION ---
202
+ // Plugins will now handle their own reachability and dependency validation.
203
+ if (this.pluginRegistry) {
204
+ const activePlugins = await this.pluginRegistry.getActivePlugins(pkgDir);
205
+ for (const plugin of activePlugins) {
206
+ if (typeof plugin.onDiscovery === 'function') {
207
+ await plugin.onDiscovery({
208
+ pkgDir,
209
+ data,
210
+ reachableFiles,
211
+ queue,
212
+ projectGraph: this.projectGraph,
213
+ context: this
214
+ });
215
+ }
216
+ }
217
+ }
218
+ } catch (e) {}
219
+ }
220
+
221
+ // 2. BFS Traversal
222
+ while (queue.length > 0) {
223
+ const currentPath = queue.shift();
224
+ const node = this.projectGraph.get(currentPath);
225
+
226
+ if (!node) continue;
227
+
228
+ // Track outgoing edges (static and linked dynamic imports)
229
+ if (node.outgoingEdges) {
230
+ for (const outgoingPath of node.outgoingEdges) {
231
+ const normalizedPath = outgoingPath.replace(/\\/g, '/');
232
+ if (!reachableFiles.has(normalizedPath)) {
233
+ reachableFiles.add(normalizedPath);
234
+ queue.push(normalizedPath);
235
+ }
236
+ }
237
+ }
238
+
239
+ // UPGRADE: Handle non-literal dynamic imports (calculatedDynamicImports)
240
+ if ((node.calculatedDynamicImports && node.calculatedDynamicImports.length > 0) || (node.dynamicImports && node.dynamicImports.has('__DYNAMIC_PATTERN__'))) {
241
+ const dir = path.dirname(currentPath);
242
+ // Conservative: If we have a calculated dynamic import, any file in the same or sub directory could be a target
243
+ for (const [otherPath, _] of this.projectGraph.entries()) {
244
+ // Fix: Check if otherPath is in the same directory or a subdirectory of 'dir'
245
+ if (otherPath !== currentPath && otherPath.startsWith(dir) && !reachableFiles.has(otherPath)) {
246
+ // Additional check: If it's in a 'plugins' or 'modules' folder, it's very likely a dynamic target
247
+ const isLikelyDynamicTarget = otherPath.includes('/plugins/') || otherPath.includes('/modules/') || otherPath.includes('/dynamic/');
248
+ if (isLikelyDynamicTarget) {
249
+ reachableFiles.add(otherPath);
250
+ queue.push(otherPath);
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ // UPGRADE: Handle Worker/Threads usage (new Worker('path'))
257
+ for (const chain of node.propertyAccessChains) {
258
+ if (chain.includes('new Worker(') || chain.includes('Worker(')) {
259
+ // Check if any file path is mentioned in raw strings
260
+ for (const str of node.rawStringReferences) {
261
+ if (str.startsWith('.') || str.startsWith('/')) {
262
+ try {
263
+ const resolved = path.resolve(path.dirname(currentPath), str);
264
+ const extensions = ['.mjs', '.ts', '.mjs', '.cjs'];
265
+ for (const ext of extensions) {
266
+ const p = resolved.endsWith(ext) ? resolved : resolved + ext;
267
+ const normalized = p.replace(/\\/g, '/');
268
+ if (this.projectGraph.has(normalized) && !reachableFiles.has(normalized)) {
269
+ reachableFiles.add(normalized);
270
+ queue.push(normalized);
271
+ }
272
+ }
273
+ } catch (e) {}
274
+ }
275
+ }
276
+ }
277
+ }
278
+
279
+ // Additional check for unlinked dynamic imports (conservative)
280
+ if (node.dynamicImports) {
281
+ for (const dynamicPath of node.dynamicImports) {
282
+ if (dynamicPath.startsWith('__DYNAMIC_PATTERN__:')) {
283
+ const pattern = dynamicPath.split(':')[1];
284
+ const dir = path.dirname(currentPath);
285
+ for (const [otherPath, _] of this.projectGraph.entries()) {
286
+ if (otherPath.startsWith(dir) && !reachableFiles.has(otherPath)) {
287
+ reachableFiles.add(otherPath);
288
+ queue.push(otherPath);
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ }
295
+
296
+ // --- ANALYSIS PHASE ---
297
+ if (this.verbose) {
298
+ console.log(`[Reachability] Total reachable files: ${reachableFiles.size}`);
299
+ for (const f of reachableFiles) console.log(` • Reachable: ${path.relative(this.cwd, f)}`);
300
+ }
301
+ for (const [filePath, node] of this.projectGraph.entries()) {
302
+ const isSuppressed = node.localSuppressedRules.has('*') || node.localSuppressedRules.has('unused-file');
303
+ const isReachable = reachableFiles.has(filePath);
304
+
305
+ // Check for Orphaned Files
306
+ if (!isReachable && !isSuppressed) {
307
+ const relPath = path.relative(this.cwd, filePath).replace(/\\/g, '/');
308
+ report.orphanedFiles.push(relPath);
309
+ }
310
+
311
+ // Check for Dead Exports
312
+ if (isReachable) {
313
+ const isPublicAPI = node.isEntry || node.isLibraryEntry;
314
+
315
+ for (const [symbol, meta] of node.internalExports.entries()) {
316
+ if (symbol === '*' || symbol === 'default' || node.localSuppressedRules.has('unused-export') || node.localSuppressedRules.has(`unused-export:${symbol}`)) {
317
+ continue;
318
+ }
319
+
320
+ const isSymbolUsed = isPublicAPI || node.isSymbolReferencedExternally(symbol, this.projectGraph);
321
+
322
+ if (!isSymbolUsed) {
323
+ const loc = node.symbolSourceLocations.get(symbol) || { line: 0, column: 0 };
324
+ report.deadExports.push({
325
+ symbol,
326
+ file: path.relative(this.cwd, filePath),
327
+ line: loc.line
328
+ });
329
+ }
330
+
331
+ // Member Analysis
332
+ if (meta.members && meta.members.length > 0 && isSymbolUsed) {
333
+ for (const member of meta.members) {
334
+ const fullMemberName = `${symbol}.${member.name}`;
335
+
336
+ if (isPublicAPI && member.isPublic !== false) {
337
+ continue;
338
+ }
339
+
340
+ // UPGRADE: Check for dynamic usage in ANY reachable file, not just direct imports
341
+ let isMemberUsed = false;
342
+ for (const [otherPath, otherNode] of this.projectGraph.entries()) {
343
+ if (reachableFiles.has(otherPath)) {
344
+ if (otherNode.instantiatedIdentifiers.has(member.name) ||
345
+ otherNode.propertyAccessChains.has(fullMemberName) ||
346
+ otherNode.rawStringReferences.has(member.name)) {
347
+ isMemberUsed = true;
348
+ break;
349
+ }
350
+ }
351
+ }
352
+
353
+ if (!isMemberUsed && !node.isSymbolReferencedExternally(fullMemberName, this.projectGraph)) {
354
+ const loc = node.symbolSourceLocations.get(fullMemberName) || { line: 0, column: 0 };
355
+ report.deadExports.push({
356
+ symbol: fullMemberName,
357
+ file: path.relative(this.cwd, filePath),
358
+ line: loc.line
359
+ });
360
+ }
361
+ }
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ // --- DEPENDENCY ANALYSIS ---
368
+ const usedByReachableFiles = new Set();
369
+ reachableFiles.forEach(filePath => {
370
+ const node = this.projectGraph.get(filePath);
371
+ if (node) {
372
+ node.externalPackageUsage.forEach(pkg => usedByReachableFiles.add(pkg));
373
+
374
+ // Conservative check for dynamic imports in reachable files
375
+ if (node.calculatedDynamicImports) {
376
+ node.calculatedDynamicImports.forEach(entry => {
377
+ const expr = typeof entry === 'string' ? entry : (entry.pattern || entry.text || '');
378
+ // If it looks like a package name (no dots/slashes), assume it's used
379
+ if (expr && !expr.includes('.') && !expr.includes('/') && !expr.includes('`') && !expr.includes("'") && !expr.includes('"')) {
380
+ usedByReachableFiles.add(expr);
381
+ }
382
+ // Check raw strings for potential package names
383
+ node.rawStringReferences.forEach(str => {
384
+ if (!str.startsWith('.') && !str.startsWith('/')) {
385
+ const pkg = str.startsWith('@') ? str.split('/').slice(0, 2).join('/') : str.split('/')[0];
386
+ usedByReachableFiles.add(pkg);
387
+ }
388
+ });
389
+ });
390
+ }
391
+ }
392
+ });
393
+
394
+ for (const [manifestPath, deps] of this.manifestDependencies.entries()) {
395
+ const allDeps = [...(deps.dependencies || []), ...(deps.devDependencies || [])];
396
+ const hasTypeScriptFiles = Array.from(this.projectGraph.keys()).some(f => f.endsWith('.ts') || f.endsWith('.tsx'));
397
+
398
+ for (const dep of allDeps) {
399
+ if (dep.startsWith('@types/') || dep === 'entkapp' || (dep === 'typescript' && hasTypeScriptFiles)) {
400
+ continue;
401
+ }
402
+
403
+ // UPGRADE: Be more aggressive in detecting unused dependencies.
404
+ // Check both externalPackageUsage and rawStringReferences.
405
+ const isUsed = usedByReachableFiles.has(dep) ||
406
+ Array.from(reachableFiles).some(f => {
407
+ const node = this.projectGraph.get(f);
408
+ return node && (node.externalPackageUsage.has(dep) || node.rawStringReferences.has(dep));
409
+ });
410
+
411
+ if (!isUsed) {
412
+ report.unusedDependencies.push({
413
+ package: dep,
414
+ type: deps.dependencies.includes(dep) ? 'dependency' : 'devDependency',
415
+ manifest: path.relative(this.cwd, manifestPath)
416
+ });
417
+ this.importedUnusedPackages.add(dep);
418
+ }
419
+ }
420
+ }
421
+
422
+ report.unimportedUsedPackages = Array.from(this.unimportedUsedPackages);
423
+ report.importedUnusedPackages = Array.from(this.importedUnusedPackages);
424
+ report.unusedBinaries = Array.from(this.unusedBinaries);
425
+
426
+ return report;
427
+ }
428
+ }
@@ -0,0 +1,82 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ import { glob } from 'glob';
4
+
5
+ /**
6
+ * Entkapp Initializer: Analyzes the codebase to generate a package.json
7
+ * with correctly categorized dependencies and devDependencies.
8
+ */
9
+ export class Initializer {
10
+ constructor(context) {
11
+ this.context = context;
12
+ }
13
+
14
+ async run() {
15
+ console.log("🚀 Starting entkapp intelligent initialization...");
16
+ const cwd = this.context.cwd || process.cwd();
17
+
18
+ // 1. Scan for all JS/TS files
19
+ const files = await glob('**/*.{js,ts,jsx,tsx,vue,svelte,astro}', {
20
+ ignore: ['node_modules/**', 'dist/**', '.git/**'],
21
+ cwd
22
+ });
23
+
24
+ const dependencies = new Set();
25
+ const devDependencies = new Set();
26
+
27
+ // Common dev tool indicators
28
+ const devIndicators = ['test', 'spec', 'config', 'stories', 'mock'];
29
+
30
+ console.log(`🔍 Analyzing ${files.length} files for imports...`);
31
+
32
+ for (const file of files) {
33
+ const content = await fs.readFile(path.join(cwd, file), 'utf8');
34
+ const isDevFile = devIndicators.some(ind => file.toLowerCase().includes(ind));
35
+
36
+ // Simple but effective regex for imports
37
+ const importMatches = content.matchAll(/(?:import|from|require)\s*\(?\s*['"]([^' "./][^'"]*)['"]/g);
38
+
39
+ for (const match of importMatches) {
40
+ const pkg = this.extractPackageName(match[1]);
41
+ if (pkg && !this.isBuiltIn(pkg)) {
42
+ if (isDevFile) devDependencies.add(pkg);
43
+ else dependencies.add(pkg);
44
+ }
45
+ }
46
+ }
47
+
48
+ // 2. Generate package.json
49
+ const pkgJson = {
50
+ name: path.basename(cwd),
51
+ version: "1.0.0",
52
+ description: "Initialized with entkapp",
53
+ main: "index.mjs",
54
+ scripts: {
55
+ "entkapp:run": "entkapp -r",
56
+ "entkapp:check": "entkapp -r --verbose"
57
+ },
58
+ dependencies: Object.fromEntries([...dependencies].sort().map(d => [d, "latest"])),
59
+ devDependencies: Object.fromEntries([...devDependencies].sort().map(d => [d, "latest"]))
60
+ };
61
+
62
+ await fs.writeFile(path.join(cwd, 'package.json'), JSON.stringify(pkgJson, null, 2));
63
+ console.log("✅ package.json generated with analyzed dependencies.");
64
+
65
+ // 3. Create /entkapp folder
66
+ await fs.mkdir(path.join(cwd, 'entkapp'), { recursive: true });
67
+ console.log("✅ /entkapp configuration folder created.");
68
+ }
69
+
70
+ extractPackageName(specifier) {
71
+ if (specifier.startsWith('@')) {
72
+ const parts = specifier.split('/');
73
+ return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : null;
74
+ }
75
+ return specifier.split('/')[0];
76
+ }
77
+
78
+ isBuiltIn(pkg) {
79
+ const builtins = ['path', 'fs', 'os', 'crypto', 'http', 'https', 'stream', 'util', 'events', 'module', 'process'];
80
+ return builtins.includes(pkg) || pkg.startsWith('node:');
81
+ }
82
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * CodeSmellAnalyzer: Performs deep static analysis to find runtime risks and logic smells.
3
+ */
4
+ export class CodeSmellAnalyzer {
5
+ constructor(context) {
6
+ this.context = context;
7
+ this.issues = [];
8
+ this.rules = {
9
+ 'potential-null-pointer': {
10
+ message: 'Potential Null Pointer Risk: Accessing property on an object that might be undefined.',
11
+ link: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cant_access_property'
12
+ },
13
+ 'infinite-loop-risk': {
14
+ message: 'Potential Infinite Loop: Loop condition does not seem to change within the body.',
15
+ link: 'https://en.wikipedia.org/wiki/Infinite_loop'
16
+ },
17
+ 'implicit-type-coercion': {
18
+ message: 'Implicit Type Coercion: Using loose equality (==) can lead to unexpected runtime behavior.',
19
+ link: 'https://dorey.github.io/JavaScript-Equality-Table/'
20
+ }
21
+ };
22
+ }
23
+
24
+ analyze(fileNode) {
25
+ if (!fileNode.ast) return;
26
+ this.walk(fileNode.ast, fileNode);
27
+ }
28
+
29
+ walk(node, fileNode) {
30
+ if (!node) return;
31
+
32
+ // 1. Check for Loose Equality (==)
33
+ if (node.type === 'BinaryExpression' && (node.operator === '==' || node.operator === '!=')) {
34
+ this.addIssue('implicit-type-coercion', node, fileNode);
35
+ }
36
+
37
+ // 2. Check for Potential Infinite Loops (While loops with static conditions)
38
+ if (node.type === 'WhileStatement' && node.test.type === 'Literal' && node.test.value === true) {
39
+ // Check if there is a break or return inside
40
+ if (!this.hasExitStatement(node.body)) {
41
+ this.addIssue('infinite-loop-risk', node, fileNode);
42
+ }
43
+ }
44
+
45
+ // 3. Check for Null Pointer Risks (Accessing properties on potentially uninitialized vars)
46
+ if (node.type === 'MemberExpression' && node.object.type === 'Identifier') {
47
+ const varName = node.object.name;
48
+ if (this.isPotentiallyNull(varName, fileNode)) {
49
+ this.addIssue('potential-null-pointer', node, fileNode);
50
+ }
51
+ }
52
+
53
+ // Recursively walk the AST
54
+ for (const key in node) {
55
+ if (node[key] && typeof node[key] === 'object') {
56
+ if (Array.isArray(node[key])) {
57
+ node[key].forEach(child => this.walk(child, fileNode));
58
+ } else {
59
+ this.walk(node[key], fileNode);
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ addIssue(ruleId, node, fileNode) {
66
+ const rule = this.rules[ruleId];
67
+ fileNode.diagnostics = fileNode.diagnostics || [];
68
+ fileNode.diagnostics.push({
69
+ ruleId,
70
+ message: rule.message,
71
+ link: rule.link,
72
+ line: node.start ? this.getLineNumber(node.start, fileNode.content) : 0,
73
+ severity: 'warning'
74
+ });
75
+ }
76
+
77
+ hasExitStatement(node) {
78
+ let found = false;
79
+ const check = (n) => {
80
+ if (!n || found) return;
81
+ if (n.type === 'BreakStatement' || n.type === 'ReturnStatement' || n.type === 'ThrowStatement') {
82
+ found = true;
83
+ return;
84
+ }
85
+ for (const key in n) {
86
+ if (n[key] && typeof n[key] === 'object') {
87
+ if (Array.isArray(n[key])) n[key].forEach(check);
88
+ else check(n[key]);
89
+ }
90
+ }
91
+ };
92
+ check(node);
93
+ return found;
94
+ }
95
+
96
+ isPotentiallyNull(name, fileNode) {
97
+ // Simple heuristic: if it's a variable declared without init or in a try-catch
98
+ const symbol = fileNode.symbolTable?.get(name);
99
+ return symbol && symbol.type === 'variable' && !symbol.initialized;
100
+ }
101
+
102
+ getLineNumber(pos, content) {
103
+ if (!content) return 0;
104
+ return content.substring(0, pos).split('\n').length;
105
+ }
106
+ }
@@ -0,0 +1,11 @@
1
+ import { OxcAnalyzer as CoreOxcAnalyzer } from '../ast/OxcAnalyzer.mjs';
2
+
3
+ /**
4
+ * Proxy for the Core OxcAnalyzer to maintain backward compatibility
5
+ * while centralizing the implementation.
6
+ */
7
+ export class OxcAnalyzer extends CoreOxcAnalyzer {
8
+ constructor(context) {
9
+ super(context);
10
+ }
11
+ }