laxy-verify 1.2.1 → 1.2.3
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/README.md +193 -64
- package/dist/a11y-deep.d.ts +20 -0
- package/dist/a11y-deep.js +161 -0
- package/dist/ai-analysis.d.ts +28 -0
- package/dist/ai-analysis.js +32 -0
- package/dist/audit/broken-links.d.ts +5 -1
- package/dist/audit/broken-links.js +23 -12
- package/dist/bundle-size.d.ts +14 -0
- package/dist/bundle-size.js +209 -0
- package/dist/cli.js +432 -16
- package/dist/compare-env.d.ts +23 -0
- package/dist/compare-env.js +55 -0
- package/dist/config.d.ts +50 -0
- package/dist/config.js +149 -4
- package/dist/entitlement.d.ts +2 -0
- package/dist/entitlement.js +5 -1
- package/dist/init-analysis.d.ts +6 -0
- package/dist/init-analysis.js +302 -0
- package/dist/init.js +66 -0
- package/dist/lighthouse.d.ts +31 -1
- package/dist/lighthouse.js +76 -3
- package/dist/outdated-check.d.ts +17 -0
- package/dist/outdated-check.js +123 -0
- package/dist/report-markdown.d.ts +14 -0
- package/dist/report-markdown.js +21 -0
- package/dist/route-discovery.d.ts +7 -0
- package/dist/route-discovery.js +108 -0
- package/dist/secret-scan.d.ts +15 -0
- package/dist/secret-scan.js +218 -0
- package/dist/security-audit.d.ts +9 -1
- package/dist/security-audit.js +87 -24
- package/dist/seo-deep.d.ts +24 -0
- package/dist/seo-deep.js +147 -0
- package/dist/typecheck.d.ts +8 -0
- package/dist/typecheck.js +99 -0
- package/dist/verification-core/report.js +117 -0
- package/dist/verification-core/types.d.ts +58 -2
- package/dist/visual-diff.d.ts +8 -1
- package/dist/visual-diff.js +53 -8
- package/dist/vitals-budget.d.ts +23 -0
- package/dist/vitals-budget.js +168 -0
- package/package.json +1 -1
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runVitalsBudget = runVitalsBudget;
|
|
4
|
+
/**
|
|
5
|
+
* Core Web Vitals budget check.
|
|
6
|
+
*
|
|
7
|
+
* Uses Puppeteer to measure LCP, CLS, and INP against configurable
|
|
8
|
+
* budgets. Does NOT claim FID — INP is the modern replacement.
|
|
9
|
+
* Blocker-capable: budget violations surface as warnings.
|
|
10
|
+
*/
|
|
11
|
+
let puppeteerModule = null;
|
|
12
|
+
try {
|
|
13
|
+
puppeteerModule = require("puppeteer");
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// Puppeteer not available — vitals budget will be skipped
|
|
17
|
+
}
|
|
18
|
+
const DEFAULT_BUDGET = {
|
|
19
|
+
lcp: 2500,
|
|
20
|
+
cls: 0.1,
|
|
21
|
+
inp: 200,
|
|
22
|
+
};
|
|
23
|
+
async function runVitalsBudget(url, budget) {
|
|
24
|
+
if (!puppeteerModule) {
|
|
25
|
+
return {
|
|
26
|
+
passed: true,
|
|
27
|
+
metrics: [],
|
|
28
|
+
lcp: null,
|
|
29
|
+
cls: null,
|
|
30
|
+
inp: null,
|
|
31
|
+
url,
|
|
32
|
+
skipped: true,
|
|
33
|
+
summary: "Skipped (puppeteer not available)",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const effectiveBudget = { ...DEFAULT_BUDGET, ...budget };
|
|
37
|
+
console.log(" Running Core Web Vitals budget check...");
|
|
38
|
+
const puppeteer = puppeteerModule.default || puppeteerModule;
|
|
39
|
+
let browser;
|
|
40
|
+
try {
|
|
41
|
+
browser = await puppeteer.launch({
|
|
42
|
+
headless: true,
|
|
43
|
+
args: [
|
|
44
|
+
"--no-sandbox",
|
|
45
|
+
"--disable-setuid-sandbox",
|
|
46
|
+
"--disable-extensions",
|
|
47
|
+
"--disable-component-update",
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
const page = await browser.newPage();
|
|
51
|
+
await page.setViewport({ width: 1350, height: 940 });
|
|
52
|
+
// Inject web-vitals measurement script before navigation
|
|
53
|
+
await page.evaluateOnNewDocument(() => {
|
|
54
|
+
window.__laxyVitals = {};
|
|
55
|
+
// LCP
|
|
56
|
+
try {
|
|
57
|
+
new PerformanceObserver((list) => {
|
|
58
|
+
const entries = list.getEntries();
|
|
59
|
+
const last = entries[entries.length - 1];
|
|
60
|
+
if (last) {
|
|
61
|
+
window.__laxyVitals.lcp = last.startTime;
|
|
62
|
+
}
|
|
63
|
+
}).observe({ type: "largest-contentful-paint", buffered: true });
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
// CLS
|
|
67
|
+
try {
|
|
68
|
+
let clsValue = 0;
|
|
69
|
+
new PerformanceObserver((list) => {
|
|
70
|
+
for (const entry of list.getEntries()) {
|
|
71
|
+
if (!entry.hadRecentInput) {
|
|
72
|
+
clsValue += entry.value;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
window.__laxyVitals.cls = clsValue;
|
|
76
|
+
}).observe({ type: "layout-shift", buffered: true });
|
|
77
|
+
}
|
|
78
|
+
catch { }
|
|
79
|
+
// INP (Event Timing)
|
|
80
|
+
try {
|
|
81
|
+
let worstInp = 0;
|
|
82
|
+
new PerformanceObserver((list) => {
|
|
83
|
+
for (const entry of list.getEntries()) {
|
|
84
|
+
const duration = entry.duration;
|
|
85
|
+
if (duration > worstInp) {
|
|
86
|
+
worstInp = duration;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
window.__laxyVitals.inp = worstInp;
|
|
90
|
+
}).observe({ type: "event", buffered: true });
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
});
|
|
94
|
+
await page.goto(url, { waitUntil: "networkidle2", timeout: 30000 });
|
|
95
|
+
// Wait for LCP to stabilize
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
97
|
+
// Simulate some interactions to get INP
|
|
98
|
+
try {
|
|
99
|
+
await page.mouse.click(100, 100);
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
101
|
+
await page.keyboard.press("Tab");
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// interaction may fail on minimal pages
|
|
106
|
+
}
|
|
107
|
+
// Wait a bit more for metrics to settle
|
|
108
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
109
|
+
const vitals = await page.evaluate(() => {
|
|
110
|
+
const v = window.__laxyVitals;
|
|
111
|
+
return {
|
|
112
|
+
lcp: v.lcp ?? 0,
|
|
113
|
+
cls: v.cls ?? 0,
|
|
114
|
+
inp: v.inp ?? 0,
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const metrics = [
|
|
118
|
+
{
|
|
119
|
+
name: "LCP",
|
|
120
|
+
value: Math.round(vitals.lcp),
|
|
121
|
+
unit: "ms",
|
|
122
|
+
budget: effectiveBudget.lcp,
|
|
123
|
+
passed: vitals.lcp <= effectiveBudget.lcp,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: "CLS",
|
|
127
|
+
value: Math.round(vitals.cls * 1000) / 1000,
|
|
128
|
+
unit: "score",
|
|
129
|
+
budget: effectiveBudget.cls,
|
|
130
|
+
passed: vitals.cls <= effectiveBudget.cls,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "INP",
|
|
134
|
+
value: Math.round(vitals.inp),
|
|
135
|
+
unit: "ms",
|
|
136
|
+
budget: effectiveBudget.inp,
|
|
137
|
+
passed: vitals.inp <= effectiveBudget.inp,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
const passed = metrics.every((m) => m.passed);
|
|
141
|
+
const failedMetrics = metrics.filter((m) => !m.passed);
|
|
142
|
+
const summary = passed
|
|
143
|
+
? `LCP ${Math.round(vitals.lcp)}ms, CLS ${(vitals.cls).toFixed(3)}, INP ${Math.round(vitals.inp)}ms — within budget`
|
|
144
|
+
: failedMetrics.map((m) => `${m.name} ${m.value}${m.unit} > ${m.budget}${m.unit}`).join("; ");
|
|
145
|
+
console.log(` Vitals budget: ${summary}`);
|
|
146
|
+
for (const m of metrics) {
|
|
147
|
+
const status = m.passed ? "OK" : "OVER";
|
|
148
|
+
console.log(` ${m.name}: ${m.value}${m.unit} / ${m.budget}${m.unit} ${status}`);
|
|
149
|
+
}
|
|
150
|
+
return { passed, metrics, lcp: Math.round(vitals.lcp), cls: Math.round(vitals.cls * 1000) / 1000, inp: Math.round(vitals.inp), url, skipped: false, summary };
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
console.error(` Vitals budget error: ${err instanceof Error ? err.message : String(err)}`);
|
|
154
|
+
return {
|
|
155
|
+
passed: true,
|
|
156
|
+
metrics: [],
|
|
157
|
+
lcp: null,
|
|
158
|
+
cls: null,
|
|
159
|
+
inp: null,
|
|
160
|
+
url,
|
|
161
|
+
skipped: true,
|
|
162
|
+
summary: `Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
await browser?.close();
|
|
167
|
+
}
|
|
168
|
+
}
|