react-doctor-cli-dev 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/backend/.env +3 -0
- package/backend/dist/index.js +43 -0
- package/backend/dist/middleware/auth.js +16 -0
- package/backend/dist/routes/reports.js +93 -0
- package/backend/package-lock.json +2000 -0
- package/backend/package.json +30 -0
- package/backend/src/db.ts +24 -0
- package/backend/src/index.ts +49 -0
- package/backend/src/middleware/auth.ts +21 -0
- package/backend/src/routes/reports.ts +110 -0
- package/backend/tsconfig.json +12 -0
- package/cli/bin/react-doctor.js +29 -0
- package/cli/dist/commands/analyze.js +125 -0
- package/cli/dist/commands/full.js +366 -0
- package/cli/dist/commands/install.js +138 -0
- package/cli/dist/commands/profile.js +166 -0
- package/cli/dist/index.js +78 -0
- package/cli/dist/ui.js +113 -0
- package/cli/package-lock.json +936 -0
- package/cli/package.json +34 -0
- package/cli/src/commands/analyze.ts +162 -0
- package/cli/src/commands/full.ts +574 -0
- package/cli/src/commands/install.ts +163 -0
- package/cli/src/commands/profile.ts +246 -0
- package/cli/src/index.ts +84 -0
- package/cli/src/ui.ts +120 -0
- package/cli/tsconfig.json +16 -0
- package/core/report-compiler/index.ts +359 -0
- package/core/report-compiler/test-report-compiler.ts +126 -0
- package/core/rule-engine/context-builder.ts +146 -0
- package/core/rule-engine/evaluator.ts +131 -0
- package/core/rule-engine/index.ts +222 -0
- package/core/rule-engine/rules.json +304 -0
- package/core/rule-engine/suggestion-builder.ts +209 -0
- package/core/rule-engine/test-rule-engine.ts +144 -0
- package/core/rule-engine/types.ts +202 -0
- package/core/runtime/profiler/browser.ts +121 -0
- package/core/runtime/profiler/collectors.ts +216 -0
- package/core/runtime/profiler/index.ts +311 -0
- package/core/runtime/profiler/porfiler.ts +967 -0
- package/core/runtime/profiler/route-scanner.ts +76 -0
- package/core/runtime/profiler/score.ts +59 -0
- package/core/runtime/profiler/server.ts +115 -0
- package/core/runtime/profiler/types.ts +65 -0
- package/core/runtime/test-runtime-profiler.ts +226 -0
- package/core/static-ana/static/analyzer.ts +145 -0
- package/core/static-ana/static/ast-parser.ts +31 -0
- package/core/static-ana/static/detectors/console-log.ts +49 -0
- package/core/static-ana/static/detectors/dead-code.ts +51 -0
- package/core/static-ana/static/detectors/effect-loop.ts +45 -0
- package/core/static-ana/static/detectors/index.ts +16 -0
- package/core/static-ana/static/detectors/inline-function.ts +59 -0
- package/core/static-ana/static/detectors/inline-style.ts +52 -0
- package/core/static-ana/static/detectors/large-component.ts +79 -0
- package/core/static-ana/static/detectors/missing-key.ts +56 -0
- package/core/static-ana/static/detectors/missing-memo.ts +59 -0
- package/core/static-ana/static/detectors/prop-drilling.ts +66 -0
- package/core/static-ana/static/helpers.ts +81 -0
- package/core/static-ana/static/scanner.ts +93 -0
- package/core/static-ana/test-analyzer.ts +115 -0
- package/core/static-ana/types.ts +25 -0
- package/core/tests/mock-react-project/src/app.tsx +22 -0
- package/core/tests/mock-react-project/src/components/Button.tsx +9 -0
- package/core/tests/mock-react-project/src/components/Header.tsx +3 -0
- package/core/tests/mock-react-project/src/components/ListTesting.tsx +51 -0
- package/core/tests/mock-react-project/src/components/UserDashboard.tsx +66 -0
- package/core/tests/mock-react-project/src/utils.ts +4 -0
- package/package.json +55 -0
- package/react-doctor-cli-dev-1.0.0.tgz +0 -0
- package/shared/dist/index.d.ts +2 -0
- package/shared/dist/index.js +19 -0
- package/shared/dist/schemas.d.ts +91 -0
- package/shared/dist/schemas.js +82 -0
- package/shared/dist/types.d.ts +44 -0
- package/shared/dist/types.js +2 -0
- package/shared/package-lock.json +47 -0
- package/shared/package.json +21 -0
- package/shared/src/index.ts +4 -0
- package/shared/src/schemas.ts +136 -0
- package/shared/src/types.ts +137 -0
- package/shared/tsconfig.json +15 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { glob } from "glob";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
export interface ScannedFile {
|
|
5
|
+
path: string; // Full absolute path
|
|
6
|
+
relativePath: string; // Relative to project root
|
|
7
|
+
name: string; // Filename only
|
|
8
|
+
extension: string; // .tsx or .jsx
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class FileScanner {
|
|
12
|
+
/**
|
|
13
|
+
* Find all React component files (.jsx, .tsx) in a project
|
|
14
|
+
* @param projectPath - Absolute or relative path to React project root
|
|
15
|
+
* @returns Array of scanned file metadata
|
|
16
|
+
*/
|
|
17
|
+
async findFiles(projectPath: string): Promise<ScannedFile[]> {
|
|
18
|
+
// 1. Ensure the path is absolute and uses forward slashes
|
|
19
|
+
const normalizedPath = path.resolve(projectPath).replace(/\\/g, "/");
|
|
20
|
+
|
|
21
|
+
// 2. Try to scan src/ folder first (standard React location)
|
|
22
|
+
const srcPattern = `${normalizedPath}/src/**/*.{ts,js,jsx,tsx}`;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
// 3. Run the glob search (focusing on src/ folder)
|
|
26
|
+
let files = await glob(srcPattern, {
|
|
27
|
+
ignore: [
|
|
28
|
+
"**/node_modules/**",
|
|
29
|
+
"**/dist/**",
|
|
30
|
+
"**/build/**",
|
|
31
|
+
"**/.next/**",
|
|
32
|
+
"**/coverage/**",
|
|
33
|
+
"**/*.test.{jsx,tsx}", // Skip test files
|
|
34
|
+
"**/*.spec.{jsx,tsx}", // Skip spec files
|
|
35
|
+
"**/*.stories.{jsx,tsx}", // Skip Storybook files
|
|
36
|
+
],
|
|
37
|
+
nodir: true, // We only want files, not folders
|
|
38
|
+
absolute: true, // Return absolute paths
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// 4. If no files found in src/, try scanning entire project as fallback
|
|
42
|
+
if (files.length === 0) {
|
|
43
|
+
console.warn(`⚠️ No files found in src/, scanning entire project...`);
|
|
44
|
+
const rootPattern = `${normalizedPath}/**/*.{ts,js,jsx,tsx}`;
|
|
45
|
+
|
|
46
|
+
files = await glob(rootPattern, {
|
|
47
|
+
ignore: [
|
|
48
|
+
"**/node_modules/**",
|
|
49
|
+
"**/dist/**",
|
|
50
|
+
"**/build/**",
|
|
51
|
+
"**/.next/**",
|
|
52
|
+
"**/coverage/**",
|
|
53
|
+
"**/*.test.{jsx,tsx}",
|
|
54
|
+
"**/*.spec.{jsx,tsx}",
|
|
55
|
+
"**/*.stories.{jsx,tsx}",
|
|
56
|
+
],
|
|
57
|
+
nodir: true,
|
|
58
|
+
absolute: true,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(`✅ Found ${files.length} React component files`);
|
|
63
|
+
|
|
64
|
+
// 5. Map to structured metadata
|
|
65
|
+
return files.map(filePath => ({
|
|
66
|
+
path: filePath,
|
|
67
|
+
relativePath: path.relative(normalizedPath, filePath),
|
|
68
|
+
name: path.basename(filePath),
|
|
69
|
+
extension: path.extname(filePath),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error("❌ Scanner Error:", error);
|
|
74
|
+
throw new Error(`Failed to scan files: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get statistics about scanned files
|
|
80
|
+
*/
|
|
81
|
+
getStats(files: ScannedFile[]): {
|
|
82
|
+
total: number;
|
|
83
|
+
byExtension: Record<string, number>;
|
|
84
|
+
} {
|
|
85
|
+
return {
|
|
86
|
+
total: files.length,
|
|
87
|
+
byExtension: files.reduce((acc, file) => {
|
|
88
|
+
acc[file.extension] = (acc[file.extension] || 0) + 1;
|
|
89
|
+
return acc;
|
|
90
|
+
}, {} as Record<string, number>),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { FileScanner } from './static/scanner';
|
|
2
|
+
import { StaticAnalyzer } from './static/analyzer';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export async function testCompleteAnalysis() {
|
|
6
|
+
console.log("\n" + "=".repeat(70));
|
|
7
|
+
console.log("🩺 React Doctor - Complete Static Analysis Test");
|
|
8
|
+
console.log("=".repeat(70));
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const scanner = new FileScanner();
|
|
12
|
+
const targetDir = path.join(process.cwd(), 'tests', 'mock-react-project');
|
|
13
|
+
|
|
14
|
+
const files = await scanner.findFiles(targetDir);
|
|
15
|
+
|
|
16
|
+
if (files.length === 0) {
|
|
17
|
+
console.log("⚠️ No React files found!");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const analyzer = new StaticAnalyzer();
|
|
22
|
+
const report = await analyzer.analyze(files);
|
|
23
|
+
|
|
24
|
+
// ======================================================================
|
|
25
|
+
// 🏆 NEW: HEALTH GRADE SECTION
|
|
26
|
+
// ======================================================================
|
|
27
|
+
console.log("\n" + "=".repeat(70));
|
|
28
|
+
console.log(`🏆 PROJECT HEALTH GRADE: ${report.grade}`);
|
|
29
|
+
console.log("=".repeat(70));
|
|
30
|
+
console.log(getDoctorRecommendation(report.grade));
|
|
31
|
+
console.log("=".repeat(70));
|
|
32
|
+
|
|
33
|
+
// Display basic stats
|
|
34
|
+
console.log(`\n📊 Analysis Summary:`);
|
|
35
|
+
console.log(`Timestamp: ${new Date(report.timestamp).toLocaleString()}`);
|
|
36
|
+
console.log(`Files analyzed: ${report.filesAnalyzed}`);
|
|
37
|
+
console.log(`Total issues: ${report.issues.length}`);
|
|
38
|
+
|
|
39
|
+
const summary = analyzer.getSummary(report);
|
|
40
|
+
console.log(`\n🎯 Issues by Severity:`);
|
|
41
|
+
console.log(` 🔴 Critical: ${summary.critical}`);
|
|
42
|
+
console.log(` 🟡 Warnings: ${summary.warnings}`);
|
|
43
|
+
console.log(` 🔵 Info: ${summary.info}`);
|
|
44
|
+
|
|
45
|
+
console.log(`\n🔍 Issues by Type:`);
|
|
46
|
+
Object.entries(summary.byDetector).forEach(([type, count]) => {
|
|
47
|
+
console.log(` ${type.padEnd(10)}: ${count}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Display issues with corrected math
|
|
51
|
+
const DISPLAY_LIMIT = 30;
|
|
52
|
+
if (report.issues.length > 0) {
|
|
53
|
+
console.log(`\n📋 Detailed Issues (showing up to ${DISPLAY_LIMIT}):`);
|
|
54
|
+
console.log("-".repeat(70));
|
|
55
|
+
|
|
56
|
+
report.issues.slice(0, DISPLAY_LIMIT).forEach((issue, index) => {
|
|
57
|
+
console.log(`\n${index + 1}. ${issue.file}:${issue.line}:${issue.column || 0}`);
|
|
58
|
+
console.log(` Component: ${issue.component}`);
|
|
59
|
+
console.log(` Severity: ${getSeverityIcon(issue.severity)} ${issue.severity}`);
|
|
60
|
+
console.log(` Message: ${issue.message}`);
|
|
61
|
+
console.log(` 💡 Fix: ${issue.suggestion}`);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Fixed the negative math bug here:
|
|
65
|
+
if (report.issues.length > DISPLAY_LIMIT) {
|
|
66
|
+
console.log(`\n... and ${report.issues.length - DISPLAY_LIMIT} more issue(s)`);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.log(`\n✨ No issues found! Your code is in peak condition!`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log("\n" + "=".repeat(70));
|
|
73
|
+
const reportPath = analyzer.saveReport(report);
|
|
74
|
+
console.log("\n" + "=".repeat(70));
|
|
75
|
+
console.log(`💾 JSON Report saved to: ${path.basename(reportPath)}`);
|
|
76
|
+
console.log("=".repeat(70));
|
|
77
|
+
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("\n❌ Test Failed:");
|
|
80
|
+
console.error((error as Error).message);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function getSeverityIcon(severity: string): string {
|
|
86
|
+
switch (severity) {
|
|
87
|
+
case 'critical': return '🔴';
|
|
88
|
+
case 'warning': return '🟡';
|
|
89
|
+
case 'info': return '🔵';
|
|
90
|
+
default: return '⚪';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 🩺 Provides a clinical recommendation based on the grade
|
|
96
|
+
*/
|
|
97
|
+
function getDoctorRecommendation(grade: string): string {
|
|
98
|
+
switch (grade) {
|
|
99
|
+
case 'A+': case 'A':
|
|
100
|
+
return "✅ DOCTOR'S NOTE: Excellent health. Continue with regular checkups.";
|
|
101
|
+
case 'B':
|
|
102
|
+
return "🟡 DOCTOR'S NOTE: Minor congestion detected. Consider cleaning up warnings.";
|
|
103
|
+
case 'C':
|
|
104
|
+
return "⚠️ DOCTOR'S NOTE: Chronic issues found. Technical debt is accumulating.";
|
|
105
|
+
case 'D':
|
|
106
|
+
return "🟠 DOCTOR'S NOTE: Patient in poor condition. Immediate refactoring advised.";
|
|
107
|
+
case 'F':
|
|
108
|
+
return "🚨 DOCTOR'S NOTE: CRITICAL CONDITION. Fix infinite loops and memory leaks immediately!";
|
|
109
|
+
default:
|
|
110
|
+
return "🩺 DOCTOR'S NOTE: Analysis inconclusive.";
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
testCompleteAnalysis();
|
|
115
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a single issue found in a component
|
|
3
|
+
*/
|
|
4
|
+
export interface ComponentIssue {
|
|
5
|
+
id: string; // Unique identifier (e.g., "large-component-App.tsx-12")
|
|
6
|
+
component: string; // Component name (e.g., "UserDashboard")
|
|
7
|
+
file: string; // File path
|
|
8
|
+
line: number; // Line number where issue was found
|
|
9
|
+
column?: number; // Column number (optional)
|
|
10
|
+
severity: 'critical' | 'warning' | 'info';
|
|
11
|
+
message: string; // What's wrong (e.g., "Component is 450 lines")
|
|
12
|
+
suggestion: string; // How to fix it (e.g., "Split into smaller components")
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Complete static analysis report
|
|
17
|
+
*/
|
|
18
|
+
export interface StaticReport {
|
|
19
|
+
timestamp: string;
|
|
20
|
+
componentCount: number; // Number of components analyzed
|
|
21
|
+
issues: ComponentIssue[];
|
|
22
|
+
filesAnalyzed: number;
|
|
23
|
+
filesFailed: number;
|
|
24
|
+
grade: string;
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
// This component has multiple issues for testing
|
|
4
|
+
export default function App() {
|
|
5
|
+
console.log('App rendered'); // Issue: console.log
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<h1>React Doctor Test App</h1>
|
|
10
|
+
|
|
11
|
+
{/* Issue: Inline function */}
|
|
12
|
+
<button onClick={() => console.log('clicked')}>
|
|
13
|
+
Click Me
|
|
14
|
+
</button>
|
|
15
|
+
|
|
16
|
+
{/* Another inline function */}
|
|
17
|
+
<button onClick={() => alert('test')}>
|
|
18
|
+
Alert
|
|
19
|
+
</button>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
export default function ListTesting() {
|
|
4
|
+
const items = ['Apple', 'Banana', 'Cherry'];
|
|
5
|
+
|
|
6
|
+
// 1. ❌ THE DISEASE: Map Failure
|
|
7
|
+
const renderMap = () => {
|
|
8
|
+
return items.map(item => (
|
|
9
|
+
<div>{item}</div>
|
|
10
|
+
/* ✅ THE CURE:
|
|
11
|
+
<div key={item}>{item}</div>
|
|
12
|
+
*/
|
|
13
|
+
));
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// 2. ❌ THE DISEASE: Literal Array Failure
|
|
17
|
+
const staticList = [
|
|
18
|
+
<span>First Item</span>,
|
|
19
|
+
<span>Second Item</span>,
|
|
20
|
+
/* ✅ THE CURE:
|
|
21
|
+
<span key="first">First Item</span>,
|
|
22
|
+
<span key="second">Second Item</span>,
|
|
23
|
+
*/
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className="list-container">
|
|
28
|
+
{renderMap()}
|
|
29
|
+
|
|
30
|
+
<ul>
|
|
31
|
+
{/* 3. ❌ THE DISEASE: Array.from Failure */}
|
|
32
|
+
{Array.from({ length: 3 }).map((_, i) => (
|
|
33
|
+
<li>Item {i}</li>
|
|
34
|
+
/* ✅ THE CURE:
|
|
35
|
+
<li key={i}>Item {i}</li>
|
|
36
|
+
(Note: Use 'i' only if the list order is static!)
|
|
37
|
+
*/
|
|
38
|
+
))}
|
|
39
|
+
</ul>
|
|
40
|
+
|
|
41
|
+
<div className="static-section">
|
|
42
|
+
{staticList}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
{/* 4. ✨ HEALTHY: Should NOT be flagged by the Doctor */}
|
|
46
|
+
{items.map(item => (
|
|
47
|
+
<p key={item}>{item}</p>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { fetchUser } from './api'; // Imagine this exists
|
|
3
|
+
import { GhostData } from './data'; // DEAD CODE: Unused import
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 1. GRANDPARENT: Has the data
|
|
7
|
+
*/
|
|
8
|
+
export default function UserDashboard({ theme }) {
|
|
9
|
+
const [user, setUser] = useState({ name: 'Shaheen', role: 'Admin' });
|
|
10
|
+
const [ticks, setTicks] = useState(0);
|
|
11
|
+
const unusedSecret = "12345"; // DEAD CODE: Unused variable
|
|
12
|
+
|
|
13
|
+
console.log("Dashboard rendering..."); // CONSOLE LOG
|
|
14
|
+
|
|
15
|
+
// 2. INFINITE LOOP: Missing dependency array
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setTicks(ticks + 1);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<div className="dashboard">
|
|
22
|
+
<h1>User Panel</h1>
|
|
23
|
+
{/* Passing 'user' to Content (Drilling starts here) */}
|
|
24
|
+
<Content user={user} theme={theme} />
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 3. PARENT (The Middleman): PROP DRILLING candidate
|
|
31
|
+
* Receives 'user', doesn't use it, just passes it to Profile.
|
|
32
|
+
*/
|
|
33
|
+
function Content({ user, theme }) {
|
|
34
|
+
return (
|
|
35
|
+
<main
|
|
36
|
+
// 4. LARGE INLINE STYLE: > 5 properties
|
|
37
|
+
style={{
|
|
38
|
+
margin: '20px',
|
|
39
|
+
padding: '10px',
|
|
40
|
+
border: '1px solid black',
|
|
41
|
+
borderRadius: '5px',
|
|
42
|
+
backgroundColor: '#f0f0f0',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
flexDirection: 'column'
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<h2>Main Content</h2>
|
|
48
|
+
<Profile user={user} theme={theme} />
|
|
49
|
+
</main>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 5. CHILD: Finally uses the data
|
|
55
|
+
*/
|
|
56
|
+
function Profile({ user, theme }) {
|
|
57
|
+
return (
|
|
58
|
+
<div className={`profile-${theme}`}>
|
|
59
|
+
<p>Name: {user.name}</p>
|
|
60
|
+
{/* 6. INLINE FUNCTION */}
|
|
61
|
+
<button onClick={() => console.log('Deleted!')}>
|
|
62
|
+
Delete Account
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-doctor-cli-dev",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "React performance analyzer with static analysis, runtime profiling, rule engine, and dashboard upload",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
|
|
8
|
+
"bin": {
|
|
9
|
+
"react-doctor": "./cli/bin/react-doctor.js"
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
"scripts": {},
|
|
13
|
+
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react",
|
|
16
|
+
"performance",
|
|
17
|
+
"profiler",
|
|
18
|
+
"cli",
|
|
19
|
+
"analyzer"
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
"author": "Ozma",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@babel/parser": "^7.29.0",
|
|
27
|
+
"@babel/traverse": "^7.29.0",
|
|
28
|
+
"@babel/types": "^7.29.0",
|
|
29
|
+
"axios": "^1.6.0",
|
|
30
|
+
"better-sqlite3": "^12.10.0",
|
|
31
|
+
"chalk": "^4.1.2",
|
|
32
|
+
"commander": "^12.0.0",
|
|
33
|
+
"cors": "^2.8.6",
|
|
34
|
+
"dotenv": "^17.4.2",
|
|
35
|
+
"express": "^5.2.1",
|
|
36
|
+
"fs-extra": "^11.3.4",
|
|
37
|
+
"glob": "^13.0.6",
|
|
38
|
+
"helmet": "^8.2.0",
|
|
39
|
+
"ora": "^5.4.1",
|
|
40
|
+
"puppeteer-core": "^22.0.0",
|
|
41
|
+
"ts-node": "^10.9.2",
|
|
42
|
+
"typescript": "^5.0.0",
|
|
43
|
+
"web-vitals": "^5.1.0"
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@types/babel__traverse": "^7.28.0",
|
|
48
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
49
|
+
"@types/cors": "^2.8.19",
|
|
50
|
+
"@types/express": "^5.0.6",
|
|
51
|
+
"@types/fs-extra": "^11.0.4",
|
|
52
|
+
"@types/node": "^25.9.1",
|
|
53
|
+
"nodemon": "^3.1.14"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
// Exporting all types in one common used file
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
__exportStar(require("./schemas"), exports);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
|
|
3
|
+
export declare const WebVitalsSchema: v.ObjectSchema<{
|
|
4
|
+
readonly lcp: v.NumberSchema<undefined>;
|
|
5
|
+
readonly fcp: v.NumberSchema<undefined>;
|
|
6
|
+
readonly cls: v.NumberSchema<undefined>;
|
|
7
|
+
readonly inp: v.NumberSchema<undefined>;
|
|
8
|
+
readonly ttfb: v.NumberSchema<undefined>;
|
|
9
|
+
}, undefined>;
|
|
10
|
+
export declare const ComponentIssueSchema: v.ObjectSchema<{
|
|
11
|
+
readonly id: v.StringSchema<undefined>;
|
|
12
|
+
readonly component: v.StringSchema<undefined>;
|
|
13
|
+
readonly file: v.StringSchema<undefined>;
|
|
14
|
+
readonly line: v.NumberSchema<undefined>;
|
|
15
|
+
readonly severity: v.PicklistSchema<["critical", "warning", "info"], undefined>;
|
|
16
|
+
readonly message: v.StringSchema<undefined>;
|
|
17
|
+
readonly suggestion: v.StringSchema<undefined>;
|
|
18
|
+
}, undefined>;
|
|
19
|
+
export declare const StaticReportSchema: v.ObjectSchema<{
|
|
20
|
+
readonly timestamp: v.StringSchema<undefined>;
|
|
21
|
+
readonly issues: v.ArraySchema<v.ObjectSchema<{
|
|
22
|
+
readonly id: v.StringSchema<undefined>;
|
|
23
|
+
readonly component: v.StringSchema<undefined>;
|
|
24
|
+
readonly file: v.StringSchema<undefined>;
|
|
25
|
+
readonly line: v.NumberSchema<undefined>;
|
|
26
|
+
readonly severity: v.PicklistSchema<["critical", "warning", "info"], undefined>;
|
|
27
|
+
readonly message: v.StringSchema<undefined>;
|
|
28
|
+
readonly suggestion: v.StringSchema<undefined>;
|
|
29
|
+
}, undefined>, undefined>;
|
|
30
|
+
readonly bundleSize: v.NumberSchema<undefined>;
|
|
31
|
+
readonly componentCount: v.NumberSchema<undefined>;
|
|
32
|
+
}, undefined>;
|
|
33
|
+
export declare const RuntimeReportSchema: v.ObjectSchema<{
|
|
34
|
+
readonly timestamp: v.StringSchema<undefined>;
|
|
35
|
+
readonly metrics: v.ObjectSchema<{
|
|
36
|
+
readonly lcp: v.NumberSchema<undefined>;
|
|
37
|
+
readonly fcp: v.NumberSchema<undefined>;
|
|
38
|
+
readonly cls: v.NumberSchema<undefined>;
|
|
39
|
+
readonly inp: v.NumberSchema<undefined>;
|
|
40
|
+
readonly ttfb: v.NumberSchema<undefined>;
|
|
41
|
+
}, undefined>;
|
|
42
|
+
readonly rerenders: v.RecordSchema<v.StringSchema<undefined>, v.NumberSchema<undefined>, undefined>;
|
|
43
|
+
readonly commitDurations: v.ArraySchema<v.NumberSchema<undefined>, undefined>;
|
|
44
|
+
}, undefined>;
|
|
45
|
+
export declare const SuggestionSchema: v.ObjectSchema<{
|
|
46
|
+
readonly id: v.StringSchema<undefined>;
|
|
47
|
+
readonly title: v.StringSchema<undefined>;
|
|
48
|
+
readonly description: v.StringSchema<undefined>;
|
|
49
|
+
readonly severity: v.PicklistSchema<["critical", "warning", "info"], undefined>;
|
|
50
|
+
readonly affectedComponent: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
51
|
+
readonly fix: v.StringSchema<undefined>;
|
|
52
|
+
}, undefined>;
|
|
53
|
+
export declare const FinalReportSchema: v.ObjectSchema<{
|
|
54
|
+
readonly projectName: v.StringSchema<undefined>;
|
|
55
|
+
readonly analyzedAt: v.StringSchema<undefined>;
|
|
56
|
+
readonly static: v.ObjectSchema<{
|
|
57
|
+
readonly timestamp: v.StringSchema<undefined>;
|
|
58
|
+
readonly issues: v.ArraySchema<v.ObjectSchema<{
|
|
59
|
+
readonly id: v.StringSchema<undefined>;
|
|
60
|
+
readonly component: v.StringSchema<undefined>;
|
|
61
|
+
readonly file: v.StringSchema<undefined>;
|
|
62
|
+
readonly line: v.NumberSchema<undefined>;
|
|
63
|
+
readonly severity: v.PicklistSchema<["critical", "warning", "info"], undefined>;
|
|
64
|
+
readonly message: v.StringSchema<undefined>;
|
|
65
|
+
readonly suggestion: v.StringSchema<undefined>;
|
|
66
|
+
}, undefined>, undefined>;
|
|
67
|
+
readonly bundleSize: v.NumberSchema<undefined>;
|
|
68
|
+
readonly componentCount: v.NumberSchema<undefined>;
|
|
69
|
+
}, undefined>;
|
|
70
|
+
readonly runtime: v.ObjectSchema<{
|
|
71
|
+
readonly timestamp: v.StringSchema<undefined>;
|
|
72
|
+
readonly metrics: v.ObjectSchema<{
|
|
73
|
+
readonly lcp: v.NumberSchema<undefined>;
|
|
74
|
+
readonly fcp: v.NumberSchema<undefined>;
|
|
75
|
+
readonly cls: v.NumberSchema<undefined>;
|
|
76
|
+
readonly inp: v.NumberSchema<undefined>;
|
|
77
|
+
readonly ttfb: v.NumberSchema<undefined>;
|
|
78
|
+
}, undefined>;
|
|
79
|
+
readonly rerenders: v.RecordSchema<v.StringSchema<undefined>, v.NumberSchema<undefined>, undefined>;
|
|
80
|
+
readonly commitDurations: v.ArraySchema<v.NumberSchema<undefined>, undefined>;
|
|
81
|
+
}, undefined>;
|
|
82
|
+
readonly suggestions: v.ArraySchema<v.ObjectSchema<{
|
|
83
|
+
readonly id: v.StringSchema<undefined>;
|
|
84
|
+
readonly title: v.StringSchema<undefined>;
|
|
85
|
+
readonly description: v.StringSchema<undefined>;
|
|
86
|
+
readonly severity: v.PicklistSchema<["critical", "warning", "info"], undefined>;
|
|
87
|
+
readonly affectedComponent: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
88
|
+
readonly fix: v.StringSchema<undefined>;
|
|
89
|
+
}, undefined>, undefined>;
|
|
90
|
+
readonly performanceScore: v.SchemaWithPipe<readonly [v.NumberSchema<undefined>, v.MinValueAction<number, 0, undefined>, v.MaxValueAction<number, 100, undefined>]>;
|
|
91
|
+
}, undefined>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FinalReportSchema = exports.SuggestionSchema = exports.RuntimeReportSchema = exports.StaticReportSchema = exports.ComponentIssueSchema = exports.WebVitalsSchema = void 0;
|
|
37
|
+
const v = __importStar(require("valibot"));
|
|
38
|
+
// Valibot schemas for validation
|
|
39
|
+
exports.WebVitalsSchema = v.object({
|
|
40
|
+
lcp: v.number(),
|
|
41
|
+
fcp: v.number(),
|
|
42
|
+
cls: v.number(),
|
|
43
|
+
inp: v.number(),
|
|
44
|
+
ttfb: v.number(),
|
|
45
|
+
});
|
|
46
|
+
exports.ComponentIssueSchema = v.object({
|
|
47
|
+
id: v.string(),
|
|
48
|
+
component: v.string(),
|
|
49
|
+
file: v.string(),
|
|
50
|
+
line: v.number(),
|
|
51
|
+
severity: v.picklist(['critical', 'warning', 'info']),
|
|
52
|
+
message: v.string(),
|
|
53
|
+
suggestion: v.string(),
|
|
54
|
+
});
|
|
55
|
+
exports.StaticReportSchema = v.object({
|
|
56
|
+
timestamp: v.string(),
|
|
57
|
+
issues: v.array(exports.ComponentIssueSchema),
|
|
58
|
+
bundleSize: v.number(),
|
|
59
|
+
componentCount: v.number(),
|
|
60
|
+
});
|
|
61
|
+
exports.RuntimeReportSchema = v.object({
|
|
62
|
+
timestamp: v.string(),
|
|
63
|
+
metrics: exports.WebVitalsSchema,
|
|
64
|
+
rerenders: v.record(v.string(), v.number()),
|
|
65
|
+
commitDurations: v.array(v.number()),
|
|
66
|
+
});
|
|
67
|
+
exports.SuggestionSchema = v.object({
|
|
68
|
+
id: v.string(),
|
|
69
|
+
title: v.string(),
|
|
70
|
+
description: v.string(),
|
|
71
|
+
severity: v.picklist(['critical', 'warning', 'info']),
|
|
72
|
+
affectedComponent: v.optional(v.string()),
|
|
73
|
+
fix: v.string(),
|
|
74
|
+
});
|
|
75
|
+
exports.FinalReportSchema = v.object({
|
|
76
|
+
projectName: v.string(),
|
|
77
|
+
analyzedAt: v.string(),
|
|
78
|
+
static: exports.StaticReportSchema,
|
|
79
|
+
runtime: exports.RuntimeReportSchema,
|
|
80
|
+
suggestions: v.array(exports.SuggestionSchema),
|
|
81
|
+
performanceScore: v.pipe(v.number(), v.minValue(0), v.maxValue(100)),
|
|
82
|
+
});
|