laxy-verify 1.1.22 → 1.1.24
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 +186 -144
- package/dist/audit/broken-links.d.ts +21 -0
- package/dist/audit/broken-links.js +86 -0
- package/dist/cli.js +72 -68
- package/dist/comment.js +21 -15
- package/dist/crawler.js +3 -3
- package/dist/e2e.d.ts +2 -1
- package/dist/e2e.js +9 -9
- package/dist/entitlement.d.ts +4 -7
- package/dist/entitlement.js +16 -42
- package/dist/report-markdown.js +3 -7
- package/dist/status.js +4 -2
- package/dist/verification-core/report.js +18 -52
- package/dist/verification-core/tier-policy.js +8 -8
- package/dist/verification-core/types.d.ts +7 -2
- package/package.json +67 -67
package/README.md
CHANGED
|
@@ -1,123 +1,156 @@
|
|
|
1
1
|
# laxy-verify
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`laxy-verify` is a deployment blocker gate for frontend apps.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
**Free**: Run verification manually on any project.
|
|
6
|
+
**Pro**: GitHub Actions auto-runs it on every PR + Slack/Discord alerts when things break.
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
Run it on a frontend app:
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
|
|
13
|
+
cd your-project
|
|
14
14
|
npx laxy-verify .
|
|
15
|
-
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Generate config plus GitHub Actions workflow:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx laxy-verify --init
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That creates:
|
|
24
|
+
|
|
25
|
+
- `.laxy.yml`
|
|
26
|
+
- `.github/workflows/laxy-verify.yml`
|
|
27
|
+
|
|
28
|
+
Log in to unlock Pro features:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
16
31
|
npx laxy-verify login
|
|
17
32
|
npx laxy-verify whoami
|
|
18
|
-
npx laxy-verify --help
|
|
19
33
|
```
|
|
20
34
|
|
|
21
|
-
|
|
35
|
+
For CI, set `LAXY_TOKEN` instead of interactive login:
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
env:
|
|
39
|
+
LAXY_TOKEN: ${{ secrets.LAXY_TOKEN }}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Why laxy-verify?
|
|
43
|
+
|
|
44
|
+
Most teams already have some mix of:
|
|
45
|
+
|
|
46
|
+
- `npm run build`
|
|
47
|
+
- Lighthouse
|
|
48
|
+
- Playwright or smoke checks
|
|
49
|
+
- CI status rules
|
|
50
|
+
|
|
51
|
+
The gap is not "can these tools exist together?" The gap is "who turns that pile of output into a safe merge or release decision?"
|
|
52
|
+
|
|
53
|
+
`laxy-verify` gives you:
|
|
54
|
+
|
|
55
|
+
- one command instead of a custom build plus audit plus smoke-check script stack
|
|
56
|
+
- one result file instead of scattered logs
|
|
57
|
+
- one blocker-first decision instead of "build passed, but do we actually trust this release?"
|
|
58
|
+
- **Pro**: GitHub Actions auto-run + Slack/Discord alerts so you never miss a failure
|
|
59
|
+
|
|
60
|
+
This is most useful if you ship frontend apps and want a practical gate before:
|
|
61
|
+
|
|
62
|
+
- merge
|
|
63
|
+
- client review
|
|
64
|
+
- QA handoff
|
|
65
|
+
- production release
|
|
66
|
+
|
|
67
|
+
## The failures it is meant to catch
|
|
68
|
+
|
|
69
|
+
Use `laxy-verify` when you want to catch things like:
|
|
22
70
|
|
|
71
|
+
- the production build passes locally but fails in CI
|
|
72
|
+
- the app opens, but a key button or form flow is broken
|
|
73
|
+
- desktop looks fine, but a mobile CTA is pushed out of view
|
|
74
|
+
- Lighthouse looks acceptable, but the user-visible path is still not safe to ship
|
|
75
|
+
- a PR needs a clear hold reason instead of a vague "something failed"
|
|
76
|
+
|
|
77
|
+
## What it actually checks
|
|
78
|
+
|
|
79
|
+
A standard run includes:
|
|
80
|
+
|
|
81
|
+
- production build success
|
|
82
|
+
- Lighthouse thresholds (3 runs for stable evidence)
|
|
83
|
+
- E2E scenarios for user-visible flows
|
|
84
|
+
- multi-viewport checks (desktop, tablet, mobile)
|
|
85
|
+
- blocker-aware reporting and release decisions
|
|
86
|
+
|
|
87
|
+
With a logged-in account, additional checks run:
|
|
88
|
+
|
|
89
|
+
- security audit
|
|
90
|
+
- visual diff evidence
|
|
91
|
+
- stability pass (second E2E run)
|
|
92
|
+
|
|
93
|
+
## What you get from one run
|
|
94
|
+
|
|
95
|
+
- a release decision such as `quick-pass`, `client-ready`, `release-ready`, `hold`, or `investigate`
|
|
23
96
|
- a verification grade: `Gold`, `Silver`, `Bronze`, or `Unverified`
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
- `laxy-verify-report.md` on paid plans for human review and AI handoff
|
|
97
|
+
- `.laxy-result.json` for CI and automation
|
|
98
|
+
- `laxy-verify-report.md` for human review and AI handoff
|
|
27
99
|
|
|
28
|
-
|
|
100
|
+
Grades still exist, but they are not the main point. The main point is whether the run found blockers you should stop on.
|
|
29
101
|
|
|
30
|
-
|
|
102
|
+
## Quick start
|
|
103
|
+
|
|
104
|
+
Run it on a frontend app:
|
|
31
105
|
|
|
32
106
|
```bash
|
|
33
107
|
cd your-project
|
|
34
108
|
npx laxy-verify .
|
|
35
109
|
```
|
|
36
110
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
### 2. Generate config and CI workflow
|
|
111
|
+
Generate config plus CI workflow:
|
|
40
112
|
|
|
41
113
|
```bash
|
|
42
114
|
npx laxy-verify --init
|
|
43
115
|
```
|
|
44
116
|
|
|
45
|
-
|
|
117
|
+
That creates:
|
|
46
118
|
|
|
47
119
|
- `.laxy.yml`
|
|
48
120
|
- `.github/workflows/laxy-verify.yml`
|
|
49
121
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
Once committed, each PR gets a verification run, grade output, and optional GitHub reporting.
|
|
53
|
-
|
|
54
|
-
### 4. Unlock paid plan features
|
|
122
|
+
Log in to unlock paid plan features:
|
|
55
123
|
|
|
56
124
|
```bash
|
|
57
125
|
npx laxy-verify login
|
|
58
126
|
npx laxy-verify whoami
|
|
59
127
|
```
|
|
60
128
|
|
|
61
|
-
For CI, set `LAXY_TOKEN` instead of
|
|
129
|
+
For CI, set `LAXY_TOKEN` instead of interactive login:
|
|
62
130
|
|
|
63
131
|
```yaml
|
|
64
132
|
env:
|
|
65
133
|
LAXY_TOKEN: ${{ secrets.LAXY_TOKEN }}
|
|
66
134
|
```
|
|
67
135
|
|
|
68
|
-
##
|
|
69
|
-
|
|
70
|
-
- production build success
|
|
71
|
-
- Lighthouse thresholds
|
|
72
|
-
- verify E2E scenarios for real user flows
|
|
73
|
-
- Pro+ viewport and visual regression evidence
|
|
74
|
-
- plan-aware verdicts for local runs and CI
|
|
75
|
-
|
|
76
|
-
## Verification Tiers
|
|
77
|
-
|
|
78
|
-
| Plan | Question it answers |
|
|
79
|
-
|------|---------------------|
|
|
80
|
-
| Free | Any critical issues right now? |
|
|
81
|
-
| Pro | Ready to show a client? |
|
|
82
|
-
| Pro+ | Ready for production? |
|
|
83
|
-
|
|
84
|
-
## Grades
|
|
85
|
-
|
|
86
|
-
| Grade | Meaning |
|
|
87
|
-
|-------|---------|
|
|
88
|
-
| Gold | Build passed + E2E passed + Lighthouse passed + Pro+ viewport evidence passed |
|
|
89
|
-
| Silver | Build passed + E2E passed |
|
|
90
|
-
| Bronze | Build passed |
|
|
91
|
-
| Unverified | Build failed |
|
|
92
|
-
|
|
93
|
-
## Plan Differences
|
|
94
|
-
|
|
95
|
-
| Feature | Free | Pro | Pro+ |
|
|
96
|
-
|---------|------|-----|------|
|
|
97
|
-
| Build verification | Yes | Yes | Yes |
|
|
98
|
-
| Lighthouse | 1 run | 3 runs | 3 runs |
|
|
99
|
-
| Verify E2E | Smoke checks | Client-facing flow checks | Client-facing flow checks + release evidence |
|
|
100
|
-
| Detailed report view | No | Yes | Yes |
|
|
101
|
-
| `laxy-verify-report.md` export | No | Yes | Yes |
|
|
102
|
-
| Multi-viewport verification | No | No | Yes |
|
|
103
|
-
| Visual diff | No | No | Yes |
|
|
104
|
-
| Failure analysis signals | No | No | Yes |
|
|
136
|
+
## Example workflow
|
|
105
137
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
138
|
+
1. Run `npx laxy-verify .` locally before opening or merging a PR.
|
|
139
|
+
2. Fix broken builds, broken flows, and visible regressions.
|
|
140
|
+
3. Commit `.laxy.yml`.
|
|
141
|
+
4. Run `npx laxy-verify --init`.
|
|
142
|
+
5. Let the GitHub Action apply the same gate on every PR.
|
|
109
143
|
|
|
110
|
-
##
|
|
144
|
+
## Example output
|
|
111
145
|
|
|
112
146
|
```text
|
|
113
|
-
|
|
147
|
+
Decision: release-ready
|
|
114
148
|
Grade: Gold
|
|
115
|
-
Verdict: release-ready
|
|
116
149
|
|
|
117
150
|
Passed:
|
|
118
151
|
- production build
|
|
119
152
|
- Lighthouse thresholds
|
|
120
|
-
- core
|
|
153
|
+
- core user flows
|
|
121
154
|
- desktop, tablet, and mobile viewport checks
|
|
122
155
|
|
|
123
156
|
Artifacts:
|
|
@@ -125,19 +158,46 @@ Artifacts:
|
|
|
125
158
|
- laxy-verify-report.md
|
|
126
159
|
```
|
|
127
160
|
|
|
128
|
-
##
|
|
161
|
+
## The decision it helps you make
|
|
129
162
|
|
|
130
|
-
|
|
163
|
+
`laxy-verify` answers a delivery decision, not just a score:
|
|
164
|
+
|
|
165
|
+
- Would this break for real users right now?
|
|
166
|
+
- What would block a client demo or QA handoff?
|
|
167
|
+
- Is there enough evidence to merge or release with confidence?
|
|
168
|
+
|
|
169
|
+
Log in to unlock deeper checks (security audit, visual diff, stability pass).
|
|
170
|
+
|
|
171
|
+
## Grades
|
|
172
|
+
|
|
173
|
+
| Grade | Meaning |
|
|
174
|
+
|---|---|
|
|
175
|
+
| Gold | Build passed, E2E passed, Lighthouse passed, and release-level evidence passed |
|
|
176
|
+
| Silver | Build passed and E2E passed |
|
|
177
|
+
| Bronze | Build passed |
|
|
178
|
+
| Unverified | Build failed |
|
|
179
|
+
|
|
180
|
+
Default Lighthouse thresholds:
|
|
181
|
+
|
|
182
|
+
- Performance `>= 70`
|
|
183
|
+
- Accessibility `>= 85`
|
|
184
|
+
- SEO `>= 80`
|
|
185
|
+
- Best Practices `>= 80`
|
|
186
|
+
|
|
187
|
+
## Config
|
|
188
|
+
|
|
189
|
+
All fields in `.laxy.yml` are optional.
|
|
131
190
|
|
|
132
191
|
```yaml
|
|
133
|
-
framework:
|
|
192
|
+
framework: auto
|
|
134
193
|
build_command: ""
|
|
135
194
|
dev_command: ""
|
|
136
|
-
package_manager:
|
|
195
|
+
package_manager: auto
|
|
137
196
|
port: 3000
|
|
138
197
|
build_timeout: 300
|
|
139
198
|
dev_timeout: 60
|
|
140
199
|
lighthouse_runs: 1
|
|
200
|
+
fail_on: bronze
|
|
141
201
|
|
|
142
202
|
thresholds:
|
|
143
203
|
performance: 70
|
|
@@ -145,16 +205,21 @@ thresholds:
|
|
|
145
205
|
seo: 80
|
|
146
206
|
best_practices: 80
|
|
147
207
|
|
|
148
|
-
|
|
208
|
+
crawl: false
|
|
209
|
+
max_crawl_depth: 3
|
|
210
|
+
max_crawl_pages: 10
|
|
211
|
+
browsers:
|
|
212
|
+
- chromium
|
|
149
213
|
```
|
|
150
214
|
|
|
151
|
-
|
|
215
|
+
Useful adjustments:
|
|
152
216
|
|
|
153
|
-
- raise `fail_on`
|
|
154
|
-
- set `
|
|
155
|
-
- increase `lighthouse_runs`
|
|
217
|
+
- raise `fail_on` in CI when you want stricter gates
|
|
218
|
+
- set `build_command` or `dev_command` if auto-detection is not enough
|
|
219
|
+
- increase `lighthouse_runs` for more stable performance evidence
|
|
220
|
+
- point the CLI at the actual app directory in a monorepo
|
|
156
221
|
|
|
157
|
-
## CLI
|
|
222
|
+
## CLI
|
|
158
223
|
|
|
159
224
|
```text
|
|
160
225
|
npx laxy-verify [project-dir]
|
|
@@ -165,7 +230,7 @@ Options:
|
|
|
165
230
|
--config <path>
|
|
166
231
|
--fail-on unverified|bronze|silver|gold
|
|
167
232
|
--skip-lighthouse
|
|
168
|
-
--plan-override free|pro|
|
|
233
|
+
--plan-override free|pro|team
|
|
169
234
|
--badge
|
|
170
235
|
--init
|
|
171
236
|
--multi-viewport
|
|
@@ -177,87 +242,64 @@ Subcommands:
|
|
|
177
242
|
whoami
|
|
178
243
|
```
|
|
179
244
|
|
|
180
|
-
`--plan-override` is for downgrade testing only.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
Each run writes `.laxy-result.json`.
|
|
187
|
-
|
|
188
|
-
Paid plans also write a readable markdown summary to `laxy-verify-report.md`.
|
|
189
|
-
|
|
190
|
-
- `Pro`: client-ready delivery report
|
|
191
|
-
- `Pro+`: release-readiness report with viewport and visual evidence
|
|
192
|
-
|
|
193
|
-
Exit behavior follows the verification verdict, not just the legacy grade.
|
|
194
|
-
|
|
195
|
-
- `build-failed` -> exit 1
|
|
196
|
-
- `hold` -> exit 1
|
|
197
|
-
- `Pro+ investigate` -> exit 1
|
|
198
|
-
- plain lower-tier pass states can still exit 0
|
|
199
|
-
|
|
200
|
-
```json
|
|
201
|
-
{
|
|
202
|
-
"grade": "Gold",
|
|
203
|
-
"timestamp": "2026-04-09T09:00:00Z",
|
|
204
|
-
"build": { "success": true, "durationMs": 12000, "errors": [] },
|
|
205
|
-
"e2e": { "passed": 5, "failed": 0, "total": 5, "results": [] },
|
|
206
|
-
"lighthouse": { "performance": 82, "accessibility": 94, "seo": 90, "bestPractices": 92, "runs": 3 },
|
|
207
|
-
"multiViewport": {
|
|
208
|
-
"allPassed": true,
|
|
209
|
-
"summary": "Desktop, tablet, and mobile checks passed."
|
|
210
|
-
},
|
|
211
|
-
"visualDiff": {
|
|
212
|
-
"verdict": "pass",
|
|
213
|
-
"differencePercentage": 0
|
|
214
|
-
},
|
|
215
|
-
"verification": {
|
|
216
|
-
"tier": "pro_plus",
|
|
217
|
-
"report": { "verdict": "release-ready" }
|
|
218
|
-
},
|
|
219
|
-
"exitCode": 0,
|
|
220
|
-
"_plan": "pro_plus"
|
|
221
|
-
}
|
|
222
|
-
```
|
|
245
|
+
`--plan-override` is for downgrade testing only. It can simulate lower tiers, but it will not let you exceed your real entitlement.
|
|
246
|
+
|
|
247
|
+
## Result files
|
|
248
|
+
|
|
249
|
+
Every run writes `.laxy-result.json`.
|
|
223
250
|
|
|
224
|
-
|
|
251
|
+
When the run finds something worth reviewing, it also writes `laxy-verify-report.md`.
|
|
225
252
|
|
|
226
|
-
|
|
253
|
+
Typical use:
|
|
254
|
+
|
|
255
|
+
- `.laxy-result.json` for CI parsing and machine decisions
|
|
256
|
+
- `laxy-verify-report.md` for human review, PR discussion, or AI-assisted fixes
|
|
257
|
+
|
|
258
|
+
The markdown report is designed to be readable and easy to paste into coding tools.
|
|
227
259
|
|
|
228
260
|
It includes:
|
|
229
261
|
|
|
230
|
-
- the main decision in plain English
|
|
262
|
+
- the main stop-or-ship decision in plain English
|
|
231
263
|
- what passed
|
|
232
264
|
- blockers and warnings
|
|
233
265
|
- exact verification evidence
|
|
234
|
-
- failed
|
|
235
|
-
- a `Copy For AI` section
|
|
266
|
+
- failed scenarios
|
|
267
|
+
- a `Copy For AI` section
|
|
268
|
+
|
|
269
|
+
## What this is good at
|
|
270
|
+
|
|
271
|
+
Use `laxy-verify` when you want:
|
|
272
|
+
|
|
273
|
+
- a merge or release gate for frontend apps
|
|
274
|
+
- one repeatable command for build plus audit plus visible-flow verification
|
|
275
|
+
- a decision that non-authors can understand
|
|
276
|
+
- JSON output for automation without building your own wrapper
|
|
236
277
|
|
|
237
|
-
##
|
|
278
|
+
## What this is not
|
|
238
279
|
|
|
239
|
-
-
|
|
240
|
-
- Monorepos should point `laxy-verify` at the actual app directory.
|
|
241
|
-
- `playwright` is optional. The CLI can run without it.
|
|
242
|
-
- Pro+ viewport and visual checks increase runtime.
|
|
280
|
+
`laxy-verify` is not trying to replace:
|
|
243
281
|
|
|
244
|
-
|
|
282
|
+
- your full Playwright suite
|
|
283
|
+
- deep visual QA by designers
|
|
284
|
+
- production observability
|
|
285
|
+
- manual exploratory testing
|
|
245
286
|
|
|
246
|
-
|
|
247
|
-
They intentionally break build, navigation, coverage, performance, viewport behavior, and visual stability so the verifier can be tested against known failure modes.
|
|
287
|
+
It is a pre-merge and pre-release verification layer, not your entire quality system.
|
|
248
288
|
|
|
249
289
|
## Limitations
|
|
250
290
|
|
|
251
|
-
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
291
|
+
- monorepos should target the real app subdirectory
|
|
292
|
+
- dev-server-based Lighthouse is not identical to production hosting
|
|
293
|
+
- visual diff and viewport checks increase runtime
|
|
294
|
+
- best stability is on current LTS Node releases
|
|
295
|
+
|
|
296
|
+
## Requirements
|
|
297
|
+
|
|
298
|
+
- Node `>=20.18.0 <25`
|
|
299
|
+
- a frontend app with a runnable build flow
|
|
300
|
+
- optional: `playwright` if your project already uses it
|
|
255
301
|
|
|
256
302
|
## Links
|
|
257
303
|
|
|
258
304
|
- GitHub: https://github.com/SUNgm24/Laxy/tree/main/laxy-verify
|
|
259
305
|
- Issues: https://github.com/SUNgm24/Laxy/issues
|
|
260
|
-
|
|
261
|
-
## License
|
|
262
|
-
|
|
263
|
-
MIT
|
|
@@ -0,0 +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>;
|
|
@@ -0,0 +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
|
+
}
|