seo-crawl-audit 0.1.0 → 0.7.1

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 CHANGED
@@ -1,47 +1,74 @@
1
1
  # SEO Crawl Audit
2
2
 
3
- `seo-audit` is a local-first command-line crawler that finds current on-page SEO
4
- problems and detects accidental SEO regressions after a site changes.
5
-
6
- It is free and open-source software released under the MIT License. It needs no
7
- account, API key, browser extension, or hosted dashboard. Scan results stay on
8
- your computer in readable JSON and self-contained HTML files.
9
-
10
- ## What you get
11
-
12
- - automatic sitemap and sitemap-index discovery;
13
- - a simple terminal menu for 100 pages, the whole sitemap, batches, or a custom
14
- limit;
15
- - a live progress indicator;
16
- - automatic recovery after an interrupted scan without requesting completed
17
- pages again;
18
- - a partial HTML report that remains useful even if the process is stopped;
19
- - a saved baseline for later regression checks;
20
- - exit codes suitable for CI and pull-request checks.
3
+ Free, open-source, local-first SEO crawler and regression audit tool.
4
+
5
+ `seo-audit` crawls server-rendered pages, saves a versioned JSON snapshot,
6
+ finds concrete SEO problems, and detects regressions after a deployment. It
7
+ needs no account or API key. Page HTML is processed in memory; normalized
8
+ results stay in local JSON, checkpoint, CSV, and self-contained HTML files.
9
+
10
+ [Open the interactive example report](https://melnixdev.github.io/seo-crawl-audit/)
11
+ or [view its source file](examples/quotes-toscrape-report.html).
12
+
13
+ ## Highlights
14
+
15
+ - automatic `robots.txt`, sitemap, sitemap-index, and gzip sitemap support;
16
+ - bounded concurrency with a per-origin request delay;
17
+ - retries for timeouts, `429`, and temporary `5xx`, including `Retry-After`;
18
+ - redirect-chain and redirect-loop detection;
19
+ - 40+ explicit audit and regression rules with stable fingerprints;
20
+ - SnapshotV2 with automatic baseline v1 migration;
21
+ - `new`, `ongoing`, `resolved`, and `unchanged` issue lifecycle;
22
+ - resumable scans and a useful report even after interruption;
23
+ - local SnapshotV2 history with issue, page-count, sitemap, and crawl-depth
24
+ trends—without a hosted account;
25
+ - English/Ukrainian report localization, filters, CSV export, print layout,
26
+ and local report branding;
27
+ - interactive issue statistics for severity, frequent checks, ownership, and
28
+ regression lifecycle, with chart-to-table filtering;
29
+ - a production-versus-preview release guard with safe environment-based
30
+ authentication for private deployments;
31
+ - configuration, suppressions with expiry, severity overrides, and budgets;
32
+ - a self-contained GitHub Action that runs inside the GitHub runner;
33
+ - typed engine API with injectable `fetch`, checkpoints, events, logger, and
34
+ `AbortSignal`.
35
+
36
+ The project intentionally does not calculate an opaque overall SEO score.
37
+ Every finding names the affected URL, rule, risk, evidence, owner, and suggested
38
+ remediation.
21
39
 
22
40
  ## Requirements and installation
23
41
 
24
42
  Node.js 20.19 or newer is required.
25
43
 
44
+ Run without installing:
45
+
26
46
  ```bash
27
- git clone https://github.com/MelnixDev/seo-crawl-audit.git
28
- cd seo-crawl-audit
29
- npm install
30
- npm link
47
+ npx seo-crawl-audit https://example.com/
31
48
  ```
32
49
 
33
- After linking, `seo-audit` can be run from any directory:
50
+ Or install the CLI globally:
34
51
 
35
52
  ```bash
53
+ npm install --global seo-crawl-audit
36
54
  seo-audit --version
37
- seo-audit --help
38
55
  ```
39
56
 
40
- ## Five-minute demo
57
+ For repository development:
58
+
59
+ ```bash
60
+ git clone https://github.com/MelnixDev/seo-crawl-audit.git
61
+ cd seo-crawl-audit
62
+ npm install
63
+ npm run build
64
+ npm link --workspace seo-crawl-audit
65
+ ```
66
+
67
+ ## Five-minute example
41
68
 
42
69
  [Quotes to Scrape](https://quotes.toscrape.com/) is a public educational site
43
- made for crawler practice. It is unrelated to this project and keeps the demo
44
- small and reproducible.
70
+ made for crawler practice. It is unrelated to this project and keeps the
71
+ example small.
45
72
 
46
73
  ```bash
47
74
  seo-audit scan https://quotes.toscrape.com/ \
@@ -51,15 +78,7 @@ seo-audit scan https://quotes.toscrape.com/ \
51
78
  --report quotes-report.html
52
79
  ```
53
80
 
54
- This scans ten internal pages, writes a JSON baseline, and creates a filterable
55
- HTML report. In the current demo scan, the tool finds missing descriptions and
56
- canonical URLs while the pages themselves remain reachable.
57
-
58
- [View the included example report](examples/quotes-toscrape-report.html). GitHub
59
- may show the HTML source; download the file and open it in any browser to use
60
- the filters.
61
-
62
- Run a fresh comparison later:
81
+ Run a comparison later:
63
82
 
64
83
  ```bash
65
84
  seo-audit check https://quotes.toscrape.com/ \
@@ -72,7 +91,7 @@ seo-audit check https://quotes.toscrape.com/ \
72
91
 
73
92
  ### `seo-audit <url>`
74
93
 
75
- A shortcut for `seo-audit scan <url>`.
94
+ Shortcut for `seo-audit scan <url>`.
76
95
 
77
96
  ```bash
78
97
  seo-audit https://example.com/
@@ -80,7 +99,8 @@ seo-audit https://example.com/
80
99
 
81
100
  ### `seo-audit scan <url>`
82
101
 
83
- Crawls a site, audits its current SEO state, and saves a baseline.
102
+ Crawls a site, audits its current state, saves SnapshotV2, and optionally writes
103
+ an HTML report.
84
104
 
85
105
  ```bash
86
106
  seo-audit scan https://example.com/
@@ -88,25 +108,14 @@ seo-audit scan https://example.com/ --pages 250
88
108
  seo-audit scan https://example.com/ --all
89
109
  ```
90
110
 
91
- By default, the command:
92
-
93
- 1. reads `robots.txt`;
94
- 2. looks for a declared sitemap;
95
- 3. tries `/sitemap.xml` and `/sitemap_index.xml` when needed;
96
- 4. shows an interactive scan-size menu when a sitemap is found;
97
- 5. saves the baseline to `.seo-audit.json`;
98
- 6. creates `seo-audit-report.html` in an interactive terminal.
99
-
100
- The menu offers the first 100 pages, the whole sitemap, batches of 100 with
101
- confirmation, or any positive number entered by the user. `--pages` and `--all`
102
- skip that menu.
103
-
104
- If no sitemap is found, the interactive command asks for its full URL. Pressing
105
- Enter instead starts a same-origin internal-link crawl.
111
+ When a sitemap is found in an interactive terminal, the menu offers the first
112
+ 100 pages, every sitemap URL, groups of 100 with confirmation, or a custom
113
+ number. `--pages` and `--all` skip the menu. If no sitemap is found, press Enter
114
+ to continue through same-origin internal links, or enter the full sitemap URL.
106
115
 
107
116
  ### `seo-audit check [url]`
108
117
 
109
- Crawls the site again and compares it with a saved baseline.
118
+ Crawls again and compares the result with a saved baseline.
110
119
 
111
120
  ```bash
112
121
  seo-audit check
@@ -114,236 +123,325 @@ seo-audit check https://preview.example.com/
114
123
  seo-audit check --baseline production-seo.json --strict
115
124
  ```
116
125
 
117
- When `url` is omitted, the source URL stored in the baseline is used. Supplying
118
- a different origin is useful for comparing a production baseline with a preview
119
- deployment; same-path page and canonical URLs are mapped between the origins.
120
-
121
- An error-level regression returns exit code `1`. Warnings also return `1` when
122
- `--strict` is enabled.
126
+ Without `url`, the saved site URL is used. A different origin supports
127
+ production-to-preview comparison while keeping paths comparable. New
128
+ error-level findings return exit code `1`; `--strict` also blocks on warnings.
129
+ If `scan` receives SIGINT or SIGTERM, it flushes completed pages, writes the
130
+ partial snapshot/report, keeps the checkpoint, and exits with code `130`.
123
131
 
124
- ### `seo-audit report [baseline]`
132
+ ### `seo-audit compare`
125
133
 
126
- Builds a new HTML audit report from an existing baseline without crawling or
127
- making network requests.
134
+ Scans production, requests the same paths from a preview deployment, and
135
+ reports only SEO regressions introduced by the preview:
128
136
 
129
137
  ```bash
130
- seo-audit report
131
- seo-audit report quotes-baseline.json
132
- seo-audit report quotes-baseline.json --report quotes-report.html
138
+ seo-audit compare \
139
+ --production https://example.com/ \
140
+ --preview https://preview-123.example.dev/ \
141
+ --pages 100 \
142
+ --report preview-seo-report.html
133
143
  ```
134
144
 
135
- The default input is `.seo-audit.json`, and the default output is
136
- `seo-audit-report.html`.
137
-
138
- ## HTML report
139
-
140
- The report is one portable HTML file with no external scripts, fonts, tracking,
141
- or uploaded data. It contains:
142
-
143
- - summary cards for checked and affected pages;
144
- - error, warning, and informational issue counts;
145
- - free-text search across URL, rule, message, before, and after values;
146
- - severity and rule filters;
147
- - paginated results with 100, 250, or 500 rows per page;
148
- - direct links to affected pages;
149
- - before/after values for regression checks.
150
-
151
- During an interactive `scan`, the report is created immediately and refreshed
152
- after the first completed request batch, then periodically as the scan grows.
153
- Writes are atomic, so interruption should not leave a half-written HTML file.
154
- A partial report clearly says that it contains saved results and shows
155
- `checked / target` page counts.
156
-
157
- Interactive `scan` and `check` commands create the default report automatically.
158
- For scripts or CI, request one explicitly:
145
+ For a protected deployment, put request headers in an environment variable as
146
+ a JSON object and pass only its name on the command line:
159
147
 
160
148
  ```bash
161
- seo-audit scan https://example.com/ --report audit.html
162
- seo-audit check --report regressions.html
149
+ export SEO_AUDIT_PREVIEW_HEADERS='{"Authorization":"Bearer …"}'
150
+ seo-audit compare \
151
+ --production https://example.com/ \
152
+ --preview https://preview-123.example.dev/ \
153
+ --preview-headers-env SEO_AUDIT_PREVIEW_HEADERS
163
154
  ```
164
155
 
165
- Use `--no-report` when an HTML file is not needed.
166
-
167
- ## Resume cache and interrupted scans
156
+ Header values are used only by the injected request adapter. They are never
157
+ written to snapshots, reports, checkpoints, or JSON output. Errors block the
158
+ release with exit code `1`; `--strict` also blocks on warnings.
168
159
 
169
- `scan` stores completed page results in an append-only checkpoint beside the
170
- baseline. For the default `.seo-audit.json` output, its name is:
171
-
172
- ```text
173
- .seo-audit.checkpoint.ndjson
174
- ```
160
+ ### `seo-audit report [baseline]`
175
161
 
176
- The checkpoint contains extracted SEO results and discovered links, not full
177
- page HTML. Each completed request batch is appended immediately. If the process
178
- is stopped, run the same compatible scan command again:
162
+ Regenerates HTML from a saved snapshot without network requests.
179
163
 
180
164
  ```bash
181
- seo-audit https://example.com/ --all
165
+ seo-audit report
166
+ seo-audit report quotes-baseline.json --report quotes-report.html
182
167
  ```
183
168
 
184
- The command reports how many pages were recovered and skips those URLs. A cache
185
- is reused only when the start URL, sitemap, query-string policy, and robots
186
- policy match. An incompatible checkpoint is replaced automatically.
169
+ ### `seo-audit history [url]`
187
170
 
188
- The checkpoint is removed after the requested scan target finishes. It remains
189
- when a stepped scan is stopped before its target or when the process is
190
- interrupted. Checkpoint files are excluded by the included `.gitignore`.
191
-
192
- To deliberately make every request without reading or writing a checkpoint:
171
+ Lists locally saved runs and creates an HTML report with issue trends:
193
172
 
194
173
  ```bash
195
- seo-audit https://example.com/ --pages 100 --no-cache
174
+ seo-audit history https://example.com/
175
+ seo-audit history --history-dir ./audit-history --report history.html
196
176
  ```
197
177
 
198
- `check` never reuses the scan checkpoint because regression detection must
199
- observe the current response from every baseline URL.
200
-
201
- ## What is checked
178
+ Compare any two saved runs without crawling:
202
179
 
203
- The baseline audit reports:
204
-
205
- - unreachable pages and request failures;
206
- - HTTP 4xx and 5xx responses;
207
- - pages blocked by `robots.txt`;
208
- - missing titles;
209
- - missing meta descriptions;
210
- - missing canonical URLs;
211
- - missing or multiple H1 headings;
212
- - `noindex` directives.
213
-
214
- The regression check detects:
215
-
216
- - a previously working page becoming unavailable;
217
- - a new `noindex` directive;
218
- - a page becoming blocked by `robots.txt`;
219
- - a removed or changed title;
220
- - a removed meta description;
221
- - a removed or changed canonical URL;
222
- - a removed H1 heading;
223
- - a changed redirect destination;
224
- - a changed `robots.txt`.
225
-
226
- Errors represent changes or conditions likely to block indexing or make a page
227
- unusable. Warnings identify important metadata problems. Informational findings
228
- are worth reviewing but do not fail a normal check.
180
+ ```bash
181
+ seo-audit history \
182
+ --from .seo-audit/history/older.snapshot.json \
183
+ --to .seo-audit/history/newer.snapshot.json \
184
+ --report selected-runs.html
185
+ ```
229
186
 
230
187
  ## Options
231
188
 
232
189
  ```text
233
190
  --baseline <file> Baseline input for check (default: .seo-audit.json)
234
- --output <file> Baseline output for scan (default: .seo-audit.json)
191
+ --config <file> Config file (default: seo-audit.config.json)
192
+ --output <file> Snapshot output for scan (default: .seo-audit.json)
235
193
  --report <file> HTML report output (default: seo-audit-report.html)
236
194
  --no-report Disable automatic HTML report generation
237
- --no-cache Disable scan checkpoint caching and resume
195
+ --no-cache Disable checkpoint caching and resume
238
196
  --pages <number> Scan an exact number of pages
239
197
  --all Scan every URL found in the sitemap
240
198
  --max-pages <number> Deprecated alias for --pages
241
199
  --concurrency <number> Concurrent requests (default: 5)
200
+ --delay <ms> Delay between starts per origin (default: 100)
242
201
  --timeout <ms> Per-request timeout (default: 10000)
243
202
  --sitemap <url> Use a specific sitemap or sitemap index
244
203
  --no-sitemap Skip sitemap discovery and crawl internal links
245
204
  --include-query Treat query-string URLs as separate pages
246
205
  --ignore-robots Ignore robots.txt disallow rules
247
206
  --strict Fail check on warnings as well as errors
248
- --json Print machine-readable command output
249
- --help Show command help
207
+ --production <url> Production URL for compare
208
+ --preview <url> Preview deployment URL for compare
209
+ --production-headers-env <name>
210
+ Read production headers from a JSON environment variable
211
+ --preview-headers-env <name>
212
+ Read preview headers from a JSON environment variable
213
+ --history-dir <path> Local snapshot history directory
214
+ --no-history Do not save this scan to local history
215
+ --from <snapshot> Older snapshot for an explicit history comparison
216
+ --to <snapshot> Newer snapshot for an explicit history comparison
217
+ --json Print machine-readable output
218
+ --help Show help
250
219
  --version Show the installed version
251
220
  ```
252
221
 
253
- `--sitemap` and `--no-sitemap` cannot be used together. `--all` requires a
254
- sitemap because an internal-link crawl cannot know the site's complete URL set
255
- in advance.
256
-
257
- ## Files
258
-
259
- ### Baseline JSON
260
-
261
- The baseline records the source configuration, sitemap and robots metadata, and
262
- the extracted SEO state of every scanned page. It is deterministic enough to
263
- review and commit when a team wants versioned regression protection.
222
+ ## Configuration
223
+
224
+ The repository includes [`seo-audit.config.json`](seo-audit.config.json) and a
225
+ [JSON Schema](packages/core/config.schema.json). CLI flags take precedence over
226
+ the config file, which takes precedence over the saved baseline, followed by
227
+ safe defaults.
228
+
229
+ ```json
230
+ {
231
+ "$schema": "./packages/core/config.schema.json",
232
+ "url": "https://example.com/",
233
+ "sitemap": "auto",
234
+ "maxPages": 100,
235
+ "concurrency": 5,
236
+ "delay": 100,
237
+ "timeout": 10000,
238
+ "respectRobots": true,
239
+ "includeQuery": false,
240
+ "enabledRules": null,
241
+ "severityOverrides": {
242
+ "low-word-count": "info"
243
+ },
244
+ "suppressions": [
245
+ {
246
+ "rule": "missing-description",
247
+ "urlPattern": "/legal/**",
248
+ "reason": "Description intentionally omitted",
249
+ "expiresAt": "2027-01-01"
250
+ }
251
+ ],
252
+ "regressionBudgets": {
253
+ "error": 0,
254
+ "warning": 3
255
+ },
256
+ "report": {
257
+ "agencyName": "Example Agency",
258
+ "logo": "https://example.com/logo.png",
259
+ "primaryColor": "#3157d5"
260
+ }
261
+ }
262
+ ```
264
263
 
265
- ### Checkpoint NDJSON
264
+ Expired suppressions stop matching automatically, so the issue appears again.
265
+ `urlPattern` accepts `*` within one path segment and `**` across segments.
266
266
 
267
- The checkpoint is temporary recovery state. It is append-only for efficient
268
- large scans and should not be committed.
267
+ ## What is checked
269
268
 
270
- ### HTML report
269
+ The rule registry covers access and HTTP failures, meta and header `noindex`,
270
+ robots blocking, canonical validity and targets, redirects, sitemap consistency,
271
+ titles, descriptions, headings, internal links, orphan pages, hreflang,
272
+ structured data syntax, language declarations, exact duplicate content, social
273
+ metadata, image alt attributes, and low word count.
274
+
275
+ Regression-only checks cover newly introduced indexing blocks, status changes,
276
+ metadata removal or edits, redirect changes, robots changes, and sharp sitemap
277
+ URL loss.
278
+
279
+ [Read the rule-by-rule documentation](https://github.com/MelnixDev/seo-crawl-audit/blob/main/docs/rules.md) ·
280
+ [Українська версія довідника правил](https://github.com/MelnixDev/seo-crawl-audit/blob/main/docs/rules.uk.md).
281
+
282
+ ## Snapshots, lifecycle, and checkpoints
283
+
284
+ SnapshotV2 records schema, engine and rule-set versions, generated time, config
285
+ hash, robots and sitemap state, normalized page data, link-graph summary, and
286
+ crawl statistics. Baseline schema v1 is upgraded automatically when read.
287
+
288
+ Issues use a stable 24-character fingerprint. A comparison separates:
289
+
290
+ - `newIssues` — absent from the previous snapshot;
291
+ - `ongoingIssues` — same issue identity with changed evidence;
292
+ - `resolvedIssues` — present before but absent now;
293
+ - `unchangedIssues` — same identity and evidence.
294
+
295
+ `DiffResult.complete` is `false` for partial or truncated comparisons. Findings
296
+ on checked pages are still reported, while unchecked pages are not falsely
297
+ classified as missing or resolved.
298
+
299
+ During `scan`, completed normalized page results are appended to
300
+ `.seo-audit.checkpoint.ndjson`. Full HTML is not cached. Running the same
301
+ compatible command resumes without requesting saved pages again. The checkpoint
302
+ is removed after completion and retained after interruption. Successful pages
303
+ are reused; transient failures and HTTP 5xx results are refreshed. Only an
304
+ unfinished final NDJSON record is recoverable—earlier corruption is reported.
305
+
306
+ Every completed scan and check also saves a full SnapshotV2 beside the selected
307
+ baseline under `.seo-audit/history/`. Use `--history-dir` to move it or
308
+ `--no-history` for an ephemeral run. History is read directly from disk; no URL,
309
+ snapshot, or metric is uploaded. The HTML trend view shows errors, warnings,
310
+ informational findings, new/resolved issues, page count, sitemap count, and
311
+ maximum crawl depth.
312
+
313
+ ## HTML and CSV report
314
+
315
+ The portable HTML file contains no external script, font, or tracking request.
316
+ It includes:
317
+
318
+ - an `English / Українська` language selector that translates the complete
319
+ interface, rule names, findings, remediation, lifecycle labels, and CSV;
320
+ - dependency-free interactive charts for severity, most frequent checks,
321
+ inferred page templates, and owner or regression-lifecycle distribution;
322
+ selecting a chart item filters
323
+ the issue table and selecting it again clears that filter;
324
+ - summary cards and partial-scan state;
325
+ - current and lifecycle tabs;
326
+ - severity, rule, inferred template, owner, and URL/text filters;
327
+ - evidence, before/after values, remediation, and fingerprint;
328
+ - engine and rule-set versions;
329
+ - client-side CSV export;
330
+ - print-friendly PDF layout;
331
+ - optional local agency name, logo, and primary color;
332
+ - clear clean-report and no-filter-match states.
333
+
334
+ Template grouping turns repeated findings such as `/products/red-shoe` and
335
+ `/products/blue-shirt` into `/products/:slug`. Numeric IDs, UUIDs, dates, and
336
+ hashes have stable placeholders. Grouping changes only presentation: rule IDs,
337
+ evidence, snapshots, and issue fingerprints remain unchanged.
338
+
339
+ Writes are atomic. Interactive scans refresh a partial report while results are
340
+ being checkpointed, so interruption does not discard already completed work.
341
+
342
+ ## GitHub Action
343
+
344
+ The Action crawls entirely inside the GitHub runner. It produces exit status,
345
+ JSON summary, HTML report, job summary, and annotations for error-level
346
+ regressions.
271
347
 
272
- The report is a self-contained human-readable artifact. It can be opened
273
- locally, attached to a CI run, or shared as a file without exposing data to a
274
- third-party service.
348
+ ```yaml
349
+ name: SEO regression check
350
+ on: [pull_request]
275
351
 
276
- ## JSON output and exit codes
352
+ jobs:
353
+ seo:
354
+ runs-on: ubuntu-latest
355
+ steps:
356
+ - uses: actions/checkout@v4
357
+ - uses: MelnixDev/seo-crawl-audit@v0
358
+ id: seo
359
+ with:
360
+ url: https://preview.example.com/
361
+ baseline: .seo-audit.json
362
+ config: seo-audit.config.json
363
+ fail-on: error
364
+ report: seo-audit-report.html
365
+ - uses: actions/upload-artifact@v4
366
+ with:
367
+ name: seo-crawl-audit-report
368
+ path: |
369
+ ${{ steps.seo.outputs.report }}
370
+ ${{ steps.seo.outputs.summary }}
371
+ ```
277
372
 
278
- Use `--json` for automation:
373
+ [Read all Action inputs and outputs](docs/github-action.md).
279
374
 
280
- ```bash
281
- seo-audit check --json
282
- ```
375
+ ## Core API and repository structure
283
376
 
284
- Exit codes:
377
+ The npm workspace separates reusable concerns:
285
378
 
286
379
  ```text
287
- 0 Scan completed, or no blocking regressions were found
288
- 1 SEO regressions were found
289
- 2 Invalid input, invalid baseline, or crawler failure
380
+ packages/core @seo-crawl-audit/core — scan, audit, diff, snapshots, reports
381
+ packages/cli seo-crawl-audit — terminal UX and npm executable
382
+ packages/action @seo-crawl-audit/action — GitHub runner entry point
290
383
  ```
291
384
 
292
- With `--strict`, warnings count as blocking regressions for exit code `1`.
385
+ ```ts
386
+ import { audit, planScan, scan } from "@seo-crawl-audit/core";
387
+ import { createFileCheckpointStore } from "@seo-crawl-audit/core/node";
293
388
 
294
- ## GitHub Actions example
389
+ const plan = await planScan({ url: "https://example.com/", maxPages: 100 });
390
+ const result = await scan(plan, {
391
+ signal: controller.signal,
392
+ checkpointStore: createFileCheckpointStore(".seo-audit.checkpoint.ndjson"),
393
+ onEvent(event) {
394
+ if (event.type === "progress") console.log(event.completed, event.total);
395
+ },
396
+ });
295
397
 
296
- Create and commit a production baseline first. Then compare a deployed preview
297
- in pull requests:
398
+ const issues = audit(result.snapshot);
399
+ ```
298
400
 
299
- ```yaml
300
- name: SEO regression check
401
+ [Read the typed public API guide](docs/public-api.md) and the
402
+ [architecture notes](docs/architecture.md).
301
403
 
302
- on:
303
- pull_request:
404
+ ## Responsible crawling and safe defaults
304
405
 
305
- jobs:
306
- seo:
307
- runs-on: ubuntu-latest
308
- steps:
309
- - uses: actions/checkout@v4
310
- - uses: actions/setup-node@v4
311
- with:
312
- node-version: 22
313
- - run: npm ci
314
- - run: node bin/seo-audit.js check https://preview.example.com/ --strict --json
315
- ```
406
+ - delay: `100 ms` between request starts per origin;
407
+ - concurrency: `5`;
408
+ - timeout: `10 s`;
409
+ - `robots.txt` respected;
410
+ - same-origin page discovery;
411
+ - query strings excluded;
412
+ - HTML limited to `5 MiB` and robots to `512 KiB`;
413
+ - retries are bounded and use exponential backoff with jitter;
414
+ - `Retry-After` is honored up to 30 seconds;
415
+ - redirects are limited to 10.
316
416
 
317
- Add `--report seo-regressions.html` and upload that file as a workflow artifact
318
- when a human-readable CI report is useful.
417
+ Use a longer delay for a small site or a server you do not control:
319
418
 
320
- ## Responsible crawling
419
+ ```bash
420
+ seo-audit https://example.com/ --delay 500
421
+ ```
321
422
 
322
- The crawler respects `robots.txt` by default, stays on the starting origin,
323
- limits concurrency to five requests, and applies a ten-second timeout. Increase
324
- concurrency carefully and only on sites you are allowed to test.
423
+ Use `--delay 0` only for local or explicitly controlled fixtures.
325
424
 
326
425
  ## Current scope
327
426
 
328
- - server-rendered HTML only; JavaScript rendering is not included;
329
- - same-origin crawling;
330
- - sitemap and sitemap-index seeding;
331
- - query strings are removed by default to avoid crawl traps;
332
- - non-HTML URLs are not parsed for SEO metadata;
333
- - HTML responses are limited to 5 MiB;
334
- - `robots.txt` responses are limited to 512 KiB;
335
- - authenticated pages are not supported.
427
+ - server-rendered HTML; JavaScript rendering is not included;
428
+ - same-origin crawl discovery;
429
+ - sitemap and internal-link seeding;
430
+ - non-HTML responses are recorded but not parsed for page metadata;
431
+ - authenticated pages are not supported;
432
+ - results are normalized and ordered for reproducible output.
336
433
 
337
434
  ## Development
338
435
 
339
436
  ```bash
437
+ npm run build
340
438
  npm test
341
439
  npm run check
342
440
  ```
343
441
 
344
- The test suite covers crawling, robots rules, sitemap discovery, URL mapping,
345
- baseline comparisons, checkpoint recovery, HTML report safety, filtering data,
346
- and CLI behavior.
442
+ CI covers Node.js 20, 22, and 24. Tests use local HTTP fixtures for robots,
443
+ sitemaps, gzip, retries, redirects, cancellation, checkpoint resume, migration,
444
+ rules, lifecycle, HTML escaping, npm packaging, and the GitHub Action.
347
445
 
348
446
  ## License
349
447