laxy-verify 1.3.2 → 1.3.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 +5 -0
- package/dist/multi-viewport.js +6 -6
- package/dist/route-discovery.d.ts +1 -0
- package/dist/route-discovery.js +31 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -434,6 +434,11 @@ It is a pre-merge and pre-release verification layer, not your entire quality sy
|
|
|
434
434
|
|
|
435
435
|
## Changelog
|
|
436
436
|
|
|
437
|
+
### v1.3.3 - Cleaner output and route validation
|
|
438
|
+
|
|
439
|
+
- Removed `Pro` and `Pro+` labels from verification logs so `npx laxy-verify .` prints plan-neutral output
|
|
440
|
+
- Runtime-discovered routes are now validated before they are used for Lighthouse and broken-link checks, so bogus `404` bundle snippets do not poison the verification result
|
|
441
|
+
|
|
437
442
|
### v1.3.2 - Auto port fallback
|
|
438
443
|
|
|
439
444
|
- If port `3000` or your configured verification port is already in use, `laxy-verify` now starts its temporary dev server on the next available port automatically
|
package/dist/multi-viewport.js
CHANGED
|
@@ -38,7 +38,7 @@ exports.printMultiViewportResults = printMultiViewportResults;
|
|
|
38
38
|
exports.allViewportsPass = allViewportsPass;
|
|
39
39
|
exports.runMobileLighthouse = runMobileLighthouse;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
41
|
+
* Multi-viewport Lighthouse checks.
|
|
42
42
|
*
|
|
43
43
|
* Each viewport runs through the same direct Lighthouse execution path used by
|
|
44
44
|
* the main verify flow so Windows cleanup behavior is consistent.
|
|
@@ -257,7 +257,7 @@ function compareWithBaseline(viewport, screenshot) {
|
|
|
257
257
|
return { viewport: viewport.name, diffPercent: Math.round(diff * 100) / 100, baselineCreated: false };
|
|
258
258
|
}
|
|
259
259
|
async function runMultiViewportLighthouse(port) {
|
|
260
|
-
console.log("\n
|
|
260
|
+
console.log("\n Running multi-viewport Lighthouse checks (desktop, tablet, mobile)...");
|
|
261
261
|
const tempRoot = path.join(process.cwd(), ".laxy-tmp", `multi-viewport-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
262
262
|
fs.mkdirSync(tempRoot, { recursive: true });
|
|
263
263
|
const results = { desktop: null, tablet: null, mobile: null };
|
|
@@ -299,7 +299,7 @@ function printMultiViewportResults(scores, thresholds) {
|
|
|
299
299
|
tablet: "Tablet",
|
|
300
300
|
mobile: "Mobile",
|
|
301
301
|
};
|
|
302
|
-
console.log("\n
|
|
302
|
+
console.log("\n Multi-viewport results:");
|
|
303
303
|
for (const viewport of labels) {
|
|
304
304
|
const score = scores[viewport];
|
|
305
305
|
if (!score) {
|
|
@@ -329,17 +329,17 @@ function allViewportsPass(scores, thresholds) {
|
|
|
329
329
|
* full multi-viewport overhead. Lets Pro users catch mobile regressions.
|
|
330
330
|
*/
|
|
331
331
|
async function runMobileLighthouse(port) {
|
|
332
|
-
console.log("\n
|
|
332
|
+
console.log("\n Running mobile Lighthouse check...");
|
|
333
333
|
const mobileViewport = VIEWPORTS.find((v) => v.name === "mobile");
|
|
334
334
|
const tempRoot = path.join(process.cwd(), ".laxy-tmp", `mobile-lh-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
335
335
|
fs.mkdirSync(tempRoot, { recursive: true });
|
|
336
336
|
try {
|
|
337
337
|
const scores = await runLighthouseForViewport(port, mobileViewport, tempRoot);
|
|
338
338
|
if (scores) {
|
|
339
|
-
console.log(`
|
|
339
|
+
console.log(` Mobile: P=${scores.performance} A=${scores.accessibility} SEO=${scores.seo} BP=${scores.bestPractices}`);
|
|
340
340
|
}
|
|
341
341
|
else {
|
|
342
|
-
console.log("
|
|
342
|
+
console.log(" Mobile Lighthouse: failed to collect");
|
|
343
343
|
}
|
|
344
344
|
return scores;
|
|
345
345
|
}
|
|
@@ -4,4 +4,5 @@ export interface RuntimeRouteDiscoveryResult {
|
|
|
4
4
|
}
|
|
5
5
|
export declare function extractScriptUrlsFromHtml(html: string, baseUrl: string): string[];
|
|
6
6
|
export declare function extractRoutesFromText(content: string): string[];
|
|
7
|
+
export declare function filterReachableRoutes(baseUrl: string, routes: string[]): Promise<string[]>;
|
|
7
8
|
export declare function discoverRuntimeRoutes(baseUrl: string): Promise<RuntimeRouteDiscoveryResult>;
|
package/dist/route-discovery.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractScriptUrlsFromHtml = extractScriptUrlsFromHtml;
|
|
4
4
|
exports.extractRoutesFromText = extractRoutesFromText;
|
|
5
|
+
exports.filterReachableRoutes = filterReachableRoutes;
|
|
5
6
|
exports.discoverRuntimeRoutes = discoverRuntimeRoutes;
|
|
6
7
|
const SCRIPT_SRC_REGEX = /<script[^>]+src=["']([^"'#?]+(?:\?[^"'#]*)?)["']/gi;
|
|
7
8
|
const HTML_ROUTE_REGEX = /(?:href|data-href)=["'](\/[^"'#? ]*)/gi;
|
|
@@ -72,6 +73,34 @@ function extractRoutesFromText(content) {
|
|
|
72
73
|
}
|
|
73
74
|
return routes;
|
|
74
75
|
}
|
|
76
|
+
async function probeRuntimeRoute(baseUrl, route) {
|
|
77
|
+
try {
|
|
78
|
+
const res = await fetch(new URL(route, baseUrl), {
|
|
79
|
+
signal: AbortSignal.timeout(5000),
|
|
80
|
+
redirect: "follow",
|
|
81
|
+
headers: { accept: "text/html,application/xhtml+xml" },
|
|
82
|
+
});
|
|
83
|
+
if (res.status === 404 || res.status === 410) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
const contentType = res.headers.get("content-type")?.toLowerCase() ?? "";
|
|
87
|
+
if (res.status >= 200 && res.status < 300 && contentType && !contentType.includes("text/html")) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function filterReachableRoutes(baseUrl, routes) {
|
|
97
|
+
const candidates = Array.from(new Set(routes));
|
|
98
|
+
const checks = await Promise.all(candidates.map(async (route) => ({
|
|
99
|
+
route,
|
|
100
|
+
reachable: await probeRuntimeRoute(baseUrl, route),
|
|
101
|
+
})));
|
|
102
|
+
return checks.filter((item) => item.reachable).map((item) => item.route);
|
|
103
|
+
}
|
|
75
104
|
async function discoverRuntimeRoutes(baseUrl) {
|
|
76
105
|
const htmlRes = await fetch(baseUrl, {
|
|
77
106
|
signal: AbortSignal.timeout(8000),
|
|
@@ -101,8 +130,9 @@ async function discoverRuntimeRoutes(baseUrl) {
|
|
|
101
130
|
// Skip chunk fetch failures. This is best-effort coverage expansion.
|
|
102
131
|
}
|
|
103
132
|
}));
|
|
133
|
+
const validatedRoutes = await filterReachableRoutes(baseUrl, Array.from(routes));
|
|
104
134
|
return {
|
|
105
|
-
routes:
|
|
135
|
+
routes: validatedRoutes.sort((a, b) => a.localeCompare(b)),
|
|
106
136
|
scriptUrls,
|
|
107
137
|
};
|
|
108
138
|
}
|