@jdevalk/astro-seo-graph 0.7.0 → 0.9.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/AGENTS.md +167 -0
- package/README.md +35 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/indexnow.d.ts +2 -3
- package/dist/indexnow.d.ts.map +1 -1
- package/dist/indexnow.js +2 -2
- package/dist/indexnow.js.map +1 -1
- package/dist/integration.d.ts +58 -0
- package/dist/integration.d.ts.map +1 -1
- package/dist/integration.js +122 -14
- package/dist/integration.js.map +1 -1
- package/dist/llms-txt.d.ts +51 -0
- package/dist/llms-txt.d.ts.map +1 -0
- package/dist/llms-txt.js +53 -0
- package/dist/llms-txt.js.map +1 -0
- package/package.json +2 -2
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,35 @@ Two packages:
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
+
## Contents
|
|
28
|
+
|
|
29
|
+
**Schema core** — concepts and builders for the JSON-LD graph:
|
|
30
|
+
- [Architecture](#architecture)
|
|
31
|
+
- [Installation](#installation)
|
|
32
|
+
- [The @id system](#the-id-system)
|
|
33
|
+
- [Piece builders reference](#piece-builders-reference)
|
|
34
|
+
|
|
35
|
+
**Recipes and patterns** — how to model real sites:
|
|
36
|
+
- [Site type recipes](#site-type-recipes)
|
|
37
|
+
- [Trust and credibility signals](#trust-and-credibility-signals)
|
|
38
|
+
- [Choosing the right Article subtype](#choosing-the-right-article-subtype)
|
|
39
|
+
- [Actions: telling agents what they can do](#actions-telling-agents-what-they-can-do)
|
|
40
|
+
- [Multi-type entities](#multi-type-entities)
|
|
41
|
+
- [Rich Organization patterns](#rich-organization-patterns)
|
|
42
|
+
- [Rich Person patterns](#rich-person-patterns)
|
|
43
|
+
- [Reference implementations](#reference-implementations)
|
|
44
|
+
|
|
45
|
+
**Astro integration** — runtime component and build-time checks:
|
|
46
|
+
- [Astro integration guide](#astro-integration-guide) — the `<Seo>` component, hreflang, schema endpoints
|
|
47
|
+
- [Build-time integration](#build-time-integration) — `seoGraph()` hook, H1 validation, metadata uniqueness, IndexNow, `llms.txt`
|
|
48
|
+
- [Complete integration example](#complete-integration-example)
|
|
49
|
+
- [Advanced patterns](#advanced-patterns)
|
|
50
|
+
- [Common mistakes](#common-mistakes)
|
|
51
|
+
- [Validating your output](#validating-your-output)
|
|
52
|
+
- [Repository structure](#repository-structure)
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
27
56
|
## Architecture
|
|
28
57
|
|
|
29
58
|
```
|
|
@@ -2576,6 +2605,144 @@ const links = buildAlternateLinks({
|
|
|
2576
2605
|
|
|
2577
2606
|
---
|
|
2578
2607
|
|
|
2608
|
+
## Build-time integration
|
|
2609
|
+
|
|
2610
|
+
`@jdevalk/astro-seo-graph/integration` exports a default `seoGraph()`
|
|
2611
|
+
function that returns an Astro integration. It hooks `astro:build:done`
|
|
2612
|
+
to run cross-page SEO checks and optional post-build actions against the
|
|
2613
|
+
static HTML output. SSR pages aren't on disk at build time, so they're
|
|
2614
|
+
not checked.
|
|
2615
|
+
|
|
2616
|
+
```js
|
|
2617
|
+
// astro.config.mjs
|
|
2618
|
+
import { defineConfig } from 'astro/config';
|
|
2619
|
+
import seoGraph from '@jdevalk/astro-seo-graph/integration';
|
|
2620
|
+
|
|
2621
|
+
export default defineConfig({
|
|
2622
|
+
site: 'https://example.com',
|
|
2623
|
+
integrations: [
|
|
2624
|
+
seoGraph({
|
|
2625
|
+
// All options below are optional; listed with defaults.
|
|
2626
|
+
validateH1: true,
|
|
2627
|
+
validateUniqueMetadata: true,
|
|
2628
|
+
// indexNow: { ... },
|
|
2629
|
+
// llmsTxt: { ... },
|
|
2630
|
+
}),
|
|
2631
|
+
],
|
|
2632
|
+
});
|
|
2633
|
+
```
|
|
2634
|
+
|
|
2635
|
+
### Options
|
|
2636
|
+
|
|
2637
|
+
| Option | Default | Purpose |
|
|
2638
|
+
| ------------------------ | ------- | -------------------------------------------------------------------------- |
|
|
2639
|
+
| `validateH1` | `true` | Warn when a page has zero or >1 `<h1>` elements. |
|
|
2640
|
+
| `validateUniqueMetadata` | `true` | Warn when two pages share the same `<title>` or meta description. |
|
|
2641
|
+
| `indexNow` | — | Submit built URLs to IndexNow. Omit to disable. |
|
|
2642
|
+
| `llmsTxt` | — | Generate `llms.txt` at the root of the build output. Omit to disable. |
|
|
2643
|
+
|
|
2644
|
+
### H1 and metadata validation
|
|
2645
|
+
|
|
2646
|
+
`validateH1` flags pages with missing or duplicate `<h1>` elements — the
|
|
2647
|
+
most common on-page SEO/accessibility miss. `validateUniqueMetadata`
|
|
2648
|
+
flags `<title>` or `<meta name="description">` values that repeat across
|
|
2649
|
+
pages; duplicates hurt Google's ability to pick a canonical result and
|
|
2650
|
+
can only be spotted across the whole corpus.
|
|
2651
|
+
|
|
2652
|
+
Both extractors are exported for reuse:
|
|
2653
|
+
|
|
2654
|
+
```ts
|
|
2655
|
+
import { countH1s, extractTitle, extractMetaDescription } from '@jdevalk/astro-seo-graph/integration';
|
|
2656
|
+
```
|
|
2657
|
+
|
|
2658
|
+
### IndexNow submission
|
|
2659
|
+
|
|
2660
|
+
```js
|
|
2661
|
+
seoGraph({
|
|
2662
|
+
indexNow: {
|
|
2663
|
+
key: process.env.INDEXNOW_KEY!, // 8–128 hex chars
|
|
2664
|
+
host: 'example.com',
|
|
2665
|
+
siteUrl: 'https://example.com',
|
|
2666
|
+
// keyLocation?: defaults to https://<host>/<key>.txt
|
|
2667
|
+
// endpoint?: defaults to api.indexnow.org
|
|
2668
|
+
// filter?: (url) => boolean — drop URLs before submission
|
|
2669
|
+
},
|
|
2670
|
+
});
|
|
2671
|
+
```
|
|
2672
|
+
|
|
2673
|
+
Only URLs on `host` are submitted. `index.html` paths are rewritten to
|
|
2674
|
+
their trailing-slash form.
|
|
2675
|
+
|
|
2676
|
+
**Deploy the key file first.** IndexNow verifies ownership by fetching
|
|
2677
|
+
`https://<host>/<key>.txt` on every submission. Submissions sent before
|
|
2678
|
+
the key is reachable in production get rejected (HTTP 403) and the key
|
|
2679
|
+
is treated as invalid — you'll have to rotate it. Serve the key via
|
|
2680
|
+
`createIndexNowKeyRoute` (see below), deploy, confirm the `.txt` loads
|
|
2681
|
+
over HTTPS, *then* enable `indexNow` in the integration.
|
|
2682
|
+
|
|
2683
|
+
```ts
|
|
2684
|
+
// src/pages/[your-key-here].txt.ts
|
|
2685
|
+
import { createIndexNowKeyRoute } from '@jdevalk/astro-seo-graph';
|
|
2686
|
+
|
|
2687
|
+
export const GET = createIndexNowKeyRoute({ key: 'your-key-here' });
|
|
2688
|
+
```
|
|
2689
|
+
|
|
2690
|
+
The filename (minus `.txt.ts`) must equal the key.
|
|
2691
|
+
|
|
2692
|
+
### llms.txt generation
|
|
2693
|
+
|
|
2694
|
+
Generates an [`llms.txt`](https://llmstxt.org) file — a markdown summary
|
|
2695
|
+
of the site that LLMs can use as a concise entry point.
|
|
2696
|
+
|
|
2697
|
+
```js
|
|
2698
|
+
seoGraph({
|
|
2699
|
+
llmsTxt: {
|
|
2700
|
+
title: 'Example Site',
|
|
2701
|
+
siteUrl: 'https://example.com',
|
|
2702
|
+
summary: 'A demo site about X, Y, and Z.',
|
|
2703
|
+
// details?: extra paragraphs between summary and sections
|
|
2704
|
+
// sections?: user-supplied sections (skips auto-collection)
|
|
2705
|
+
// filter?: (url) => boolean — drop URLs from auto-section
|
|
2706
|
+
// autoSectionName?: defaults to 'Pages'
|
|
2707
|
+
// outputPath?: defaults to 'llms.txt'
|
|
2708
|
+
},
|
|
2709
|
+
});
|
|
2710
|
+
```
|
|
2711
|
+
|
|
2712
|
+
By default, one "Pages" section is auto-generated from every built HTML
|
|
2713
|
+
file's `<title>` + meta description. Supply `sections` to take full
|
|
2714
|
+
control of the structure:
|
|
2715
|
+
|
|
2716
|
+
```js
|
|
2717
|
+
llmsTxt: {
|
|
2718
|
+
title: 'Example Site',
|
|
2719
|
+
siteUrl: 'https://example.com',
|
|
2720
|
+
sections: [
|
|
2721
|
+
{
|
|
2722
|
+
name: 'Docs',
|
|
2723
|
+
links: [
|
|
2724
|
+
{ url: 'https://example.com/docs/intro/', title: 'Intro', description: 'Start here' },
|
|
2725
|
+
],
|
|
2726
|
+
},
|
|
2727
|
+
{ name: 'Blog', links: [/* ... */] },
|
|
2728
|
+
],
|
|
2729
|
+
}
|
|
2730
|
+
```
|
|
2731
|
+
|
|
2732
|
+
The renderer is also exported for non-Astro contexts:
|
|
2733
|
+
|
|
2734
|
+
```ts
|
|
2735
|
+
import { renderLlmsTxt } from '@jdevalk/astro-seo-graph';
|
|
2736
|
+
|
|
2737
|
+
const markdown = renderLlmsTxt({
|
|
2738
|
+
title: 'Example',
|
|
2739
|
+
summary: 'A demo site.',
|
|
2740
|
+
sections: [{ name: 'Pages', links: [{ url: 'https://x/', title: 'Home' }] }],
|
|
2741
|
+
});
|
|
2742
|
+
```
|
|
2743
|
+
|
|
2744
|
+
---
|
|
2745
|
+
|
|
2579
2746
|
## Complete integration example
|
|
2580
2747
|
|
|
2581
2748
|
Here's how joost.blog wires everything together. Use this as a reference
|
package/README.md
CHANGED
|
@@ -339,8 +339,12 @@ actions. Currently:
|
|
|
339
339
|
|
|
340
340
|
- Warns about built pages with zero or more than one `<h1>` element (a
|
|
341
341
|
common SEO and accessibility issue).
|
|
342
|
+
- Warns about duplicate `<title>` or meta description values across the
|
|
343
|
+
built corpus.
|
|
342
344
|
- Optionally submits built URLs to [IndexNow](https://www.indexnow.org)
|
|
343
345
|
after the build completes.
|
|
346
|
+
- Optionally generates an [`llms.txt`](https://llmstxt.org) file at the
|
|
347
|
+
root of the build output, summarising the site for LLMs.
|
|
344
348
|
|
|
345
349
|
```js
|
|
346
350
|
// astro.config.mjs
|
|
@@ -362,16 +366,25 @@ export default defineConfig({
|
|
|
362
366
|
|
|
363
367
|
Options:
|
|
364
368
|
|
|
365
|
-
| Prop
|
|
366
|
-
|
|
|
367
|
-
| `validateH1`
|
|
368
|
-
| `
|
|
369
|
+
| Prop | Default | Description |
|
|
370
|
+
| ------------------------ | ------- | --------------------------------------------------------------- |
|
|
371
|
+
| `validateH1` | `true` | Warn about pages without exactly one `<h1>` |
|
|
372
|
+
| `validateUniqueMetadata` | `true` | Warn about duplicate `<title>` or meta description across pages |
|
|
373
|
+
| `indexNow` | — | Submit built URLs to IndexNow. See below for sub-options. |
|
|
374
|
+
| `llmsTxt` | — | Generate `llms.txt` at the build root. See below for sub-options. |
|
|
369
375
|
|
|
370
376
|
`indexNow` sub-options: `key` (8–128 hex chars), `host` (bare host, e.g.
|
|
371
377
|
`example.com`), `siteUrl` (absolute origin), `keyLocation?` (defaults to
|
|
372
378
|
`https://<host>/<key>.txt`), `endpoint?` (defaults to `api.indexnow.org`),
|
|
373
379
|
`filter?` (drop URLs for which the callback returns `false`).
|
|
374
380
|
|
|
381
|
+
`llmsTxt` sub-options: `title` (required H1), `siteUrl` (required, used to
|
|
382
|
+
resolve crawled HTML paths), `summary?` (rendered as a blockquote),
|
|
383
|
+
`details?` (extra paragraphs), `sections?` (user-supplied sections; when
|
|
384
|
+
given, no pages are auto-collected), `filter?` (drop URLs from the
|
|
385
|
+
auto-generated section), `autoSectionName?` (defaults to `Pages`),
|
|
386
|
+
`outputPath?` (defaults to `llms.txt`).
|
|
387
|
+
|
|
375
388
|
`index.html` paths are rewritten to their trailing-slash form. Only static
|
|
376
389
|
pages are checked/submitted (SSR pages aren't on disk at build time).
|
|
377
390
|
|
|
@@ -391,6 +404,24 @@ The filename (minus `.txt.ts`) must equal the key. Pair this with the
|
|
|
391
404
|
`indexNow` integration option above, or call `submitToIndexNow` from your
|
|
392
405
|
own deploy hook.
|
|
393
406
|
|
|
407
|
+
> **Deploy the key file first.** IndexNow verifies host ownership by
|
|
408
|
+
> fetching `https://<host>/<key>.txt` on every submission. Submissions
|
|
409
|
+
> sent before the key file is reachable in production get rejected
|
|
410
|
+
> (HTTP 403) and the key is treated as invalid going forward — you'll
|
|
411
|
+
> have to rotate it. Ship the route, deploy, confirm the `.txt` loads
|
|
412
|
+
> over HTTPS, *then* enable `indexNow` in the integration.
|
|
413
|
+
|
|
414
|
+
## Validating your output
|
|
415
|
+
|
|
416
|
+
The build-time integration checks only catch a narrow set of issues.
|
|
417
|
+
After deploying, verify the rendered JSON-LD against:
|
|
418
|
+
|
|
419
|
+
1. [Google Rich Results Test](https://search.google.com/test/rich-results)
|
|
420
|
+
2. [Schema.org Validator](https://validator.schema.org/)
|
|
421
|
+
|
|
422
|
+
See [AGENTS.md](https://github.com/jdevalk/seo-graph/blob/main/AGENTS.md#validating-your-output)
|
|
423
|
+
for details on what to look for.
|
|
424
|
+
|
|
394
425
|
## License
|
|
395
426
|
|
|
396
427
|
MIT © Joost de Valk
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,6 @@ export { breadcrumbsFromUrl } from './breadcrumbs.js';
|
|
|
11
11
|
export type { BreadcrumbsFromUrlInput } from './breadcrumbs.js';
|
|
12
12
|
export { createIndexNowKeyRoute, submitToIndexNow, validateIndexNowKey } from './indexnow.js';
|
|
13
13
|
export type { IndexNowKeyRouteOptions, IndexNowSubmitResult } from './indexnow.js';
|
|
14
|
+
export { renderLlmsTxt } from './llms-txt.js';
|
|
15
|
+
export type { LlmsTxtInput, LlmsTxtSection, LlmsTxtLink } from './llms-txt.js';
|
|
14
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC9F,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE/E,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,uBAAuB,EAAE,MAAM,kBAAkB,CAAC;AAEhE,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAC9F,YAAY,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAEnF,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,4 +10,5 @@ export { buildAstroSeoProps } from './components/seo-props.js';
|
|
|
10
10
|
export { buildAlternateLinks } from './alternates.js';
|
|
11
11
|
export { breadcrumbsFromUrl } from './breadcrumbs.js';
|
|
12
12
|
export { createIndexNowKeyRoute, submitToIndexNow, validateIndexNowKey } from './indexnow.js';
|
|
13
|
+
export { renderLlmsTxt } from './llms-txt.js';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAGtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGtD,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAG9F,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/indexnow.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { APIRoute } from 'astro';
|
|
2
|
-
import { submitToIndexNow, validateIndexNowKey, type IndexNowSubmitResult } from '@jdevalk/seo-graph-core';
|
|
3
2
|
export interface IndexNowKeyRouteOptions {
|
|
4
3
|
/** IndexNow key (8–128 hex characters). */
|
|
5
4
|
key: string;
|
|
@@ -20,6 +19,6 @@ export interface IndexNowKeyRouteOptions {
|
|
|
20
19
|
* ```
|
|
21
20
|
*/
|
|
22
21
|
export declare function createIndexNowKeyRoute(options: IndexNowKeyRouteOptions): APIRoute;
|
|
23
|
-
export { submitToIndexNow, validateIndexNowKey };
|
|
24
|
-
export type { IndexNowSubmitResult };
|
|
22
|
+
export { submitToIndexNow, validateIndexNowKey } from '@jdevalk/seo-graph-core';
|
|
23
|
+
export type { IndexNowSubmitResult } from '@jdevalk/seo-graph-core';
|
|
25
24
|
//# sourceMappingURL=indexnow.d.ts.map
|
package/dist/indexnow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexnow.d.ts","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"indexnow.d.ts","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAGtC,MAAM,WAAW,uBAAuB;IACpC,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,QAAQ,CAYjF;AAED,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAChF,YAAY,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/indexnow.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getIndexNowKeyFileContent
|
|
1
|
+
import { getIndexNowKeyFileContent } from '@jdevalk/seo-graph-core';
|
|
2
2
|
/**
|
|
3
3
|
* Returns an Astro `APIRoute` that serves the IndexNow key verification
|
|
4
4
|
* file. Place this at `src/pages/[key].txt.ts` or `src/pages/<key>.txt.ts`
|
|
@@ -24,5 +24,5 @@ export function createIndexNowKeyRoute(options) {
|
|
|
24
24
|
return new Response(body, { headers });
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
-
export { submitToIndexNow, validateIndexNowKey };
|
|
27
|
+
export { submitToIndexNow, validateIndexNowKey } from '@jdevalk/seo-graph-core';
|
|
28
28
|
//# sourceMappingURL=indexnow.js.map
|
package/dist/indexnow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexnow.js","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"indexnow.js","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,yBAAyB,CAAC;AASpE;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,OAAgC;IACnE,MAAM,IAAI,GAAG,yBAAyB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpD,MAAM,YAAY,GACd,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IAExF,OAAO,KAAK,IAAI,EAAE;QACd,MAAM,OAAO,GAA2B;YACpC,cAAc,EAAE,2BAA2B;SAC9C,CAAC;QACF,IAAI,YAAY,KAAK,IAAI;YAAE,OAAO,CAAC,eAAe,CAAC,GAAG,YAAY,CAAC;QACnE,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC3C,CAAC,CAAC;AACN,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/integration.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type LlmsTxtSection } from './llms-txt.js';
|
|
1
2
|
interface BuildDoneHook {
|
|
2
3
|
dir: URL;
|
|
3
4
|
logger: {
|
|
@@ -39,6 +40,37 @@ export interface IndexNowIntegrationOptions {
|
|
|
39
40
|
*/
|
|
40
41
|
filter?: (url: string) => boolean;
|
|
41
42
|
}
|
|
43
|
+
export interface LlmsTxtIntegrationOptions {
|
|
44
|
+
/** H1 of the generated `llms.txt`. */
|
|
45
|
+
title: string;
|
|
46
|
+
/** Absolute site origin used to resolve built HTML paths into URLs. */
|
|
47
|
+
siteUrl: string;
|
|
48
|
+
/** Optional blockquote summary shown under the title. */
|
|
49
|
+
summary?: string;
|
|
50
|
+
/** Optional paragraphs of context between summary and sections. */
|
|
51
|
+
details?: string;
|
|
52
|
+
/**
|
|
53
|
+
* User-supplied sections. When provided, these are used verbatim and
|
|
54
|
+
* no pages are auto-collected from the build output. When omitted, a
|
|
55
|
+
* single "Pages" section is generated from all built HTML files.
|
|
56
|
+
*/
|
|
57
|
+
sections?: LlmsTxtSection[];
|
|
58
|
+
/**
|
|
59
|
+
* Filter which crawled URLs end up in the auto-generated section.
|
|
60
|
+
* Ignored when `sections` is supplied.
|
|
61
|
+
*/
|
|
62
|
+
filter?: (url: string) => boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Section name used for auto-generated pages. Defaults to `Pages`.
|
|
65
|
+
* Ignored when `sections` is supplied.
|
|
66
|
+
*/
|
|
67
|
+
autoSectionName?: string;
|
|
68
|
+
/**
|
|
69
|
+
* Output file path relative to the build output directory. Defaults
|
|
70
|
+
* to `llms.txt`.
|
|
71
|
+
*/
|
|
72
|
+
outputPath?: string;
|
|
73
|
+
}
|
|
42
74
|
export interface SeoGraphIntegrationOptions {
|
|
43
75
|
/**
|
|
44
76
|
* Warn when a built page has zero or more than one `<h1>` element.
|
|
@@ -46,12 +78,27 @@ export interface SeoGraphIntegrationOptions {
|
|
|
46
78
|
* not present on disk at build time).
|
|
47
79
|
*/
|
|
48
80
|
validateH1?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Warn when two or more built pages share the same `<title>` or
|
|
83
|
+
* meta description. Duplicate metadata is an SEO smell that can only
|
|
84
|
+
* be detected across the whole corpus. Defaults to `true`.
|
|
85
|
+
*
|
|
86
|
+
* Pages without a title or description are reported separately by
|
|
87
|
+
* the H1 validator's siblings — this check only compares values that
|
|
88
|
+
* are present on multiple pages.
|
|
89
|
+
*/
|
|
90
|
+
validateUniqueMetadata?: boolean;
|
|
49
91
|
/**
|
|
50
92
|
* Submit built URLs to IndexNow after the build completes. Omit to
|
|
51
93
|
* disable. Only URLs on `host` are submitted; URLs with trailing
|
|
52
94
|
* `index.html` are rewritten to their directory form.
|
|
53
95
|
*/
|
|
54
96
|
indexNow?: IndexNowIntegrationOptions;
|
|
97
|
+
/**
|
|
98
|
+
* Generate an `llms.txt` file at the root of the build output. Omit
|
|
99
|
+
* to disable.
|
|
100
|
+
*/
|
|
101
|
+
llmsTxt?: LlmsTxtIntegrationOptions;
|
|
55
102
|
}
|
|
56
103
|
/**
|
|
57
104
|
* Turn a built HTML file path (relative to the outDir, e.g.
|
|
@@ -66,6 +113,17 @@ export declare function htmlFileToUrl(relativePath: string, siteUrl: string): st
|
|
|
66
113
|
* a lint warning.
|
|
67
114
|
*/
|
|
68
115
|
export declare function countH1s(html: string): number;
|
|
116
|
+
/**
|
|
117
|
+
* Extract the first `<title>` element's text content. Returns `null` when
|
|
118
|
+
* no title tag is found. Whitespace-collapsed and entity-decoded so
|
|
119
|
+
* duplicate-detection compares rendered text, not raw HTML.
|
|
120
|
+
*/
|
|
121
|
+
export declare function extractTitle(html: string): string | null;
|
|
122
|
+
/**
|
|
123
|
+
* Extract the `content` attribute of the first `<meta name="description">`
|
|
124
|
+
* tag. Returns `null` when absent. Entity-decoded for duplicate detection.
|
|
125
|
+
*/
|
|
126
|
+
export declare function extractMetaDescription(html: string): string | null;
|
|
69
127
|
/**
|
|
70
128
|
* Astro integration for `@jdevalk/astro-seo-graph`.
|
|
71
129
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiB,KAAK,cAAc,EAAE,MAAM,eAAe,CAAC;AAKnE,UAAU,aAAa;IACnB,GAAG,EAAE,GAAG,CAAC;IAET,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAC5F;AAED,UAAU,oBAAoB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACH,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACtE,CAAC;CACL;AAED,MAAM,WAAW,0BAA0B;IACvC,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;CACrC;AAED,MAAM,WAAW,yBAAyB;IACtC,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,0BAA0B;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,0BAA0B,CAAC;IACtC;;;OAGG;IACH,OAAO,CAAC,EAAE,yBAAyB,CAAC;CACvC;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAc3E;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7C;AAgBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAKxD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQlE;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,GAAE,0BAA+B,GAAG,oBAAoB,CAqK/F"}
|
package/dist/integration.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { readFile, readdir } from 'node:fs/promises';
|
|
1
|
+
import { readFile, readdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { submitToIndexNow } from '@jdevalk/seo-graph-core';
|
|
5
|
+
import { renderLlmsTxt } from './llms-txt.js';
|
|
5
6
|
/**
|
|
6
7
|
* Turn a built HTML file path (relative to the outDir, e.g.
|
|
7
8
|
* `blog/post/index.html`) into an absolute URL on `siteUrl`. Rewrites
|
|
@@ -35,6 +36,44 @@ export function countH1s(html) {
|
|
|
35
36
|
const matches = html.match(/<h1[\s>]/gi);
|
|
36
37
|
return matches ? matches.length : 0;
|
|
37
38
|
}
|
|
39
|
+
const HTML_ENTITIES = {
|
|
40
|
+
'&': '&',
|
|
41
|
+
'<': '<',
|
|
42
|
+
'>': '>',
|
|
43
|
+
'"': '"',
|
|
44
|
+
''': "'",
|
|
45
|
+
''': "'",
|
|
46
|
+
' ': ' ',
|
|
47
|
+
};
|
|
48
|
+
function decodeHtmlEntities(value) {
|
|
49
|
+
return value.replace(/&(?:amp|lt|gt|quot|apos|#39|nbsp);/g, (m) => HTML_ENTITIES[m] ?? m);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract the first `<title>` element's text content. Returns `null` when
|
|
53
|
+
* no title tag is found. Whitespace-collapsed and entity-decoded so
|
|
54
|
+
* duplicate-detection compares rendered text, not raw HTML.
|
|
55
|
+
*/
|
|
56
|
+
export function extractTitle(html) {
|
|
57
|
+
const match = html.match(/<title[^>]*>([\s\S]*?)<\/title>/i);
|
|
58
|
+
if (!match)
|
|
59
|
+
return null;
|
|
60
|
+
const text = decodeHtmlEntities(match[1]).replace(/\s+/g, ' ').trim();
|
|
61
|
+
return text.length > 0 ? text : null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Extract the `content` attribute of the first `<meta name="description">`
|
|
65
|
+
* tag. Returns `null` when absent. Entity-decoded for duplicate detection.
|
|
66
|
+
*/
|
|
67
|
+
export function extractMetaDescription(html) {
|
|
68
|
+
const re = /<meta\s+[^>]*name\s*=\s*["']description["'][^>]*content\s*=\s*["']([^"']*)["'][^>]*>|<meta\s+[^>]*content\s*=\s*["']([^"']*)["'][^>]*name\s*=\s*["']description["'][^>]*>/i;
|
|
69
|
+
const match = html.match(re);
|
|
70
|
+
if (!match)
|
|
71
|
+
return null;
|
|
72
|
+
const raw = (match[1] ?? match[2] ?? '').trim();
|
|
73
|
+
if (!raw)
|
|
74
|
+
return null;
|
|
75
|
+
return decodeHtmlEntities(raw);
|
|
76
|
+
}
|
|
38
77
|
async function collectHtmlFiles(dir, base = dir) {
|
|
39
78
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
40
79
|
const files = [];
|
|
@@ -67,36 +106,91 @@ async function collectHtmlFiles(dir, base = dir) {
|
|
|
67
106
|
* ```
|
|
68
107
|
*/
|
|
69
108
|
export default function seoGraph(options = {}) {
|
|
70
|
-
const { validateH1 = true, indexNow } = options;
|
|
109
|
+
const { validateH1 = true, validateUniqueMetadata = true, indexNow, llmsTxt, } = options;
|
|
110
|
+
const autoLlmsTxt = llmsTxt && !llmsTxt.sections;
|
|
71
111
|
return {
|
|
72
112
|
name: '@jdevalk/astro-seo-graph',
|
|
73
113
|
hooks: {
|
|
74
114
|
'astro:build:done': async ({ dir, logger }) => {
|
|
75
115
|
const buildDir = fileURLToPath(dir);
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
116
|
+
const needsContentScan = validateH1 || validateUniqueMetadata || autoLlmsTxt;
|
|
117
|
+
const htmlFiles = needsContentScan || indexNow || llmsTxt
|
|
118
|
+
? await collectHtmlFiles(buildDir)
|
|
119
|
+
: [];
|
|
120
|
+
const autoLinks = [];
|
|
121
|
+
const h1Missing = [];
|
|
122
|
+
const h1Multiple = [];
|
|
123
|
+
const titlesByValue = new Map();
|
|
124
|
+
const descriptionsByValue = new Map();
|
|
125
|
+
if (needsContentScan) {
|
|
80
126
|
for (const file of htmlFiles) {
|
|
81
127
|
const content = await readFile(join(buildDir, file), 'utf8');
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
128
|
+
if (validateH1) {
|
|
129
|
+
const count = countH1s(content);
|
|
130
|
+
if (count === 0)
|
|
131
|
+
h1Missing.push(file);
|
|
132
|
+
else if (count > 1)
|
|
133
|
+
h1Multiple.push({ file, count });
|
|
134
|
+
}
|
|
135
|
+
const title = validateUniqueMetadata || autoLlmsTxt ? extractTitle(content) : null;
|
|
136
|
+
const description = validateUniqueMetadata || autoLlmsTxt
|
|
137
|
+
? extractMetaDescription(content)
|
|
138
|
+
: null;
|
|
139
|
+
if (validateUniqueMetadata) {
|
|
140
|
+
if (title) {
|
|
141
|
+
const list = titlesByValue.get(title) ?? [];
|
|
142
|
+
list.push(file);
|
|
143
|
+
titlesByValue.set(title, list);
|
|
144
|
+
}
|
|
145
|
+
if (description) {
|
|
146
|
+
const list = descriptionsByValue.get(description) ?? [];
|
|
147
|
+
list.push(file);
|
|
148
|
+
descriptionsByValue.set(description, list);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (autoLlmsTxt && llmsTxt) {
|
|
152
|
+
const url = htmlFileToUrl(file, llmsTxt.siteUrl);
|
|
153
|
+
if (!llmsTxt.filter || llmsTxt.filter(url)) {
|
|
154
|
+
autoLinks.push({
|
|
155
|
+
url,
|
|
156
|
+
title: title ?? url,
|
|
157
|
+
description: description ?? undefined,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
87
161
|
}
|
|
88
|
-
|
|
162
|
+
}
|
|
163
|
+
if (validateH1) {
|
|
164
|
+
if (h1Missing.length === 0 && h1Multiple.length === 0) {
|
|
89
165
|
logger.info(`H1 validation: ${htmlFiles.length} pages checked, all good.`);
|
|
90
166
|
}
|
|
91
167
|
else {
|
|
92
|
-
for (const file of
|
|
168
|
+
for (const file of h1Missing) {
|
|
93
169
|
logger.warn(`H1 validation: ${file} has no <h1>.`);
|
|
94
170
|
}
|
|
95
|
-
for (const { file, count } of
|
|
171
|
+
for (const { file, count } of h1Multiple) {
|
|
96
172
|
logger.warn(`H1 validation: ${file} has ${count} <h1> elements (expected 1).`);
|
|
97
173
|
}
|
|
98
174
|
}
|
|
99
175
|
}
|
|
176
|
+
if (validateUniqueMetadata) {
|
|
177
|
+
const dupTitles = [...titlesByValue.entries()].filter(([, files]) => files.length > 1);
|
|
178
|
+
const dupDescriptions = [...descriptionsByValue.entries()].filter(([, files]) => files.length > 1);
|
|
179
|
+
if (dupTitles.length === 0 && dupDescriptions.length === 0) {
|
|
180
|
+
logger.info(`Metadata uniqueness: ${htmlFiles.length} pages checked, all good.`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
for (const [title, files] of dupTitles) {
|
|
184
|
+
logger.warn(`Metadata uniqueness: title ${JSON.stringify(title)} appears on ${files.length} pages: ${files.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
for (const [description, files] of dupDescriptions) {
|
|
187
|
+
const preview = description.length > 80
|
|
188
|
+
? description.slice(0, 77) + '…'
|
|
189
|
+
: description;
|
|
190
|
+
logger.warn(`Metadata uniqueness: description ${JSON.stringify(preview)} appears on ${files.length} pages: ${files.join(', ')}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
100
194
|
if (indexNow) {
|
|
101
195
|
const urls = htmlFiles
|
|
102
196
|
.map((f) => htmlFileToUrl(f, indexNow.siteUrl))
|
|
@@ -122,6 +216,20 @@ export default function seoGraph(options = {}) {
|
|
|
122
216
|
}
|
|
123
217
|
}
|
|
124
218
|
}
|
|
219
|
+
if (llmsTxt) {
|
|
220
|
+
const sections = llmsTxt.sections ??
|
|
221
|
+
[{ name: llmsTxt.autoSectionName ?? 'Pages', links: autoLinks }];
|
|
222
|
+
const rendered = renderLlmsTxt({
|
|
223
|
+
title: llmsTxt.title,
|
|
224
|
+
summary: llmsTxt.summary,
|
|
225
|
+
details: llmsTxt.details,
|
|
226
|
+
sections,
|
|
227
|
+
});
|
|
228
|
+
const outPath = join(buildDir, llmsTxt.outputPath ?? 'llms.txt');
|
|
229
|
+
await writeFile(outPath, rendered, 'utf8');
|
|
230
|
+
const linkCount = sections.reduce((n, s) => n + s.links.length, 0);
|
|
231
|
+
logger.info(`llms.txt: wrote ${relative(buildDir, outPath)} with ${linkCount} link(s).`);
|
|
232
|
+
}
|
|
125
233
|
},
|
|
126
234
|
},
|
|
127
235
|
};
|
package/dist/integration.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an `llms.txt` file — a markdown document that gives LLMs a
|
|
3
|
+
* concise, structured map of a site. Spec: https://llmstxt.org
|
|
4
|
+
*
|
|
5
|
+
* Shape:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* # Title
|
|
9
|
+
*
|
|
10
|
+
* > Summary (optional blockquote)
|
|
11
|
+
*
|
|
12
|
+
* Details paragraph(s) (optional)
|
|
13
|
+
*
|
|
14
|
+
* ## Section name
|
|
15
|
+
*
|
|
16
|
+
* - [Link title](url): optional description
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Callers can supply sections explicitly or let the integration
|
|
20
|
+
* auto-generate a single "Pages" section from crawled HTML.
|
|
21
|
+
*/
|
|
22
|
+
export interface LlmsTxtLink {
|
|
23
|
+
/** Absolute URL. */
|
|
24
|
+
url: string;
|
|
25
|
+
/** Link label. Usually the page `<title>`. */
|
|
26
|
+
title: string;
|
|
27
|
+
/** Optional short description. Usually the meta description. */
|
|
28
|
+
description?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface LlmsTxtSection {
|
|
31
|
+
/** Section heading (rendered as `## name`). */
|
|
32
|
+
name: string;
|
|
33
|
+
links: LlmsTxtLink[];
|
|
34
|
+
}
|
|
35
|
+
export interface LlmsTxtInput {
|
|
36
|
+
/** H1 of the document. Required. */
|
|
37
|
+
title: string;
|
|
38
|
+
/** Short blockquote summary shown under the title. */
|
|
39
|
+
summary?: string;
|
|
40
|
+
/** Extra paragraphs of context between summary and sections. */
|
|
41
|
+
details?: string;
|
|
42
|
+
sections: LlmsTxtSection[];
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Render an `LlmsTxtInput` to a markdown string. Pure.
|
|
46
|
+
*
|
|
47
|
+
* Empty sections are omitted. A trailing newline is included so the file
|
|
48
|
+
* ends cleanly on disk.
|
|
49
|
+
*/
|
|
50
|
+
export declare function renderLlmsTxt(input: LlmsTxtInput): string;
|
|
51
|
+
//# sourceMappingURL=llms-txt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.d.ts","sourceRoot":"","sources":["../src/llms-txt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,MAAM,WAAW,WAAW;IACxB,oBAAoB;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,8CAA8C;IAC9C,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,cAAc;IAC3B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IACzB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC9B;AAMD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAwBzD"}
|
package/dist/llms-txt.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build an `llms.txt` file — a markdown document that gives LLMs a
|
|
3
|
+
* concise, structured map of a site. Spec: https://llmstxt.org
|
|
4
|
+
*
|
|
5
|
+
* Shape:
|
|
6
|
+
*
|
|
7
|
+
* ```
|
|
8
|
+
* # Title
|
|
9
|
+
*
|
|
10
|
+
* > Summary (optional blockquote)
|
|
11
|
+
*
|
|
12
|
+
* Details paragraph(s) (optional)
|
|
13
|
+
*
|
|
14
|
+
* ## Section name
|
|
15
|
+
*
|
|
16
|
+
* - [Link title](url): optional description
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Callers can supply sections explicitly or let the integration
|
|
20
|
+
* auto-generate a single "Pages" section from crawled HTML.
|
|
21
|
+
*/
|
|
22
|
+
function escapeLinkTitle(title) {
|
|
23
|
+
return title.replace(/\[/g, '\\[').replace(/\]/g, '\\]');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Render an `LlmsTxtInput` to a markdown string. Pure.
|
|
27
|
+
*
|
|
28
|
+
* Empty sections are omitted. A trailing newline is included so the file
|
|
29
|
+
* ends cleanly on disk.
|
|
30
|
+
*/
|
|
31
|
+
export function renderLlmsTxt(input) {
|
|
32
|
+
const parts = [`# ${input.title.trim()}`];
|
|
33
|
+
if (input.summary?.trim()) {
|
|
34
|
+
parts.push(`> ${input.summary.trim()}`);
|
|
35
|
+
}
|
|
36
|
+
if (input.details?.trim()) {
|
|
37
|
+
parts.push(input.details.trim());
|
|
38
|
+
}
|
|
39
|
+
for (const section of input.sections) {
|
|
40
|
+
if (section.links.length === 0)
|
|
41
|
+
continue;
|
|
42
|
+
parts.push(`## ${section.name.trim()}`);
|
|
43
|
+
const lines = section.links.map((link) => {
|
|
44
|
+
const label = escapeLinkTitle(link.title.trim() || link.url);
|
|
45
|
+
const base = `- [${label}](${link.url})`;
|
|
46
|
+
const desc = link.description?.trim();
|
|
47
|
+
return desc ? `${base}: ${desc}` : base;
|
|
48
|
+
});
|
|
49
|
+
parts.push(lines.join('\n'));
|
|
50
|
+
}
|
|
51
|
+
return parts.join('\n\n') + '\n';
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=llms-txt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llms-txt.js","sourceRoot":"","sources":["../src/llms-txt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AA2BH,SAAS,eAAe,CAAC,KAAa;IAClC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;AAC7D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC7C,MAAM,KAAK,GAAa,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAEpD,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,SAAS;QACzC,KAAK,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACrC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;YAC7D,MAAM,IAAI,GAAG,MAAM,KAAK,KAAK,IAAI,CAAC,GAAG,GAAG,CAAC;YACzC,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5C,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;AACrC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jdevalk/astro-seo-graph",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Astro integration for @jdevalk/seo-graph-core. Seo component, route factories, content-collection aggregator, Zod content helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"astro-seo": "^1.1.0",
|
|
53
53
|
"schema-dts": "^2.0.0",
|
|
54
54
|
"zod": "^3.24.0",
|
|
55
|
-
"@jdevalk/seo-graph-core": "0.6.
|
|
55
|
+
"@jdevalk/seo-graph-core": "0.6.1"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@types/node": "^22.0.0",
|