@vibecheckai/cli 3.5.0 → 3.5.2
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/bin/registry.js +214 -237
- package/bin/runners/cli-utils.js +33 -2
- package/bin/runners/context/analyzer.js +52 -1
- package/bin/runners/context/generators/cursor.js +2 -49
- package/bin/runners/context/git-context.js +3 -1
- package/bin/runners/context/team-conventions.js +33 -7
- package/bin/runners/lib/analysis-core.js +25 -5
- package/bin/runners/lib/analyzers.js +431 -481
- package/bin/runners/lib/default-config.js +127 -0
- package/bin/runners/lib/doctor/modules/security.js +3 -1
- package/bin/runners/lib/engine/ast-cache.js +210 -0
- package/bin/runners/lib/engine/auth-extractor.js +211 -0
- package/bin/runners/lib/engine/billing-extractor.js +112 -0
- package/bin/runners/lib/engine/enforcement-extractor.js +100 -0
- package/bin/runners/lib/engine/env-extractor.js +207 -0
- package/bin/runners/lib/engine/express-extractor.js +208 -0
- package/bin/runners/lib/engine/extractors.js +849 -0
- package/bin/runners/lib/engine/index.js +207 -0
- package/bin/runners/lib/engine/repo-index.js +514 -0
- package/bin/runners/lib/engine/types.js +124 -0
- package/bin/runners/lib/engines/accessibility-engine.js +18 -218
- package/bin/runners/lib/engines/api-consistency-engine.js +30 -335
- package/bin/runners/lib/engines/cross-file-analysis-engine.js +27 -292
- package/bin/runners/lib/engines/empty-catch-engine.js +17 -127
- package/bin/runners/lib/engines/mock-data-engine.js +10 -53
- package/bin/runners/lib/engines/performance-issues-engine.js +36 -176
- package/bin/runners/lib/engines/security-vulnerabilities-engine.js +54 -382
- package/bin/runners/lib/engines/type-aware-engine.js +39 -263
- package/bin/runners/lib/engines/vibecheck-engines/index.js +13 -122
- package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +73 -373
- package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
- package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
- package/bin/runners/lib/entitlements-v2.js +73 -97
- package/bin/runners/lib/error-handler.js +44 -3
- package/bin/runners/lib/error-messages.js +289 -0
- package/bin/runners/lib/evidence-pack.js +7 -1
- package/bin/runners/lib/finding-id.js +69 -0
- package/bin/runners/lib/finding-sorter.js +89 -0
- package/bin/runners/lib/html-proof-report.js +700 -350
- package/bin/runners/lib/missions/plan.js +6 -46
- package/bin/runners/lib/missions/templates.js +0 -232
- package/bin/runners/lib/next-action.js +560 -0
- package/bin/runners/lib/prerequisites.js +149 -0
- package/bin/runners/lib/route-detection.js +137 -68
- package/bin/runners/lib/scan-output.js +91 -76
- package/bin/runners/lib/scan-runner.js +135 -0
- package/bin/runners/lib/schemas/ajv-validator.js +464 -0
- package/bin/runners/lib/schemas/error-envelope.schema.json +105 -0
- package/bin/runners/lib/schemas/finding-v3.schema.json +151 -0
- package/bin/runners/lib/schemas/report-artifact.schema.json +120 -0
- package/bin/runners/lib/schemas/run-request.schema.json +108 -0
- package/bin/runners/lib/schemas/validator.js +27 -0
- package/bin/runners/lib/schemas/verdict.schema.json +140 -0
- package/bin/runners/lib/ship-output-enterprise.js +23 -23
- package/bin/runners/lib/ship-output.js +75 -31
- package/bin/runners/lib/terminal-ui.js +6 -113
- package/bin/runners/lib/truth.js +351 -10
- package/bin/runners/lib/unified-cli-output.js +430 -603
- package/bin/runners/lib/unified-output.js +13 -9
- package/bin/runners/runAIAgent.js +10 -5
- package/bin/runners/runAgent.js +0 -3
- package/bin/runners/runAllowlist.js +389 -0
- package/bin/runners/runApprove.js +0 -33
- package/bin/runners/runAuth.js +73 -45
- package/bin/runners/runCheckpoint.js +51 -11
- package/bin/runners/runClassify.js +85 -21
- package/bin/runners/runContext.js +0 -3
- package/bin/runners/runDoctor.js +41 -28
- package/bin/runners/runEvidencePack.js +362 -0
- package/bin/runners/runFirewall.js +0 -3
- package/bin/runners/runFirewallHook.js +0 -3
- package/bin/runners/runFix.js +66 -76
- package/bin/runners/runGuard.js +18 -411
- package/bin/runners/runInit.js +113 -30
- package/bin/runners/runLabs.js +424 -0
- package/bin/runners/runMcp.js +19 -25
- package/bin/runners/runPolish.js +64 -240
- package/bin/runners/runPromptFirewall.js +12 -5
- package/bin/runners/runProve.js +57 -22
- package/bin/runners/runQuickstart.js +531 -0
- package/bin/runners/runReality.js +59 -68
- package/bin/runners/runReport.js +38 -33
- package/bin/runners/runRuntime.js +8 -5
- package/bin/runners/runScan.js +1413 -190
- package/bin/runners/runShip.js +113 -719
- package/bin/runners/runTruth.js +0 -3
- package/bin/runners/runValidate.js +13 -9
- package/bin/runners/runWatch.js +23 -14
- package/bin/scan.js +6 -1
- package/bin/vibecheck.js +204 -185
- package/mcp-server/deprecation-middleware.js +282 -0
- package/mcp-server/handlers/index.ts +15 -0
- package/mcp-server/handlers/tool-handler.ts +554 -0
- package/mcp-server/index-v1.js +698 -0
- package/mcp-server/index.js +210 -238
- package/mcp-server/lib/cache-wrapper.cjs +383 -0
- package/mcp-server/lib/error-envelope.js +138 -0
- package/mcp-server/lib/executor.ts +499 -0
- package/mcp-server/lib/index.ts +19 -0
- package/mcp-server/lib/rate-limiter.js +166 -0
- package/mcp-server/lib/sandbox.test.ts +519 -0
- package/mcp-server/lib/sandbox.ts +395 -0
- package/mcp-server/lib/types.ts +267 -0
- package/mcp-server/package.json +12 -3
- package/mcp-server/registry/tool-registry.js +794 -0
- package/mcp-server/registry/tools.json +605 -0
- package/mcp-server/registry.test.ts +334 -0
- package/mcp-server/tests/tier-gating.test.js +297 -0
- package/mcp-server/tier-auth.js +378 -45
- package/mcp-server/tools-v3.js +353 -442
- package/mcp-server/tsconfig.json +37 -0
- package/mcp-server/vibecheck-2.0-tools.js +14 -1
- package/package.json +1 -1
- package/bin/runners/lib/agent-firewall/learning/learning-engine.js +0 -849
- package/bin/runners/lib/audit-logger.js +0 -532
- package/bin/runners/lib/authority/authorities/architecture.js +0 -364
- package/bin/runners/lib/authority/authorities/compliance.js +0 -341
- package/bin/runners/lib/authority/authorities/human.js +0 -343
- package/bin/runners/lib/authority/authorities/quality.js +0 -420
- package/bin/runners/lib/authority/authorities/security.js +0 -228
- package/bin/runners/lib/authority/index.js +0 -293
- package/bin/runners/lib/bundle/bundle-intelligence.js +0 -846
- package/bin/runners/lib/cli-charts.js +0 -368
- package/bin/runners/lib/cli-config-display.js +0 -405
- package/bin/runners/lib/cli-demo.js +0 -275
- package/bin/runners/lib/cli-errors.js +0 -438
- package/bin/runners/lib/cli-help-formatter.js +0 -439
- package/bin/runners/lib/cli-interactive-menu.js +0 -509
- package/bin/runners/lib/cli-prompts.js +0 -441
- package/bin/runners/lib/cli-scan-cards.js +0 -362
- package/bin/runners/lib/compliance-reporter.js +0 -710
- package/bin/runners/lib/conductor/index.js +0 -671
- package/bin/runners/lib/easy/README.md +0 -123
- package/bin/runners/lib/easy/index.js +0 -140
- package/bin/runners/lib/easy/interactive-wizard.js +0 -788
- package/bin/runners/lib/easy/one-click-firewall.js +0 -564
- package/bin/runners/lib/easy/zero-config-reality.js +0 -714
- package/bin/runners/lib/engines/async-patterns-engine.js +0 -444
- package/bin/runners/lib/engines/bundle-size-engine.js +0 -433
- package/bin/runners/lib/engines/confidence-scoring.js +0 -276
- package/bin/runners/lib/engines/context-detection.js +0 -264
- package/bin/runners/lib/engines/database-patterns-engine.js +0 -429
- package/bin/runners/lib/engines/duplicate-code-engine.js +0 -354
- package/bin/runners/lib/engines/env-variables-engine.js +0 -458
- package/bin/runners/lib/engines/error-handling-engine.js +0 -437
- package/bin/runners/lib/engines/false-positive-prevention.js +0 -630
- package/bin/runners/lib/engines/framework-adapters/index.js +0 -607
- package/bin/runners/lib/engines/framework-detection.js +0 -508
- package/bin/runners/lib/engines/import-order-engine.js +0 -429
- package/bin/runners/lib/engines/naming-conventions-engine.js +0 -544
- package/bin/runners/lib/engines/noise-reduction-engine.js +0 -452
- package/bin/runners/lib/engines/orchestrator.js +0 -334
- package/bin/runners/lib/engines/react-patterns-engine.js +0 -457
- package/bin/runners/lib/engines/vibecheck-engines/lib/ai-hallucination-engine.js +0 -806
- package/bin/runners/lib/engines/vibecheck-engines/lib/smart-fix-engine.js +0 -577
- package/bin/runners/lib/engines/vibecheck-engines/lib/vibe-score-engine.js +0 -543
- package/bin/runners/lib/engines/vibecheck-engines.js +0 -514
- package/bin/runners/lib/enhanced-features/index.js +0 -305
- package/bin/runners/lib/enhanced-output.js +0 -631
- package/bin/runners/lib/enterprise.js +0 -300
- package/bin/runners/lib/firewall/command-validator.js +0 -351
- package/bin/runners/lib/firewall/config.js +0 -341
- package/bin/runners/lib/firewall/content-validator.js +0 -519
- package/bin/runners/lib/firewall/index.js +0 -101
- package/bin/runners/lib/firewall/path-validator.js +0 -256
- package/bin/runners/lib/intelligence/cross-repo-intelligence.js +0 -817
- package/bin/runners/lib/mcp-utils.js +0 -425
- package/bin/runners/lib/output/index.js +0 -1022
- package/bin/runners/lib/policy-engine.js +0 -652
- package/bin/runners/lib/polish/autofix/accessibility-fixes.js +0 -333
- package/bin/runners/lib/polish/autofix/async-handlers.js +0 -273
- package/bin/runners/lib/polish/autofix/dead-code.js +0 -280
- package/bin/runners/lib/polish/autofix/imports-optimizer.js +0 -344
- package/bin/runners/lib/polish/autofix/index.js +0 -200
- package/bin/runners/lib/polish/autofix/remove-consoles.js +0 -209
- package/bin/runners/lib/polish/autofix/strengthen-types.js +0 -245
- package/bin/runners/lib/polish/backend-checks.js +0 -148
- package/bin/runners/lib/polish/documentation-checks.js +0 -111
- package/bin/runners/lib/polish/frontend-checks.js +0 -168
- package/bin/runners/lib/polish/index.js +0 -71
- package/bin/runners/lib/polish/infrastructure-checks.js +0 -131
- package/bin/runners/lib/polish/library-detection.js +0 -175
- package/bin/runners/lib/polish/performance-checks.js +0 -100
- package/bin/runners/lib/polish/security-checks.js +0 -148
- package/bin/runners/lib/polish/utils.js +0 -203
- package/bin/runners/lib/prompt-builder.js +0 -540
- package/bin/runners/lib/proof-certificate.js +0 -634
- package/bin/runners/lib/reality/accessibility-audit.js +0 -946
- package/bin/runners/lib/reality/api-contract-validator.js +0 -1012
- package/bin/runners/lib/reality/chaos-engineering.js +0 -1084
- package/bin/runners/lib/reality/performance-tracker.js +0 -1077
- package/bin/runners/lib/reality/scenario-generator.js +0 -1404
- package/bin/runners/lib/reality/visual-regression.js +0 -852
- package/bin/runners/lib/reality-profiler.js +0 -717
- package/bin/runners/lib/replay/flight-recorder-viewer.js +0 -1160
- package/bin/runners/lib/review/ai-code-review.js +0 -832
- package/bin/runners/lib/rules/custom-rule-engine.js +0 -985
- package/bin/runners/lib/sbom-generator.js +0 -641
- package/bin/runners/lib/scan-output-enhanced.js +0 -512
- package/bin/runners/lib/security/owasp-scanner.js +0 -939
- package/bin/runners/lib/validators/contract-validator.js +0 -283
- package/bin/runners/lib/validators/dead-export-detector.js +0 -279
- package/bin/runners/lib/validators/dep-audit.js +0 -245
- package/bin/runners/lib/validators/env-validator.js +0 -319
- package/bin/runners/lib/validators/index.js +0 -120
- package/bin/runners/lib/validators/license-checker.js +0 -252
- package/bin/runners/lib/validators/route-validator.js +0 -290
- package/bin/runners/runAuthority.js +0 -528
- package/bin/runners/runConductor.js +0 -772
- package/bin/runners/runContainer.js +0 -366
- package/bin/runners/runEasy.js +0 -410
- package/bin/runners/runIaC.js +0 -372
- package/bin/runners/runVibe.js +0 -791
- package/mcp-server/tools.js +0 -495
|
@@ -1,846 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Bundle Intelligence Engine
|
|
3
|
-
*
|
|
4
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
-
* COMPETITIVE MOAT FEATURE - Smart Bundle Analysis & Optimization
|
|
6
|
-
* ═══════════════════════════════════════════════════════════════════════════════
|
|
7
|
-
*
|
|
8
|
-
* This engine analyzes JavaScript bundles to find optimization opportunities
|
|
9
|
-
* that most developers miss. It goes beyond basic bundle analysis.
|
|
10
|
-
*
|
|
11
|
-
* Features:
|
|
12
|
-
* - Dead import detection (imported but not used)
|
|
13
|
-
* - Tree-shaking opportunities
|
|
14
|
-
* - Duplicate dependency detection
|
|
15
|
-
* - Bundle size impact analysis
|
|
16
|
-
* - Code splitting recommendations
|
|
17
|
-
* - Dynamic import suggestions
|
|
18
|
-
* - Side-effect analysis
|
|
19
|
-
* - Module graph visualization
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
"use strict";
|
|
23
|
-
|
|
24
|
-
const fs = require("fs");
|
|
25
|
-
const path = require("path");
|
|
26
|
-
|
|
27
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
-
// LARGE PACKAGE DATABASE (common packages and their tree-shakeable alternatives)
|
|
29
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
30
|
-
|
|
31
|
-
const LARGE_PACKAGES = {
|
|
32
|
-
lodash: {
|
|
33
|
-
size: "~70KB",
|
|
34
|
-
alternative: "lodash-es or individual imports",
|
|
35
|
-
treeShakeable: false,
|
|
36
|
-
imports: {
|
|
37
|
-
debounce: { alternative: "lodash/debounce", size: "~1KB" },
|
|
38
|
-
throttle: { alternative: "lodash/throttle", size: "~1KB" },
|
|
39
|
-
cloneDeep: { alternative: "lodash/cloneDeep", size: "~5KB" },
|
|
40
|
-
merge: { alternative: "lodash/merge", size: "~3KB" },
|
|
41
|
-
get: { alternative: "lodash/get", size: "~1KB" },
|
|
42
|
-
set: { alternative: "lodash/set", size: "~2KB" },
|
|
43
|
-
isEqual: { alternative: "lodash/isEqual", size: "~4KB" }
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
moment: {
|
|
47
|
-
size: "~300KB with locales",
|
|
48
|
-
alternative: "date-fns or dayjs",
|
|
49
|
-
treeShakeable: false,
|
|
50
|
-
modernAlternatives: ["date-fns", "dayjs", "luxon"]
|
|
51
|
-
},
|
|
52
|
-
"date-fns": {
|
|
53
|
-
size: "~80KB full",
|
|
54
|
-
treeShakeable: true,
|
|
55
|
-
note: "Import individual functions for best results"
|
|
56
|
-
},
|
|
57
|
-
rxjs: {
|
|
58
|
-
size: "~140KB",
|
|
59
|
-
treeShakeable: true,
|
|
60
|
-
note: "Use deep imports: rxjs/operators"
|
|
61
|
-
},
|
|
62
|
-
ramda: {
|
|
63
|
-
size: "~45KB",
|
|
64
|
-
treeShakeable: false,
|
|
65
|
-
alternative: "Individual imports or ramda-es"
|
|
66
|
-
},
|
|
67
|
-
"core-js": {
|
|
68
|
-
size: "~150KB full",
|
|
69
|
-
note: "Use @babel/preset-env with useBuiltIns for selective polyfills"
|
|
70
|
-
},
|
|
71
|
-
antd: {
|
|
72
|
-
size: "~1MB+ full",
|
|
73
|
-
treeShakeable: true,
|
|
74
|
-
note: "Import specific components: import { Button } from 'antd'"
|
|
75
|
-
},
|
|
76
|
-
"@mui/material": {
|
|
77
|
-
size: "~800KB+ full",
|
|
78
|
-
treeShakeable: true,
|
|
79
|
-
note: "Import specific components"
|
|
80
|
-
},
|
|
81
|
-
"react-icons": {
|
|
82
|
-
size: "~10MB full",
|
|
83
|
-
treeShakeable: true,
|
|
84
|
-
note: "Import from specific icon pack: react-icons/fa"
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
89
|
-
// IMPORT ANALYZER
|
|
90
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
91
|
-
|
|
92
|
-
class ImportAnalyzer {
|
|
93
|
-
constructor(content, filePath) {
|
|
94
|
-
this.content = content;
|
|
95
|
-
this.filePath = filePath;
|
|
96
|
-
this.imports = [];
|
|
97
|
-
this.exports = [];
|
|
98
|
-
this.usages = new Map();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Analyze all imports in the file
|
|
103
|
-
*/
|
|
104
|
-
analyze() {
|
|
105
|
-
this.extractImports();
|
|
106
|
-
this.extractExports();
|
|
107
|
-
this.findUsages();
|
|
108
|
-
return this.getAnalysis();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Extract import statements
|
|
113
|
-
*/
|
|
114
|
-
extractImports() {
|
|
115
|
-
// ES6 named imports: import { a, b } from 'module'
|
|
116
|
-
const namedImportRegex = /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
117
|
-
let match;
|
|
118
|
-
|
|
119
|
-
while ((match = namedImportRegex.exec(this.content)) !== null) {
|
|
120
|
-
const names = match[1].split(",").map(n => {
|
|
121
|
-
const parts = n.trim().split(/\s+as\s+/);
|
|
122
|
-
return {
|
|
123
|
-
imported: parts[0].trim(),
|
|
124
|
-
local: (parts[1] || parts[0]).trim()
|
|
125
|
-
};
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
for (const name of names) {
|
|
129
|
-
this.imports.push({
|
|
130
|
-
type: "named",
|
|
131
|
-
module: match[2],
|
|
132
|
-
imported: name.imported,
|
|
133
|
-
local: name.local,
|
|
134
|
-
line: this.getLineNumber(match.index)
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ES6 default imports: import X from 'module'
|
|
140
|
-
const defaultImportRegex = /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
|
141
|
-
|
|
142
|
-
while ((match = defaultImportRegex.exec(this.content)) !== null) {
|
|
143
|
-
// Skip if it's actually a named import we already caught
|
|
144
|
-
if (this.content.substring(match.index, match.index + 20).includes("{")) {
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
this.imports.push({
|
|
149
|
-
type: "default",
|
|
150
|
-
module: match[2],
|
|
151
|
-
local: match[1],
|
|
152
|
-
line: this.getLineNumber(match.index)
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ES6 namespace imports: import * as X from 'module'
|
|
157
|
-
const namespaceImportRegex = /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g;
|
|
158
|
-
|
|
159
|
-
while ((match = namespaceImportRegex.exec(this.content)) !== null) {
|
|
160
|
-
this.imports.push({
|
|
161
|
-
type: "namespace",
|
|
162
|
-
module: match[2],
|
|
163
|
-
local: match[1],
|
|
164
|
-
line: this.getLineNumber(match.index)
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Side-effect imports: import 'module'
|
|
169
|
-
const sideEffectRegex = /import\s*['"]([^'"]+)['"]\s*;/g;
|
|
170
|
-
|
|
171
|
-
while ((match = sideEffectRegex.exec(this.content)) !== null) {
|
|
172
|
-
this.imports.push({
|
|
173
|
-
type: "sideEffect",
|
|
174
|
-
module: match[1],
|
|
175
|
-
line: this.getLineNumber(match.index)
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Dynamic imports: import('module')
|
|
180
|
-
const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
181
|
-
|
|
182
|
-
while ((match = dynamicImportRegex.exec(this.content)) !== null) {
|
|
183
|
-
this.imports.push({
|
|
184
|
-
type: "dynamic",
|
|
185
|
-
module: match[1],
|
|
186
|
-
line: this.getLineNumber(match.index)
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// CommonJS require: const x = require('module')
|
|
191
|
-
const requireRegex = /(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
192
|
-
|
|
193
|
-
while ((match = requireRegex.exec(this.content)) !== null) {
|
|
194
|
-
if (match[1]) {
|
|
195
|
-
// Destructured require
|
|
196
|
-
const names = match[1].split(",").map(n => n.trim().split(":")[0].trim());
|
|
197
|
-
for (const name of names) {
|
|
198
|
-
this.imports.push({
|
|
199
|
-
type: "commonjs",
|
|
200
|
-
module: match[3],
|
|
201
|
-
local: name,
|
|
202
|
-
line: this.getLineNumber(match.index)
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
} else {
|
|
206
|
-
// Default require
|
|
207
|
-
this.imports.push({
|
|
208
|
-
type: "commonjs",
|
|
209
|
-
module: match[3],
|
|
210
|
-
local: match[2],
|
|
211
|
-
line: this.getLineNumber(match.index)
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Extract export statements
|
|
219
|
-
*/
|
|
220
|
-
extractExports() {
|
|
221
|
-
// Named exports: export { a, b }
|
|
222
|
-
const namedExportRegex = /export\s*\{([^}]+)\}/g;
|
|
223
|
-
let match;
|
|
224
|
-
|
|
225
|
-
while ((match = namedExportRegex.exec(this.content)) !== null) {
|
|
226
|
-
const names = match[1].split(",").map(n => n.trim().split(/\s+as\s+/)[0].trim());
|
|
227
|
-
for (const name of names) {
|
|
228
|
-
this.exports.push({ type: "named", name });
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Export declarations: export const/function/class
|
|
233
|
-
const exportDeclRegex = /export\s+(?:const|let|var|function|class|async\s+function)\s+(\w+)/g;
|
|
234
|
-
|
|
235
|
-
while ((match = exportDeclRegex.exec(this.content)) !== null) {
|
|
236
|
-
this.exports.push({ type: "declaration", name: match[1] });
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Default export
|
|
240
|
-
if (/export\s+default\s/.test(this.content)) {
|
|
241
|
-
this.exports.push({ type: "default" });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Find usages of imported identifiers
|
|
247
|
-
*/
|
|
248
|
-
findUsages() {
|
|
249
|
-
// Remove import/export lines and comments for cleaner usage detection
|
|
250
|
-
const codeOnly = this.content
|
|
251
|
-
.replace(/import\s+.*?from\s*['"][^'"]+['"]\s*;?/g, "")
|
|
252
|
-
.replace(/export\s+.*?;/g, "")
|
|
253
|
-
.replace(/\/\/.*$/gm, "")
|
|
254
|
-
.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
255
|
-
|
|
256
|
-
for (const imp of this.imports) {
|
|
257
|
-
if (imp.local) {
|
|
258
|
-
// Count usages of the local name
|
|
259
|
-
const usageRegex = new RegExp(`\\b${imp.local}\\b`, "g");
|
|
260
|
-
const matches = codeOnly.match(usageRegex) || [];
|
|
261
|
-
this.usages.set(imp.local, matches.length);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Get analysis results
|
|
268
|
-
*/
|
|
269
|
-
getAnalysis() {
|
|
270
|
-
const deadImports = [];
|
|
271
|
-
const namespaceIssues = [];
|
|
272
|
-
const largePackageIssues = [];
|
|
273
|
-
|
|
274
|
-
for (const imp of this.imports) {
|
|
275
|
-
// Check for dead imports
|
|
276
|
-
if (imp.local && imp.type !== "sideEffect") {
|
|
277
|
-
const usage = this.usages.get(imp.local) || 0;
|
|
278
|
-
if (usage === 0) {
|
|
279
|
-
deadImports.push({
|
|
280
|
-
...imp,
|
|
281
|
-
issue: "unused",
|
|
282
|
-
message: `'${imp.local}' is imported but never used`
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Check for namespace imports that could be specific
|
|
288
|
-
if (imp.type === "namespace") {
|
|
289
|
-
const usageCount = this.usages.get(imp.local) || 0;
|
|
290
|
-
if (usageCount < 5) {
|
|
291
|
-
namespaceIssues.push({
|
|
292
|
-
...imp,
|
|
293
|
-
issue: "namespace",
|
|
294
|
-
message: `Namespace import of '${imp.module}' - consider named imports for better tree-shaking`,
|
|
295
|
-
usageCount
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Check for large packages
|
|
301
|
-
const packageName = this.getPackageName(imp.module);
|
|
302
|
-
if (LARGE_PACKAGES[packageName]) {
|
|
303
|
-
const pkgInfo = LARGE_PACKAGES[packageName];
|
|
304
|
-
if (!pkgInfo.treeShakeable) {
|
|
305
|
-
largePackageIssues.push({
|
|
306
|
-
...imp,
|
|
307
|
-
issue: "largePackage",
|
|
308
|
-
package: packageName,
|
|
309
|
-
size: pkgInfo.size,
|
|
310
|
-
alternative: pkgInfo.alternative,
|
|
311
|
-
message: `'${packageName}' (${pkgInfo.size}) is not tree-shakeable`
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
file: this.filePath,
|
|
319
|
-
imports: this.imports,
|
|
320
|
-
exports: this.exports,
|
|
321
|
-
issues: {
|
|
322
|
-
deadImports,
|
|
323
|
-
namespaceIssues,
|
|
324
|
-
largePackageIssues,
|
|
325
|
-
total: deadImports.length + namespaceIssues.length + largePackageIssues.length
|
|
326
|
-
},
|
|
327
|
-
usages: Object.fromEntries(this.usages)
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
/**
|
|
332
|
-
* Get line number from index
|
|
333
|
-
*/
|
|
334
|
-
getLineNumber(index) {
|
|
335
|
-
return this.content.substring(0, index).split("\n").length;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Extract package name from import path
|
|
340
|
-
*/
|
|
341
|
-
getPackageName(importPath) {
|
|
342
|
-
// Handle scoped packages (@org/package)
|
|
343
|
-
if (importPath.startsWith("@")) {
|
|
344
|
-
const parts = importPath.split("/");
|
|
345
|
-
return parts.length >= 2 ? `${parts[0]}/${parts[1]}` : importPath;
|
|
346
|
-
}
|
|
347
|
-
// Regular packages
|
|
348
|
-
return importPath.split("/")[0];
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
353
|
-
// BUNDLE INTELLIGENCE ENGINE
|
|
354
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
355
|
-
|
|
356
|
-
class BundleIntelligenceEngine {
|
|
357
|
-
constructor(options = {}) {
|
|
358
|
-
this.projectRoot = options.projectRoot || process.cwd();
|
|
359
|
-
this.results = [];
|
|
360
|
-
this.moduleGraph = new Map();
|
|
361
|
-
this.packageSizes = new Map();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Analyze a single file
|
|
366
|
-
*/
|
|
367
|
-
analyzeFile(filePath, content = null) {
|
|
368
|
-
const fullPath = path.isAbsolute(filePath)
|
|
369
|
-
? filePath
|
|
370
|
-
: path.join(this.projectRoot, filePath);
|
|
371
|
-
|
|
372
|
-
if (content === null) {
|
|
373
|
-
if (!fs.existsSync(fullPath)) {
|
|
374
|
-
return { file: filePath, error: "File not found" };
|
|
375
|
-
}
|
|
376
|
-
content = fs.readFileSync(fullPath, "utf8");
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const analyzer = new ImportAnalyzer(content, filePath);
|
|
380
|
-
return analyzer.analyze();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Analyze entire project
|
|
385
|
-
*/
|
|
386
|
-
analyzeProject() {
|
|
387
|
-
const files = this.collectFiles();
|
|
388
|
-
this.results = [];
|
|
389
|
-
|
|
390
|
-
for (const file of files) {
|
|
391
|
-
const result = this.analyzeFile(file);
|
|
392
|
-
this.results.push(result);
|
|
393
|
-
|
|
394
|
-
// Build module graph
|
|
395
|
-
this.moduleGraph.set(file, result.imports.map(i => i.module));
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return this.generateReport();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Collect JavaScript/TypeScript files
|
|
403
|
-
*/
|
|
404
|
-
collectFiles(dir = this.projectRoot, collected = []) {
|
|
405
|
-
const extensions = [".js", ".jsx", ".ts", ".tsx", ".mjs"];
|
|
406
|
-
const ignoreDirs = ["node_modules", "dist", "build", ".next", "coverage", ".git"];
|
|
407
|
-
|
|
408
|
-
try {
|
|
409
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
410
|
-
|
|
411
|
-
for (const entry of entries) {
|
|
412
|
-
if (ignoreDirs.includes(entry.name)) continue;
|
|
413
|
-
|
|
414
|
-
const fullPath = path.join(dir, entry.name);
|
|
415
|
-
const relativePath = path.relative(this.projectRoot, fullPath);
|
|
416
|
-
|
|
417
|
-
if (entry.isDirectory()) {
|
|
418
|
-
this.collectFiles(fullPath, collected);
|
|
419
|
-
} else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
420
|
-
collected.push(relativePath);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
} catch {
|
|
424
|
-
// Skip unreadable directories
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
return collected;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Generate comprehensive report
|
|
432
|
-
*/
|
|
433
|
-
generateReport() {
|
|
434
|
-
const allIssues = this.results.flatMap(r => [
|
|
435
|
-
...r.issues.deadImports,
|
|
436
|
-
...r.issues.namespaceIssues,
|
|
437
|
-
...r.issues.largePackageIssues
|
|
438
|
-
]);
|
|
439
|
-
|
|
440
|
-
// Group by issue type
|
|
441
|
-
const byType = {
|
|
442
|
-
unused: allIssues.filter(i => i.issue === "unused"),
|
|
443
|
-
namespace: allIssues.filter(i => i.issue === "namespace"),
|
|
444
|
-
largePackage: allIssues.filter(i => i.issue === "largePackage")
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
// Find duplicate dependencies
|
|
448
|
-
const duplicates = this.findDuplicateDependencies();
|
|
449
|
-
|
|
450
|
-
// Calculate potential savings
|
|
451
|
-
const savings = this.calculatePotentialSavings(byType, duplicates);
|
|
452
|
-
|
|
453
|
-
// Generate code splitting recommendations
|
|
454
|
-
const codeSplitting = this.analyzeCodeSplitting();
|
|
455
|
-
|
|
456
|
-
// Analyze circular dependencies
|
|
457
|
-
const circular = this.findCircularDependencies();
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
timestamp: new Date().toISOString(),
|
|
461
|
-
summary: {
|
|
462
|
-
filesAnalyzed: this.results.length,
|
|
463
|
-
totalIssues: allIssues.length,
|
|
464
|
-
deadImports: byType.unused.length,
|
|
465
|
-
namespaceIssues: byType.namespace.length,
|
|
466
|
-
largePackages: byType.largePackage.length,
|
|
467
|
-
duplicateDependencies: duplicates.length,
|
|
468
|
-
circularDependencies: circular.length,
|
|
469
|
-
potentialSavings: savings
|
|
470
|
-
},
|
|
471
|
-
issues: {
|
|
472
|
-
deadImports: byType.unused.map(i => ({
|
|
473
|
-
file: i.file,
|
|
474
|
-
line: i.line,
|
|
475
|
-
import: i.local,
|
|
476
|
-
from: i.module,
|
|
477
|
-
action: "Remove unused import"
|
|
478
|
-
})),
|
|
479
|
-
namespaceImports: byType.namespace.map(i => ({
|
|
480
|
-
file: i.file,
|
|
481
|
-
line: i.line,
|
|
482
|
-
import: `* as ${i.local}`,
|
|
483
|
-
from: i.module,
|
|
484
|
-
usages: i.usageCount,
|
|
485
|
-
action: "Convert to named imports for tree-shaking"
|
|
486
|
-
})),
|
|
487
|
-
largePackages: this.aggregateLargePackages(byType.largePackage),
|
|
488
|
-
duplicates,
|
|
489
|
-
circular
|
|
490
|
-
},
|
|
491
|
-
recommendations: this.generateRecommendations(byType, duplicates, codeSplitting),
|
|
492
|
-
codeSplitting
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
/**
|
|
497
|
-
* Find duplicate dependencies across files
|
|
498
|
-
*/
|
|
499
|
-
findDuplicateDependencies() {
|
|
500
|
-
const packageVersions = new Map();
|
|
501
|
-
|
|
502
|
-
// Read package.json and package-lock.json
|
|
503
|
-
try {
|
|
504
|
-
const lockPath = path.join(this.projectRoot, "package-lock.json");
|
|
505
|
-
if (fs.existsSync(lockPath)) {
|
|
506
|
-
const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
507
|
-
|
|
508
|
-
// Analyze dependencies for duplicates
|
|
509
|
-
const packages = lock.packages || lock.dependencies || {};
|
|
510
|
-
|
|
511
|
-
for (const [pkgPath, info] of Object.entries(packages)) {
|
|
512
|
-
if (!pkgPath) continue;
|
|
513
|
-
|
|
514
|
-
const name = pkgPath.replace(/^node_modules\//, "").split("/node_modules/").pop();
|
|
515
|
-
if (!name) continue;
|
|
516
|
-
|
|
517
|
-
if (!packageVersions.has(name)) {
|
|
518
|
-
packageVersions.set(name, []);
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
packageVersions.get(name).push({
|
|
522
|
-
version: info.version,
|
|
523
|
-
path: pkgPath
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
} catch {
|
|
528
|
-
// Skip if no lock file
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const duplicates = [];
|
|
532
|
-
for (const [name, versions] of packageVersions) {
|
|
533
|
-
const uniqueVersions = [...new Set(versions.map(v => v.version))];
|
|
534
|
-
if (uniqueVersions.length > 1) {
|
|
535
|
-
duplicates.push({
|
|
536
|
-
package: name,
|
|
537
|
-
versions: uniqueVersions,
|
|
538
|
-
instances: versions.length,
|
|
539
|
-
action: "Dedupe to single version"
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
return duplicates;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Calculate potential bundle size savings
|
|
549
|
-
*/
|
|
550
|
-
calculatePotentialSavings(byType, duplicates) {
|
|
551
|
-
let savings = {
|
|
552
|
-
fromDeadImports: "~0KB",
|
|
553
|
-
fromTreeShaking: "Unknown",
|
|
554
|
-
fromDedupe: "Unknown",
|
|
555
|
-
total: "Unknown"
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
// Estimate savings from large packages
|
|
559
|
-
const largePackageSavings = byType.largePackage.reduce((total, issue) => {
|
|
560
|
-
const size = issue.size?.match(/~?(\d+)/)?.[1];
|
|
561
|
-
return total + (parseInt(size) || 0);
|
|
562
|
-
}, 0);
|
|
563
|
-
|
|
564
|
-
if (largePackageSavings > 0) {
|
|
565
|
-
savings.fromTreeShaking = `~${largePackageSavings}KB (potential)`;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
// Estimate dead import savings (rough)
|
|
569
|
-
if (byType.unused.length > 0) {
|
|
570
|
-
savings.fromDeadImports = `~${byType.unused.length * 0.5}KB (estimated)`;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
return savings;
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
/**
|
|
577
|
-
* Analyze code splitting opportunities
|
|
578
|
-
*/
|
|
579
|
-
analyzeCodeSplitting() {
|
|
580
|
-
const opportunities = [];
|
|
581
|
-
|
|
582
|
-
// Find route-based splitting opportunities
|
|
583
|
-
const routeFiles = this.results.filter(r =>
|
|
584
|
-
r.file.includes("/pages/") ||
|
|
585
|
-
r.file.includes("/routes/") ||
|
|
586
|
-
r.file.includes("/app/")
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
// Find heavy components
|
|
590
|
-
for (const result of this.results) {
|
|
591
|
-
const heavyImports = result.imports.filter(i => {
|
|
592
|
-
const pkg = i.module.split("/")[0];
|
|
593
|
-
return LARGE_PACKAGES[pkg];
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
if (heavyImports.length > 0 && !result.file.includes("layout")) {
|
|
597
|
-
opportunities.push({
|
|
598
|
-
file: result.file,
|
|
599
|
-
type: "heavy_component",
|
|
600
|
-
heavyImports: heavyImports.map(i => i.module),
|
|
601
|
-
recommendation: "Consider lazy loading with React.lazy() or dynamic import"
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
// Find common chunks
|
|
607
|
-
const importCounts = new Map();
|
|
608
|
-
for (const result of this.results) {
|
|
609
|
-
for (const imp of result.imports) {
|
|
610
|
-
const key = imp.module;
|
|
611
|
-
importCounts.set(key, (importCounts.get(key) || 0) + 1);
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
const commonModules = [...importCounts.entries()]
|
|
616
|
-
.filter(([mod, count]) => count > 5 && !mod.startsWith("."))
|
|
617
|
-
.sort((a, b) => b[1] - a[1])
|
|
618
|
-
.slice(0, 10);
|
|
619
|
-
|
|
620
|
-
if (commonModules.length > 0) {
|
|
621
|
-
opportunities.push({
|
|
622
|
-
type: "common_chunk",
|
|
623
|
-
modules: commonModules.map(([mod, count]) => ({ module: mod, usedIn: count })),
|
|
624
|
-
recommendation: "These modules are widely used - good candidates for commons chunk"
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
return opportunities;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Find circular dependencies
|
|
633
|
-
*/
|
|
634
|
-
findCircularDependencies() {
|
|
635
|
-
const circular = [];
|
|
636
|
-
const visited = new Set();
|
|
637
|
-
const stack = new Set();
|
|
638
|
-
|
|
639
|
-
const dfs = (file, path = []) => {
|
|
640
|
-
if (stack.has(file)) {
|
|
641
|
-
// Found circular dependency
|
|
642
|
-
const cycleStart = path.indexOf(file);
|
|
643
|
-
const cycle = [...path.slice(cycleStart), file];
|
|
644
|
-
circular.push(cycle);
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
if (visited.has(file)) return;
|
|
649
|
-
|
|
650
|
-
visited.add(file);
|
|
651
|
-
stack.add(file);
|
|
652
|
-
|
|
653
|
-
const deps = this.moduleGraph.get(file) || [];
|
|
654
|
-
for (const dep of deps) {
|
|
655
|
-
// Only check relative imports
|
|
656
|
-
if (dep.startsWith(".")) {
|
|
657
|
-
const resolved = this.resolveImport(file, dep);
|
|
658
|
-
if (resolved) {
|
|
659
|
-
dfs(resolved, [...path, file]);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
stack.delete(file);
|
|
665
|
-
};
|
|
666
|
-
|
|
667
|
-
for (const file of this.moduleGraph.keys()) {
|
|
668
|
-
dfs(file);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Deduplicate cycles
|
|
672
|
-
const uniqueCycles = [];
|
|
673
|
-
const seen = new Set();
|
|
674
|
-
|
|
675
|
-
for (const cycle of circular) {
|
|
676
|
-
const key = [...cycle].sort().join("->");
|
|
677
|
-
if (!seen.has(key)) {
|
|
678
|
-
seen.add(key);
|
|
679
|
-
uniqueCycles.push({
|
|
680
|
-
files: cycle,
|
|
681
|
-
length: cycle.length
|
|
682
|
-
});
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return uniqueCycles;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* Resolve relative import to file path
|
|
691
|
-
*/
|
|
692
|
-
resolveImport(fromFile, importPath) {
|
|
693
|
-
const extensions = ["", ".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.tsx", "/index.js"];
|
|
694
|
-
const dir = path.dirname(fromFile);
|
|
695
|
-
const basePath = path.join(dir, importPath);
|
|
696
|
-
|
|
697
|
-
for (const ext of extensions) {
|
|
698
|
-
const resolved = basePath + ext;
|
|
699
|
-
if (this.moduleGraph.has(resolved)) {
|
|
700
|
-
return resolved;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
return null;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
* Aggregate large package issues
|
|
709
|
-
*/
|
|
710
|
-
aggregateLargePackages(issues) {
|
|
711
|
-
const byPackage = new Map();
|
|
712
|
-
|
|
713
|
-
for (const issue of issues) {
|
|
714
|
-
if (!byPackage.has(issue.package)) {
|
|
715
|
-
byPackage.set(issue.package, {
|
|
716
|
-
package: issue.package,
|
|
717
|
-
size: issue.size,
|
|
718
|
-
alternative: issue.alternative,
|
|
719
|
-
usedIn: []
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
byPackage.get(issue.package).usedIn.push(issue.file);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
return [...byPackage.values()];
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* Generate actionable recommendations
|
|
730
|
-
*/
|
|
731
|
-
generateRecommendations(byType, duplicates, codeSplitting) {
|
|
732
|
-
const recommendations = [];
|
|
733
|
-
|
|
734
|
-
// Dead import recommendation
|
|
735
|
-
if (byType.unused.length > 0) {
|
|
736
|
-
recommendations.push({
|
|
737
|
-
priority: "high",
|
|
738
|
-
type: "cleanup",
|
|
739
|
-
title: "Remove Dead Imports",
|
|
740
|
-
description: `${byType.unused.length} unused imports found`,
|
|
741
|
-
impact: "Reduces bundle size and improves code clarity",
|
|
742
|
-
action: "Run ESLint with no-unused-vars or use IDE quick-fix"
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
// Tree-shaking recommendation
|
|
747
|
-
if (byType.namespace.length > 0) {
|
|
748
|
-
recommendations.push({
|
|
749
|
-
priority: "high",
|
|
750
|
-
type: "optimization",
|
|
751
|
-
title: "Enable Better Tree-Shaking",
|
|
752
|
-
description: `${byType.namespace.length} namespace imports could be converted`,
|
|
753
|
-
impact: "Enables bundler to remove unused code",
|
|
754
|
-
action: "Convert 'import * as X' to 'import { specific } from'"
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
// Large package recommendations
|
|
759
|
-
if (byType.largePackage.length > 0) {
|
|
760
|
-
const packages = [...new Set(byType.largePackage.map(i => i.package))];
|
|
761
|
-
recommendations.push({
|
|
762
|
-
priority: "high",
|
|
763
|
-
type: "replacement",
|
|
764
|
-
title: "Replace Large Dependencies",
|
|
765
|
-
description: `${packages.length} large packages found: ${packages.join(", ")}`,
|
|
766
|
-
impact: "Could reduce bundle by 100KB+",
|
|
767
|
-
action: "Consider lighter alternatives or selective imports"
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
// Duplicate dependencies
|
|
772
|
-
if (duplicates.length > 0) {
|
|
773
|
-
recommendations.push({
|
|
774
|
-
priority: "medium",
|
|
775
|
-
type: "dedupe",
|
|
776
|
-
title: "Deduplicate Dependencies",
|
|
777
|
-
description: `${duplicates.length} packages have multiple versions`,
|
|
778
|
-
impact: "Reduces node_modules size and potential bundle bloat",
|
|
779
|
-
action: "Run 'npm dedupe' or update version ranges"
|
|
780
|
-
});
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// Code splitting
|
|
784
|
-
const heavyComponents = codeSplitting.filter(c => c.type === "heavy_component");
|
|
785
|
-
if (heavyComponents.length > 0) {
|
|
786
|
-
recommendations.push({
|
|
787
|
-
priority: "medium",
|
|
788
|
-
type: "splitting",
|
|
789
|
-
title: "Implement Code Splitting",
|
|
790
|
-
description: `${heavyComponents.length} components could be lazy loaded`,
|
|
791
|
-
impact: "Faster initial page load",
|
|
792
|
-
action: "Use React.lazy() or dynamic import() for heavy components"
|
|
793
|
-
});
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
return recommendations.sort((a, b) => {
|
|
797
|
-
const priority = { high: 0, medium: 1, low: 2 };
|
|
798
|
-
return priority[a.priority] - priority[b.priority];
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* Get quick fixes for common issues
|
|
804
|
-
*/
|
|
805
|
-
getQuickFixes() {
|
|
806
|
-
const fixes = [];
|
|
807
|
-
|
|
808
|
-
for (const result of this.results) {
|
|
809
|
-
// Dead import fixes
|
|
810
|
-
for (const issue of result.issues.deadImports) {
|
|
811
|
-
fixes.push({
|
|
812
|
-
file: issue.file,
|
|
813
|
-
line: issue.line,
|
|
814
|
-
type: "remove",
|
|
815
|
-
description: `Remove unused import '${issue.local}'`,
|
|
816
|
-
originalImport: issue.imported,
|
|
817
|
-
module: issue.module
|
|
818
|
-
});
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// Namespace import fixes
|
|
822
|
-
for (const issue of result.issues.namespaceIssues) {
|
|
823
|
-
fixes.push({
|
|
824
|
-
file: issue.file,
|
|
825
|
-
line: issue.line,
|
|
826
|
-
type: "refactor",
|
|
827
|
-
description: `Convert namespace import to named imports`,
|
|
828
|
-
original: `import * as ${issue.local} from '${issue.module}'`,
|
|
829
|
-
suggestion: `Identify used exports and import them specifically`
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
return fixes;
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
839
|
-
// EXPORTS
|
|
840
|
-
// ═══════════════════════════════════════════════════════════════════════════════
|
|
841
|
-
|
|
842
|
-
module.exports = {
|
|
843
|
-
BundleIntelligenceEngine,
|
|
844
|
-
ImportAnalyzer,
|
|
845
|
-
LARGE_PACKAGES
|
|
846
|
-
};
|