@jdevalk/astro-seo-graph 1.4.1 → 2.1.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 +46 -0
- package/README.md +131 -2
- package/dist/aggregator.js.map +1 -1
- package/dist/alternates.js.map +1 -1
- package/dist/api-catalog.js.map +1 -1
- package/dist/breadcrumbs.js.map +1 -1
- package/dist/components/seo-context.js.map +1 -1
- package/dist/components/seo-props.js.map +1 -1
- package/dist/content-helpers.d.ts +6 -31
- package/dist/content-helpers.d.ts.map +1 -1
- package/dist/content-helpers.js.map +1 -1
- package/dist/git-lastmod.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/indexnow-manifest.d.ts +73 -0
- package/dist/indexnow-manifest.d.ts.map +1 -0
- package/dist/indexnow-manifest.js +96 -0
- package/dist/indexnow-manifest.js.map +1 -0
- package/dist/indexnow.js.map +1 -1
- package/dist/integration.d.ts +86 -1
- package/dist/integration.d.ts.map +1 -1
- package/dist/integration.js +109 -10
- package/dist/integration.js.map +1 -1
- package/dist/llms-txt.js.map +1 -1
- package/dist/markdown-alternate.js.map +1 -1
- package/dist/markdown-routes.js.map +1 -1
- package/dist/routes.js.map +1 -1
- package/package.json +7 -7
package/AGENTS.md
CHANGED
|
@@ -2833,6 +2833,52 @@ export const GET = createIndexNowKeyRoute({ key: 'your-key-here' });
|
|
|
2833
2833
|
|
|
2834
2834
|
The filename (minus `.txt.ts`) must equal the key.
|
|
2835
2835
|
|
|
2836
|
+
#### Gating submission to the production branch
|
|
2837
|
+
|
|
2838
|
+
Preview and feature-branch deploys share the same IndexNow key as
|
|
2839
|
+
production but shouldn't notify search engines — submitting preview
|
|
2840
|
+
URLs wastes daily quota and can get the key flagged invalid (the host
|
|
2841
|
+
hasn't served those URLs yet).
|
|
2842
|
+
|
|
2843
|
+
`indexNowOnBranch(branch, options, productionBranch?)` passes the
|
|
2844
|
+
options through unchanged when `branch` matches `productionBranch`
|
|
2845
|
+
(default `"main"`) and returns `undefined` otherwise, so `seoGraph()`
|
|
2846
|
+
skips submission entirely on non-production deploys.
|
|
2847
|
+
|
|
2848
|
+
> **`import.meta.env` is not available in `astro.config.mjs`.** Astro
|
|
2849
|
+
> only exposes `import.meta.env` inside components and pages processed
|
|
2850
|
+
> by the Vite pipeline. Config files run in Node.js before Vite
|
|
2851
|
+
> starts, so you must read branch variables via `process.env` instead.
|
|
2852
|
+
|
|
2853
|
+
```js
|
|
2854
|
+
import { defineConfig } from 'astro/config';
|
|
2855
|
+
import seoGraph, { indexNowOnBranch } from '@jdevalk/astro-seo-graph/integration';
|
|
2856
|
+
|
|
2857
|
+
export default defineConfig({
|
|
2858
|
+
integrations: [
|
|
2859
|
+
seoGraph({
|
|
2860
|
+
indexNow: indexNowOnBranch(process.env.CF_PAGES_BRANCH ?? '', {
|
|
2861
|
+
key: process.env.INDEXNOW_KEY,
|
|
2862
|
+
host: 'example.com',
|
|
2863
|
+
siteUrl: 'https://example.com',
|
|
2864
|
+
}),
|
|
2865
|
+
}),
|
|
2866
|
+
],
|
|
2867
|
+
});
|
|
2868
|
+
```
|
|
2869
|
+
|
|
2870
|
+
Platform branch variables:
|
|
2871
|
+
|
|
2872
|
+
- Cloudflare Pages: `process.env.CF_PAGES_BRANCH`
|
|
2873
|
+
- Vercel: `process.env.VERCEL_GIT_COMMIT_REF`
|
|
2874
|
+
- Netlify: `process.env.BRANCH`
|
|
2875
|
+
|
|
2876
|
+
Pass a third argument to use a branch name other than `"main"`:
|
|
2877
|
+
|
|
2878
|
+
```js
|
|
2879
|
+
indexNowOnBranch(process.env.CF_PAGES_BRANCH ?? '', options, 'production');
|
|
2880
|
+
```
|
|
2881
|
+
|
|
2836
2882
|
### llms.txt generation
|
|
2837
2883
|
|
|
2838
2884
|
Generates an [`llms.txt`](https://llmstxt.org) file — a markdown summary
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ schema.org best practices — see [AGENTS.md](https://github.com/jdevalk/seo-gra
|
|
|
20
20
|
|
|
21
21
|
| API | Purpose |
|
|
22
22
|
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
23
|
-
| **`<Seo>`** (`./Seo.astro`) | Single head component covering `<title>`, meta description, canonical, Open Graph, Twitter card, hreflang alternates, and optional JSON-LD `@graph`.
|
|
23
|
+
| **`<Seo>`** (`./Seo.astro`) | Single head component covering `<title>`, meta description, canonical, Open Graph, Twitter card, hreflang alternates, and optional JSON-LD `@graph`. Renders every `<head>` tag directly — no `astro-seo` dependency. |
|
|
24
24
|
| **`createSchemaEndpoint`** | Factory returning an Astro `APIRoute` handler that serves a corpus-wide JSON-LD `@graph` for a content collection. |
|
|
25
25
|
| **`createSchemaMap`** | Factory returning an `APIRoute` handler that emits a sitemap-style XML listing of your site's schema endpoints — the discovery point for agent crawlers. |
|
|
26
26
|
| **`aggregate`** | Shared engine behind the endpoint factories. Walks a list of entries, runs a caller-supplied mapper, deduplicates by `@id`. |
|
|
@@ -86,6 +86,47 @@ const graph = buildSchemaGraph({
|
|
|
86
86
|
</html>
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
### Props
|
|
90
|
+
|
|
91
|
+
All props are optional except `title`.
|
|
92
|
+
|
|
93
|
+
| Prop | Type | Default | Description |
|
|
94
|
+
| --------------------- | ----------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `title` | `string` | — | Page title. **Required.** |
|
|
96
|
+
| `titleTemplate` | `string` | raw `title` | Template for the full `<title>`; `%s` is replaced with `title` (e.g. `"%s \| Joost.blog"`). |
|
|
97
|
+
| `description` | `string` | — | Meta description. |
|
|
98
|
+
| `canonical` | `string \| URL` | current URL, query stripped | Explicit canonical override. Omitted entirely when `noindex`. |
|
|
99
|
+
| `preserveQueryParams` | `boolean` | `false` | Keep the query string on the default canonical. No effect when `canonical` is set. |
|
|
100
|
+
| `ogType` | `'website' \| 'article' \| 'profile' \| 'book'` | `'website'` | Open Graph type. |
|
|
101
|
+
| `ogImage` | `string` | — | Absolute URL of the share image. |
|
|
102
|
+
| `ogImageAlt` | `string` | — | Alt text for the share image. |
|
|
103
|
+
| `ogImageWidth` | `number` | — | Share image width in pixels. |
|
|
104
|
+
| `ogImageHeight` | `number` | — | Share image height in pixels. |
|
|
105
|
+
| `siteName` | `string` | — | Site name shown in OG tags. |
|
|
106
|
+
| `locale` | `string` | `'en_US'` | OG locale. |
|
|
107
|
+
| `twitter` | `object` | — | Twitter card metadata — see [below](#twitter-and-article-sub-objects). |
|
|
108
|
+
| `article` | `object` | — | Article OG metadata — see [below](#twitter-and-article-sub-objects). Only when `ogType` is `'article'`. |
|
|
109
|
+
| `noindex` | `boolean` | `false` | Emit `noindex` in the robots meta (also drops the canonical). |
|
|
110
|
+
| `nofollow` | `boolean` | `false` | Emit `nofollow` in the robots meta. |
|
|
111
|
+
| `articlePublisher` | `string` | — | Facebook page URL; emitted as `article:publisher` when `ogType` is `'article'`. |
|
|
112
|
+
| `author` | `string` | `article.authors[0]` | Value for `<meta name="author">`. |
|
|
113
|
+
| `graph` | `Record<string, unknown> \| null` | — | JSON-LD `@graph` to inject as `<script type="application/ld+json">`. `null`/omit skips it. |
|
|
114
|
+
| `extraLinks` | `Array<Record<string, string>>` | — | Extra `<link>` tags (icons, sitemap, RSS alternate, …). |
|
|
115
|
+
| `extraMeta` | `Array<Record<string, string>>` | — | Extra `<meta>` tags. |
|
|
116
|
+
| `alternates` | `BuildAlternateLinksInput` | — | hreflang alternate-language annotations — see [hreflang alternates](#hreflang-alternates). |
|
|
117
|
+
|
|
118
|
+
#### `twitter` and `article` sub-objects
|
|
119
|
+
|
|
120
|
+
`twitter` (all fields optional): `site`, `creator`,
|
|
121
|
+
`card` (`'summary' \| 'summary_large_image' \| 'app' \| 'player'`, default
|
|
122
|
+
`'summary_large_image'`), `title`, `description`, `image`, `imageAlt`. The four
|
|
123
|
+
content fields fall back to their `og:` counterparts when omitted — see the
|
|
124
|
+
Twitter tag dedup note below.
|
|
125
|
+
|
|
126
|
+
`article` (all fields optional; emitted only when `ogType` is `'article'`):
|
|
127
|
+
`publishedTime`, `modifiedTime`, `expirationTime` (`Date \| string`), `authors`
|
|
128
|
+
(`string[]`), `tags` (`string[]`), `section`.
|
|
129
|
+
|
|
89
130
|
### `<Seo>` behavior notes
|
|
90
131
|
|
|
91
132
|
- **Robots defaults.** `max-snippet:-1`, `max-image-preview:large`, and
|
|
@@ -591,7 +632,8 @@ Options:
|
|
|
591
632
|
`example.com`), `siteUrl` (absolute origin), `keyLocation?` (defaults to
|
|
592
633
|
`https://<host>/<key>.txt`), `endpoint?` (defaults to `api.indexnow.org`),
|
|
593
634
|
`filter?` (drop URLs for which the callback returns `false`; composed on
|
|
594
|
-
top of the built-in `/404` exclusion)
|
|
635
|
+
top of the built-in `/404` exclusion), `incremental?` (submit only changed
|
|
636
|
+
URLs — see [Incremental IndexNow submission](#incremental-indexnow-submission)).
|
|
595
637
|
|
|
596
638
|
`validateMetadataLength` accepts `true`/`false` for the defaults, or an
|
|
597
639
|
object to override bounds. Length is measured on the whitespace-collapsed,
|
|
@@ -678,6 +720,93 @@ own deploy hook.
|
|
|
678
720
|
> have to rotate it. Ship the route, deploy, confirm the `.txt` loads
|
|
679
721
|
> over HTTPS, _then_ enable `indexNow` in the integration.
|
|
680
722
|
|
|
723
|
+
### Gating submission to the production branch
|
|
724
|
+
|
|
725
|
+
Preview and feature-branch deploys share the same IndexNow key as
|
|
726
|
+
production but shouldn't notify search engines — submitting preview
|
|
727
|
+
URLs wastes daily quota and can get the key flagged invalid (the host
|
|
728
|
+
hasn't served those URLs yet).
|
|
729
|
+
|
|
730
|
+
`indexNowOnBranch(branch, options, productionBranch?)` passes the
|
|
731
|
+
options through unchanged when `branch` matches `productionBranch`
|
|
732
|
+
(default `"main"`) and returns `undefined` otherwise, so `seoGraph()`
|
|
733
|
+
skips submission entirely on non-production deploys.
|
|
734
|
+
|
|
735
|
+
> **`import.meta.env` is not available in `astro.config.mjs`.** Astro
|
|
736
|
+
> only exposes `import.meta.env` inside components and pages processed
|
|
737
|
+
> by the Vite pipeline. Config files run in Node.js before Vite
|
|
738
|
+
> starts, so you must read branch variables via `process.env` instead.
|
|
739
|
+
|
|
740
|
+
```js
|
|
741
|
+
import { defineConfig } from 'astro/config';
|
|
742
|
+
import seoGraph, { indexNowOnBranch } from '@jdevalk/astro-seo-graph/integration';
|
|
743
|
+
|
|
744
|
+
export default defineConfig({
|
|
745
|
+
integrations: [
|
|
746
|
+
seoGraph({
|
|
747
|
+
indexNow: indexNowOnBranch(process.env.CF_PAGES_BRANCH ?? '', {
|
|
748
|
+
key: process.env.INDEXNOW_KEY,
|
|
749
|
+
host: 'example.com',
|
|
750
|
+
siteUrl: 'https://example.com',
|
|
751
|
+
}),
|
|
752
|
+
}),
|
|
753
|
+
],
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
Platform branch variables:
|
|
758
|
+
|
|
759
|
+
- Cloudflare Pages: `process.env.CF_PAGES_BRANCH`
|
|
760
|
+
- Vercel: `process.env.VERCEL_GIT_COMMIT_REF`
|
|
761
|
+
- Netlify: `process.env.BRANCH`
|
|
762
|
+
|
|
763
|
+
Pass a third argument to use a branch name other than `"main"`:
|
|
764
|
+
|
|
765
|
+
```js
|
|
766
|
+
indexNowOnBranch(process.env.CF_PAGES_BRANCH ?? '', options, 'production');
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
## Incremental IndexNow submission
|
|
770
|
+
|
|
771
|
+
By default the integration submits **every** eligible URL on every build. The
|
|
772
|
+
[IndexNow spec](https://www.indexnow.org/documentation) asks senders to submit
|
|
773
|
+
only URLs that were **added, updated, or deleted**, and full resubmits can trip
|
|
774
|
+
a host's rate limit (HTTP 429). Set `indexNow.incremental` to submit just the
|
|
775
|
+
difference:
|
|
776
|
+
|
|
777
|
+
```js
|
|
778
|
+
seoGraph({
|
|
779
|
+
indexNow: {
|
|
780
|
+
key: process.env.INDEXNOW_KEY,
|
|
781
|
+
host: 'example.com',
|
|
782
|
+
siteUrl: 'https://example.com',
|
|
783
|
+
incremental: true,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
On each build the integration hashes every eligible page into a manifest,
|
|
789
|
+
**fetches the previously published manifest from the live site**, diffs the two,
|
|
790
|
+
and submits only the changed URLs (added + updated + deleted). It then writes
|
|
791
|
+
the new manifest into the build output (default `indexnow-manifest.json` at the
|
|
792
|
+
site root) so it ships with the deploy and becomes the next build's baseline.
|
|
793
|
+
|
|
794
|
+
Because the previous state lives on the live site — not local disk or a
|
|
795
|
+
key/value store — this behaves identically whether you build locally or in CI,
|
|
796
|
+
and adds no infrastructure (the manifest is a static file; the only network call
|
|
797
|
+
is one `GET` at build time). The manifest just lists URLs and opaque hashes —
|
|
798
|
+
the same URLs your sitemap already exposes.
|
|
799
|
+
|
|
800
|
+
`incremental` sub-options (all optional): `manifestPath` (build-output path and
|
|
801
|
+
served URL path; default `indexnow-manifest.json`), `manifestUrl` (absolute URL
|
|
802
|
+
to fetch the previous manifest from; default `<siteUrl>/<manifestPath>`),
|
|
803
|
+
`normalize` (`(html, url) => string` to strip per-build volatile markup — CSP
|
|
804
|
+
nonces, timestamps — before hashing, so unchanged pages don't read as modified),
|
|
805
|
+
and `onError` (`'skip'` (default) or `'full'` — what to do when the previous
|
|
806
|
+
manifest can't be fetched or parsed for a reason other than a clean `404`; a
|
|
807
|
+
`404` is always treated as a first run and submits everything once). `'skip'`
|
|
808
|
+
means a transient fetch failure can never trigger an accidental full resubmit.
|
|
809
|
+
|
|
681
810
|
## Validating your output
|
|
682
811
|
|
|
683
812
|
The build-time integration checks only catch a narrow set of issues.
|
package/dist/aggregator.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,yBAAyB,CAAC;AAmBjF;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAQ,OAAiC;IAC9D,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IACD,OAAO;QACH,UAAU,EAAE,oBAAoB;QAChC,QAAQ,EAAE,oBAAoB,CAAC,MAAM,CAAC;KACzC,CAAC;AACN,CAAC"}
|
|
1
|
+
{"version":3,"file":"aggregator.js","sourceRoot":"","sources":["../src/aggregator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,yBAAyB,CAAC;AAmBjF;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CAAQ,OAAiC;IAC9D,MAAM,MAAM,GAAkB,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAClC,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACL,CAAC;IACD,OAAO;QACH,UAAU,EAAE,oBAAoB;QAChC,QAAQ,EAAE,oBAAoB,CAAC,MAAM,CAAC;KACzC,CAAC;AACN,CAAC","sourcesContent":["import { deduplicateByGraphId, type GraphEntity } from '@jdevalk/seo-graph-core';\n\nexport interface AggregatorOptions<Entry> {\n /** Content entries to walk, typically from Astro's `getCollection`. */\n entries: readonly Entry[];\n /**\n * Map a single entry to an array of schema.org pieces. Return an\n * empty array to skip the entry entirely (e.g. drafts). Pieces\n * returned here are concatenated across entries; the aggregator\n * deduplicates by `@id` with first-occurrence-wins semantics.\n */\n mapper: (entry: Entry) => ReadonlyArray<GraphEntity>;\n}\n\nexport interface AggregatedGraph {\n '@context': 'https://schema.org';\n '@graph': GraphEntity[];\n}\n\n/**\n * Walk a list of content entries, run the caller-supplied mapper over\n * each one, concatenate all resulting pieces, and deduplicate by\n * `@id`. Returns a ready-to-serialize `@graph` envelope.\n *\n * This is the shared engine behind `createSchemaEndpoint` — use it\n * directly if you need custom wrapping, caching, or multiple-collection\n * merging that the endpoint factories don't expose.\n */\nexport function aggregate<Entry>(options: AggregatorOptions<Entry>): AggregatedGraph {\n const pieces: GraphEntity[] = [];\n for (const entry of options.entries) {\n for (const piece of options.mapper(entry)) {\n pieces.push(piece);\n }\n }\n return {\n '@context': 'https://schema.org',\n '@graph': deduplicateByGraphId(pieces),\n };\n}\n"]}
|
package/dist/alternates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alternates.js","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"alternates.js","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * hreflang alternate-language link builder.\n *\n * Pure function: no DOM, no fetch, no Astro runtime. Safe to import from\n * non-Astro TypeScript (in particular, from `@jdevalk/emdash-plugin-seo`'s\n * EmDash `page:metadata` hook).\n *\n * Callers provide a set of `{ hreflang, href }` entries; this module\n * normalizes BCP 47 tags, validates URLs, dedupes, and appends an\n * `x-default` entry. It does NOT source translation data, construct URLs\n * from slugs, or know about any particular CMS.\n */\n\n/** Output shape: a single rendered `<link rel=\"alternate\" hreflang=\"…\" href=\"…\">` tag. */\nexport interface AlternateLink {\n /** Always `\"alternate\"`. */\n rel: 'alternate';\n /** Normalized BCP 47 tag (e.g. `\"fr-CA\"`) or the literal `\"x-default\"`. */\n hreflang: string;\n /** Absolute `http(s)://` URL string. */\n href: string;\n}\n\n/** Input shape accepted by `buildAlternateLinks`. */\nexport interface BuildAlternateLinksInput {\n /**\n * BCP 47 locale tag of the default sibling, used for the `x-default`\n * entry. Matched case-insensitively against the normalized\n * `entries[].hreflang` values. If omitted (or no match), the first\n * surviving entry is used as the default.\n */\n defaultLocale?: string;\n\n /**\n * Per-locale URL entries. Order is preserved in the output. The\n * caller MUST include the current page itself — Google treats\n * self-referential hreflang as required, not optional.\n */\n entries: ReadonlyArray<{\n /** BCP 47 tag, e.g. `\"en\"`, `\"fr\"`, `\"fr-CA\"`. */\n hreflang: string;\n /** Absolute URL. Relative or non-http(s) values are dropped. */\n href: string | URL;\n }>;\n}\n\n/** BCP 47 subtag structure we handle: language-[script]-[region]. */\nconst BCP47_RE = /^([a-z]{2,3})(?:-([a-z]{4}))?(?:-([a-z]{2,3}))?$/i;\n\n/**\n * Normalize a BCP 47 locale tag to conventional casing:\n * language subtag lowercase, script subtag title-case, region subtag\n * uppercase. Returns `null` for empty / whitespace-only input.\n *\n * For tags that don't match the language-script-region pattern\n * (variants, extended tags, private-use, grandfathered), this falls\n * back to lowercasing the entire tag. hreflang rarely uses those.\n */\nfunction normalizeLocaleTag(raw: string): string | null {\n const trimmed = raw.trim();\n if (trimmed.length === 0) return null;\n\n const match = BCP47_RE.exec(trimmed);\n if (match === null) return trimmed.toLowerCase();\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const [, lang, script, region] = match;\n let out = lang!.toLowerCase();\n if (script !== undefined) {\n out += '-' + script.charAt(0).toUpperCase() + script.slice(1).toLowerCase();\n }\n if (region !== undefined) {\n out += '-' + region.toUpperCase();\n }\n return out;\n}\n\n/**\n * Validate that a URL-ish value is absolute http(s). Returns the\n * stringified URL on success, `null` on failure.\n */\nfunction validateAbsoluteUrl(href: string | URL): string | null {\n try {\n const str = typeof href === 'string' ? href : href.toString();\n const parsed = new URL(str);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return null;\n }\n return parsed.toString();\n } catch {\n return null;\n }\n}\n\n/**\n * Build a deduplicated, normalized set of hreflang alternate links,\n * including an `x-default` entry.\n *\n * Behaviour:\n * - Returns `[]` when fewer than 2 entries survive validation (a\n * single-locale page has no meaningful alternates).\n * - Drops entries with malformed / non-http(s) URLs.\n * - Drops entries with empty hreflang.\n * - Drops entries whose input hreflang is the literal `\"x-default\"` —\n * that's reserved and added automatically.\n * - On duplicate normalized hreflang, the first entry wins.\n * - Preserves input order for per-locale entries.\n * - Appends `x-default` as the final entry, pointing at the\n * `defaultLocale` match (or the first entry as fallback).\n *\n * @param input Entries plus optional default locale hint.\n * @returns Deduped, normalized link tags. Never throws.\n */\nexport function buildAlternateLinks(input: BuildAlternateLinksInput): AlternateLink[] {\n const seen = new Set<string>();\n const validated: AlternateLink[] = [];\n\n for (const entry of input.entries) {\n const normalized = normalizeLocaleTag(entry.hreflang);\n if (normalized === null) continue;\n if (normalized === 'x-default') continue; // reserved\n if (seen.has(normalized)) continue;\n\n const href = validateAbsoluteUrl(entry.href);\n if (href === null) continue;\n\n seen.add(normalized);\n validated.push({ rel: 'alternate', hreflang: normalized, href });\n }\n\n // Short-circuit: one (or zero) surviving entries is not a meaningful\n // hreflang set. Emit nothing rather than a self-referential single\n // tag. Callers who want that behaviour can skip the helper.\n if (validated.length < 2) return [];\n\n // Pick the x-default target. defaultLocale is matched\n // case-insensitively against the normalized tags.\n let defaultEntry = validated[0]!;\n if (input.defaultLocale !== undefined) {\n const normalizedDefault = normalizeLocaleTag(input.defaultLocale);\n if (normalizedDefault !== null) {\n const match = validated.find((v) => v.hreflang === normalizedDefault);\n if (match !== undefined) defaultEntry = match;\n }\n }\n\n return [...validated, { rel: 'alternate', hreflang: 'x-default', href: defaultEntry.href }];\n}\n"]}
|
package/dist/api-catalog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-catalog.js","sourceRoot":"","sources":["../src/api-catalog.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"api-catalog.js","sourceRoot":"","sources":["../src/api-catalog.ts"],"names":[],"mappings":"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","sourcesContent":["import type { APIRoute } from 'astro';\n\n/**\n * Standard path for the API catalog per RFC 9727. Exported so callers\n * can reference it from `_headers` files, schemamap entries, or\n * documentation links without duplicating the string.\n */\nexport const CATALOG_PATH = '/.well-known/api-catalog';\n\nexport interface ApiCatalogEntry {\n /**\n * URL or path of the API endpoint. Relative paths are resolved\n * against `siteUrl`; absolute URLs pass through unchanged.\n */\n anchor: string;\n /**\n * Documentation URL(s). Single string is normalized to a one-item\n * array. Relative paths resolved against `siteUrl`.\n */\n serviceDoc?: string | readonly string[];\n /**\n * Type URL(s). For schema.org endpoints, a `https://schema.org/<Type>`\n * URL. For OpenAPI-described services, the OAS document URL. Single\n * string is normalized to a one-item array.\n */\n type?: string | readonly string[];\n}\n\nexport interface ApiCatalogSchemaEndpointEntry {\n /** Path of the endpoint, e.g. `/schema/post.json`. */\n path: string;\n /**\n * Schema.org type emitted, e.g. `BlogPosting`. Becomes\n * `https://schema.org/<Type>` in the `type` field.\n */\n schemaType: string;\n /** Optional documentation URL. */\n serviceDoc?: string;\n}\n\nexport interface ApiCatalogSchemaMapEntry {\n /** Path of the schemamap, e.g. `/schemamap.xml`. */\n path: string;\n /** Optional documentation URL. */\n serviceDoc?: string;\n}\n\nexport interface ApiCatalogOptions {\n /** Canonical site URL. Trailing slash is stripped. */\n siteUrl: string;\n /**\n * Schema.org JSON endpoints, typically the same set wired into\n * `createSchemaEndpoint` calls. Each entry is emitted with\n * `type: [{ href: \"https://schema.org/<schemaType>\" }]`.\n */\n schemaEndpoints?: readonly ApiCatalogSchemaEndpointEntry[];\n /**\n * Schema map endpoint, typically the path of the route created by\n * `createSchemaMap`. Emitted without a `type` (no standard type\n * exists for the schemamap format).\n */\n schemaMap?: ApiCatalogSchemaMapEntry;\n /** Site-specific APIs not covered by the package's own factories. */\n additional?: readonly ApiCatalogEntry[];\n /** Defaults to `max-age=300`. Pass `null` to omit. */\n cacheControl?: string | null;\n /**\n * Content-Type header. Defaults to `application/linkset+json` per\n * RFC 9727 / RFC 9264. Override only if you have a reason.\n */\n contentType?: string;\n /** JSON indentation. Defaults to `2`. Pass `0` for compact output. */\n indent?: number;\n}\n\ninterface LinksetEntry {\n anchor: string;\n 'service-doc'?: { href: string }[];\n type?: { href: string }[];\n}\n\nfunction absolutize(value: string, site: string): string {\n if (/^https?:\\/\\//i.test(value)) return value;\n const path = value.startsWith('/') ? value : `/${value}`;\n return `${site}${path}`;\n}\n\nfunction toHrefArray(\n value: string | readonly string[] | undefined,\n site: string,\n): { href: string }[] | undefined {\n if (value === undefined) return undefined;\n const arr = typeof value === 'string' ? [value] : value;\n if (arr.length === 0) return undefined;\n return arr.map((v) => ({ href: absolutize(v, site) }));\n}\n\n/**\n * Returns an Astro `APIRoute` that serves an RFC 9727 API catalog\n * (`application/linkset+json`, RFC 9264) at `/.well-known/api-catalog`.\n *\n * Lists the site's APIs — schema.org JSON endpoints, the schema map,\n * and any additional services — with anchor URLs, optional service-doc\n * URLs, and type pointers. Drop the returned handler into\n * `src/pages/.well-known/api-catalog.ts`.\n *\n * @example\n * ```ts\n * import { createApiCatalog } from '@jdevalk/astro-seo-graph';\n *\n * export const GET = createApiCatalog({\n * siteUrl: 'https://example.com',\n * schemaEndpoints: [\n * { path: '/schema/post.json', schemaType: 'BlogPosting' },\n * ],\n * schemaMap: { path: '/schemamap.xml' },\n * });\n * ```\n */\nexport function createApiCatalog(options: ApiCatalogOptions): APIRoute {\n const cacheControl = options.cacheControl === undefined ? 'max-age=300' : options.cacheControl;\n const contentType = options.contentType ?? 'application/linkset+json';\n const indent = options.indent ?? 2;\n const site = options.siteUrl.replace(/\\/+$/, '');\n\n return async () => {\n const linkset: LinksetEntry[] = [];\n\n if (options.schemaEndpoints) {\n for (const e of options.schemaEndpoints) {\n const entry: LinksetEntry = { anchor: absolutize(e.path, site) };\n const sd = toHrefArray(e.serviceDoc, site);\n if (sd) entry['service-doc'] = sd;\n entry.type = [{ href: `https://schema.org/${e.schemaType}` }];\n linkset.push(entry);\n }\n }\n\n if (options.schemaMap) {\n const entry: LinksetEntry = { anchor: absolutize(options.schemaMap.path, site) };\n const sd = toHrefArray(options.schemaMap.serviceDoc, site);\n if (sd) entry['service-doc'] = sd;\n linkset.push(entry);\n }\n\n if (options.additional) {\n for (const e of options.additional) {\n const entry: LinksetEntry = { anchor: absolutize(e.anchor, site) };\n const sd = toHrefArray(e.serviceDoc, site);\n if (sd) entry['service-doc'] = sd;\n const t = toHrefArray(e.type, site);\n if (t) entry.type = t;\n linkset.push(entry);\n }\n }\n\n const headers: Record<string, string> = {\n 'Content-Type': contentType,\n 'X-Robots-Tag': 'noindex, follow',\n };\n if (cacheControl !== null) headers['Cache-Control'] = cacheControl;\n\n return new Response(JSON.stringify({ linkset }, null, indent), { headers });\n };\n}\n"]}
|
package/dist/breadcrumbs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0CH;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAY;IAC/B,OAAO,IAAI;SACN,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAA8B;IAC7D,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,KAAK,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC;IAErE,MAAM,iBAAiB,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;IAC/E,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C,+CAA+C;IAC/C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,2DAA2D;IAC3D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAqB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAE7E,iEAAiE;IACjE,8BAA8B;IAC9B,IAAI,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC;IACtC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,WAAW,GAAG,aAAa,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC;QACnD,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,yCAAyC;IACzC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE9C,OAAO,KAAK,CAAC;AACjB,CAAC"}
|
|
1
|
+
{"version":3,"file":"breadcrumbs.js","sourceRoot":"","sources":["../src/breadcrumbs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA0CH;;;;GAIG;AACH,SAAS,aAAa,CAAC,IAAY;IAC/B,OAAO,IAAI;SACN,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;SAC3D,IAAI,CAAC,GAAG,CAAC,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAS,aAAa,CAAC,GAAW;IAC9B,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAA8B;IAC7D,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,KAAK,GAAG,EAAE,EAAE,IAAI,GAAG,EAAE,EAAE,GAAG,KAAK,CAAC;IAErE,MAAM,iBAAiB,GAAG,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACvD,MAAM,OAAO,GAAG,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;IAC/E,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7C,+CAA+C;IAC/C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC9C,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxE,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAEzD,2DAA2D;IAC3D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,MAAM,KAAK,GAAqB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAE7E,iEAAiE;IACjE,8BAA8B;IAC9B,IAAI,WAAW,GAAG,UAAU,CAAC,QAAQ,CAAC;IACtC,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,WAAW,GAAG,aAAa,CAAC,WAAW,GAAG,OAAO,CAAC,CAAC;QACnD,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QACnC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,aAAa,CAAC,OAAO,CAAC,CAAC;QACtD,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,UAAU,CAAC,MAAM,GAAG,WAAW,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,yCAAyC;IACzC,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE9C,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["/**\n * Breadcrumb derivation helper for Astro pages.\n *\n * Pure function: no DOM, no fetch, no Astro runtime. Takes a page URL\n * (typically `Astro.url`) and derives an ordered breadcrumb trail from\n * its path segments. The returned `BreadcrumbItem[]` array is passed\n * straight to `buildBreadcrumbList` from `@jdevalk/seo-graph-core`.\n *\n * Callers control display names via the `names` map and can omit\n * segments via `skip`. Segments without a mapped name are title-cased\n * from their slug (e.g. `open-source` → `Open Source`).\n */\n\nimport type { BreadcrumbItem } from '@jdevalk/seo-graph-core';\n\nexport interface BreadcrumbsFromUrlInput {\n /**\n * The full page URL. Typically `Astro.url` inside a layout, or any\n * absolute URL string.\n */\n url: URL | string;\n\n /**\n * Site origin with optional base path, e.g. `'https://example.com'`\n * or `'https://example.com/docs'`. Used to construct absolute URLs\n * for each crumb. Must not end with a slash.\n */\n siteUrl: string;\n\n /** Display name for the current (last) page. */\n pageName: string;\n\n /**\n * Display name for the root crumb. Defaults to `'Home'`.\n */\n homeName?: string;\n\n /**\n * Map of path segments to display names. Keys are individual slug\n * segments (e.g. `'blog'`, `'open-source'`). Segments not in this\n * map are title-cased from their slug.\n */\n names?: Readonly<Record<string, string>>;\n\n /**\n * Segments to exclude from the breadcrumb trail. The pages they\n * point to are still valid URLs — they just won't appear as crumbs.\n * For example, `['category']` skips a `/blog/category/` crumb while\n * still including `/blog/category/open-source/`.\n */\n skip?: readonly string[];\n}\n\n/**\n * Title-case a URL slug: split on hyphens, capitalize each word.\n *\n * `'open-source'` → `'Open Source'`\n */\nfunction titleCaseSlug(slug: string): string {\n return slug\n .split('-')\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ');\n}\n\n/**\n * Ensure `url` ends with exactly one trailing slash.\n */\nfunction trailingSlash(url: string): string {\n return url.endsWith('/') ? url : url + '/';\n}\n\n/**\n * Derive a breadcrumb trail from a page URL.\n *\n * Always includes a root (\"Home\") crumb and the current page as the\n * last crumb. Intermediate crumbs are derived from path segments\n * between root and the page, skipping any segments listed in `skip`.\n *\n * @returns Ordered `BreadcrumbItem[]`, root first. Pass directly to\n * `buildBreadcrumbList`'s `items` field.\n */\nexport function breadcrumbsFromUrl(input: BreadcrumbsFromUrlInput): BreadcrumbItem[] {\n const { pageName, homeName = 'Home', names = {}, skip = [] } = input;\n\n const normalizedSiteUrl = trailingSlash(input.siteUrl);\n const pageUrl = typeof input.url === 'string' ? new URL(input.url) : input.url;\n const pageHref = trailingSlash(pageUrl.href);\n\n // Derive the path relative to the site origin.\n const siteOrigin = new URL(normalizedSiteUrl);\n const relativePath = pageUrl.pathname.slice(siteOrigin.pathname.length);\n const segments = relativePath.split('/').filter(Boolean);\n\n // When the page is the site root, there is only one crumb.\n if (segments.length === 0) {\n return [{ name: pageName, url: pageHref }];\n }\n\n const skipSet = new Set(skip);\n const items: BreadcrumbItem[] = [{ name: homeName, url: normalizedSiteUrl }];\n\n // Build intermediate crumbs (everything except the last segment,\n // which is the current page).\n let accumulated = siteOrigin.pathname;\n for (const segment of segments.slice(0, -1)) {\n accumulated = trailingSlash(accumulated + segment);\n if (skipSet.has(segment)) continue;\n const name = names[segment] ?? titleCaseSlug(segment);\n items.push({ name, url: siteOrigin.origin + accumulated });\n }\n\n // Current page is always the last crumb.\n items.push({ name: pageName, url: pageHref });\n\n return items;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-context.js","sourceRoot":"","sources":["../../src/components/seo-context.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,mBAAmB,EAAsB,MAAM,kBAAkB,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AAGvD;;;GAGG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,+DAA+D,CAAC;AAoF7F,SAAS,WAAW,CAAC,KAAgC;IACjD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAC/D,CAAC;AAED,SAAS,gBAAgB,CAAC,GAAW;IACjC,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,CAAC,MAAM,GAAG,EAAE,CAAC;QACnB,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,GAAG,CAAC;IACf,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,eAAe,CAAC,KAAe,EAAE,OAAe;IAC5D,MAAM,SAAS,GAAG,KAAK,CAAC,aAAa;QACjC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC;QAChD,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC;IAElB,mEAAmE;IACnE,iDAAiD;IACjD,IAAI,SAA6B,CAAC;IAClC,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QAChC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC;IAC3C,CAAC;SAAM,IAAI,KAAK,CAAC,mBAAmB,EAAE,CAAC;QACnC,SAAS,GAAG,OAAO,CAAC;IACxB,CAAC;SAAM,CAAC;QACJ,SAAS,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC1C,CAAC;IACD,IAAI,KAAK,CAAC,OAAO;QAAE,SAAS,GAAG,SAAS,CAAC;IAEzC,iEAAiE;IACjE,mEAAmE;IACnE,UAAU;IACV,MAAM,KAAK,GAAG,SAAS,IAAI,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAErD,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,SAAS,CAAC;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,IAAI,OAAO,CAAC;IAEvC,MAAM,EAAE,GAAqB;QACzB,KAAK,EAAE,SAAS;QAChB,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,OAAO;QACd,GAAG,EAAE,KAAK;QACV,MAAM;KACT,CAAC;IACF,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,EAAE,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACxE,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,EAAE,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAE/D,uEAAuE;IACvE,oEAAoE;IACpE,+BAA+B;IAC/B,IAAI,KAAK,CAAC,UAAU,EAAE,OAAO,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnE,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;QACxD,MAAM,eAAe,GAAa,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;YAClD,MAAM,SAAS,GAAG,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC;YAC9D,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;gBAAE,SAAS;YACjC,IAAI,QAAQ,KAAK,MAAM;gBAAE,SAAS;YAClC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,SAAS,KAAK,WAAW;gBAAE,SAAS;YACzE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACnB,eAAe,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC;YAAE,EAAE,CAAC,eAAe,GAAG,eAAe,CAAC;IACzE,CAAC;IAED,sEAAsE;IACtE,qEAAqE;IACrE,uBAAuB;IACvB,IAAI,OAAO,EAAE,CAAC;QACV,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;YAAE,EAAE,CAAC,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC;QACnE,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;YAAE,EAAE,CAAC,UAAU,GAAG,KAAK,CAAC,YAAY,CAAC;QACzE,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;YAAE,EAAE,CAAC,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC;IAChF,CAAC;IAED,oDAAoD;IACpD,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,gBAAgB,CAAC,EAAE,CAAC;QACpE,MAAM,OAAO,GAA6C,EAAE,CAAC;QAC7D,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAChB,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;YAC3D,IAAI,SAAS,KAAK,SAAS;gBAAE,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC;YAC/D,MAAM,QAAQ,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;YACzD,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,CAAC,YAAY,GAAG,QAAQ,CAAC;YAC5D,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;YAC7D,IAAI,UAAU,KAAK,SAAS;gBAAE,OAAO,CAAC,cAAc,GAAG,UAAU,CAAC;YAClE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YACjF,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS;gBAAE,OAAO,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;YACxE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;gBAAE,OAAO,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QACrF,CAAC;QACD,IAAI,KAAK,CAAC,gBAAgB,KAAK,SAAS;YAAE,OAAO,CAAC,SAAS,GAAG,KAAK,CAAC,gBAAgB,CAAC;QACrF,EAAE,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,+DAA+D;IAC/D,IAAI,OAA8B,CAAC;IACnC,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QAC9B,MAAM,CAAC,GAAuC;YAC1C,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,IAAI,qBAAqB;SACpD,CAAC;QACF,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,SAAS;YAAE,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAClE,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS;YAAE,CAAC,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;QAC3E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YACzE,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAClC,CAAC;QACD,IACI,KAAK,CAAC,OAAO,CAAC,WAAW,KAAK,SAAS;YACvC,KAAK,CAAC,OAAO,CAAC,WAAW,KAAK,KAAK,CAAC,WAAW,EACjD,CAAC;YACC,CAAC,CAAC,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC;QAC9C,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;YACvE,CAAC,CAAC,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAClC,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,KAAK,KAAK,CAAC,UAAU,EAAE,CAAC;YACtF,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC;QACxC,CAAC;QACD,OAAO,GAAG,CAAC,CAAC;IAChB,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAE9F,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAE/D,MAAM,qBAAqB,GAAG,WAAW,CAAC,SAAS,IAAI,KAAK,CAAC,CAAC;IAE9D,MAAM,GAAG,GAAe;QACpB,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE;YACJ,OAAO,EAAE,KAAK,CAAC,OAAO,KAAK,IAAI;YAC/B,QAAQ,EAAE,KAAK,CAAC,QAAQ,KAAK,IAAI;YACjC,MAAM,EAAE,aAAa;SACxB;QACD,EAAE;QACF,SAAS;QACT,qBAAqB;QACrB,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;QAClC,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,EAAE;KACnC,CAAC;IACF,IAAI,SAAS,KAAK,SAAS;QAAE,GAAG,CAAC,SAAS,GAAG,SAAS,CAAC;IACvD,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IACzE,IAAI,OAAO,KAAK,SAAS;QAAE,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC;IACjD,IAAI,UAAU,KAAK,SAAS;QAAE,GAAG,CAAC,UAAU,GAAG,UAAU,CAAC;IAC1D,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IAEvD,OAAO,GAAG,CAAC;AACf,CAAC"}
|
|
1
|
+
{"version":3,"file":"seo-context.js","sourceRoot":"","sources":["../../src/components/seo-context.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Normalized, flat, render-ready projection of `SeoProps`. The `<Seo>`\n * template consumes this directly — no further reshaping, no\n * conditionals beyond \"field present?\". Keeping normalization here (pure\n * TypeScript) means vitest can assert on every decision without touching\n * Astro's renderer.\n */\n\nimport { buildAlternateLinks, type AlternateLink } from '../alternates.js';\nimport { deriveMdUrl } from '../markdown-alternate.js';\nimport type { SeoProps } from './seo-props.js';\n\n/**\n * Opted-in robots directives for maximum snippet and preview sizes in\n * search results. Appended after any `noindex`/`nofollow` prefix.\n */\nexport const ROBOTS_EXTRAS = 'max-snippet:-1, max-image-preview:large, max-video-preview:-1';\n\n/**\n * Render-ready normalization of `SeoProps`. Every field has been resolved\n * against defaults, every `Date` coerced to ISO string, every optional\n * block either populated or omitted. Templates just iterate.\n */\nexport interface SeoContext {\n /** Final `<title>` text, with `titleTemplate` already applied. */\n title: string;\n /** Meta description. Omitted from output when undefined. */\n description?: string;\n /**\n * Canonical URL. Already query-stripped (or preserved) per\n * `preserveQueryParams`. `undefined` when the page is `noindex` —\n * callers must not emit a canonical in that case.\n */\n canonical?: string;\n /** Resolved robots directives. Always present (extras always apply). */\n robots: {\n noindex: boolean;\n nofollow: boolean;\n /** Directives beyond noindex/nofollow — typically `ROBOTS_EXTRAS`. */\n extras: string;\n };\n /**\n * Open Graph fields. `title`, `type`, `url` are always populated.\n * `image` may be an empty string when no share image was provided.\n */\n og: {\n title: string;\n type: string;\n image: string;\n url: string;\n description?: string;\n siteName?: string;\n locale: string;\n localeAlternate?: readonly string[];\n imageAlt?: string;\n imageWidth?: number;\n imageHeight?: number;\n article?: {\n publishedTime?: string;\n modifiedTime?: string;\n expirationTime?: string;\n authors?: readonly string[];\n tags?: readonly string[];\n section?: string;\n publisher?: string;\n };\n };\n /** Twitter tags. Omitted entirely when the caller passed no `twitter` block. */\n twitter?: {\n card: 'summary' | 'summary_large_image' | 'app' | 'player';\n site?: string;\n creator?: string;\n title?: string;\n description?: string;\n image?: string;\n imageAlt?: string;\n };\n /**\n * hreflang alternate entries (including `x-default`). Empty array when\n * the caller passed no `alternates`, or when fewer than 2 entries\n * survive validation.\n */\n hreflangs: readonly AlternateLink[];\n /** Resolved author name for `<meta name=\"author\">`. */\n authorName?: string;\n /**\n * Derived href for the auto-emitted `<link rel=\"alternate\"\n * type=\"text/markdown\">`. Empty string when no canonical is available\n * or when canonical can't be parsed — callers must not emit a link in\n * that case. The build-time disable flag is checked in the template,\n * not here (pure TS can't see the Vite define).\n */\n markdownAlternateHref: string;\n /** Passthrough extras. Empty arrays when not provided. */\n extraLinks: ReadonlyArray<Record<string, string>>;\n extraMeta: ReadonlyArray<Record<string, string>>;\n /** JSON-LD graph to inject. `null`/`undefined` → skip the script tag. */\n graph?: Record<string, unknown> | null;\n}\n\nfunction toIsoString(value: Date | string | undefined): string | undefined {\n if (value === undefined) return undefined;\n return value instanceof Date ? value.toISOString() : value;\n}\n\nfunction stripQueryParams(url: string): string {\n try {\n const parsed = new URL(url);\n parsed.search = '';\n return parsed.toString();\n } catch {\n return url;\n }\n}\n\n/**\n * Build the flat `SeoContext` from user-provided `SeoProps` and the\n * current page URL (usually `Astro.url.href`). Pure function; no Astro\n * runtime access.\n *\n * Invariants worth knowing:\n * - `canonical` is omitted when `noindex` is true (Google recommendation).\n * - `og:url` falls back to the query-stripped page URL when canonical\n * is omitted, so share previews stay stable even on noindex pages.\n * - Twitter override fields are suppressed when they equal their OG\n * counterparts (Twitter falls back to OG automatically, so emitting\n * the duplicate is noise).\n * - `og:locale:alternate` is derived from `alternates` by converting\n * hyphenated BCP 47 to underscore form and skipping entries that\n * match the primary locale.\n */\nexport function buildSeoContext(props: SeoProps, pageUrl: string): SeoContext {\n const fullTitle = props.titleTemplate\n ? props.titleTemplate.replace('%s', props.title)\n : props.title;\n\n // Canonical: explicit > preserveQueryParams > strip query. Omitted\n // entirely when noindex (Google recommendation).\n let canonical: string | undefined;\n if (props.canonical !== undefined) {\n canonical = props.canonical.toString();\n } else if (props.preserveQueryParams) {\n canonical = pageUrl;\n } else {\n canonical = stripQueryParams(pageUrl);\n }\n if (props.noindex) canonical = undefined;\n\n // og:url still points at the canonical or stripped page URL even\n // when canonical is omitted — share previews shouldn't carry query\n // params.\n const ogUrl = canonical ?? stripQueryParams(pageUrl);\n\n const ogType = props.ogType ?? 'website';\n const ogImage = props.ogImage ?? '';\n const locale = props.locale ?? 'en_US';\n\n const og: SeoContext['og'] = {\n title: fullTitle,\n type: ogType,\n image: ogImage,\n url: ogUrl,\n locale,\n };\n if (props.description !== undefined) og.description = props.description;\n if (props.siteName !== undefined) og.siteName = props.siteName;\n\n // og:locale:alternate: convert hyphens → underscores, skip dupes, skip\n // the primary locale, and skip language-only entries whose language\n // already matches the primary.\n if (props.alternates?.entries && props.alternates.entries.length > 1) {\n const primaryLang = locale.split('_')[0]?.toLowerCase();\n const localeAlternate: string[] = [];\n const seen = new Set<string>();\n for (const entry of props.alternates.entries) {\n const ogLocale = entry.hreflang.replace('-', '_');\n const entryLang = entry.hreflang.split('-')[0]?.toLowerCase();\n if (seen.has(ogLocale)) continue;\n if (ogLocale === locale) continue;\n if (!entry.hreflang.includes('-') && entryLang === primaryLang) continue;\n seen.add(ogLocale);\n localeAlternate.push(ogLocale);\n }\n if (localeAlternate.length > 0) og.localeAlternate = localeAlternate;\n }\n\n // OG image metadata: only when at least one dimension/alt is set, and\n // only when an image URL is actually present (width/height without a\n // URL is meaningless).\n if (ogImage) {\n if (props.ogImageAlt !== undefined) og.imageAlt = props.ogImageAlt;\n if (props.ogImageWidth !== undefined) og.imageWidth = props.ogImageWidth;\n if (props.ogImageHeight !== undefined) og.imageHeight = props.ogImageHeight;\n }\n\n // Article metadata: only when ogType === 'article'.\n if (ogType === 'article' && (props.article || props.articlePublisher)) {\n const article: NonNullable<SeoContext['og']['article']> = {};\n if (props.article) {\n const published = toIsoString(props.article.publishedTime);\n if (published !== undefined) article.publishedTime = published;\n const modified = toIsoString(props.article.modifiedTime);\n if (modified !== undefined) article.modifiedTime = modified;\n const expiration = toIsoString(props.article.expirationTime);\n if (expiration !== undefined) article.expirationTime = expiration;\n if (props.article.authors !== undefined) article.authors = props.article.authors;\n if (props.article.tags !== undefined) article.tags = props.article.tags;\n if (props.article.section !== undefined) article.section = props.article.section;\n }\n if (props.articlePublisher !== undefined) article.publisher = props.articlePublisher;\n og.article = article;\n }\n\n // Twitter: suppress overrides that equal their OG counterpart.\n let twitter: SeoContext['twitter'];\n if (props.twitter !== undefined) {\n const t: NonNullable<SeoContext['twitter']> = {\n card: props.twitter.card ?? 'summary_large_image',\n };\n if (props.twitter.site !== undefined) t.site = props.twitter.site;\n if (props.twitter.creator !== undefined) t.creator = props.twitter.creator;\n if (props.twitter.title !== undefined && props.twitter.title !== fullTitle) {\n t.title = props.twitter.title;\n }\n if (\n props.twitter.description !== undefined &&\n props.twitter.description !== props.description\n ) {\n t.description = props.twitter.description;\n }\n if (props.twitter.image !== undefined && props.twitter.image !== ogImage) {\n t.image = props.twitter.image;\n }\n if (props.twitter.imageAlt !== undefined && props.twitter.imageAlt !== props.ogImageAlt) {\n t.imageAlt = props.twitter.imageAlt;\n }\n twitter = t;\n }\n\n const hreflangs = props.alternates !== undefined ? buildAlternateLinks(props.alternates) : [];\n\n const authorName = props.author ?? props.article?.authors?.[0];\n\n const markdownAlternateHref = deriveMdUrl(canonical ?? ogUrl);\n\n const ctx: SeoContext = {\n title: fullTitle,\n robots: {\n noindex: props.noindex === true,\n nofollow: props.nofollow === true,\n extras: ROBOTS_EXTRAS,\n },\n og,\n hreflangs,\n markdownAlternateHref,\n extraLinks: props.extraLinks ?? [],\n extraMeta: props.extraMeta ?? [],\n };\n if (canonical !== undefined) ctx.canonical = canonical;\n if (props.description !== undefined) ctx.description = props.description;\n if (twitter !== undefined) ctx.twitter = twitter;\n if (authorName !== undefined) ctx.authorName = authorName;\n if (props.graph !== undefined) ctx.graph = props.graph;\n\n return ctx;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-props.js","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
1
|
+
{"version":3,"file":"seo-props.js","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/**\n * Public props surface of the <Seo> component. The `.astro` template\n * normalizes these via `buildSeoContext` (in `seo-context.ts`) and\n * renders the resulting flat shape directly — no intermediate adapter.\n */\n\nimport type { BuildAlternateLinksInput } from '../alternates.js';\n\n/** Public props surface of the <Seo> component. */\nexport interface SeoProps {\n /** Page title. Required. */\n title: string;\n /**\n * Template for composing the full `<title>`. Use `%s` where the\n * title should be substituted. Defaults to the raw `title`.\n *\n * @example `\"%s | Joost.blog\"` → `\"My Post | Joost.blog\"`\n */\n titleTemplate?: string;\n /** Meta description. */\n description?: string;\n /**\n * Canonical URL. Defaults to the current page URL with the query\n * string stripped. Set `preserveQueryParams` to keep the query\n * string, or pass an explicit `canonical` to override entirely.\n *\n * Omitted from the output when `noindex` is true.\n */\n canonical?: string | URL;\n /**\n * Keep the query string in the default canonical URL. Defaults to\n * `false` (query params are stripped — the SEO-correct behavior for\n * most cases). Has no effect when `canonical` is explicitly set.\n */\n preserveQueryParams?: boolean;\n /** Open Graph type. Defaults to `'website'`. */\n ogType?: 'website' | 'article' | 'profile' | 'book';\n /** Absolute URL of the share image. */\n ogImage?: string;\n /** Alt text for the share image. */\n ogImageAlt?: string;\n /** Width of the share image in pixels. */\n ogImageWidth?: number;\n /** Height of the share image in pixels. */\n ogImageHeight?: number;\n /** Site name shown in OG tags, e.g. `\"Joost.blog\"`. */\n siteName?: string;\n /** OG locale. Defaults to `'en_US'`. */\n locale?: string;\n /**\n * Twitter card metadata. Only Twitter-specific tags (card, site,\n * creator) are emitted by default — twitter:title, :description,\n * :image, and :image:alt fall back to their `og:` counterparts\n * automatically. Set `title`, `description`, `image`, or `imageAlt`\n * here only when you want Twitter-specific content that differs\n * from the OG values.\n */\n twitter?: {\n /** Twitter handle of the site owner, e.g. `'@jdevalk'`. */\n site?: string;\n /** Twitter handle of the author. */\n creator?: string;\n /** Card type. Defaults to `'summary_large_image'`. */\n card?: 'summary' | 'summary_large_image' | 'app' | 'player';\n /** Override twitter:title. Omit to fall back to og:title. */\n title?: string;\n /** Override twitter:description. Omit to fall back to og:description. */\n description?: string;\n /** Override twitter:image. Omit to fall back to og:image. */\n image?: string;\n /** Override twitter:image:alt. Omit to fall back to og:image:alt. */\n imageAlt?: string;\n };\n /**\n * Article-specific OG metadata. Only emitted when `ogType` is\n * `'article'`.\n */\n article?: {\n publishedTime?: Date | string;\n modifiedTime?: Date | string;\n expirationTime?: Date | string;\n authors?: readonly string[];\n tags?: readonly string[];\n section?: string;\n };\n /** Emit `<meta name=\"robots\" content=\"noindex, follow, max-*\">`. */\n noindex?: boolean;\n /** Emit `nofollow` in the robots meta. */\n nofollow?: boolean;\n /**\n * Facebook page URL of the publisher. Emitted as `article:publisher`\n * when `ogType` is `'article'`.\n */\n articlePublisher?: string;\n /**\n * Author name for the `<meta name=\"author\">` tag. If omitted but\n * `article.authors` is set, the first author is used.\n */\n author?: string;\n /**\n * JSON-LD `@graph` envelope to inject as\n * `<script type=\"application/ld+json\">`. Typically the output of\n * `buildSchemaGraph(...)` or `assembleGraph(...)` from\n * `@jdevalk/seo-graph-core`. Pass `null` or omit to skip JSON-LD.\n */\n graph?: Record<string, unknown> | null;\n /** Extra `<link>` tags (icons, sitemap, RSS alternate, etc.). */\n extraLinks?: ReadonlyArray<Record<string, string>>;\n /** Extra `<meta>` tags (author, custom fields). */\n extraMeta?: ReadonlyArray<Record<string, string>>;\n /**\n * hreflang alternate-language annotations.\n *\n * Emits one `<link rel=\"alternate\" hreflang=\"…\" href=\"…\">` per\n * entry, plus an `x-default` entry pointing at the default-locale\n * sibling (or the first entry, if no default match is found). Also\n * emits matching `og:locale:alternate` tags.\n */\n alternates?: BuildAlternateLinksInput;\n}\n"]}
|
|
@@ -33,13 +33,7 @@ type AstroImageFunction = any;
|
|
|
33
33
|
export declare function imageSchema(image: AstroImageFunction): z.ZodObject<{
|
|
34
34
|
src: any;
|
|
35
35
|
alt: z.ZodString;
|
|
36
|
-
},
|
|
37
|
-
alt: string;
|
|
38
|
-
src?: any;
|
|
39
|
-
}, {
|
|
40
|
-
alt: string;
|
|
41
|
-
src?: any;
|
|
42
|
-
}>;
|
|
36
|
+
}, z.core.$strip>;
|
|
43
37
|
/**
|
|
44
38
|
* Zod schema for a nested `seo` field holding per-entry SEO overrides:
|
|
45
39
|
* title, description, share image, and page type. Enforces reasonable
|
|
@@ -63,30 +57,11 @@ export declare function seoSchema(image: AstroImageFunction): z.ZodObject<{
|
|
|
63
57
|
image: z.ZodOptional<z.ZodObject<{
|
|
64
58
|
src: any;
|
|
65
59
|
alt: z.ZodString;
|
|
66
|
-
},
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
alt: string;
|
|
71
|
-
src?: any;
|
|
60
|
+
}, z.core.$strip>>;
|
|
61
|
+
pageType: z.ZodDefault<z.ZodEnum<{
|
|
62
|
+
website: "website";
|
|
63
|
+
article: "article";
|
|
72
64
|
}>>;
|
|
73
|
-
|
|
74
|
-
}, "strip", z.ZodTypeAny, {
|
|
75
|
-
pageType: "website" | "article";
|
|
76
|
-
title?: string | undefined;
|
|
77
|
-
description?: string | undefined;
|
|
78
|
-
image?: {
|
|
79
|
-
alt: string;
|
|
80
|
-
src?: any;
|
|
81
|
-
} | undefined;
|
|
82
|
-
}, {
|
|
83
|
-
title?: string | undefined;
|
|
84
|
-
description?: string | undefined;
|
|
85
|
-
image?: {
|
|
86
|
-
alt: string;
|
|
87
|
-
src?: any;
|
|
88
|
-
} | undefined;
|
|
89
|
-
pageType?: "website" | "article" | undefined;
|
|
90
|
-
}>;
|
|
65
|
+
}, z.core.$strip>;
|
|
91
66
|
export {};
|
|
92
67
|
//# sourceMappingURL=content-helpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-helpers.d.ts","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AAEH,KAAK,kBAAkB,GAAG,GAAG,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB
|
|
1
|
+
{"version":3,"file":"content-helpers.d.ts","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AAEH,KAAK,kBAAkB,GAAG,GAAG,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB;;;kBAKpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,kBAAkB;;;;;;;;;;;kBAOlD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,WAAW,CAAC,KAAyB;IACjD,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,GAAG,EAAE,KAAK,EAAE;QACZ,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAyB;IAC/C,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QACnD,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;QACpC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC9D,CAAC,CAAC;AACP,CAAC"}
|
|
1
|
+
{"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,WAAW,CAAC,KAAyB;IACjD,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,GAAG,EAAE,KAAK,EAAE;QACZ,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAyB;IAC/C,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QACnD,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;QACpC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC9D,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { z } from 'zod';\n\n/**\n * Astro content-collection `image` function signature, captured as `any`\n * to avoid importing from the `astro:content` virtual module (which\n * only resolves inside an Astro project, not in a standalone package).\n * Users pass through their own `image` from Astro's schema callback.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype AstroImageFunction = any;\n\n/**\n * Zod schema for an image field on a content entry. Pair with Astro's\n * `image()` from the collection schema factory so the image is processed\n * through Astro's asset pipeline.\n *\n * `alt` is **required** — missing alt text is an accessibility failure\n * and an SEO failure. Decorative images should use `alt: ''` explicitly.\n * If you want to make the whole image optional, wrap the schema:\n * `imageSchema(image).optional()`.\n *\n * @example\n * ```ts\n * import { defineCollection, z } from 'astro:content';\n * import { imageSchema } from '@jdevalk/astro-seo-graph';\n *\n * const blog = defineCollection({\n * schema: ({ image }) =>\n * z.object({\n * title: z.string(),\n * featureImage: imageSchema(image).optional(),\n * }),\n * });\n * ```\n */\nexport function imageSchema(image: AstroImageFunction) {\n return z.object({\n src: image(),\n alt: z.string(),\n });\n}\n\n/**\n * Zod schema for a nested `seo` field holding per-entry SEO overrides:\n * title, description, share image, and page type. Enforces reasonable\n * length limits (5–120 chars for title, 15–160 for description) as a\n * lint.\n *\n * @example\n * ```ts\n * const blog = defineCollection({\n * schema: ({ image }) =>\n * z.object({\n * title: z.string(),\n * seo: seoSchema(image).optional(),\n * }),\n * });\n * ```\n */\nexport function seoSchema(image: AstroImageFunction) {\n return z.object({\n title: z.string().min(5).max(120).optional(),\n description: z.string().min(15).max(160).optional(),\n image: imageSchema(image).optional(),\n pageType: z.enum(['website', 'article']).default('website'),\n });\n}\n"]}
|
package/dist/git-lastmod.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git-lastmod.js","sourceRoot":"","sources":["../src/git-lastmod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAS7C;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,UAA6B,EAAE;IACxE,MAAM,EAAE,cAAc,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;YACtF,QAAQ,EAAE,OAAO;SACpB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiC,CAAC;YACtE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACxC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC"}
|
|
1
|
+
{"version":3,"file":"git-lastmod.js","sourceRoot":"","sources":["../src/git-lastmod.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAS7C;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,UAA6B,EAAE;IACxE,MAAM,EAAE,cAAc,GAAG,EAAE,EAAE,KAAK,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC;IACpD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,KAAK,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,QAAQ,CAAC,EAAE;YACtF,QAAQ,EAAE,OAAO;SACpB,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,CAAC,GAAG;YAAE,OAAO,IAAI,CAAC;QACtB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiC,CAAC;YACtE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAClC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YACxC,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC","sourcesContent":["import { execFileSync } from 'child_process';\n\nexport interface GitLastmodOptions {\n /** Commit hashes (short or full) to skip when searching for the last meaningful change. */\n excludeCommits?: string[];\n /** How many commits to inspect. Defaults to 10. */\n depth?: number;\n}\n\n/**\n * Returns the committer date of the most recent git commit that touched\n * `filePath`, skipping any commits listed in `excludeCommits`. Useful for\n * generating accurate `<lastmod>` values in sitemaps when bulk commits (\n * imports, reformats, renames) would otherwise produce misleading dates.\n *\n * Returns `null` when the file has no git history or git is unavailable.\n */\nexport function gitLastmod(filePath: string, options: GitLastmodOptions = {}): Date | null {\n const { excludeCommits = [], depth = 10 } = options;\n const excluded = new Set(excludeCommits.map((h) => h.slice(0, 7)));\n try {\n const log = execFileSync('git', ['log', `-${depth}`, '--format=%H\\t%cI', '--', filePath], {\n encoding: 'utf-8',\n }).trim();\n if (!log) return null;\n for (const line of log.split('\\n')) {\n const [hash, date] = line.split('\\t') as [string, string | undefined];\n if (!excluded.has(hash.slice(0, 7))) {\n return date ? new Date(date) : null;\n }\n }\n return null;\n } catch {\n return null;\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export { breadcrumbsFromUrl } from './breadcrumbs.js';
|
|
|
12
12
|
export type { BreadcrumbsFromUrlInput } from './breadcrumbs.js';
|
|
13
13
|
export { createIndexNowKeyRoute, submitToIndexNow, validateIndexNowKey } from './indexnow.js';
|
|
14
14
|
export type { IndexNowKeyRouteOptions, IndexNowSubmitResult } from './indexnow.js';
|
|
15
|
+
export { indexNowOnBranch } from './integration.js';
|
|
16
|
+
export type { IndexNowIntegrationOptions } from './integration.js';
|
|
17
|
+
export { buildUrlManifest, diffManifests, changedUrls, hashContent, serializeManifest, parseManifest, DEFAULT_HASH_ALGORITHM, MANIFEST_VERSION, } from './indexnow-manifest.js';
|
|
18
|
+
export type { UrlManifest, ManifestEntry, ManifestDiff, SerializedManifest, } from './indexnow-manifest.js';
|
|
15
19
|
export { renderLlmsTxt } from './llms-txt.js';
|
|
16
20
|
export type { LlmsTxtInput, LlmsTxtSection, LlmsTxtLink } from './llms-txt.js';
|
|
17
21
|
export { renderMarkdownAlternate, deriveMdUrl } from './markdown-alternate.js';
|
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,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC7E,YAAY,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,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;AAE/E,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/E,YAAY,EACR,4BAA4B,EAC5B,8BAA8B,EAC9B,yBAAyB,GAC5B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAClE,YAAY,EACR,iBAAiB,EACjB,eAAe,EACf,6BAA6B,EAC7B,wBAAwB,GAC3B,MAAM,kBAAkB,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,YAAY,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC7E,YAAY,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,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,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AACnE,OAAO,EACH,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,gBAAgB,GACnB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACR,WAAW,EACX,aAAa,EACb,YAAY,EACZ,kBAAkB,GACrB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAE/E,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC/E,YAAY,EACR,4BAA4B,EAC5B,8BAA8B,EAC9B,yBAAyB,GAC5B,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,YAAY,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAEpE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAE1D,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAClE,YAAY,EACR,iBAAiB,EACjB,eAAe,EACf,6BAA6B,EAC7B,wBAAwB,GAC3B,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,8 @@ export { buildSeoContext, ROBOTS_EXTRAS } from './components/seo-context.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 { indexNowOnBranch } from './integration.js';
|
|
14
|
+
export { buildUrlManifest, diffManifests, changedUrls, hashContent, serializeManifest, parseManifest, DEFAULT_HASH_ALGORITHM, MANIFEST_VERSION, } from './indexnow-manifest.js';
|
|
13
15
|
export { renderLlmsTxt } from './llms-txt.js';
|
|
14
16
|
export { renderMarkdownAlternate, deriveMdUrl } from './markdown-alternate.js';
|
|
15
17
|
export { createMarkdownEndpoint } from './markdown-routes.js';
|
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;AAG9D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG7E,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;AAG9C,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAO/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,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;AAG9D,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAG7E,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,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEpD,OAAO,EACH,gBAAgB,EAChB,aAAa,EACb,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,sBAAsB,EACtB,gBAAgB,GACnB,MAAM,wBAAwB,CAAC;AAQhC,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAE,uBAAuB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAO/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["// @jdevalk/astro-seo-graph — Astro integration for agent-ready schema.org SEO.\n//\n// The `<Seo>` component lives at `./Seo.astro` and must be imported via\n// the subpath export: `import Seo from '@jdevalk/astro-seo-graph/Seo.astro'`.\n// Its props surface is `SeoProps`, re-exported below.\n\nexport { aggregate } from './aggregator.js';\nexport type { AggregatorOptions, AggregatedGraph } from './aggregator.js';\n\nexport { createSchemaEndpoint, createSchemaMap } from './routes.js';\nexport type { SchemaEndpointOptions, SchemaMapEntry, SchemaMapOptions } from './routes.js';\n\nexport { seoSchema, imageSchema } from './content-helpers.js';\n\nexport type { SeoProps } from './components/seo-props.js';\nexport { buildSeoContext, ROBOTS_EXTRAS } from './components/seo-context.js';\nexport type { SeoContext } from './components/seo-context.js';\n\nexport { buildAlternateLinks } from './alternates.js';\nexport type { AlternateLink, BuildAlternateLinksInput } from './alternates.js';\n\nexport { breadcrumbsFromUrl } from './breadcrumbs.js';\nexport type { BreadcrumbsFromUrlInput } from './breadcrumbs.js';\n\nexport { createIndexNowKeyRoute, submitToIndexNow, validateIndexNowKey } from './indexnow.js';\nexport type { IndexNowKeyRouteOptions, IndexNowSubmitResult } from './indexnow.js';\n\nexport { indexNowOnBranch } from './integration.js';\nexport type { IndexNowIntegrationOptions } from './integration.js';\nexport {\n buildUrlManifest,\n diffManifests,\n changedUrls,\n hashContent,\n serializeManifest,\n parseManifest,\n DEFAULT_HASH_ALGORITHM,\n MANIFEST_VERSION,\n} from './indexnow-manifest.js';\nexport type {\n UrlManifest,\n ManifestEntry,\n ManifestDiff,\n SerializedManifest,\n} from './indexnow-manifest.js';\n\nexport { renderLlmsTxt } from './llms-txt.js';\nexport type { LlmsTxtInput, LlmsTxtSection, LlmsTxtLink } from './llms-txt.js';\n\nexport { renderMarkdownAlternate, deriveMdUrl } from './markdown-alternate.js';\nexport type {\n MarkdownAlternateFrontmatter,\n RenderMarkdownAlternateOptions,\n RenderedMarkdownAlternate,\n} from './markdown-alternate.js';\n\nexport { createMarkdownEndpoint } from './markdown-routes.js';\nexport type { MarkdownEndpointOptions } from './markdown-routes.js';\n\nexport { gitLastmod } from './git-lastmod.js';\nexport type { GitLastmodOptions } from './git-lastmod.js';\n\nexport { createApiCatalog, CATALOG_PATH } from './api-catalog.js';\nexport type {\n ApiCatalogOptions,\n ApiCatalogEntry,\n ApiCatalogSchemaEndpointEntry,\n ApiCatalogSchemaMapEntry,\n} from './api-catalog.js';\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental-IndexNow manifest helpers.
|
|
3
|
+
*
|
|
4
|
+
* The integration submits the whole site to IndexNow on every build, which
|
|
5
|
+
* the IndexNow spec discourages (submit only added/updated/deleted URLs) and
|
|
6
|
+
* which can trip per-host rate limits. These pure helpers let a caller compute
|
|
7
|
+
* a content-hash manifest of the built pages, diff it against the previously
|
|
8
|
+
* published manifest, and submit only the URLs that actually changed.
|
|
9
|
+
*
|
|
10
|
+
* The helpers are runtime-agnostic and IO-free: the caller decides how to
|
|
11
|
+
* read the built pages, where to fetch the previous manifest from, and where
|
|
12
|
+
* to publish the new one. The integration wires them to the Astro build
|
|
13
|
+
* output and the live site (see `seoGraph({ indexNow: { incremental } })`).
|
|
14
|
+
*/
|
|
15
|
+
/** Mapping of each eligible page URL to a short content hash. */
|
|
16
|
+
export type UrlManifest = Record<string, string>;
|
|
17
|
+
/** A single page's URL plus the content to hash (typically built HTML). */
|
|
18
|
+
export interface ManifestEntry {
|
|
19
|
+
url: string;
|
|
20
|
+
content: string;
|
|
21
|
+
}
|
|
22
|
+
/** Identifier for the default hash, recorded in the serialized manifest. */
|
|
23
|
+
export declare const DEFAULT_HASH_ALGORITHM = "sha256-16";
|
|
24
|
+
/** Current serialized-manifest format version. */
|
|
25
|
+
export declare const MANIFEST_VERSION = 1;
|
|
26
|
+
/** The on-disk / over-the-wire manifest shape. */
|
|
27
|
+
export interface SerializedManifest {
|
|
28
|
+
version: number;
|
|
29
|
+
algorithm: string;
|
|
30
|
+
urls: UrlManifest;
|
|
31
|
+
}
|
|
32
|
+
/** SHA-256 of `content` as hex, truncated to 16 chars (64 bits of collision space). */
|
|
33
|
+
export declare function hashContent(content: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* Build a `{ url: hash }` manifest from the eligible page entries.
|
|
36
|
+
*
|
|
37
|
+
* Pass a custom `hash` to normalize volatile markup (per-build nonces,
|
|
38
|
+
* timestamps, build ids) before hashing, so unchanged content doesn't read as
|
|
39
|
+
* "updated". The default hashes the raw content.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildUrlManifest(entries: readonly ManifestEntry[], hash?: (content: string, url: string) => string): UrlManifest;
|
|
42
|
+
/** The three change sets produced by comparing two manifests. */
|
|
43
|
+
export interface ManifestDiff {
|
|
44
|
+
/** URLs present now but not in the previous manifest. */
|
|
45
|
+
added: string[];
|
|
46
|
+
/** URLs present in both, with a different hash. */
|
|
47
|
+
updated: string[];
|
|
48
|
+
/** URLs present previously but gone now. */
|
|
49
|
+
deleted: string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Diff a previous manifest against the current one. Result arrays are sorted
|
|
53
|
+
* for stable logs and deterministic output.
|
|
54
|
+
*/
|
|
55
|
+
export declare function diffManifests(prev: UrlManifest, curr: UrlManifest): ManifestDiff;
|
|
56
|
+
/**
|
|
57
|
+
* Flatten a diff into the URL set to submit to IndexNow: added + updated +
|
|
58
|
+
* deleted. IndexNow accepts removed URLs (the engine recrawls and drops the
|
|
59
|
+
* 404/410), so deletions are included.
|
|
60
|
+
*/
|
|
61
|
+
export declare function changedUrls(diff: ManifestDiff): string[];
|
|
62
|
+
/**
|
|
63
|
+
* Serialize a manifest to stable JSON with sorted keys (so the published file
|
|
64
|
+
* diffs cleanly between deploys).
|
|
65
|
+
*/
|
|
66
|
+
export declare function serializeManifest(urls: UrlManifest, algorithm?: string): string;
|
|
67
|
+
/**
|
|
68
|
+
* Parse a previously published manifest. Returns the `{ url: hash }` map, or
|
|
69
|
+
* `null` when the input is missing or not a recognizable manifest — the caller
|
|
70
|
+
* decides whether that means "first run" or "fetch failed, skip submission".
|
|
71
|
+
*/
|
|
72
|
+
export declare function parseManifest(text: string | null | undefined): UrlManifest | null;
|
|
73
|
+
//# sourceMappingURL=indexnow-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"indexnow-manifest.d.ts","sourceRoot":"","sources":["../src/indexnow-manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEjD,2EAA2E;AAC3E,MAAM,WAAW,aAAa;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;CACnB;AAED,4EAA4E;AAC5E,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAElD,kDAAkD;AAClD,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,kDAAkD;AAClD,MAAM,WAAW,kBAAkB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;CACrB;AAED,uFAAuF;AACvF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,SAAS,aAAa,EAAE,EACjC,IAAI,GAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,MAA0C,GACnF,WAAW,CAMb;AAED,iEAAiE;AACjE,MAAM,WAAW,YAAY;IACzB,yDAAyD;IACzD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mDAAmD;IACnD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,WAAW,GAAG,YAAY,CAiBhF;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAExD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,WAAW,EACjB,SAAS,GAAE,MAA+B,GAC3C,MAAM,CAQR;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,WAAW,GAAG,IAAI,CAgBjF"}
|