react-doctor-cli-dev 1.0.3 → 1.0.5
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 +0 -1
- package/backend/data/reports.db +0 -0
- package/backend/data/screenshots/1--fcp.png +0 -0
- package/backend/data/screenshots/1--fullLoad.png +0 -0
- package/backend/data/screenshots/2--fcp.png +0 -0
- package/backend/data/screenshots/2--fullLoad.png +0 -0
- package/backend/data/screenshots/3--fcp.png +0 -0
- package/backend/data/screenshots/3--fullLoad.png +0 -0
- package/backend/data/screenshots/4--fcp.png +0 -0
- package/backend/data/screenshots/4--fullLoad.png +0 -0
- package/backend/dist/db.js +13 -56
- package/backend/dist/index.js +13 -20
- package/backend/dist/routes/reports.js +3 -35
- package/backend/src/db.ts +16 -61
- package/backend/src/index.ts +25 -32
- package/backend/src/routes/reports.ts +3 -1
- package/package.json +1 -1
package/backend/.env
CHANGED
package/backend/data/reports.db
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/backend/dist/db.js
CHANGED
|
@@ -1,69 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// ─────────────────────────────────────────────────────────────
|
|
3
|
-
// backend/src/db.ts
|
|
4
|
-
//
|
|
5
|
-
// SQLite database via better-sqlite3 (synchronous, fast).
|
|
6
|
-
//
|
|
7
|
-
// SCHEMA:
|
|
8
|
-
//
|
|
9
|
-
// reports — one row per analysis run
|
|
10
|
-
// id — auto-increment PK
|
|
11
|
-
// project — project name / hostname
|
|
12
|
-
// score — 0-100 overall performance score
|
|
13
|
-
// grade — A+, A, B, C, D, F
|
|
14
|
-
// analyzed_at — ISO timestamp from the CLI
|
|
15
|
-
// created_at — when the row was inserted
|
|
16
|
-
// static_json — full StaticReport as JSON string
|
|
17
|
-
// runtime_json — full RuntimeReport map as JSON string
|
|
18
|
-
// (screenshots stripped — stored as files)
|
|
19
|
-
// suggestions — full Suggestion[] as JSON string
|
|
20
|
-
//
|
|
21
|
-
// WHY SPLIT COLUMNS INSTEAD OF ONE payload COLUMN?
|
|
22
|
-
// The old schema stored everything in a single `payload` TEXT
|
|
23
|
-
// column. This meant the dashboard couldn't query individual
|
|
24
|
-
// fields without parsing the whole blob. Splitting into three
|
|
25
|
-
// typed columns lets us:
|
|
26
|
-
// • SELECT only the part we need (e.g. just static_json for
|
|
27
|
-
// the issues page)
|
|
28
|
-
// • Keep the list endpoint fast (no giant blobs per row)
|
|
29
|
-
// • Add indices on score/grade later without changing schema
|
|
30
|
-
//
|
|
31
|
-
// SCREENSHOT STORAGE:
|
|
32
|
-
// Screenshots are base64 PNGs — up to 200KB each. Storing them
|
|
33
|
-
// in SQLite bloats the DB and makes every query slower. Instead:
|
|
34
|
-
// • The upload route strips dataUrls from runtime_json
|
|
35
|
-
// • Saves each screenshot as a .png file in data/screenshots/
|
|
36
|
-
// • Replaces dataUrl with a relative path /screenshots/<file>
|
|
37
|
-
// The dashboard fetches screenshots via the static file route.
|
|
38
|
-
// ─────────────────────────────────────────────────────────────
|
|
39
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
4
|
};
|
|
42
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
-
exports.screenshotsDir = void 0;
|
|
44
6
|
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
45
7
|
const path_1 = __importDefault(require("path"));
|
|
46
8
|
const fs_1 = __importDefault(require("fs"));
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const dbPath = process.env.DB_PATH || DEFAULT_DB;
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
// Store DB in user's home directory — works regardless of where process spawns from
|
|
11
|
+
const dbDir = path_1.default.join(os_1.default.homedir(), ".react-doctor");
|
|
12
|
+
const dbPath = process.env.DB_PATH || path_1.default.join(dbDir, "reports.db");
|
|
52
13
|
fs_1.default.mkdirSync(path_1.default.dirname(dbPath), { recursive: true });
|
|
53
|
-
fs_1.default.mkdirSync(path_1.default.join(path_1.default.dirname(dbPath), "screenshots"), { recursive: true });
|
|
54
14
|
const db = new better_sqlite3_1.default(dbPath);
|
|
55
15
|
db.exec(`
|
|
56
|
-
|
|
57
|
-
id
|
|
58
|
-
project
|
|
59
|
-
score
|
|
60
|
-
grade
|
|
61
|
-
analyzed_at
|
|
62
|
-
created_at
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
suggestions TEXT NOT NULL
|
|
66
|
-
);
|
|
16
|
+
CREATE TABLE IF NOT EXISTS reports (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
project TEXT NOT NULL,
|
|
19
|
+
score INTEGER NOT NULL,
|
|
20
|
+
grade TEXT NOT NULL,
|
|
21
|
+
analyzed_at TEXT NOT NULL,
|
|
22
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
23
|
+
payload TEXT NOT NULL
|
|
24
|
+
);
|
|
67
25
|
`);
|
|
68
|
-
exports.screenshotsDir = path_1.default.join(path_1.default.dirname(dbPath), "screenshots");
|
|
69
26
|
exports.default = db;
|
package/backend/dist/index.js
CHANGED
|
@@ -9,34 +9,27 @@ const helmet_1 = __importDefault(require("helmet"));
|
|
|
9
9
|
const dotenv_1 = __importDefault(require("dotenv"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const reports_1 = __importDefault(require("./routes/reports"));
|
|
12
|
-
|
|
13
|
-
dotenv_1.default.config({ path: path_1.default.join(__dirname,
|
|
12
|
+
// Load .env from the backend folder, not from cwd
|
|
13
|
+
dotenv_1.default.config({ path: path_1.default.join(__dirname, '..', '.env') });
|
|
14
14
|
const app = (0, express_1.default)();
|
|
15
15
|
const PORT = process.env.PORT || 3000;
|
|
16
|
-
|
|
16
|
+
const API_KEY = process.env.API_KEY || "react-doctor-secret-key-change-this";
|
|
17
|
+
// Make API_KEY available globally so auth middleware can use it
|
|
18
|
+
process.env.API_KEY = API_KEY;
|
|
17
19
|
app.use((0, helmet_1.default)());
|
|
18
20
|
app.use((0, cors_1.default)());
|
|
19
|
-
app.use(express_1.default.json({ limit:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
app.use("/screenshots", express_1.default.static(db_1.screenshotsDir));
|
|
24
|
-
// ── Routes ────────────────────────────────────────────────────
|
|
25
|
-
app.use("/api/reports", reports_1.default);
|
|
26
|
-
// ── Health check ──────────────────────────────────────────────
|
|
27
|
-
app.get("/health", (_req, res) => {
|
|
28
|
-
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
21
|
+
app.use(express_1.default.json({ limit: '50mb' }));
|
|
22
|
+
app.use('/api/reports', reports_1.default);
|
|
23
|
+
app.get('/health', (_req, res) => {
|
|
24
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
29
25
|
});
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
res.status(404).json({ message: "Route not found" });
|
|
26
|
+
app.use((req, res) => {
|
|
27
|
+
res.status(404).json({ message: 'Route not found' });
|
|
33
28
|
});
|
|
34
|
-
// ── Error handler ─────────────────────────────────────────────
|
|
35
29
|
app.use((err, _req, res, _next) => {
|
|
36
30
|
console.error(err.stack);
|
|
37
|
-
res.status(500).json({ message:
|
|
31
|
+
res.status(500).json({ message: 'Internal Server Error' });
|
|
38
32
|
});
|
|
39
|
-
// ── Start ─────────────────────────────────────────────────────
|
|
40
33
|
app.listen(PORT, () => {
|
|
41
|
-
console.log(
|
|
34
|
+
console.log(`🩺 React Doctor backend running on http://localhost:${PORT}`);
|
|
42
35
|
});
|
|
@@ -31,39 +31,6 @@
|
|
|
31
31
|
// /screenshots/<filename>
|
|
32
32
|
// so the dashboard can load them as normal <img> tags.
|
|
33
33
|
// ─────────────────────────────────────────────────────────────
|
|
34
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
35
|
-
if (k2 === undefined) k2 = k;
|
|
36
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
37
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
38
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
39
|
-
}
|
|
40
|
-
Object.defineProperty(o, k2, desc);
|
|
41
|
-
}) : (function(o, m, k, k2) {
|
|
42
|
-
if (k2 === undefined) k2 = k;
|
|
43
|
-
o[k2] = m[k];
|
|
44
|
-
}));
|
|
45
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
46
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
47
|
-
}) : function(o, v) {
|
|
48
|
-
o["default"] = v;
|
|
49
|
-
});
|
|
50
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
51
|
-
var ownKeys = function(o) {
|
|
52
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
53
|
-
var ar = [];
|
|
54
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
55
|
-
return ar;
|
|
56
|
-
};
|
|
57
|
-
return ownKeys(o);
|
|
58
|
-
};
|
|
59
|
-
return function (mod) {
|
|
60
|
-
if (mod && mod.__esModule) return mod;
|
|
61
|
-
var result = {};
|
|
62
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
63
|
-
__setModuleDefault(result, mod);
|
|
64
|
-
return result;
|
|
65
|
-
};
|
|
66
|
-
})();
|
|
67
34
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
68
35
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
69
36
|
};
|
|
@@ -71,8 +38,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
71
38
|
const express_1 = require("express");
|
|
72
39
|
const path_1 = __importDefault(require("path"));
|
|
73
40
|
const fs_1 = __importDefault(require("fs"));
|
|
74
|
-
const db_1 =
|
|
41
|
+
const db_1 = __importDefault(require("../db"));
|
|
75
42
|
const auth_1 = require("../middleware/auth");
|
|
43
|
+
const screenshotsDir = "data/screenshots";
|
|
76
44
|
const router = (0, express_1.Router)();
|
|
77
45
|
// ── GET /api/reports ─────────────────────────────────────────
|
|
78
46
|
// Summary list — no blobs, just the columns the history page needs.
|
|
@@ -241,7 +209,7 @@ function saveScreenshots(reportId, pending) {
|
|
|
241
209
|
const safeRoute = shot.routeKey.replace(/[/:]/g, "-").replace(/^-+/, "");
|
|
242
210
|
const safeLabel = shot.label.replace(/[^a-z0-9]/gi, "-");
|
|
243
211
|
const filename = `${reportId}-${safeRoute}-${safeLabel}.png`;
|
|
244
|
-
const fullPath = path_1.default.join(
|
|
212
|
+
const fullPath = path_1.default.join(screenshotsDir, filename);
|
|
245
213
|
try {
|
|
246
214
|
fs_1.default.writeFileSync(fullPath, shot.buffer);
|
|
247
215
|
saved.push({
|
package/backend/src/db.ts
CHANGED
|
@@ -1,70 +1,25 @@
|
|
|
1
|
-
// ─────────────────────────────────────────────────────────────
|
|
2
|
-
// backend/src/db.ts
|
|
3
|
-
//
|
|
4
|
-
// SQLite database via better-sqlite3 (synchronous, fast).
|
|
5
|
-
//
|
|
6
|
-
// SCHEMA:
|
|
7
|
-
//
|
|
8
|
-
// reports — one row per analysis run
|
|
9
|
-
// id — auto-increment PK
|
|
10
|
-
// project — project name / hostname
|
|
11
|
-
// score — 0-100 overall performance score
|
|
12
|
-
// grade — A+, A, B, C, D, F
|
|
13
|
-
// analyzed_at — ISO timestamp from the CLI
|
|
14
|
-
// created_at — when the row was inserted
|
|
15
|
-
// static_json — full StaticReport as JSON string
|
|
16
|
-
// runtime_json — full RuntimeReport map as JSON string
|
|
17
|
-
// (screenshots stripped — stored as files)
|
|
18
|
-
// suggestions — full Suggestion[] as JSON string
|
|
19
|
-
//
|
|
20
|
-
// WHY SPLIT COLUMNS INSTEAD OF ONE payload COLUMN?
|
|
21
|
-
// The old schema stored everything in a single `payload` TEXT
|
|
22
|
-
// column. This meant the dashboard couldn't query individual
|
|
23
|
-
// fields without parsing the whole blob. Splitting into three
|
|
24
|
-
// typed columns lets us:
|
|
25
|
-
// • SELECT only the part we need (e.g. just static_json for
|
|
26
|
-
// the issues page)
|
|
27
|
-
// • Keep the list endpoint fast (no giant blobs per row)
|
|
28
|
-
// • Add indices on score/grade later without changing schema
|
|
29
|
-
//
|
|
30
|
-
// SCREENSHOT STORAGE:
|
|
31
|
-
// Screenshots are base64 PNGs — up to 200KB each. Storing them
|
|
32
|
-
// in SQLite bloats the DB and makes every query slower. Instead:
|
|
33
|
-
// • The upload route strips dataUrls from runtime_json
|
|
34
|
-
// • Saves each screenshot as a .png file in data/screenshots/
|
|
35
|
-
// • Replaces dataUrl with a relative path /screenshots/<file>
|
|
36
|
-
// The dashboard fetches screenshots via the static file route.
|
|
37
|
-
// ─────────────────────────────────────────────────────────────
|
|
38
|
-
|
|
39
1
|
import Database from "better-sqlite3";
|
|
40
|
-
import path
|
|
41
|
-
import fs
|
|
42
|
-
import
|
|
43
|
-
|
|
44
|
-
dotenv.config({ path: path.join(__dirname, "..", ".env") });
|
|
2
|
+
import path from "path";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
45
5
|
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const dbPath
|
|
6
|
+
// Store DB in user's home directory — works regardless of where process spawns from
|
|
7
|
+
const dbDir = path.join(os.homedir(), ".react-doctor");
|
|
8
|
+
const dbPath = process.env.DB_PATH || path.join(dbDir, "reports.db");
|
|
49
9
|
|
|
50
10
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
51
|
-
fs.mkdirSync(path.join(path.dirname(dbPath), "screenshots"), { recursive: true });
|
|
52
11
|
|
|
53
12
|
const db = new Database(dbPath);
|
|
54
|
-
|
|
55
13
|
db.exec(`
|
|
56
|
-
|
|
57
|
-
id
|
|
58
|
-
project
|
|
59
|
-
score
|
|
60
|
-
grade
|
|
61
|
-
analyzed_at
|
|
62
|
-
created_at
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
suggestions TEXT NOT NULL
|
|
66
|
-
);
|
|
14
|
+
CREATE TABLE IF NOT EXISTS reports (
|
|
15
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
16
|
+
project TEXT NOT NULL,
|
|
17
|
+
score INTEGER NOT NULL,
|
|
18
|
+
grade TEXT NOT NULL,
|
|
19
|
+
analyzed_at TEXT NOT NULL,
|
|
20
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
21
|
+
payload TEXT NOT NULL
|
|
22
|
+
);
|
|
67
23
|
`);
|
|
68
24
|
|
|
69
|
-
export
|
|
70
|
-
export default db;
|
|
25
|
+
export default db;
|
package/backend/src/index.ts
CHANGED
|
@@ -1,46 +1,39 @@
|
|
|
1
|
-
import express, { Request, Response, NextFunction } from
|
|
2
|
-
import cors
|
|
3
|
-
import helmet
|
|
4
|
-
import dotenv
|
|
5
|
-
import path
|
|
6
|
-
import reportRoutes
|
|
7
|
-
import db
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 path from 'path';
|
|
6
|
+
import reportRoutes from './routes/reports';
|
|
7
|
+
import db from './db';
|
|
8
|
+
|
|
9
|
+
// Load .env from the backend folder, not from cwd
|
|
10
|
+
dotenv.config({ path: path.join(__dirname, '..', '.env') });
|
|
11
|
+
|
|
12
|
+
const app = express();
|
|
12
13
|
const PORT = process.env.PORT || 3000;
|
|
14
|
+
const API_KEY = process.env.API_KEY || "react-doctor-secret-key-change-this";
|
|
15
|
+
|
|
16
|
+
// Make API_KEY available globally so auth middleware can use it
|
|
17
|
+
process.env.API_KEY = API_KEY;
|
|
13
18
|
|
|
14
|
-
// ── Middleware ────────────────────────────────────────────────
|
|
15
19
|
app.use(helmet());
|
|
16
20
|
app.use(cors());
|
|
17
|
-
app.use(express.json({ limit:
|
|
21
|
+
app.use(express.json({ limit: '50mb' }));
|
|
22
|
+
app.use('/api/reports', reportRoutes);
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// The dashboard uses these as <img src="/screenshots/...">
|
|
22
|
-
app.use("/screenshots", express.static(screenshotsDir));
|
|
23
|
-
|
|
24
|
-
// ── Routes ────────────────────────────────────────────────────
|
|
25
|
-
app.use("/api/reports", reportRoutes);
|
|
26
|
-
|
|
27
|
-
// ── Health check ──────────────────────────────────────────────
|
|
28
|
-
app.get("/health", (_req: Request, res: Response) => {
|
|
29
|
-
res.json({ status: "ok", timestamp: new Date().toISOString() });
|
|
24
|
+
app.get('/health', (_req: Request, res: Response) => {
|
|
25
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
30
26
|
});
|
|
31
27
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
res.status(404).json({ message: "Route not found" });
|
|
28
|
+
app.use((req: Request, res: Response) => {
|
|
29
|
+
res.status(404).json({ message: 'Route not found' });
|
|
35
30
|
});
|
|
36
31
|
|
|
37
|
-
// ── Error handler ─────────────────────────────────────────────
|
|
38
32
|
app.use((err: Error, _req: Request, res: Response, _next: NextFunction) => {
|
|
39
33
|
console.error(err.stack);
|
|
40
|
-
res.status(500).json({ message:
|
|
34
|
+
res.status(500).json({ message: 'Internal Server Error' });
|
|
41
35
|
});
|
|
42
36
|
|
|
43
|
-
// ── Start ─────────────────────────────────────────────────────
|
|
44
37
|
app.listen(PORT, () => {
|
|
45
|
-
console.log(
|
|
46
|
-
});
|
|
38
|
+
console.log(`🩺 React Doctor backend running on http://localhost:${PORT}`);
|
|
39
|
+
});
|
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
import { Router, Request, Response, RequestHandler } from "express";
|
|
35
35
|
import path from "path";
|
|
36
36
|
import fs from "fs";
|
|
37
|
-
import db
|
|
37
|
+
import db from "../db";
|
|
38
38
|
import { requireApiKey } from "../middleware/auth";
|
|
39
39
|
|
|
40
|
+
const screenshotsDir = "data/screenshots";
|
|
41
|
+
|
|
40
42
|
const router = Router();
|
|
41
43
|
|
|
42
44
|
// ── GET /api/reports ─────────────────────────────────────────
|
package/package.json
CHANGED