import { mkdir, readFile, writeFile } from "node:fs/promises"; import { evaluateFieldBudget, percentile75, } from "../src/application/policies/performance-budgets.js"; const inputPath = process.env.FIELD_WEB_VITALS_INPUT ?? "config/performance/field-input.example.json"; const input = /** @type {{ * releaseId: string, * samples: Array<{ * timestamp: string, * consent: boolean, * releaseId: string, * routeId: string, * lcpMs: number, * cls: number, * inpMs: number * }> * }} */ (JSON.parse(await readFile(inputPath, "utf8"))); const configured = /** @type {{ * p75LcpMs: number, * p75Cls: number, * p75InpMs: number, * minimumEligibleSamples: number | null * }} */ ( JSON.parse(await readFile("config/performance/budgets.json", "utf8")).field ); const minimumEligibleSamples = process.env.MIN_ELIGIBLE_SAMPLES ? Number(process.env.MIN_ELIGIBLE_SAMPLES) : configured.minimumEligibleSamples; const end = new Date(); const start = new Date(end); start.setUTCDate(start.getUTCDate() - 28); const eligible = input.samples.filter((sample) => { const timestamp = new Date(sample.timestamp); return ( sample.consent === true && sample.releaseId === input.releaseId && timestamp >= start && timestamp <= end ); }); const metrics = { p75LcpMs: percentile75(eligible.map((sample) => sample.lcpMs)), p75Cls: percentile75(eligible.map((sample) => sample.cls)), p75InpMs: percentile75(eligible.map((sample) => sample.inpMs)), }; const thresholds = { ...configured, minimumEligibleSamples }; const result = evaluateFieldBudget( { metrics, eligibleSamples: eligible.length }, thresholds, ); const routeSamples = Object.fromEntries( Object.entries( eligible.reduce( (counts, sample) => { counts[sample.routeId] = (counts[sample.routeId] ?? 0) + 1; return counts; }, /** @type {Record} */ ({}), ), ).sort(([left], [right]) => left.localeCompare(right)), ); const report = { schemaVersion: 1, generatedAt: end.toISOString(), window: { days: 28, start: start.toISOString(), end: end.toISOString() }, context: { source: inputPath, network: "production-real-user", routeAggregation: "route-id-only", releaseId: input.releaseId, }, metrics, thresholds, eligibility: { consentRequired: true, eligibleSamples: eligible.length, minimumEligibleSamples, routeSamples, }, status: result.status, passed: result.passed, }; await mkdir("artifacts/performance", { recursive: true }); await writeFile( "artifacts/performance/field-web-vitals.json", `${JSON.stringify(report, null, 2)}\n`, ); if (!result.passed) { process.stderr.write( `Field Web Vitals: ${result.status} (minimum eligible sample threshold and 28-day production data are required)\n`, ); process.exit(1); } process.stdout.write("Field Web Vitals: PASS\n");