116 lines
2.9 KiB
TypeScript
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;
|
|
}
|