laxy-verify 1.2.0 → 1.2.2

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.
Files changed (40) hide show
  1. package/README.md +12 -17
  2. package/dist/audit/broken-links.d.ts +21 -21
  3. package/dist/audit/broken-links.js +86 -86
  4. package/dist/auth.d.ts +11 -11
  5. package/dist/auth.js +222 -222
  6. package/dist/cli.js +868 -806
  7. package/dist/comment.d.ts +21 -21
  8. package/dist/comment.js +125 -125
  9. package/dist/config.d.ts +13 -0
  10. package/dist/config.js +43 -3
  11. package/dist/crawler.d.ts +36 -36
  12. package/dist/crawler.js +357 -357
  13. package/dist/e2e.d.ts +49 -49
  14. package/dist/e2e.js +565 -565
  15. package/dist/entitlement.d.ts +11 -11
  16. package/dist/entitlement.js +90 -90
  17. package/dist/init.js +87 -87
  18. package/dist/multi-viewport.d.ts +31 -31
  19. package/dist/multi-viewport.js +298 -298
  20. package/dist/playwright-runner.d.ts +16 -16
  21. package/dist/playwright-runner.js +208 -208
  22. package/dist/report-markdown.d.ts +39 -39
  23. package/dist/report-markdown.js +386 -386
  24. package/dist/security-audit.d.ts +9 -9
  25. package/dist/security-audit.js +64 -64
  26. package/dist/serve.d.ts +13 -13
  27. package/dist/serve.js +196 -196
  28. package/dist/trend.d.ts +50 -50
  29. package/dist/trend.js +148 -148
  30. package/dist/verification-core/index.d.ts +3 -3
  31. package/dist/verification-core/index.js +19 -19
  32. package/dist/verification-core/report.d.ts +14 -14
  33. package/dist/verification-core/report.js +409 -409
  34. package/dist/verification-core/tier-policy.d.ts +13 -13
  35. package/dist/verification-core/tier-policy.js +60 -60
  36. package/dist/verification-core/types.d.ts +108 -108
  37. package/dist/verification-core/types.js +2 -2
  38. package/dist/visual-diff.d.ts +26 -26
  39. package/dist/visual-diff.js +178 -178
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -63,23 +63,18 @@ This is most useful if you ship frontend apps and want a practical gate before:
63
63
  - QA handoff
64
64
  - production release
65
65
 
66
- ## vs other tools
67
-
68
- No single competitor covers this combination. Here is where each tool stops:
69
-
70
- | | laxy-verify | LHCI | Checkly | Percy / Argos | Unlighthouse | QA Wolf |
71
- |--|--|--|--|--|--|--|
72
- | Build failure detection | yes | no | no | no | no | no |
73
- | Auto-generated E2E | yes | no | write your own | no | no | yes |
74
- | Security audit | yes | no | no | no | no | no |
75
- | Lighthouse (multi-run avg) | yes | yes | no | no | yes | no |
76
- | Visual diff | yes | no | no | yes | no | no |
77
- | Multi-viewport checks | yes | no | separate config | no | yes | no |
78
- | Ship/hold release decision | yes | score only | no | no | no | no |
79
- | Zero config to start | yes | no | no | no | partial | no |
80
- | Free full coverage | yes | yes | limited | limited | yes | enterprise |
81
-
82
- LHCI gives you Lighthouse. Percy gives you visual diffs. Checkly watches your production uptime. None of them produce a merge or release decision from one command.
66
+ ## Why not just LHCI?
67
+
68
+ | | laxy-verify | LHCI | Checkly | Percy |
69
+ |--|--|--|--|--|
70
+ | Production build failure detection | Yes | No | No | No |
71
+ | User-flow E2E verification | Yes | No | Manual setup | No |
72
+ | Lighthouse scoring | Yes | Yes | No | No |
73
+ | Visual regression check | Yes | No | No | Yes |
74
+ | Release decision (`hold` / `client-ready`) | Yes | No, score only | No | No |
75
+ | Zero-config local start | Yes | No | No | No |
76
+
77
+ LHCI measures Lighthouse. `laxy-verify` is for deciding whether this frontend is actually safe to ship.
83
78
 
84
79
  ## The failures it is meant to catch
85
80
 
@@ -1,21 +1,21 @@
1
- /**
2
- * Broken-links audit.
3
- * Uses the crawl result to find all internal links, then validates each
4
- * with an HTTP HEAD/GET request. Links that return 4xx/5xx or timeout are
5
- * reported as blockers.
6
- */
7
- import { type CrawlResult } from "../crawler.js";
8
- export interface BrokenLink {
9
- url: string;
10
- path: string;
11
- status: number;
12
- statusText: string;
13
- severity: "critical" | "high";
14
- }
15
- export interface BrokenLinksResult {
16
- brokenLinks: BrokenLink[];
17
- checkedCount: number;
18
- hasBrokenLinks: boolean;
19
- summary: string;
20
- }
21
- export declare function auditBrokenLinks(crawlResult: CrawlResult, baseUrl: string, abortSignal?: AbortSignal): Promise<BrokenLinksResult>;
1
+ /**
2
+ * Broken-links audit.
3
+ * Uses the crawl result to find all internal links, then validates each
4
+ * with an HTTP HEAD/GET request. Links that return 4xx/5xx or timeout are
5
+ * reported as blockers.
6
+ */
7
+ import { type CrawlResult } from "../crawler.js";
8
+ export interface BrokenLink {
9
+ url: string;
10
+ path: string;
11
+ status: number;
12
+ statusText: string;
13
+ severity: "critical" | "high";
14
+ }
15
+ export interface BrokenLinksResult {
16
+ brokenLinks: BrokenLink[];
17
+ checkedCount: number;
18
+ hasBrokenLinks: boolean;
19
+ summary: string;
20
+ }
21
+ export declare function auditBrokenLinks(crawlResult: CrawlResult, baseUrl: string, abortSignal?: AbortSignal): Promise<BrokenLinksResult>;
@@ -1,86 +1,86 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.auditBrokenLinks = auditBrokenLinks;
4
- const TIMEOUT_MS = 5000;
5
- const VALID_OK_STATUS = [200, 201, 202, 203, 204, 301, 302, 303, 307, 308];
6
- function isSuccessStatus(n) {
7
- return VALID_OK_STATUS.includes(n);
8
- }
9
- async function auditBrokenLinks(crawlResult, baseUrl, abortSignal) {
10
- const origin = new URL(baseUrl).origin;
11
- const allUrls = [];
12
- for (const page of crawlResult.pages) {
13
- for (const href of page.internalLinks) {
14
- try {
15
- const url = new URL(href, baseUrl).href;
16
- if (!allUrls.includes(url))
17
- allUrls.push(url);
18
- }
19
- catch {
20
- // skip malformed URLs
21
- }
22
- }
23
- }
24
- const uniqueUrls = allUrls;
25
- const brokenLinks = [];
26
- await Promise.all(uniqueUrls.map(async (url) => {
27
- if (abortSignal?.aborted)
28
- return;
29
- try {
30
- const controller = new AbortController();
31
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
32
- let status = 0;
33
- let statusText = "";
34
- try {
35
- const res = await fetch(url, {
36
- method: "HEAD",
37
- redirect: "follow",
38
- signal: controller.signal,
39
- });
40
- status = res.status;
41
- statusText = res.statusText;
42
- }
43
- catch {
44
- // Fall back to GET if HEAD is not allowed
45
- const controller2 = new AbortController();
46
- const timer2 = setTimeout(() => controller2.abort(), TIMEOUT_MS);
47
- try {
48
- const res = await fetch(url, {
49
- method: "GET",
50
- redirect: "follow",
51
- signal: controller2.signal,
52
- });
53
- status = res.status;
54
- statusText = res.statusText;
55
- }
56
- catch {
57
- status = 0;
58
- statusText = "timeout or network error";
59
- }
60
- clearTimeout(timer2);
61
- }
62
- clearTimeout(timer);
63
- if (!isSuccessStatus(status)) {
64
- const severity = status >= 500 ? "critical" : "high";
65
- let path = url;
66
- try {
67
- path = new URL(url).pathname;
68
- }
69
- catch { }
70
- brokenLinks.push({ url, path, status, statusText, severity });
71
- }
72
- }
73
- catch {
74
- // skip
75
- }
76
- }));
77
- const summary = brokenLinks.length === 0
78
- ? `All ${uniqueUrls.length} links OK`
79
- : `${brokenLinks.length} broken link(s) found`;
80
- return {
81
- brokenLinks,
82
- checkedCount: uniqueUrls.length,
83
- hasBrokenLinks: brokenLinks.length > 0,
84
- summary,
85
- };
86
- }
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.auditBrokenLinks = auditBrokenLinks;
4
+ const TIMEOUT_MS = 5000;
5
+ const VALID_OK_STATUS = [200, 201, 202, 203, 204, 301, 302, 303, 307, 308];
6
+ function isSuccessStatus(n) {
7
+ return VALID_OK_STATUS.includes(n);
8
+ }
9
+ async function auditBrokenLinks(crawlResult, baseUrl, abortSignal) {
10
+ const origin = new URL(baseUrl).origin;
11
+ const allUrls = [];
12
+ for (const page of crawlResult.pages) {
13
+ for (const href of page.internalLinks) {
14
+ try {
15
+ const url = new URL(href, baseUrl).href;
16
+ if (!allUrls.includes(url))
17
+ allUrls.push(url);
18
+ }
19
+ catch {
20
+ // skip malformed URLs
21
+ }
22
+ }
23
+ }
24
+ const uniqueUrls = allUrls;
25
+ const brokenLinks = [];
26
+ await Promise.all(uniqueUrls.map(async (url) => {
27
+ if (abortSignal?.aborted)
28
+ return;
29
+ try {
30
+ const controller = new AbortController();
31
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
32
+ let status = 0;
33
+ let statusText = "";
34
+ try {
35
+ const res = await fetch(url, {
36
+ method: "HEAD",
37
+ redirect: "follow",
38
+ signal: controller.signal,
39
+ });
40
+ status = res.status;
41
+ statusText = res.statusText;
42
+ }
43
+ catch {
44
+ // Fall back to GET if HEAD is not allowed
45
+ const controller2 = new AbortController();
46
+ const timer2 = setTimeout(() => controller2.abort(), TIMEOUT_MS);
47
+ try {
48
+ const res = await fetch(url, {
49
+ method: "GET",
50
+ redirect: "follow",
51
+ signal: controller2.signal,
52
+ });
53
+ status = res.status;
54
+ statusText = res.statusText;
55
+ }
56
+ catch {
57
+ status = 0;
58
+ statusText = "timeout or network error";
59
+ }
60
+ clearTimeout(timer2);
61
+ }
62
+ clearTimeout(timer);
63
+ if (!isSuccessStatus(status)) {
64
+ const severity = status >= 500 ? "critical" : "high";
65
+ let path = url;
66
+ try {
67
+ path = new URL(url).pathname;
68
+ }
69
+ catch { }
70
+ brokenLinks.push({ url, path, status, statusText, severity });
71
+ }
72
+ }
73
+ catch {
74
+ // skip
75
+ }
76
+ }));
77
+ const summary = brokenLinks.length === 0
78
+ ? `All ${uniqueUrls.length} links OK`
79
+ : `${brokenLinks.length} broken link(s) found`;
80
+ return {
81
+ brokenLinks,
82
+ checkedCount: uniqueUrls.length,
83
+ hasBrokenLinks: brokenLinks.length > 0,
84
+ summary,
85
+ };
86
+ }
package/dist/auth.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- export declare const LAXY_API_URL: string;
2
- export declare function loadToken(): string | null;
3
- export declare function saveToken(token: string, email: string, expiresInSec: number): void;
4
- export declare function clearToken(): void;
5
- export declare function whoami(): void;
6
- /**
7
- * Get or create a stable repo_id for the given project directory.
8
- * Stored in ~/.laxy/repos.json keyed by absolute project path.
9
- */
10
- export declare function getOrCreateRepoId(projectDir: string): string;
11
- export declare function login(emailArg?: string): Promise<void>;
1
+ export declare const LAXY_API_URL: string;
2
+ export declare function loadToken(): string | null;
3
+ export declare function saveToken(token: string, email: string, expiresInSec: number): void;
4
+ export declare function clearToken(): void;
5
+ export declare function whoami(): void;
6
+ /**
7
+ * Get or create a stable repo_id for the given project directory.
8
+ * Stored in ~/.laxy/repos.json keyed by absolute project path.
9
+ */
10
+ export declare function getOrCreateRepoId(projectDir: string): string;
11
+ export declare function login(emailArg?: string): Promise<void>;