react-doctor-cli-dev 1.0.9 → 1.0.12
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/dashboard/index.html +253 -0
- package/dashboard/package-lock.json +913 -0
- package/dashboard/package.json +19 -0
- package/dashboard/public/favicon.svg +1 -0
- package/dashboard/public/icons.svg +24 -0
- package/dashboard/src/api.js +107 -0
- package/dashboard/src/assets/hero.png +0 -0
- package/dashboard/src/assets/javascript.svg +1 -0
- package/dashboard/src/assets/vite.svg +1 -0
- package/dashboard/src/css/main.css +441 -0
- package/dashboard/src/data.js +202 -0
- package/dashboard/src/main.js +53 -0
- package/dashboard/src/pages.js +797 -0
- package/dashboard/src/router.js +77 -0
- package/dashboard/src/utils.js +163 -0
- package/dashboard/vite.config.js +19 -0
- package/data/screenshots/5-docs-fullLoad.png +0 -0
- package/data/screenshots/6--fcp.png +0 -0
- package/data/screenshots/6--fullLoad.png +0 -0
- package/data/screenshots/6-white-fullLoad.png +0 -0
- package/package.json +4 -22
- 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
- /package/{backend/data/screenshots/4--fcp.png → data/screenshots/5--fcp.png} +0 -0
- /package/{backend/data/screenshots/4--fullLoad.png → data/screenshots/5--fullLoad.png} +0 -0
- /package/{backend/data/screenshots/4-white-fullLoad.png → data/screenshots/5-white-fullLoad.png} +0 -0
- /package/{backend/data/screenshots/4-docs-fullLoad.png → data/screenshots/6-docs-fullLoad.png} +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// js/router.js — hash-based SPA router
|
|
3
|
+
// Maps #hash → page element + init function
|
|
4
|
+
// ─────────────────────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
import { initOverview, initVitals, initIssues, initSuggestions, initHistory } from './pages.js';
|
|
7
|
+
import { closeLightbox } from './utils.js';
|
|
8
|
+
|
|
9
|
+
const ROUTES = {
|
|
10
|
+
"overview": { id: "page-overview", init: initOverview },
|
|
11
|
+
"vitals": { id: "page-vitals", init: initVitals },
|
|
12
|
+
"issues": { id: "page-issues", init: initIssues },
|
|
13
|
+
"suggestions": { id: "page-suggestions", init: initSuggestions },
|
|
14
|
+
"history": { id: "page-history", init: initHistory },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let currentRoute = null;
|
|
18
|
+
|
|
19
|
+
async function navigate(hash) {
|
|
20
|
+
const route = ROUTES[hash] || ROUTES["overview"];
|
|
21
|
+
if (currentRoute === hash) return;
|
|
22
|
+
currentRoute = hash;
|
|
23
|
+
|
|
24
|
+
// Hide all pages
|
|
25
|
+
document.querySelectorAll(".page-content").forEach(el => el.classList.remove("active"));
|
|
26
|
+
// Show target
|
|
27
|
+
const page = document.getElementById(route.id);
|
|
28
|
+
if (page) page.classList.add("active");
|
|
29
|
+
|
|
30
|
+
// Update nav
|
|
31
|
+
document.querySelectorAll(".nav-item").forEach(el => {
|
|
32
|
+
el.classList.toggle("active", el.dataset.route === hash);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Update topbar
|
|
36
|
+
const titles = {
|
|
37
|
+
overview: ["Overview", "Performance summary"],
|
|
38
|
+
vitals: ["Web Vitals", "Core Web Vitals per route & device"],
|
|
39
|
+
issues: ["Code Issues", "Static analysis results"],
|
|
40
|
+
suggestions: ["Suggestions", "Rule engine recommendations"],
|
|
41
|
+
history: ["History", "Previous analysis runs"],
|
|
42
|
+
};
|
|
43
|
+
const [title, sub] = titles[hash] || titles.overview;
|
|
44
|
+
document.getElementById("topbar-title").textContent = title;
|
|
45
|
+
document.getElementById("topbar-sub").textContent = sub;
|
|
46
|
+
|
|
47
|
+
// Run page init (await it!)
|
|
48
|
+
try {
|
|
49
|
+
await route.init();
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('Error initializing page:', err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function initRouter() {
|
|
56
|
+
// Nav clicks
|
|
57
|
+
document.querySelectorAll(".nav-item[data-route]").forEach(btn => {
|
|
58
|
+
btn.addEventListener("click", () => {
|
|
59
|
+
window.location.hash = btn.dataset.route;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Hash change
|
|
64
|
+
window.addEventListener("hashchange", () => {
|
|
65
|
+
navigate(location.hash.replace("#", "") || "overview");
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Lightbox close
|
|
69
|
+
document.getElementById("lightbox").addEventListener("click", e => {
|
|
70
|
+
if (e.target === e.currentTarget) closeLightbox();
|
|
71
|
+
});
|
|
72
|
+
document.getElementById("lb-close").addEventListener("click", closeLightbox);
|
|
73
|
+
document.addEventListener("keydown", e => { if (e.key === "Escape") closeLightbox(); });
|
|
74
|
+
|
|
75
|
+
// Initial route
|
|
76
|
+
navigate(location.hash.replace("#", "") || "overview");
|
|
77
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// ─────────────────────────────────────────────────────────────
|
|
2
|
+
// js/utils.js — pure helper functions used across all pages
|
|
3
|
+
// ─────────────────────────────────────────────────────────────
|
|
4
|
+
|
|
5
|
+
export function scoreColor(score) {
|
|
6
|
+
if (score >= 90) return "var(--green)";
|
|
7
|
+
if (score >= 75) return "var(--blue)";
|
|
8
|
+
if (score >= 50) return "var(--orange)";
|
|
9
|
+
return "var(--red)";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function gradeBadgeClass(grade) {
|
|
13
|
+
const g = grade ? grade[0] : 'N';
|
|
14
|
+
if (g === "A") return "grade-a";
|
|
15
|
+
if (g === "B") return "grade-b";
|
|
16
|
+
if (g === "C") return "grade-c";
|
|
17
|
+
if (g === "D") return "grade-d";
|
|
18
|
+
return "grade-d";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function severityIcon(severity) {
|
|
22
|
+
return { critical: "🔴", warning: "🟠", info: "🔵" }[severity] || "⚪";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function severityLabel(sev) {
|
|
26
|
+
return `<span class="badge ${sev}">${severityIcon(sev)} ${sev}</span>`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Vital thresholds: [good_max, warn_max] (above warn_max = bad)
|
|
30
|
+
const VITAL_THRESHOLDS = {
|
|
31
|
+
lcp: [2500, 4000],
|
|
32
|
+
fcp: [1800, 3000],
|
|
33
|
+
ttfb: [800, 1800],
|
|
34
|
+
inp: [200, 500],
|
|
35
|
+
cls: [0.1, 0.25],
|
|
36
|
+
render: [2000, 4000], // ← Added Render Time threshold
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function vitalClass(metric, value) {
|
|
40
|
+
const [good, warn] = VITAL_THRESHOLDS[metric] || [Infinity, Infinity];
|
|
41
|
+
if (value <= good) return "good";
|
|
42
|
+
if (value <= warn) return "warn";
|
|
43
|
+
return "bad";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function vitalColor(metric, value) {
|
|
47
|
+
const cls = vitalClass(metric, value);
|
|
48
|
+
return cls === "good" ? "var(--green)" : cls === "warn" ? "var(--orange)" : "var(--red)";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Percentage fill for the bar — capped to 100%
|
|
52
|
+
export function vitalBarPct(metric, value) {
|
|
53
|
+
const limits = {
|
|
54
|
+
lcp: 5000,
|
|
55
|
+
fcp: 4000,
|
|
56
|
+
ttfb: 2500,
|
|
57
|
+
inp: 600,
|
|
58
|
+
cls: 0.35,
|
|
59
|
+
render: 6000 // ← Added Render Time limit
|
|
60
|
+
};
|
|
61
|
+
const max = limits[metric] || 100;
|
|
62
|
+
return Math.min((value / max) * 100, 100).toFixed(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function formatMs(ms) {
|
|
66
|
+
if (ms === undefined || ms === null) return "–";
|
|
67
|
+
if (ms >= 1000) return (ms / 1000).toFixed(2) + "s";
|
|
68
|
+
return ms.toFixed(0) + "ms";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatCls(v) {
|
|
72
|
+
return typeof v === "number" ? v.toFixed(3) : "–";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function formatDate(iso) {
|
|
76
|
+
if (!iso) return "–";
|
|
77
|
+
const d = new Date(iso);
|
|
78
|
+
return d.toLocaleDateString("en-GB", { day: "2-digit", month: "short", year: "numeric" }) +
|
|
79
|
+
" " + d.toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function relativeTime(iso) {
|
|
83
|
+
if (!iso) return "";
|
|
84
|
+
const diff = Date.now() - new Date(iso).getTime();
|
|
85
|
+
const m = Math.floor(diff / 60000);
|
|
86
|
+
if (m < 1) return "just now";
|
|
87
|
+
if (m < 60) return `${m}m ago`;
|
|
88
|
+
const h = Math.floor(m / 60);
|
|
89
|
+
if (h < 24) return `${h}h ago`;
|
|
90
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Build the SVG score ring with VISIBLE text ──────────────
|
|
94
|
+
export function buildScoreRing(score, size = 140, strokeWidth = 10) {
|
|
95
|
+
const r = (size - strokeWidth) / 2;
|
|
96
|
+
const c = size / 2;
|
|
97
|
+
const circ = 2 * Math.PI * r;
|
|
98
|
+
const pct = Math.min(Math.max(score / 100, 0), 1);
|
|
99
|
+
const color = scoreColor(score);
|
|
100
|
+
|
|
101
|
+
// Map score to a color for the text
|
|
102
|
+
let textColor;
|
|
103
|
+
if (score >= 90) textColor = '#3FB950'; // green
|
|
104
|
+
else if (score >= 75) textColor = '#58A6FF'; // blue
|
|
105
|
+
else if (score >= 50) textColor = '#F0883E'; // orange
|
|
106
|
+
else textColor = '#FF7B72'; // red
|
|
107
|
+
|
|
108
|
+
return `
|
|
109
|
+
<div class="score-ring" style="width:${size}px;height:${size}px;position:relative;flex-shrink:0;">
|
|
110
|
+
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" style="transform:rotate(-90deg);display:block;">
|
|
111
|
+
<circle
|
|
112
|
+
cx="${c}" cy="${c}" r="${r}"
|
|
113
|
+
fill="none"
|
|
114
|
+
stroke="#1C2333"
|
|
115
|
+
stroke-width="${strokeWidth}" />
|
|
116
|
+
<circle
|
|
117
|
+
cx="${c}" cy="${c}" r="${r}"
|
|
118
|
+
fill="none"
|
|
119
|
+
stroke="${textColor}"
|
|
120
|
+
stroke-width="${strokeWidth}"
|
|
121
|
+
stroke-linecap="round"
|
|
122
|
+
stroke-dasharray="${circ}"
|
|
123
|
+
stroke-dashoffset="${circ * (1 - pct)}"
|
|
124
|
+
style="transition: stroke-dashoffset 1.4s cubic-bezier(.4,0,.2,1);" />
|
|
125
|
+
</svg>
|
|
126
|
+
<div style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center;pointer-events:none;width:100%;z-index:10;">
|
|
127
|
+
<div style="font-size:2.4rem;font-weight:700;font-family:'JetBrains Mono',monospace;line-height:1;color:${textColor};text-shadow:0 0 20px rgba(0,0,0,0.5);">${Math.round(score)}</div>
|
|
128
|
+
<div style="font-size:0.7rem;font-weight:600;color:#8B949E;margin-top:2px;">/ 100</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Vital bar row HTML
|
|
135
|
+
export function buildVitalRow(label, metric, value) {
|
|
136
|
+
const display = metric === "cls" ? formatCls(value) : formatMs(value);
|
|
137
|
+
const color = vitalColor(metric, value);
|
|
138
|
+
const pct = vitalBarPct(metric, value);
|
|
139
|
+
return `
|
|
140
|
+
<div class="vital-row">
|
|
141
|
+
<div class="vital-row-head">
|
|
142
|
+
<span class="vname">${label}</span>
|
|
143
|
+
<span class="vval" style="color:${color}">${display}</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div class="vital-track">
|
|
146
|
+
<div class="vital-fill" style="width:${pct}%;background:${color}"></div>
|
|
147
|
+
</div>
|
|
148
|
+
</div>`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Lightbox
|
|
152
|
+
export function openLightbox(src, caption) {
|
|
153
|
+
const lb = document.getElementById("lightbox");
|
|
154
|
+
const img = lb.querySelector("img");
|
|
155
|
+
if (img) img.src = src;
|
|
156
|
+
const cap = document.getElementById("lb-caption");
|
|
157
|
+
if (cap) cap.textContent = caption || "";
|
|
158
|
+
lb.classList.add("open");
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function closeLightbox() {
|
|
162
|
+
document.getElementById("lightbox").classList.remove("open");
|
|
163
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
root: '.',
|
|
6
|
+
base: '/',
|
|
7
|
+
build: {
|
|
8
|
+
outDir: '../backend/public', // Express serves this as static
|
|
9
|
+
emptyOutDir: true,
|
|
10
|
+
},
|
|
11
|
+
server: {
|
|
12
|
+
port: 5173,
|
|
13
|
+
proxy: {
|
|
14
|
+
// Forward /api/* and /screenshots/* to the backend during dev
|
|
15
|
+
'/api': 'http://localhost:3000',
|
|
16
|
+
'/screenshots': 'http://localhost:3000',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-doctor-cli-dev",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "React performance analyzer with static analysis, runtime profiling, rule engine, and dashboard",
|
|
3
|
+
"version": "1.0.12",
|
|
4
|
+
"description": "React performance analyzer with static analysis, runtime profiling, rule engine, and dashboard upload",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"bin": {
|
|
8
8
|
"react-doctor": "./cli/bin/react-doctor.js"
|
|
9
9
|
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"postinstall": "node scripts/postinstall.js",
|
|
12
|
-
"build": "node scripts/build.js",
|
|
13
|
-
"dev": "node scripts/dev.js"
|
|
14
|
-
},
|
|
10
|
+
"scripts": {},
|
|
15
11
|
"keywords": [
|
|
16
12
|
"react",
|
|
17
13
|
"performance",
|
|
18
14
|
"profiler",
|
|
19
15
|
"cli",
|
|
20
|
-
"analyzer"
|
|
21
|
-
"dashboard",
|
|
22
|
-
"web-vitals"
|
|
16
|
+
"analyzer"
|
|
23
17
|
],
|
|
24
18
|
"author": "Ozma",
|
|
25
19
|
"license": "ISC",
|
|
@@ -54,17 +48,5 @@
|
|
|
54
48
|
"@types/fs-extra": "^11.0.4",
|
|
55
49
|
"@types/node": "^25.9.1",
|
|
56
50
|
"nodemon": "^3.1.14"
|
|
57
|
-
},
|
|
58
|
-
"files": [
|
|
59
|
-
"cli/**/*",
|
|
60
|
-
"backend/**/*",
|
|
61
|
-
"frontend/dist/**/*",
|
|
62
|
-
"core/**/*",
|
|
63
|
-
"scripts/**/*",
|
|
64
|
-
"README.md",
|
|
65
|
-
"LICENSE"
|
|
66
|
-
],
|
|
67
|
-
"engines": {
|
|
68
|
-
"node": ">=18.0.0"
|
|
69
51
|
}
|
|
70
52
|
}
|
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface WebVitals {
|
|
2
|
+
lcp: number;
|
|
3
|
+
fcp: number;
|
|
4
|
+
cls: number;
|
|
5
|
+
inp: number;
|
|
6
|
+
ttfb: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ComponentIssue {
|
|
9
|
+
id: string;
|
|
10
|
+
component: string;
|
|
11
|
+
file: string;
|
|
12
|
+
line: number;
|
|
13
|
+
severity: "critical" | "warning" | "info";
|
|
14
|
+
message: string;
|
|
15
|
+
suggestion: string;
|
|
16
|
+
}
|
|
17
|
+
export interface StaticReport {
|
|
18
|
+
timestamp: string;
|
|
19
|
+
issues: ComponentIssue[];
|
|
20
|
+
bundleSize: number;
|
|
21
|
+
componentCount: number;
|
|
22
|
+
}
|
|
23
|
+
export interface RuntimeReport {
|
|
24
|
+
timestamp: string;
|
|
25
|
+
metrics: WebVitals;
|
|
26
|
+
rerenders: Record<string, number>;
|
|
27
|
+
commitDurations: number[];
|
|
28
|
+
}
|
|
29
|
+
export interface Suggestion {
|
|
30
|
+
id: string;
|
|
31
|
+
title: string;
|
|
32
|
+
description: string;
|
|
33
|
+
severity: "critical" | "warning" | "info";
|
|
34
|
+
affectedComponent?: string;
|
|
35
|
+
fix: string;
|
|
36
|
+
}
|
|
37
|
+
export interface FinalReport {
|
|
38
|
+
projectName: string;
|
|
39
|
+
analyzedAt: string;
|
|
40
|
+
static: StaticReport;
|
|
41
|
+
runtime: RuntimeReport;
|
|
42
|
+
suggestions: Suggestion[];
|
|
43
|
+
performanceScore: number;
|
|
44
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shared",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"lockfileVersion": 3,
|
|
5
|
+
"requires": true,
|
|
6
|
+
"packages": {
|
|
7
|
+
"": {
|
|
8
|
+
"name": "shared",
|
|
9
|
+
"version": "1.0.0",
|
|
10
|
+
"license": "ISC",
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"valibot": "^1.2.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"typescript": "^5.9.3"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"node_modules/typescript": {
|
|
19
|
+
"version": "5.9.3",
|
|
20
|
+
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
|
21
|
+
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
|
22
|
+
"devOptional": true,
|
|
23
|
+
"license": "Apache-2.0",
|
|
24
|
+
"bin": {
|
|
25
|
+
"tsc": "bin/tsc",
|
|
26
|
+
"tsserver": "bin/tsserver"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=14.17"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"node_modules/valibot": {
|
|
33
|
+
"version": "1.2.0",
|
|
34
|
+
"resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
|
|
35
|
+
"integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"typescript": ">=5"
|
|
39
|
+
},
|
|
40
|
+
"peerDependenciesMeta": {
|
|
41
|
+
"typescript": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "shared",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"dev": "tsc --watch"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"type": "commonjs",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"valibot": "^1.2.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"typescript": "^5.9.3"
|
|
20
|
+
}
|
|
21
|
+
}
|