@jdevalk/astro-seo-graph 0.8.0 → 1.0.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 +188 -7
- package/README.md +23 -8
- package/dist/components/Seo.astro +117 -6
- package/dist/components/seo-context.d.ts +105 -0
- package/dist/components/seo-context.d.ts.map +1 -0
- package/dist/components/seo-context.js +189 -0
- package/dist/components/seo-context.js.map +1 -0
- package/dist/components/seo-props.d.ts +4 -67
- package/dist/components/seo-props.d.ts.map +1 -1
- package/dist/components/seo-props.js +4 -233
- package/dist/components/seo-props.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/integration.d.ts +37 -0
- package/dist/integration.d.ts.map +1 -1
- package/dist/integration.js +36 -6
- 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 -3
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized, flat, render-ready projection of `SeoProps`. The `<Seo>`
|
|
3
|
+
* template consumes this directly — no further reshaping, no
|
|
4
|
+
* conditionals beyond "field present?". Keeping normalization here (pure
|
|
5
|
+
* TypeScript) means vitest can assert on every decision without touching
|
|
6
|
+
* Astro's renderer.
|
|
7
|
+
*/
|
|
8
|
+
import { buildAlternateLinks } from '../alternates.js';
|
|
9
|
+
/**
|
|
10
|
+
* Opted-in robots directives for maximum snippet and preview sizes in
|
|
11
|
+
* search results. Appended after any `noindex`/`nofollow` prefix.
|
|
12
|
+
*/
|
|
13
|
+
export const ROBOTS_EXTRAS = 'max-snippet:-1, max-image-preview:large, max-video-preview:-1';
|
|
14
|
+
function toIsoString(value) {
|
|
15
|
+
if (value === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
18
|
+
}
|
|
19
|
+
function stripQueryParams(url) {
|
|
20
|
+
try {
|
|
21
|
+
const parsed = new URL(url);
|
|
22
|
+
parsed.search = '';
|
|
23
|
+
return parsed.toString();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build the flat `SeoContext` from user-provided `SeoProps` and the
|
|
31
|
+
* current page URL (usually `Astro.url.href`). Pure function; no Astro
|
|
32
|
+
* runtime access.
|
|
33
|
+
*
|
|
34
|
+
* Invariants worth knowing:
|
|
35
|
+
* - `canonical` is omitted when `noindex` is true (Google recommendation).
|
|
36
|
+
* - `og:url` falls back to the query-stripped page URL when canonical
|
|
37
|
+
* is omitted, so share previews stay stable even on noindex pages.
|
|
38
|
+
* - Twitter override fields are suppressed when they equal their OG
|
|
39
|
+
* counterparts (Twitter falls back to OG automatically, so emitting
|
|
40
|
+
* the duplicate is noise).
|
|
41
|
+
* - `og:locale:alternate` is derived from `alternates` by converting
|
|
42
|
+
* hyphenated BCP 47 to underscore form and skipping entries that
|
|
43
|
+
* match the primary locale.
|
|
44
|
+
*/
|
|
45
|
+
export function buildSeoContext(props, pageUrl) {
|
|
46
|
+
const fullTitle = props.titleTemplate
|
|
47
|
+
? props.titleTemplate.replace('%s', props.title)
|
|
48
|
+
: props.title;
|
|
49
|
+
// Canonical: explicit > preserveQueryParams > strip query. Omitted
|
|
50
|
+
// entirely when noindex (Google recommendation).
|
|
51
|
+
let canonical;
|
|
52
|
+
if (props.canonical !== undefined) {
|
|
53
|
+
canonical = props.canonical.toString();
|
|
54
|
+
}
|
|
55
|
+
else if (props.preserveQueryParams) {
|
|
56
|
+
canonical = pageUrl;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
canonical = stripQueryParams(pageUrl);
|
|
60
|
+
}
|
|
61
|
+
if (props.noindex)
|
|
62
|
+
canonical = undefined;
|
|
63
|
+
// og:url still points at the canonical or stripped page URL even
|
|
64
|
+
// when canonical is omitted — share previews shouldn't carry query
|
|
65
|
+
// params.
|
|
66
|
+
const ogUrl = canonical ?? stripQueryParams(pageUrl);
|
|
67
|
+
const ogType = props.ogType ?? 'website';
|
|
68
|
+
const ogImage = props.ogImage ?? '';
|
|
69
|
+
const locale = props.locale ?? 'en_US';
|
|
70
|
+
const og = {
|
|
71
|
+
title: fullTitle,
|
|
72
|
+
type: ogType,
|
|
73
|
+
image: ogImage,
|
|
74
|
+
url: ogUrl,
|
|
75
|
+
locale,
|
|
76
|
+
};
|
|
77
|
+
if (props.description !== undefined)
|
|
78
|
+
og.description = props.description;
|
|
79
|
+
if (props.siteName !== undefined)
|
|
80
|
+
og.siteName = props.siteName;
|
|
81
|
+
// og:locale:alternate: convert hyphens → underscores, skip dupes, skip
|
|
82
|
+
// the primary locale, and skip language-only entries whose language
|
|
83
|
+
// already matches the primary.
|
|
84
|
+
if (props.alternates?.entries && props.alternates.entries.length > 1) {
|
|
85
|
+
const primaryLang = locale.split('_')[0]?.toLowerCase();
|
|
86
|
+
const localeAlternate = [];
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
for (const entry of props.alternates.entries) {
|
|
89
|
+
const ogLocale = entry.hreflang.replace('-', '_');
|
|
90
|
+
const entryLang = entry.hreflang.split('-')[0]?.toLowerCase();
|
|
91
|
+
if (seen.has(ogLocale))
|
|
92
|
+
continue;
|
|
93
|
+
if (ogLocale === locale)
|
|
94
|
+
continue;
|
|
95
|
+
if (!entry.hreflang.includes('-') && entryLang === primaryLang)
|
|
96
|
+
continue;
|
|
97
|
+
seen.add(ogLocale);
|
|
98
|
+
localeAlternate.push(ogLocale);
|
|
99
|
+
}
|
|
100
|
+
if (localeAlternate.length > 0)
|
|
101
|
+
og.localeAlternate = localeAlternate;
|
|
102
|
+
}
|
|
103
|
+
// OG image metadata: only when at least one dimension/alt is set, and
|
|
104
|
+
// only when an image URL is actually present (width/height without a
|
|
105
|
+
// URL is meaningless).
|
|
106
|
+
if (ogImage) {
|
|
107
|
+
if (props.ogImageAlt !== undefined)
|
|
108
|
+
og.imageAlt = props.ogImageAlt;
|
|
109
|
+
if (props.ogImageWidth !== undefined)
|
|
110
|
+
og.imageWidth = props.ogImageWidth;
|
|
111
|
+
if (props.ogImageHeight !== undefined)
|
|
112
|
+
og.imageHeight = props.ogImageHeight;
|
|
113
|
+
}
|
|
114
|
+
// Article metadata: only when ogType === 'article'.
|
|
115
|
+
if (ogType === 'article' && (props.article || props.articlePublisher)) {
|
|
116
|
+
const article = {};
|
|
117
|
+
if (props.article) {
|
|
118
|
+
const published = toIsoString(props.article.publishedTime);
|
|
119
|
+
if (published !== undefined)
|
|
120
|
+
article.publishedTime = published;
|
|
121
|
+
const modified = toIsoString(props.article.modifiedTime);
|
|
122
|
+
if (modified !== undefined)
|
|
123
|
+
article.modifiedTime = modified;
|
|
124
|
+
const expiration = toIsoString(props.article.expirationTime);
|
|
125
|
+
if (expiration !== undefined)
|
|
126
|
+
article.expirationTime = expiration;
|
|
127
|
+
if (props.article.authors !== undefined)
|
|
128
|
+
article.authors = props.article.authors;
|
|
129
|
+
if (props.article.tags !== undefined)
|
|
130
|
+
article.tags = props.article.tags;
|
|
131
|
+
if (props.article.section !== undefined)
|
|
132
|
+
article.section = props.article.section;
|
|
133
|
+
}
|
|
134
|
+
if (props.articlePublisher !== undefined)
|
|
135
|
+
article.publisher = props.articlePublisher;
|
|
136
|
+
og.article = article;
|
|
137
|
+
}
|
|
138
|
+
// Twitter: suppress overrides that equal their OG counterpart.
|
|
139
|
+
let twitter;
|
|
140
|
+
if (props.twitter !== undefined) {
|
|
141
|
+
const t = {
|
|
142
|
+
card: props.twitter.card ?? 'summary_large_image',
|
|
143
|
+
};
|
|
144
|
+
if (props.twitter.site !== undefined)
|
|
145
|
+
t.site = props.twitter.site;
|
|
146
|
+
if (props.twitter.creator !== undefined)
|
|
147
|
+
t.creator = props.twitter.creator;
|
|
148
|
+
if (props.twitter.title !== undefined && props.twitter.title !== fullTitle) {
|
|
149
|
+
t.title = props.twitter.title;
|
|
150
|
+
}
|
|
151
|
+
if (props.twitter.description !== undefined &&
|
|
152
|
+
props.twitter.description !== props.description) {
|
|
153
|
+
t.description = props.twitter.description;
|
|
154
|
+
}
|
|
155
|
+
if (props.twitter.image !== undefined && props.twitter.image !== ogImage) {
|
|
156
|
+
t.image = props.twitter.image;
|
|
157
|
+
}
|
|
158
|
+
if (props.twitter.imageAlt !== undefined && props.twitter.imageAlt !== props.ogImageAlt) {
|
|
159
|
+
t.imageAlt = props.twitter.imageAlt;
|
|
160
|
+
}
|
|
161
|
+
twitter = t;
|
|
162
|
+
}
|
|
163
|
+
const hreflangs = props.alternates !== undefined ? buildAlternateLinks(props.alternates) : [];
|
|
164
|
+
const authorName = props.author ?? props.article?.authors?.[0];
|
|
165
|
+
const ctx = {
|
|
166
|
+
title: fullTitle,
|
|
167
|
+
robots: {
|
|
168
|
+
noindex: props.noindex === true,
|
|
169
|
+
nofollow: props.nofollow === true,
|
|
170
|
+
extras: ROBOTS_EXTRAS,
|
|
171
|
+
},
|
|
172
|
+
og,
|
|
173
|
+
hreflangs,
|
|
174
|
+
extraLinks: props.extraLinks ?? [],
|
|
175
|
+
extraMeta: props.extraMeta ?? [],
|
|
176
|
+
};
|
|
177
|
+
if (canonical !== undefined)
|
|
178
|
+
ctx.canonical = canonical;
|
|
179
|
+
if (props.description !== undefined)
|
|
180
|
+
ctx.description = props.description;
|
|
181
|
+
if (twitter !== undefined)
|
|
182
|
+
ctx.twitter = twitter;
|
|
183
|
+
if (authorName !== undefined)
|
|
184
|
+
ctx.authorName = authorName;
|
|
185
|
+
if (props.graph !== undefined)
|
|
186
|
+
ctx.graph = props.graph;
|
|
187
|
+
return ctx;
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=seo-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo-context.js","sourceRoot":"","sources":["../../src/components/seo-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* here makes it unit-testable with vitest — Astro's renderer isn't
|
|
6
|
-
* easily accessible from vitest.
|
|
2
|
+
* Public props surface of the <Seo> component. The `.astro` template
|
|
3
|
+
* normalizes these via `buildSeoContext` (in `seo-context.ts`) and
|
|
4
|
+
* renders the resulting flat shape directly — no intermediate adapter.
|
|
7
5
|
*/
|
|
8
|
-
import {
|
|
6
|
+
import type { BuildAlternateLinksInput } from '../alternates.js';
|
|
9
7
|
/** Public props surface of the <Seo> component. */
|
|
10
8
|
export interface SeoProps {
|
|
11
9
|
/** Page title. Required. */
|
|
@@ -118,65 +116,4 @@ export interface SeoProps {
|
|
|
118
116
|
*/
|
|
119
117
|
alternates?: BuildAlternateLinksInput;
|
|
120
118
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Shape consumed by `astro-seo`'s `<SEO>` component. We expose it as a
|
|
123
|
-
* type so tests can assert on exact keys / nesting rather than having
|
|
124
|
-
* to parse rendered HTML.
|
|
125
|
-
*/
|
|
126
|
-
export interface AstroSeoProps {
|
|
127
|
-
title: string;
|
|
128
|
-
description?: string;
|
|
129
|
-
canonical?: string;
|
|
130
|
-
openGraph: {
|
|
131
|
-
basic: {
|
|
132
|
-
title: string;
|
|
133
|
-
type: string;
|
|
134
|
-
image: string;
|
|
135
|
-
url: string;
|
|
136
|
-
};
|
|
137
|
-
optional?: {
|
|
138
|
-
description?: string;
|
|
139
|
-
siteName?: string;
|
|
140
|
-
locale?: string;
|
|
141
|
-
localeAlternate?: string[];
|
|
142
|
-
};
|
|
143
|
-
image?: {
|
|
144
|
-
alt?: string;
|
|
145
|
-
width?: number;
|
|
146
|
-
height?: number;
|
|
147
|
-
};
|
|
148
|
-
article?: {
|
|
149
|
-
publishedTime?: string;
|
|
150
|
-
modifiedTime?: string;
|
|
151
|
-
expirationTime?: string;
|
|
152
|
-
authors?: string[];
|
|
153
|
-
tags?: string[];
|
|
154
|
-
section?: string;
|
|
155
|
-
publisher?: string;
|
|
156
|
-
};
|
|
157
|
-
};
|
|
158
|
-
twitter?: {
|
|
159
|
-
card: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
160
|
-
site?: string;
|
|
161
|
-
creator?: string;
|
|
162
|
-
title?: string;
|
|
163
|
-
description?: string;
|
|
164
|
-
image?: string;
|
|
165
|
-
imageAlt?: string;
|
|
166
|
-
};
|
|
167
|
-
extend?: {
|
|
168
|
-
link?: Array<Record<string, string>>;
|
|
169
|
-
meta?: Array<Record<string, string>>;
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Project the public `SeoProps` onto the shape `astro-seo`'s `<SEO>`
|
|
174
|
-
* component expects. Pure function; no Astro runtime access.
|
|
175
|
-
*
|
|
176
|
-
* @param props Props as provided by the user.
|
|
177
|
-
* @param pageUrl The current page URL — usually `Astro.url.href`.
|
|
178
|
-
* Used as the canonical URL when `props.canonical` is
|
|
179
|
-
* not provided, and as the base for the OG `url`.
|
|
180
|
-
*/
|
|
181
|
-
export declare function buildAstroSeoProps(props: SeoProps, pageUrl: string): AstroSeoProps;
|
|
182
119
|
//# sourceMappingURL=seo-props.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-props.d.ts","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"seo-props.d.ts","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEjE,mDAAmD;AACnD,MAAM,WAAW,QAAQ;IACrB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gDAAgD;IAChD,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACpD,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE;QACN,2DAA2D;QAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,oCAAoC;QACpC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,sDAAsD;QACtD,IAAI,CAAC,EAAE,SAAS,GAAG,qBAAqB,GAAG,KAAK,GAAG,QAAQ,CAAC;QAC5D,6DAA6D;QAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,yEAAyE;QACzE,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,6DAA6D;QAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,qEAAqE;QACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF;;;OAGG;IACH,OAAO,CAAC,EAAE;QACN,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QAC9B,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QAC7B,cAAc,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QAC/B,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACvC,iEAAiE;IACjE,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,mDAAmD;IACnD,SAAS,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,wBAAwB,CAAC;CACzC"}
|
|
@@ -1,236 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* here makes it unit-testable with vitest — Astro's renderer isn't
|
|
6
|
-
* easily accessible from vitest.
|
|
2
|
+
* Public props surface of the <Seo> component. The `.astro` template
|
|
3
|
+
* normalizes these via `buildSeoContext` (in `seo-context.ts`) and
|
|
4
|
+
* renders the resulting flat shape directly — no intermediate adapter.
|
|
7
5
|
*/
|
|
8
|
-
|
|
9
|
-
function toIsoString(value) {
|
|
10
|
-
if (value === undefined)
|
|
11
|
-
return undefined;
|
|
12
|
-
return value instanceof Date ? value.toISOString() : value;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Build the `content` value of the `<meta name="robots">` tag. Always
|
|
16
|
-
* includes `max-snippet:-1, max-image-preview:large, max-video-preview:-1`
|
|
17
|
-
* (opt into maximum snippet/preview sizes in search results). Prepends
|
|
18
|
-
* `noindex`, `nofollow`, or `noindex, follow` as applicable.
|
|
19
|
-
*/
|
|
20
|
-
function buildRobotsContent(noindex, nofollow) {
|
|
21
|
-
const directives = [];
|
|
22
|
-
if (noindex && nofollow) {
|
|
23
|
-
directives.push('noindex', 'nofollow');
|
|
24
|
-
}
|
|
25
|
-
else if (noindex) {
|
|
26
|
-
directives.push('noindex', 'follow');
|
|
27
|
-
}
|
|
28
|
-
else if (nofollow) {
|
|
29
|
-
directives.push('nofollow');
|
|
30
|
-
}
|
|
31
|
-
directives.push('max-snippet:-1', 'max-image-preview:large', 'max-video-preview:-1');
|
|
32
|
-
return directives.join(', ');
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Strip the query string from a URL. Fragments are preserved. Falls
|
|
36
|
-
* back to returning the input unchanged if URL parsing fails.
|
|
37
|
-
*/
|
|
38
|
-
function stripQueryParams(url) {
|
|
39
|
-
try {
|
|
40
|
-
const parsed = new URL(url);
|
|
41
|
-
parsed.search = '';
|
|
42
|
-
return parsed.toString();
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
return url;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Project the public `SeoProps` onto the shape `astro-seo`'s `<SEO>`
|
|
50
|
-
* component expects. Pure function; no Astro runtime access.
|
|
51
|
-
*
|
|
52
|
-
* @param props Props as provided by the user.
|
|
53
|
-
* @param pageUrl The current page URL — usually `Astro.url.href`.
|
|
54
|
-
* Used as the canonical URL when `props.canonical` is
|
|
55
|
-
* not provided, and as the base for the OG `url`.
|
|
56
|
-
*/
|
|
57
|
-
export function buildAstroSeoProps(props, pageUrl) {
|
|
58
|
-
const fullTitle = props.titleTemplate
|
|
59
|
-
? props.titleTemplate.replace('%s', props.title)
|
|
60
|
-
: props.title;
|
|
61
|
-
// Default canonical strips query params (SEO-correct for most
|
|
62
|
-
// cases). `preserveQueryParams` opts out. Explicit `canonical`
|
|
63
|
-
// overrides entirely.
|
|
64
|
-
let canonical;
|
|
65
|
-
if (props.canonical !== undefined) {
|
|
66
|
-
canonical = props.canonical.toString();
|
|
67
|
-
}
|
|
68
|
-
else if (props.preserveQueryParams) {
|
|
69
|
-
canonical = pageUrl;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
canonical = stripQueryParams(pageUrl);
|
|
73
|
-
}
|
|
74
|
-
// Omit canonical when the page is noindex (Google recommendation).
|
|
75
|
-
if (props.noindex) {
|
|
76
|
-
canonical = undefined;
|
|
77
|
-
}
|
|
78
|
-
// og:url uses the canonical when available, otherwise the (possibly
|
|
79
|
-
// query-stripped) page URL.
|
|
80
|
-
const ogUrl = canonical ?? stripQueryParams(pageUrl);
|
|
81
|
-
const ogType = props.ogType ?? 'website';
|
|
82
|
-
const ogImage = props.ogImage ?? '';
|
|
83
|
-
const openGraph = {
|
|
84
|
-
basic: {
|
|
85
|
-
title: fullTitle,
|
|
86
|
-
type: ogType,
|
|
87
|
-
image: ogImage,
|
|
88
|
-
url: ogUrl,
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
const optional = {};
|
|
92
|
-
if (props.description !== undefined)
|
|
93
|
-
optional.description = props.description;
|
|
94
|
-
if (props.siteName !== undefined)
|
|
95
|
-
optional.siteName = props.siteName;
|
|
96
|
-
optional.locale = props.locale ?? 'en_US';
|
|
97
|
-
// og:locale:alternate from hreflang entries. Convert hyphenated
|
|
98
|
-
// BCP 47 (fr-CA) to OG-style underscores (fr_CA), and skip any
|
|
99
|
-
// entry that matches the primary og:locale (including language-only
|
|
100
|
-
// matches like hreflang='en' when og:locale='en_US').
|
|
101
|
-
if (props.alternates?.entries && props.alternates.entries.length > 1) {
|
|
102
|
-
const primaryLang = optional.locale?.split('_')[0]?.toLowerCase();
|
|
103
|
-
const localeAlternate = [];
|
|
104
|
-
const seen = new Set();
|
|
105
|
-
for (const entry of props.alternates.entries) {
|
|
106
|
-
const ogLocale = entry.hreflang.replace('-', '_');
|
|
107
|
-
const entryLang = entry.hreflang.split('-')[0]?.toLowerCase();
|
|
108
|
-
if (seen.has(ogLocale))
|
|
109
|
-
continue;
|
|
110
|
-
if (ogLocale === optional.locale)
|
|
111
|
-
continue;
|
|
112
|
-
// Skip language-only hreflang when it matches the primary locale's language.
|
|
113
|
-
if (!entry.hreflang.includes('-') && entryLang === primaryLang)
|
|
114
|
-
continue;
|
|
115
|
-
seen.add(ogLocale);
|
|
116
|
-
localeAlternate.push(ogLocale);
|
|
117
|
-
}
|
|
118
|
-
if (localeAlternate.length > 0)
|
|
119
|
-
optional.localeAlternate = localeAlternate;
|
|
120
|
-
}
|
|
121
|
-
openGraph.optional = optional;
|
|
122
|
-
const hasImageMeta = props.ogImageAlt !== undefined ||
|
|
123
|
-
props.ogImageWidth !== undefined ||
|
|
124
|
-
props.ogImageHeight !== undefined;
|
|
125
|
-
if (ogImage && hasImageMeta) {
|
|
126
|
-
const image = {};
|
|
127
|
-
if (props.ogImageAlt !== undefined)
|
|
128
|
-
image.alt = props.ogImageAlt;
|
|
129
|
-
if (props.ogImageWidth !== undefined)
|
|
130
|
-
image.width = props.ogImageWidth;
|
|
131
|
-
if (props.ogImageHeight !== undefined)
|
|
132
|
-
image.height = props.ogImageHeight;
|
|
133
|
-
openGraph.image = image;
|
|
134
|
-
}
|
|
135
|
-
if (ogType === 'article' && (props.article || props.articlePublisher)) {
|
|
136
|
-
const article = {};
|
|
137
|
-
if (props.article) {
|
|
138
|
-
const published = toIsoString(props.article.publishedTime);
|
|
139
|
-
if (published !== undefined)
|
|
140
|
-
article.publishedTime = published;
|
|
141
|
-
const modified = toIsoString(props.article.modifiedTime);
|
|
142
|
-
if (modified !== undefined)
|
|
143
|
-
article.modifiedTime = modified;
|
|
144
|
-
const expiration = toIsoString(props.article.expirationTime);
|
|
145
|
-
if (expiration !== undefined)
|
|
146
|
-
article.expirationTime = expiration;
|
|
147
|
-
if (props.article.authors !== undefined)
|
|
148
|
-
article.authors = [...props.article.authors];
|
|
149
|
-
if (props.article.tags !== undefined)
|
|
150
|
-
article.tags = [...props.article.tags];
|
|
151
|
-
if (props.article.section !== undefined)
|
|
152
|
-
article.section = props.article.section;
|
|
153
|
-
}
|
|
154
|
-
if (props.articlePublisher !== undefined)
|
|
155
|
-
article.publisher = props.articlePublisher;
|
|
156
|
-
openGraph.article = article;
|
|
157
|
-
}
|
|
158
|
-
const astroSeo = {
|
|
159
|
-
title: fullTitle,
|
|
160
|
-
openGraph,
|
|
161
|
-
};
|
|
162
|
-
if (canonical !== undefined)
|
|
163
|
-
astroSeo.canonical = canonical;
|
|
164
|
-
if (props.description !== undefined)
|
|
165
|
-
astroSeo.description = props.description;
|
|
166
|
-
if (props.twitter !== undefined) {
|
|
167
|
-
const twitter = {
|
|
168
|
-
card: props.twitter.card ?? 'summary_large_image',
|
|
169
|
-
};
|
|
170
|
-
if (props.twitter.site !== undefined)
|
|
171
|
-
twitter.site = props.twitter.site;
|
|
172
|
-
if (props.twitter.creator !== undefined)
|
|
173
|
-
twitter.creator = props.twitter.creator;
|
|
174
|
-
// Only emit twitter:title/description/image/imageAlt when the
|
|
175
|
-
// caller explicitly provided Twitter-specific overrides.
|
|
176
|
-
// Otherwise Twitter falls back to the og: counterparts
|
|
177
|
-
// automatically — emitting duplicates is noisy.
|
|
178
|
-
if (props.twitter.title !== undefined && props.twitter.title !== fullTitle) {
|
|
179
|
-
twitter.title = props.twitter.title;
|
|
180
|
-
}
|
|
181
|
-
if (props.twitter.description !== undefined &&
|
|
182
|
-
props.twitter.description !== props.description) {
|
|
183
|
-
twitter.description = props.twitter.description;
|
|
184
|
-
}
|
|
185
|
-
if (props.twitter.image !== undefined && props.twitter.image !== ogImage) {
|
|
186
|
-
twitter.image = props.twitter.image;
|
|
187
|
-
}
|
|
188
|
-
if (props.twitter.imageAlt !== undefined && props.twitter.imageAlt !== props.ogImageAlt) {
|
|
189
|
-
twitter.imageAlt = props.twitter.imageAlt;
|
|
190
|
-
}
|
|
191
|
-
astroSeo.twitter = twitter;
|
|
192
|
-
}
|
|
193
|
-
// Resolve hreflang alternates (if any). Returns [] when fewer than
|
|
194
|
-
// 2 entries survive validation, so this is cheap to call.
|
|
195
|
-
const alternateLinks = props.alternates !== undefined ? buildAlternateLinks(props.alternates) : [];
|
|
196
|
-
// Build our own robots meta (astro-seo's doesn't support max-*).
|
|
197
|
-
const robotsContent = buildRobotsContent(props.noindex, props.nofollow);
|
|
198
|
-
// Resolve author name: explicit prop takes precedence, falls back
|
|
199
|
-
// to first entry in article.authors.
|
|
200
|
-
const authorName = props.author ?? props.article?.authors?.[0];
|
|
201
|
-
const meta = [];
|
|
202
|
-
meta.push({ name: 'robots', content: robotsContent });
|
|
203
|
-
if (authorName !== undefined) {
|
|
204
|
-
meta.push({ name: 'author', content: authorName });
|
|
205
|
-
}
|
|
206
|
-
if (props.extraMeta !== undefined) {
|
|
207
|
-
for (const entry of props.extraMeta)
|
|
208
|
-
meta.push({ ...entry });
|
|
209
|
-
}
|
|
210
|
-
const hasExtraLinks = props.extraLinks !== undefined && props.extraLinks.length > 0;
|
|
211
|
-
const hasAlternates = alternateLinks.length > 0;
|
|
212
|
-
const link = [];
|
|
213
|
-
if (hasExtraLinks) {
|
|
214
|
-
for (const entry of props.extraLinks)
|
|
215
|
-
link.push({ ...entry });
|
|
216
|
-
}
|
|
217
|
-
if (hasAlternates) {
|
|
218
|
-
for (const entry of alternateLinks) {
|
|
219
|
-
link.push({
|
|
220
|
-
rel: entry.rel,
|
|
221
|
-
href: entry.href,
|
|
222
|
-
hreflang: entry.hreflang,
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
const extend = {};
|
|
227
|
-
if (link.length > 0)
|
|
228
|
-
extend.link = link;
|
|
229
|
-
if (meta.length > 0)
|
|
230
|
-
extend.meta = meta;
|
|
231
|
-
if (extend.link !== undefined || extend.meta !== undefined) {
|
|
232
|
-
astroSeo.extend = extend;
|
|
233
|
-
}
|
|
234
|
-
return astroSeo;
|
|
235
|
-
}
|
|
6
|
+
export {};
|
|
236
7
|
//# sourceMappingURL=seo-props.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-props.js","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"seo-props.js","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,12 +3,15 @@ export type { AggregatorOptions, AggregatedGraph } from './aggregator.js';
|
|
|
3
3
|
export { createSchemaEndpoint, createSchemaMap } from './routes.js';
|
|
4
4
|
export type { SchemaEndpointOptions, SchemaMapEntry, SchemaMapOptions } from './routes.js';
|
|
5
5
|
export { seoSchema, imageSchema } from './content-helpers.js';
|
|
6
|
-
export {
|
|
7
|
-
export
|
|
6
|
+
export type { SeoProps } from './components/seo-props.js';
|
|
7
|
+
export { buildSeoContext, ROBOTS_EXTRAS } from './components/seo-context.js';
|
|
8
|
+
export type { SeoContext } from './components/seo-context.js';
|
|
8
9
|
export { buildAlternateLinks } from './alternates.js';
|
|
9
10
|
export type { AlternateLink, BuildAlternateLinksInput } from './alternates.js';
|
|
10
11
|
export { breadcrumbsFromUrl } from './breadcrumbs.js';
|
|
11
12
|
export type { BreadcrumbsFromUrlInput } from './breadcrumbs.js';
|
|
12
13
|
export { createIndexNowKeyRoute, submitToIndexNow, validateIndexNowKey } from './indexnow.js';
|
|
13
14
|
export type { IndexNowKeyRouteOptions, IndexNowSubmitResult } from './indexnow.js';
|
|
15
|
+
export { renderLlmsTxt } from './llms-txt.js';
|
|
16
|
+
export type { LlmsTxtInput, LlmsTxtSection, LlmsTxtLink } from './llms-txt.js';
|
|
14
17
|
//# 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,
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
export { aggregate } from './aggregator.js';
|
|
7
7
|
export { createSchemaEndpoint, createSchemaMap } from './routes.js';
|
|
8
8
|
export { seoSchema, imageSchema } from './content-helpers.js';
|
|
9
|
-
export {
|
|
9
|
+
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 { 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;
|
|
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"}
|
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.
|
|
@@ -62,6 +94,11 @@ export interface SeoGraphIntegrationOptions {
|
|
|
62
94
|
* `index.html` are rewritten to their directory form.
|
|
63
95
|
*/
|
|
64
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;
|
|
65
102
|
}
|
|
66
103
|
/**
|
|
67
104
|
* Turn a built HTML file path (relative to the outDir, e.g.
|