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,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "nodemon --exec ts-node src/index.ts",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"start": "node dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"better-sqlite3": "^12.10.0",
|
|
16
|
+
"cors": "^2.8.6",
|
|
17
|
+
"dotenv": "^17.4.2",
|
|
18
|
+
"express": "^5.2.1",
|
|
19
|
+
"helmet": "^8.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
23
|
+
"@types/cors": "^2.8.19",
|
|
24
|
+
"@types/express": "^5.0.6",
|
|
25
|
+
"@types/node": "^25.9.1",
|
|
26
|
+
"nodemon": "^3.1.14",
|
|
27
|
+
"ts-node": "^10.9.2",
|
|
28
|
+
"typescript": "^6.0.3"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import Database from "better-sqlite3";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import dotenv from "dotenv";
|
|
6
|
+
|
|
7
|
+
dotenv.config();
|
|
8
|
+
|
|
9
|
+
const dbPath = process.env.DB_PATH || "./data/reports.db";
|
|
10
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
11
|
+
const db = new Database(dbPath);
|
|
12
|
+
db.exec(`
|
|
13
|
+
CREATE TABLE IF NOT EXISTS reports (
|
|
14
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
15
|
+
project TEXT NOT NULL,
|
|
16
|
+
score INTEGER NOT NULL,
|
|
17
|
+
grade TEXT NOT NULL,
|
|
18
|
+
analyzed_at TEXT NOT NULL,
|
|
19
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
20
|
+
payload TEXT NOT NULL
|
|
21
|
+
);
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
export default db;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import express, { Request, Response, NextFunction } from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import helmet from 'helmet';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import reportRoutes from './routes/reports';
|
|
6
|
+
import db from './db';
|
|
7
|
+
|
|
8
|
+
dotenv.config();
|
|
9
|
+
|
|
10
|
+
// Debug: Check if API_KEY is loaded
|
|
11
|
+
console.log(`[Debug] API_KEY loaded: ${process.env.API_KEY ? '✓' : '✗'}`);
|
|
12
|
+
console.log(`[Debug] API_KEY value: ${process.env.API_KEY || 'NOT SET'}`);
|
|
13
|
+
|
|
14
|
+
const app = express();
|
|
15
|
+
const PORT = process.env.PORT || 3000;
|
|
16
|
+
|
|
17
|
+
// ─── SECURITY AND PARSING MIDDLEWARE ──────────────────────────────────────────
|
|
18
|
+
app.use(helmet()); // adds security headers
|
|
19
|
+
app.use(cors()); // allows dashboard to call the API
|
|
20
|
+
app.use(express.json({ limit: '50mb' })); // reports can be large (screenshots)
|
|
21
|
+
|
|
22
|
+
// ─── ROUTES ───────────────────────────────────────────────────────────────────
|
|
23
|
+
// Use only ONE path (plural is REST convention)
|
|
24
|
+
app.use('/api/reports', reportRoutes);
|
|
25
|
+
|
|
26
|
+
// ─── HEALTH CHECK - NO AUTH, USED TO VERIFY SERVER IS UP ──────────────────────
|
|
27
|
+
app.get('/health', (_req: Request, res: Response) => {
|
|
28
|
+
res.json({
|
|
29
|
+
status: 'ok',
|
|
30
|
+
timestamp: new Date().toISOString()
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
// ─── 404 HANDLER ──────────────────────────────────────────────────────────────
|
|
36
|
+
app.use((req: Request, res: Response) => {
|
|
37
|
+
res.status(404).json({ message: 'Route not found' });
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// ─── GLOBAL ERROR HANDLER ─────────────────────────────────────────────────────
|
|
41
|
+
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
|
|
42
|
+
console.error(err.stack);
|
|
43
|
+
res.status(500).json({ message: 'Internal Server Error' });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// ─── START THE SERVER ─────────────────────────────────────────────────────────
|
|
47
|
+
app.listen(PORT, () => {
|
|
48
|
+
console.log(`React Doctor backend running on http://localhost:${PORT}`);
|
|
49
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
|
|
3
|
+
export function requireApiKey(
|
|
4
|
+
req: Request,
|
|
5
|
+
res: Response,
|
|
6
|
+
next: NextFunction
|
|
7
|
+
): void {
|
|
8
|
+
const keyHeader = req.headers["x-api-key"];
|
|
9
|
+
const key = Array.isArray(keyHeader) ? keyHeader[0] : keyHeader;
|
|
10
|
+
const expectedKey = process.env.API_KEY?.trim();
|
|
11
|
+
|
|
12
|
+
// Debug log
|
|
13
|
+
console.log(`[Auth] Received: "${key}", Expected: "${expectedKey}"`);
|
|
14
|
+
|
|
15
|
+
if (!key || key.trim() !== expectedKey) {
|
|
16
|
+
console.warn(`[Auth] API key mismatch!`);
|
|
17
|
+
res.status(401).json({ error: "Unauthorized — invalid or missing API key" });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
next();
|
|
21
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
import { Router, Request, Response, RequestHandler } from "express";
|
|
3
|
+
import db from "../db";
|
|
4
|
+
import { requireApiKey } from "../middleware/auth";
|
|
5
|
+
|
|
6
|
+
const router = Router();
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
router.get("/", (req: Request, res: Response) => {
|
|
10
|
+
try {
|
|
11
|
+
const rows = db.prepare(`
|
|
12
|
+
SELECT id, project, score, grade, analyzed_at, created_at
|
|
13
|
+
FROM reports
|
|
14
|
+
ORDER BY created_at DESC
|
|
15
|
+
LIMIT 50
|
|
16
|
+
`).all() as any[];
|
|
17
|
+
|
|
18
|
+
res.json({ count: rows.length, reports: rows });
|
|
19
|
+
} catch (err: any) {
|
|
20
|
+
res.status(500).json({ error: "Internal server error" });
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
router.get("/project/:name", (req: Request, res: Response) => {
|
|
26
|
+
try {
|
|
27
|
+
const rows = db.prepare(`
|
|
28
|
+
SELECT id, project, score, grade, analyzed_at, created_at
|
|
29
|
+
FROM reports
|
|
30
|
+
WHERE project = ?
|
|
31
|
+
ORDER BY created_at DESC
|
|
32
|
+
`).all(req.params.name) as any[];
|
|
33
|
+
|
|
34
|
+
res.json({
|
|
35
|
+
project: req.params.name,
|
|
36
|
+
count: rows.length,
|
|
37
|
+
reports: rows,
|
|
38
|
+
});
|
|
39
|
+
} catch (err: any) {
|
|
40
|
+
res.status(500).json({ error: "Internal server error" });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// ==========================================
|
|
45
|
+
// Endpoint 1: POST /api/report/upload
|
|
46
|
+
// يستقبل التقرير من الـ CLI ويتحقق منه ثم يحفظه
|
|
47
|
+
// ==========================================
|
|
48
|
+
router.post("/upload", requireApiKey as RequestHandler, (req: Request, res: Response) => {
|
|
49
|
+
try {
|
|
50
|
+
const report = req.body;
|
|
51
|
+
|
|
52
|
+
// التحقق من وجود الحقول الإلزامية الثلاثة
|
|
53
|
+
if (!report || !report.projectName || !report.analyzedAt || report.performanceScore === undefined) {
|
|
54
|
+
res.status(400).json({ error: "Invalid report — missing required fields" });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const grade = report.static?.grade ?? "N/A";
|
|
59
|
+
|
|
60
|
+
// تجهيز استعلام الإدخال لـ SQLite
|
|
61
|
+
const stmt = db.prepare(`
|
|
62
|
+
INSERT INTO reports (project, score, grade, analyzed_at, payload)
|
|
63
|
+
VALUES (?, ?, ?, ?, ?)
|
|
64
|
+
`);
|
|
65
|
+
|
|
66
|
+
// تنفيذ الاستعلام وحفظ جسم التقرير كـ string
|
|
67
|
+
const result = stmt.run(
|
|
68
|
+
report.projectName,
|
|
69
|
+
report.performanceScore,
|
|
70
|
+
grade,
|
|
71
|
+
report.analyzedAt,
|
|
72
|
+
JSON.stringify(report)
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
res.status(201).json({
|
|
76
|
+
message: "Report saved successfully",
|
|
77
|
+
id: result.lastInsertRowid,
|
|
78
|
+
});
|
|
79
|
+
} catch (err: any) {
|
|
80
|
+
console.error("Upload error:", err.message);
|
|
81
|
+
res.status(500).json({ error: "Internal server error" });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
router.get("/:id", (req: Request, res: Response) => {
|
|
86
|
+
try {
|
|
87
|
+
const row = db.prepare(
|
|
88
|
+
"SELECT * FROM reports WHERE id = ?"
|
|
89
|
+
).get(req.params.id) as any;
|
|
90
|
+
|
|
91
|
+
if (!row) {
|
|
92
|
+
res.status(404).json({ error: "Report not found" });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
res.json({
|
|
97
|
+
id: row.id,
|
|
98
|
+
project: row.project,
|
|
99
|
+
score: row.score,
|
|
100
|
+
grade: row.grade,
|
|
101
|
+
analyzedAt: row.analyzed_at,
|
|
102
|
+
createdAt: row.created_at,
|
|
103
|
+
report: JSON.parse(row.payload),
|
|
104
|
+
});
|
|
105
|
+
} catch (err: any) {
|
|
106
|
+
res.status(500).json({ error: "Internal server error" });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
export default router;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const [major] = process.versions.node.split(".").map(Number);
|
|
6
|
+
if (major < 18) {
|
|
7
|
+
console.error(
|
|
8
|
+
"\n ❌ React Doctor requires Node.js 18 or higher.\n" +
|
|
9
|
+
` You are running Node.js ${process.versions.node}.\n` +
|
|
10
|
+
" Please upgrade: https://nodejs.org\n"
|
|
11
|
+
);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
require(
|
|
16
|
+
require.resolve("ts-node", { paths: [__dirname] })
|
|
17
|
+
).register({
|
|
18
|
+
transpileOnly: true,
|
|
19
|
+
skipIgnore: true,
|
|
20
|
+
compilerOptions: {
|
|
21
|
+
target: "ES2020",
|
|
22
|
+
lib: ["ES2020"],
|
|
23
|
+
module: "commonjs",
|
|
24
|
+
esModuleInterop: true,
|
|
25
|
+
allowSyntheticDefaultImports: true,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
require("../dist/index.js");
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─────────────────────────────────────────────────────────────
|
|
3
|
+
// cli/src/commands/analyze.ts
|
|
4
|
+
//
|
|
5
|
+
// react-doctor analyze <projectPath>
|
|
6
|
+
//
|
|
7
|
+
// Runs the Static Analyzer only — no browser needed, fast.
|
|
8
|
+
// Reads JSX/TSX source files, runs all 9 detectors, and
|
|
9
|
+
// saves staticreport.json to .react-doctor/
|
|
10
|
+
//
|
|
11
|
+
// Use this when:
|
|
12
|
+
// - You want a quick code quality check
|
|
13
|
+
// - You don't have Chrome installed
|
|
14
|
+
// - You only care about code patterns, not runtime metrics
|
|
15
|
+
//
|
|
16
|
+
// Use "react-doctor full" when you want everything.
|
|
17
|
+
// ─────────────────────────────────────────────────────────────
|
|
18
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.registerAnalyzeCommand = registerAnalyzeCommand;
|
|
23
|
+
const path_1 = __importDefault(require("path"));
|
|
24
|
+
const fs_1 = __importDefault(require("fs"));
|
|
25
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
26
|
+
const ui_1 = require("../ui");
|
|
27
|
+
const full_1 = require("./full");
|
|
28
|
+
function getCoreModule(relativePath) {
|
|
29
|
+
return require(path_1.default.resolve(__dirname, "..", "..", "..", "core", relativePath));
|
|
30
|
+
}
|
|
31
|
+
// ─────────────────────────────────────────────────────────────
|
|
32
|
+
// REGISTER COMMAND
|
|
33
|
+
// ─────────────────────────────────────────────────────────────
|
|
34
|
+
function registerAnalyzeCommand(program) {
|
|
35
|
+
program
|
|
36
|
+
.command("analyze")
|
|
37
|
+
.description("Run static code analysis only (no browser required)")
|
|
38
|
+
.argument("[projectPath]", "Path to the React project (defaults to current directory)", process.cwd())
|
|
39
|
+
.option("--full", "After static analysis, also run the runtime profiler and rule engine", false)
|
|
40
|
+
.option("--no-banner", "Skip the banner")
|
|
41
|
+
.action(async (projectPath, options) => {
|
|
42
|
+
// If --full flag is passed, delegate to the full command
|
|
43
|
+
// which runs the complete pipeline
|
|
44
|
+
if (options.full) {
|
|
45
|
+
await (0, full_1.runFullCommand)(projectPath, { noBanner: options.noBanner });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const resolvedPath = path_1.default.resolve(projectPath);
|
|
49
|
+
if (!options.noBanner)
|
|
50
|
+
(0, ui_1.printBanner)();
|
|
51
|
+
// ── Validate project ────────────────────────────────────
|
|
52
|
+
if (!fs_1.default.existsSync(path_1.default.join(resolvedPath, "package.json"))) {
|
|
53
|
+
(0, ui_1.printFail)(`No package.json found at: ${resolvedPath}\n\n` +
|
|
54
|
+
` Pass the path to your React project:\n` +
|
|
55
|
+
` react-doctor analyze ./my-react-app`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
(0, ui_1.printSection)("Static Analysis");
|
|
59
|
+
(0, ui_1.printInfo)("Project", resolvedPath);
|
|
60
|
+
console.log();
|
|
61
|
+
// ── Run the static analyzer ─────────────────────────────
|
|
62
|
+
const spin = (0, ui_1.spinner)("Scanning JSX/TSX source files...");
|
|
63
|
+
try {
|
|
64
|
+
const { FileScanner } = getCoreModule("static-ana/static/scanner");
|
|
65
|
+
const { StaticAnalyzer } = getCoreModule("static-ana/static/analyzer");
|
|
66
|
+
const scanner = new FileScanner();
|
|
67
|
+
const analyzer = new StaticAnalyzer();
|
|
68
|
+
const files = await scanner.findFiles(resolvedPath);
|
|
69
|
+
spin.text = ` Analyzing ${files.length} file(s)...`;
|
|
70
|
+
const report = await analyzer.analyze(files);
|
|
71
|
+
// Save to .react-doctor/
|
|
72
|
+
const outputDir = path_1.default.join(resolvedPath, ".react-doctor");
|
|
73
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
74
|
+
fs_1.default.writeFileSync(path_1.default.join(outputDir, "staticreport.json"), JSON.stringify(report, null, 2));
|
|
75
|
+
spin.succeed(chalk_1.default.green(`Analysis complete — ${files.length} file(s) scanned`));
|
|
76
|
+
// ── Results summary ─────────────────────────────────────
|
|
77
|
+
(0, ui_1.printSection)("Results");
|
|
78
|
+
const total = report.issues?.length ?? 0;
|
|
79
|
+
const critical = report.issues?.filter((i) => i.severity === "critical").length ?? 0;
|
|
80
|
+
const warnings = report.issues?.filter((i) => i.severity === "warning").length ?? 0;
|
|
81
|
+
const infos = report.issues?.filter((i) => i.severity === "info").length ?? 0;
|
|
82
|
+
(0, ui_1.printResult)("Files analyzed", String(report.filesAnalyzed ?? 0), "info");
|
|
83
|
+
(0, ui_1.printResult)("Components", String(report.componentCount ?? 0), "info");
|
|
84
|
+
(0, ui_1.printResult)("Total issues", String(total), total > 0 ? "warn" : "good");
|
|
85
|
+
(0, ui_1.printResult)("Critical", String(critical), critical > 0 ? "poor" : "good");
|
|
86
|
+
(0, ui_1.printResult)("Warnings", String(warnings), warnings > 0 ? "warn" : "good");
|
|
87
|
+
(0, ui_1.printResult)("Info", String(infos), "info");
|
|
88
|
+
(0, ui_1.printResult)("Health grade", report.grade ?? "N/A", "info");
|
|
89
|
+
// ── Detailed issues (top 10) ────────────────────────────
|
|
90
|
+
if (total > 0) {
|
|
91
|
+
(0, ui_1.printSection)("Issues Found");
|
|
92
|
+
const sorted = [...(report.issues ?? [])].sort((a, b) => {
|
|
93
|
+
const order = { critical: 0, warning: 1, info: 2 };
|
|
94
|
+
return (order[a.severity] ?? 3) - (order[b.severity] ?? 3);
|
|
95
|
+
});
|
|
96
|
+
sorted.slice(0, 10).forEach((issue) => {
|
|
97
|
+
const icon = (0, ui_1.severityIcon)(issue.severity);
|
|
98
|
+
console.log(` ${icon} ${chalk_1.default.bold(issue.component ?? "Unknown")}`);
|
|
99
|
+
console.log(` ${chalk_1.default.gray(issue.file + ":" + issue.line)}`);
|
|
100
|
+
console.log(` ${issue.message}`);
|
|
101
|
+
console.log(` ${chalk_1.default.cyan("→")} ${issue.suggestion}`);
|
|
102
|
+
console.log();
|
|
103
|
+
});
|
|
104
|
+
if (total > 10) {
|
|
105
|
+
console.log(chalk_1.default.gray(` ... and ${total - 10} more. See the full report:\n`));
|
|
106
|
+
console.log(chalk_1.default.cyan(` ${path_1.default.join(resolvedPath, ".react-doctor", "staticreport.json")}\n`));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(chalk_1.default.green(" ✅ No issues found — your code looks great!\n"));
|
|
111
|
+
}
|
|
112
|
+
(0, ui_1.printResult)("Report saved", path_1.default.join(resolvedPath, ".react-doctor", "staticreport.json"), "info");
|
|
113
|
+
console.log();
|
|
114
|
+
(0, ui_1.printDone)("Static analysis finished.");
|
|
115
|
+
console.log(chalk_1.default.gray(" Tip: run ") +
|
|
116
|
+
chalk_1.default.cyan("react-doctor full ./") +
|
|
117
|
+
chalk_1.default.gray(" to also measure runtime performance.\n"));
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
spin.fail(chalk_1.default.red("Static analysis failed"));
|
|
121
|
+
console.log(chalk_1.default.red(`\n ${err.message}\n`));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|