Files
community-rule/lib/server/webVitals/localFileStore.ts
T
2026-04-21 07:08:31 -06:00

116 lines
2.9 KiB
TypeScript

import fs from "fs";
import path from "path";
import { logger } from "../../logger";
const WEB_VITALS_DIR = path.join(process.cwd(), ".next", "web-vitals");
export interface WebVitalData {
metric: string;
data: {
value: number;
rating: string;
};
url: string;
userAgent: string;
timestamp: string;
receivedAt: string;
}
export interface WebVitalMetrics {
[metric: string]: {
count: number;
average: number;
min: number;
max: number;
goodCount: number;
needsImprovementCount: number;
poorCount: number;
lastUpdated: string;
};
}
function ensureWebVitalsDir(): void {
if (!fs.existsSync(WEB_VITALS_DIR)) {
fs.mkdirSync(WEB_VITALS_DIR, { recursive: true });
}
}
export function appendLocalWebVital(vitalsData: WebVitalData): void {
ensureWebVitalsDir();
const filePath = path.join(WEB_VITALS_DIR, `${vitalsData.metric}.json`);
let existingData: WebVitalData[] = [];
if (fs.existsSync(filePath)) {
try {
const fileContent = fs.readFileSync(filePath, "utf8");
existingData = JSON.parse(fileContent) as WebVitalData[];
} catch (error) {
const err = error as Error;
logger.warn("Could not parse existing vitals data:", err.message);
}
}
existingData.push(vitalsData);
if (existingData.length > 100) {
existingData = existingData.slice(-100);
}
fs.writeFileSync(filePath, JSON.stringify(existingData, null, 2));
}
export function readLocalAggregatedMetrics(): WebVitalMetrics {
const metrics: WebVitalMetrics = {};
if (!fs.existsSync(WEB_VITALS_DIR)) {
return metrics;
}
const files = fs.readdirSync(WEB_VITALS_DIR);
files.forEach((file) => {
if (!file.endsWith(".json")) return;
const metric = file.replace(".json", "");
let data: WebVitalData[];
try {
const fileContent = fs.readFileSync(
path.join(WEB_VITALS_DIR, file),
"utf8",
);
data = JSON.parse(fileContent) as WebVitalData[];
} catch (error) {
logger.warn(
`Skipping corrupt web vitals file ${file}:`,
(error as Error).message,
);
return;
}
if (data.length === 0) return;
const values = data
.map((d) => d.data.value)
.filter((v) => v !== undefined);
const ratings = data
.map((d) => d.data.rating)
.filter((r) => r !== undefined);
metrics[metric] = {
count: data.length,
average:
values.length > 0
? Math.round(values.reduce((a, b) => a + b, 0) / values.length)
: 0,
min: values.length > 0 ? Math.min(...values) : 0,
max: values.length > 0 ? Math.max(...values) : 0,
goodCount: ratings.filter((r) => r === "good").length,
needsImprovementCount: ratings.filter((r) => r === "needs-improvement")
.length,
poorCount: ratings.filter((r) => r === "poor").length,
lastUpdated: data[data.length - 1]?.receivedAt || "",
};
});
return metrics;
}