pagegraph 0.5.1 → 0.6.0
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 +80 -4
- package/dist/audit.d.ts +5 -0
- package/dist/audit.js +3 -0
- package/dist/audit.js.map +1 -1
- package/dist/{graph-BlLoEOw2.d.ts → checks-BfsQtKga.d.ts} +43 -2
- package/dist/cli.js +11388 -2897
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +7 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +131 -14
- package/dist/index.js +172 -4
- package/dist/index.js.map +1 -1
- package/dist/links-sGbLkl-7.js +184 -0
- package/dist/links-sGbLkl-7.js.map +1 -0
- package/package.json +13 -8
package/README.md
CHANGED
|
@@ -22,8 +22,7 @@ flowchart LR
|
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
24
|
bun add pagegraph
|
|
25
|
-
bun add -D
|
|
26
|
-
bun add -D lighthouse # only for `pagegraph audit` performance evidence
|
|
25
|
+
bun add -D lighthouse # only for `pagegraph audit` performance evidence
|
|
27
26
|
```
|
|
28
27
|
|
|
29
28
|
| Entry | Exports | Peers |
|
|
@@ -33,11 +32,16 @@ bun add -D lighthouse # only for `pagegraph audit` performanc
|
|
|
33
32
|
| `pagegraph/vite` | `seoRouteConfig` coverage gate | `vite` |
|
|
34
33
|
| `pagegraph/config` | `defineSeoConfig`, `viteGraphLoader` | `vite` |
|
|
35
34
|
| `pagegraph/audit` | Audit services, scanner protocol, rules, and report schemas | `effect` |
|
|
36
|
-
| `pagegraph` bin
|
|
35
|
+
| `pagegraph` bin | CLI over the same graph | bundled — runs on Bun |
|
|
37
36
|
|
|
38
37
|
The core and React entries have **zero runtime dependencies** — everything above is a
|
|
39
38
|
peer, and only the entries you import need theirs installed.
|
|
40
39
|
|
|
40
|
+
The CLI **bundles Effect and the TypeSafe provider**, so it needs no Effect peers and does not
|
|
41
|
+
depend on the app's Effect RC; it runs on [Bun](https://bun.sh) (`bunx pagegraph`). `vite` stays a
|
|
42
|
+
peer — the graph commands load your app through Vite at runtime — and `lighthouse` is only needed by
|
|
43
|
+
`pagegraph audit`. Importing `pagegraph/audit` programmatically still needs `effect`.
|
|
44
|
+
|
|
41
45
|
## Quick start
|
|
42
46
|
|
|
43
47
|
**1. Bind your site identity once.** Route files never see an origin or a brand name.
|
|
@@ -245,7 +249,7 @@ severities:
|
|
|
245
249
|
- **structural** — internally broken declarations; these fail `pagegraph check` (exit 1):
|
|
246
250
|
dead or duplicate edges, path collisions, a redirect in the sitemap, a
|
|
247
251
|
sitemap/noindex contradiction, a `related` target with no `link` card, an
|
|
248
|
-
instance without a title.
|
|
252
|
+
instance without a title, or an unmet contextual-link coverage rule.
|
|
249
253
|
- **editorial** — quality smells, reported but non-failing: duplicate or mis-sized
|
|
250
254
|
titles and descriptions.
|
|
251
255
|
|
|
@@ -285,6 +289,8 @@ export default defineSeoConfig({
|
|
|
285
289
|
origin: "https://example.com",
|
|
286
290
|
disallow: routeConfig.robotsExclusions,
|
|
287
291
|
contentSignal: "search=yes, ai-input=yes, ai-train=yes",
|
|
292
|
+
// Fail `check` unless each named money page has enough contextual links.
|
|
293
|
+
coverage: [{ path: "/pricing", minInbound: 2 }, { path: "/features/*", minInbound: 1 }],
|
|
288
294
|
loadGraph: viteGraphLoader({
|
|
289
295
|
root: import.meta.dirname,
|
|
290
296
|
entry: "/lib/seo/graph.ts",
|
|
@@ -298,12 +304,82 @@ pagegraph check # CI gate — exit 1 on structural violations
|
|
|
298
304
|
pagegraph graph # the graph as a tree · --format mermaid | json
|
|
299
305
|
pagegraph inspect /pricing # one node: policy, sitemap status, in/out edges
|
|
300
306
|
pagegraph inspect <url> --live # fetch a deployed page, validate its rendered <head>
|
|
307
|
+
pagegraph links verify <url> # crawl served HTML: depth, orphans, declared-vs-rendered
|
|
308
|
+
pagegraph links candidates # propose contextual links from the declared graph
|
|
309
|
+
pagegraph links decide <file> # answer typed link questions with Jev (TYPESAFE_API_KEY)
|
|
301
310
|
pagegraph sitemap # print sitemap.xml
|
|
302
311
|
pagegraph robots # print robots.txt
|
|
303
312
|
```
|
|
304
313
|
|
|
305
314
|
Stdout is data, stderr is status — `pagegraph check --json | jq` just works.
|
|
306
315
|
|
|
316
|
+
### Verify the rendered link graph
|
|
317
|
+
|
|
318
|
+
`pagegraph links verify` crawls a site's served HTML — through the same DNS-pinned,
|
|
319
|
+
private-IP-blocked HTTP path as `pagegraph audit` — and reports what a crawler
|
|
320
|
+
actually receives: the real homepage depth, the pages with no incoming internal
|
|
321
|
+
edge (rendered orphans), and, when the app has a `seo.config.ts`, the
|
|
322
|
+
declared-vs-rendered link diff. It is bounded with `--limit` and needs no
|
|
323
|
+
framework or config:
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
pagegraph links verify https://example.com
|
|
327
|
+
pagegraph links verify https://example.com --limit 25 --json | jq
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Propose contextual links
|
|
331
|
+
|
|
332
|
+
`pagegraph links candidates` reads the declared graph and proposes
|
|
333
|
+
`(source, destination)` pairs that are plausible contextual links and not already
|
|
334
|
+
connected. Pages are grouped into clusters — a shared top-level section, or the
|
|
335
|
+
same `kind` for root-level pages — and only sitemap-eligible pages are proposed.
|
|
336
|
+
Pairs already declared as a `related` edge are excluded, and `--rendered` accepts
|
|
337
|
+
a JSON dump of already-served anchors (`[{ from, to }]` or `{ edges: [...] }`) to
|
|
338
|
+
exclude those too.
|
|
339
|
+
|
|
340
|
+
The output is a reviewable plan: human text by default, versioned JSON with
|
|
341
|
+
`--json`. Nothing is applied. `--limit` and `--cluster` bound the plan, and
|
|
342
|
+
`--decide` optionally hands the candidates to Jev for a recommendation and
|
|
343
|
+
confidence per pair (requires `TYPESAFE_API_KEY`):
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
pagegraph links candidates
|
|
347
|
+
pagegraph links candidates --cluster blog --limit 20
|
|
348
|
+
pagegraph links candidates --rendered rendered.json --json | jq
|
|
349
|
+
pagegraph links candidates --decide
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### Decide with Jev
|
|
353
|
+
|
|
354
|
+
`pagegraph links decide` answers two typed questions per candidate — *does the source have a genuine
|
|
355
|
+
reason to link to the destination?* and *is descriptive anchor text already in the copy?* — and
|
|
356
|
+
routes anything inside the confidence band `(1−t, t)` to a `review` bucket instead of auto-applying.
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
pagegraph links decide candidates.json --threshold 0.9
|
|
360
|
+
pagegraph links decide candidates.json --threshold 0.9 --json | jq
|
|
361
|
+
cat candidates.json | pagegraph links decide
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Decisions run through [Jev](https://typesafe.ai) via `@effect/ai-typesafe` (model `jev-latest`),
|
|
365
|
+
bundled into the CLI. The API key is read from the **`TYPESAFE_API_KEY` environment variable** — it
|
|
366
|
+
is deliberately not a `seo.config.ts` field, so keys never live in the repo; `seo.config.ts` carries
|
|
367
|
+
non-secret decision settings only. Without a key the command exits 1 with a clear message and an
|
|
368
|
+
empty stdout.
|
|
369
|
+
|
|
370
|
+
### Contextual-link coverage
|
|
371
|
+
|
|
372
|
+
Declare a contextual-link coverage policy in `seo.config.ts` (a `coverage` array
|
|
373
|
+
of `{ path, minInbound }`), or pass repeatable `--require-inbound "<path-glob>=<n>"`
|
|
374
|
+
flags to override it for one run. `pagegraph check` then fails (exit 1) unless
|
|
375
|
+
every sitemap-eligible page matching the glob has at least `minInbound` incoming
|
|
376
|
+
`related` edges. `*` matches within a path segment and `**` crosses segments; a
|
|
377
|
+
rule that matches no sitemap-eligible page is itself a violation.
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
pagegraph check --require-inbound "/pricing=2" --require-inbound "/features/*=1"
|
|
381
|
+
```
|
|
382
|
+
|
|
307
383
|
### Audit any website
|
|
308
384
|
|
|
309
385
|
`pagegraph audit` is framework-independent and does not need `seo.config.ts`. It
|
package/dist/audit.d.ts
CHANGED
|
@@ -98,6 +98,11 @@ interface ProbeOptions {
|
|
|
98
98
|
readonly timeoutMs: number;
|
|
99
99
|
readonly maxBodyBytes: number;
|
|
100
100
|
readonly allowPrivate: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* Capture resolved anchors from HTML bodies. Opt-in because it is the only
|
|
103
|
+
* probe field that grows with document size; the audit report leaves it unset.
|
|
104
|
+
*/
|
|
105
|
+
readonly captureAnchors?: boolean | undefined;
|
|
101
106
|
}
|
|
102
107
|
interface AuditOptions {
|
|
103
108
|
readonly concurrency?: number;
|
package/dist/audit.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { r as extractAnchors } from "./links-sGbLkl-7.js";
|
|
1
2
|
import { mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import * as Context from "effect/Context";
|
|
@@ -976,6 +977,7 @@ const probeHttp = async (request, options) => {
|
|
|
976
977
|
} : await readBoundedBody(response, options.maxBodyBytes);
|
|
977
978
|
const contentType = typeof response.headers["content-type"] === "string" ? response.headers["content-type"] : "";
|
|
978
979
|
const body = new TextDecoder().decode(bounded.bytes);
|
|
980
|
+
const anchors = options.captureAnchors === true && contentType.includes("text/html") ? extractAnchors(body, currentUrl.href) : void 0;
|
|
979
981
|
return {
|
|
980
982
|
kind: request.kind,
|
|
981
983
|
method: request.method,
|
|
@@ -993,6 +995,7 @@ const probeHttp = async (request, options) => {
|
|
|
993
995
|
bodyTruncated: bounded.truncated,
|
|
994
996
|
bodyExcerpt: bodyExcerpt(body, contentType),
|
|
995
997
|
document: body.length === 0 ? null : extractDocumentSignals(body, contentType, currentUrl),
|
|
998
|
+
anchors,
|
|
996
999
|
error: null
|
|
997
1000
|
};
|
|
998
1001
|
} catch (error) {
|