@duffcloudservices/cms 0.3.17 → 0.5.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/dist/chunk-TIGZ7RKI.js +456 -0
- package/dist/chunk-TIGZ7RKI.js.map +1 -0
- package/dist/index.d.ts +199 -263
- package/dist/index.js +8 -162
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +94 -16
- package/dist/plugins/index.js +168 -61
- package/dist/plugins/index.js.map +1 -1
- package/dist/vitepressTransform-DAhmD_YQ.d.ts +471 -0
- package/package.json +1 -1
- package/src/composables/useSEO.ts +21 -259
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import * as vue from 'vue';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Types for .dcs/seo.yaml structure
|
|
5
|
+
* Matches contracts/generated/schemas/seo.json
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Root structure of .dcs/seo.yaml
|
|
9
|
+
*/
|
|
10
|
+
interface SeoConfiguration {
|
|
11
|
+
/** Schema version */
|
|
12
|
+
version: number;
|
|
13
|
+
/** ISO timestamp of last update */
|
|
14
|
+
lastUpdated?: string;
|
|
15
|
+
/** Email or identifier of who made the update */
|
|
16
|
+
updatedBy?: string;
|
|
17
|
+
/** Global/site-wide SEO defaults */
|
|
18
|
+
global?: GlobalSeoConfig;
|
|
19
|
+
/** Page-specific SEO configurations keyed by page slug */
|
|
20
|
+
pages?: Record<string, PageSeoConfig>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Global/site-wide SEO configuration
|
|
24
|
+
*/
|
|
25
|
+
interface GlobalSeoConfig {
|
|
26
|
+
/** Site name used in titles and structured data */
|
|
27
|
+
siteName?: string;
|
|
28
|
+
/** Base URL of the site (e.g., https://example.com) */
|
|
29
|
+
siteUrl?: string;
|
|
30
|
+
/** Locale for Open Graph (e.g., en_US) */
|
|
31
|
+
locale?: string;
|
|
32
|
+
/** Default page title */
|
|
33
|
+
defaultTitle?: string;
|
|
34
|
+
/** Default meta description */
|
|
35
|
+
defaultDescription?: string;
|
|
36
|
+
/** Title template with %s placeholder (e.g., "%s | Site Name") */
|
|
37
|
+
titleTemplate?: string;
|
|
38
|
+
/** Author information for structured data */
|
|
39
|
+
author?: SeoAuthorConfig;
|
|
40
|
+
/** Social media handles */
|
|
41
|
+
social?: SeoSocialConfig;
|
|
42
|
+
/** Default images for social sharing */
|
|
43
|
+
images?: SeoImagesConfig;
|
|
44
|
+
/** Default robots directive (e.g., "index, follow") */
|
|
45
|
+
robots?: string;
|
|
46
|
+
/** Global JSON-LD schemas (Organization, WebSite, etc.) */
|
|
47
|
+
schemas?: SeoSchemaConfig[];
|
|
48
|
+
/** Search engine verification codes */
|
|
49
|
+
verification?: SeoVerificationConfig;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Page-specific SEO configuration
|
|
53
|
+
*/
|
|
54
|
+
interface PageSeoConfig {
|
|
55
|
+
/** Page title */
|
|
56
|
+
title?: string;
|
|
57
|
+
/** Meta description */
|
|
58
|
+
description?: string;
|
|
59
|
+
/** Meta keywords (comma-separated) */
|
|
60
|
+
keywords?: string;
|
|
61
|
+
/** Canonical URL */
|
|
62
|
+
canonical?: string;
|
|
63
|
+
/** Page-specific robots directive */
|
|
64
|
+
robots?: string;
|
|
65
|
+
/** Open Graph configuration */
|
|
66
|
+
openGraph?: SeoOpenGraphConfig;
|
|
67
|
+
/** Twitter Card configuration */
|
|
68
|
+
twitter?: SeoTwitterConfig;
|
|
69
|
+
/** Page-specific JSON-LD schemas */
|
|
70
|
+
schemas?: SeoSchemaConfig[];
|
|
71
|
+
/** Alternate language links */
|
|
72
|
+
alternates?: SeoAlternateConfig[];
|
|
73
|
+
/** If true, don't apply titleTemplate to this page */
|
|
74
|
+
noTitleTemplate?: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Author information for structured data
|
|
78
|
+
*/
|
|
79
|
+
interface SeoAuthorConfig {
|
|
80
|
+
/** Author name */
|
|
81
|
+
name?: string;
|
|
82
|
+
/** Author email */
|
|
83
|
+
email?: string;
|
|
84
|
+
/** Author image URL */
|
|
85
|
+
image?: string;
|
|
86
|
+
/** Job title */
|
|
87
|
+
jobTitle?: string;
|
|
88
|
+
/** Social profile URLs */
|
|
89
|
+
sameAs?: string[];
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Social media handles
|
|
93
|
+
*/
|
|
94
|
+
interface SeoSocialConfig {
|
|
95
|
+
/** Twitter handle (without @) */
|
|
96
|
+
twitter?: string;
|
|
97
|
+
/** LinkedIn company or profile slug */
|
|
98
|
+
linkedin?: string;
|
|
99
|
+
/** GitHub username */
|
|
100
|
+
github?: string;
|
|
101
|
+
/** Facebook page name */
|
|
102
|
+
facebook?: string;
|
|
103
|
+
/** Instagram username */
|
|
104
|
+
instagram?: string;
|
|
105
|
+
/** YouTube channel */
|
|
106
|
+
youtube?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Default images for social sharing
|
|
110
|
+
*/
|
|
111
|
+
interface SeoImagesConfig {
|
|
112
|
+
/** Logo image URL */
|
|
113
|
+
logo?: string;
|
|
114
|
+
/** Default Open Graph image */
|
|
115
|
+
ogDefault?: string;
|
|
116
|
+
/** Default Twitter Card image */
|
|
117
|
+
twitterDefault?: string;
|
|
118
|
+
/** Favicon URL */
|
|
119
|
+
favicon?: string;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Open Graph meta configuration
|
|
123
|
+
*/
|
|
124
|
+
interface SeoOpenGraphConfig {
|
|
125
|
+
/** OG title (defaults to page title) */
|
|
126
|
+
title?: string;
|
|
127
|
+
/** OG description (defaults to page description) */
|
|
128
|
+
description?: string;
|
|
129
|
+
/** OG image URL */
|
|
130
|
+
image?: string;
|
|
131
|
+
/** Alt text for OG image */
|
|
132
|
+
imageAlt?: string;
|
|
133
|
+
/** OG image width in pixels */
|
|
134
|
+
imageWidth?: number;
|
|
135
|
+
/** OG image height in pixels */
|
|
136
|
+
imageHeight?: number;
|
|
137
|
+
/** OG type */
|
|
138
|
+
type?: 'website' | 'article' | 'profile' | 'book' | 'music.song' | 'music.album' | 'video.movie' | 'video.episode' | 'video.tv_show' | 'video.other';
|
|
139
|
+
/** OG URL (defaults to canonical) */
|
|
140
|
+
url?: string;
|
|
141
|
+
/** Article published time (ISO 8601) */
|
|
142
|
+
publishedTime?: string;
|
|
143
|
+
/** Article modified time (ISO 8601) */
|
|
144
|
+
modifiedTime?: string;
|
|
145
|
+
/** Article author */
|
|
146
|
+
author?: string;
|
|
147
|
+
/** Article section/category */
|
|
148
|
+
section?: string;
|
|
149
|
+
/** Article tags */
|
|
150
|
+
tags?: string[];
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Twitter Card configuration
|
|
154
|
+
*/
|
|
155
|
+
interface SeoTwitterConfig {
|
|
156
|
+
/** Card type */
|
|
157
|
+
card?: 'summary' | 'summary_large_image' | 'app' | 'player';
|
|
158
|
+
/** Twitter title */
|
|
159
|
+
title?: string;
|
|
160
|
+
/** Twitter description */
|
|
161
|
+
description?: string;
|
|
162
|
+
/** Twitter image URL */
|
|
163
|
+
image?: string;
|
|
164
|
+
/** Alt text for Twitter image */
|
|
165
|
+
imageAlt?: string;
|
|
166
|
+
/** Site's Twitter handle (without @) */
|
|
167
|
+
site?: string;
|
|
168
|
+
/** Content creator's Twitter handle (without @) */
|
|
169
|
+
creator?: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* JSON-LD schema configuration
|
|
173
|
+
*/
|
|
174
|
+
interface SeoSchemaConfig {
|
|
175
|
+
/** Schema.org type (e.g., "WebSite", "Organization", "Article") */
|
|
176
|
+
type: string;
|
|
177
|
+
/** Schema properties */
|
|
178
|
+
properties?: Record<string, unknown>;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Alternate language link
|
|
182
|
+
*/
|
|
183
|
+
interface SeoAlternateConfig {
|
|
184
|
+
/** Language code (e.g., "en", "es", "x-default") */
|
|
185
|
+
hreflang: string;
|
|
186
|
+
/** URL of alternate version */
|
|
187
|
+
href: string;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Search engine verification codes
|
|
191
|
+
*/
|
|
192
|
+
interface SeoVerificationConfig {
|
|
193
|
+
/** Google Search Console verification code */
|
|
194
|
+
google?: string;
|
|
195
|
+
/** Bing Webmaster Tools verification code */
|
|
196
|
+
bing?: string;
|
|
197
|
+
/** DuckDuckGo verification (reserved for future use) */
|
|
198
|
+
duckduckgo?: string;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Resolved page SEO configuration (after merging global + page)
|
|
202
|
+
*/
|
|
203
|
+
interface ResolvedPageSeo {
|
|
204
|
+
/** Final page title */
|
|
205
|
+
title: string;
|
|
206
|
+
/** Final meta description */
|
|
207
|
+
description: string;
|
|
208
|
+
/** Page keywords (comma-separated), if configured */
|
|
209
|
+
keywords?: string;
|
|
210
|
+
/** Final canonical URL */
|
|
211
|
+
canonical: string;
|
|
212
|
+
/** Final robots directive */
|
|
213
|
+
robots: string;
|
|
214
|
+
/** Merged Open Graph configuration */
|
|
215
|
+
openGraph: Required<Pick<SeoOpenGraphConfig, 'title' | 'description' | 'type'>> & SeoOpenGraphConfig;
|
|
216
|
+
/** Merged Twitter configuration */
|
|
217
|
+
twitter: Required<Pick<SeoTwitterConfig, 'card'>> & SeoTwitterConfig;
|
|
218
|
+
/** All schemas (global + page) */
|
|
219
|
+
schemas: SeoSchemaConfig[];
|
|
220
|
+
/** Alternate links */
|
|
221
|
+
alternates: SeoAlternateConfig[];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Configuration for useSEO composable
|
|
225
|
+
*/
|
|
226
|
+
interface UseSeoConfig {
|
|
227
|
+
/** Page slug matching entry in seo.yaml */
|
|
228
|
+
pageSlug: string;
|
|
229
|
+
/** Optional page path for canonical URL generation */
|
|
230
|
+
pagePath?: string;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Return type of useSEO composable
|
|
234
|
+
*/
|
|
235
|
+
interface UseSeoReturn {
|
|
236
|
+
/** Computed page SEO configuration */
|
|
237
|
+
config: vue.ComputedRef<ResolvedPageSeo>;
|
|
238
|
+
/** Apply all meta tags via useHead */
|
|
239
|
+
applyHead: (overrides?: HeadOverrides) => void;
|
|
240
|
+
/** Get JSON-LD schema objects for the page */
|
|
241
|
+
getSchema: () => object[];
|
|
242
|
+
/** Get canonical URL for the page */
|
|
243
|
+
getCanonical: () => string;
|
|
244
|
+
/** Whether SEO config was loaded from build-time */
|
|
245
|
+
hasBuildTimeSeo: boolean;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Overrides that can be passed to applyHead
|
|
249
|
+
*/
|
|
250
|
+
interface HeadOverrides {
|
|
251
|
+
/** Override title */
|
|
252
|
+
title?: string;
|
|
253
|
+
/** Override description */
|
|
254
|
+
description?: string;
|
|
255
|
+
/** Override keywords meta tag */
|
|
256
|
+
keywords?: string;
|
|
257
|
+
/** Additional or replacement schemas */
|
|
258
|
+
schemas?: object[];
|
|
259
|
+
/** Additional meta tags */
|
|
260
|
+
meta?: Array<{
|
|
261
|
+
name?: string;
|
|
262
|
+
property?: string;
|
|
263
|
+
content: string;
|
|
264
|
+
}>;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Build-time SEO for VitePress static-site generation.
|
|
269
|
+
*
|
|
270
|
+
* VitePress 1.6 does **not** use `unhead`, so the runtime `useSEO`/`applyHead`
|
|
271
|
+
* composable is a no-op against the SSG HTML. The SSG-correct sink is the
|
|
272
|
+
* `transformPageData(pageData)` build hook: writing `<meta>`/`<link>`/JSON-LD
|
|
273
|
+
* into `pageData.frontmatter.head` (VitePress bakes those into the rendered
|
|
274
|
+
* `<head>`) and overwriting `pageData.title` / `pageData.description` (VitePress
|
|
275
|
+
* renders the `<title>` — via `titleTemplate` — and the `description` meta from
|
|
276
|
+
* those two fields).
|
|
277
|
+
*
|
|
278
|
+
* This factory generalises the bespoke `buildSeoHead`/`transformPageData` that
|
|
279
|
+
* shipped inline in a site's `.vitepress/config.ts`. It reuses the shared,
|
|
280
|
+
* framework-agnostic resolver (`resolvePageSeo`, `generateOpenGraphMeta`,
|
|
281
|
+
* `generateTwitterMeta`, `generateJsonLd`) for global + page meta / OG / Twitter
|
|
282
|
+
* / canonical and the global JSON-LD knowledge graph, and delegates **page-type
|
|
283
|
+
* JSON-LD** (e.g. Article / Place / CollectionPage / Service / FAQPage +
|
|
284
|
+
* BreadcrumbList) to a *pluggable* rule set the site supplies. None of the
|
|
285
|
+
* real-estate (or any other vertical's) schema logic lives in this package — it
|
|
286
|
+
* is all site CONFIG.
|
|
287
|
+
*
|
|
288
|
+
* It is the VitePress counterpart to the Vue-SPA per-route emitter in
|
|
289
|
+
* `dcsSeoPlugin({ emitStaticHtml: true })`; both produce identical global
|
|
290
|
+
* meta/OG/Twitter/JSON-LD from the same `seo.yaml` via the shared resolver.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```ts
|
|
294
|
+
* // docs/.vitepress/config.ts
|
|
295
|
+
* import { createSeoTransformPageData } from '@duffcloudservices/cms/plugins'
|
|
296
|
+
* import seoConfig from '../../.dcs/seo.yaml'
|
|
297
|
+
*
|
|
298
|
+
* export default defineConfig({
|
|
299
|
+
* transformPageData: createSeoTransformPageData({
|
|
300
|
+
* seoConfig,
|
|
301
|
+
* pageTypeRules: [
|
|
302
|
+
* { match: (ctx) => ctx.route.startsWith('/blogs/'), build: (ctx) => [ ... ] },
|
|
303
|
+
* // ...Place / CollectionPage / Service / FAQPage rules
|
|
304
|
+
* ],
|
|
305
|
+
* }),
|
|
306
|
+
* })
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* A VitePress `head` entry. Mirrors VitePress's `HeadConfig` without taking a
|
|
312
|
+
* dependency on the `vitepress` package (which is not a dependency of this
|
|
313
|
+
* library). The tuple forms are:
|
|
314
|
+
* ['meta', { name|property, content }]
|
|
315
|
+
* ['link', { rel, href, ... }]
|
|
316
|
+
* ['script', { type: 'application/ld+json' }, '<serialised json>']
|
|
317
|
+
*/
|
|
318
|
+
type VitePressHeadConfig = [string, Record<string, string>] | [string, Record<string, string>, string];
|
|
319
|
+
/**
|
|
320
|
+
* The minimal slice of VitePress's `PageData` this factory reads and mutates.
|
|
321
|
+
* Typed structurally so callers can pass VitePress's real `PageData` without a
|
|
322
|
+
* cast and without this package importing `vitepress`.
|
|
323
|
+
*/
|
|
324
|
+
interface VitePressPageData {
|
|
325
|
+
/** Source-relative path, e.g. `index.md`, `blogs/my-post.md`. */
|
|
326
|
+
relativePath: string;
|
|
327
|
+
/** Dynamic-route params (e.g. `{ topic: 'home-buying' }`). */
|
|
328
|
+
params?: Record<string, unknown>;
|
|
329
|
+
/** Page frontmatter; `head` is appended to here. */
|
|
330
|
+
frontmatter: Record<string, any>;
|
|
331
|
+
/** VitePress page title (drives `<title>` via `titleTemplate`). */
|
|
332
|
+
title?: string;
|
|
333
|
+
/** VitePress page description (drives the `description` meta). */
|
|
334
|
+
description?: string;
|
|
335
|
+
[key: string]: unknown;
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Context handed to the site's page-type rules and resolver hooks. Everything a
|
|
339
|
+
* site needs to derive its title/description/og/schemas for one page, computed
|
|
340
|
+
* once per page by the factory.
|
|
341
|
+
*/
|
|
342
|
+
interface SeoPageContext {
|
|
343
|
+
/** Route path, e.g. `/`, `/blogs/my-post`, `/locations/birmingham`. */
|
|
344
|
+
route: string;
|
|
345
|
+
/** Slug: `'home'` for `/`, otherwise the route without its leading slash. */
|
|
346
|
+
slug: string;
|
|
347
|
+
/** Absolute canonical URL for this route. */
|
|
348
|
+
canonical: string;
|
|
349
|
+
/** Normalised site base URL (no trailing slash), e.g. `https://example.com`. */
|
|
350
|
+
siteUrl: string;
|
|
351
|
+
/** The page's frontmatter (read-only convenience; same object as pageData). */
|
|
352
|
+
frontmatter: Record<string, any>;
|
|
353
|
+
/** The resolved global SEO config block. */
|
|
354
|
+
global: GlobalSeoConfig;
|
|
355
|
+
/** The full VitePress page data (for rules that need more than the above). */
|
|
356
|
+
pageData: VitePressPageData;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* The resolved per-page title / description / OG type a site may override.
|
|
360
|
+
* Returned by the optional `resolvePage` hook so a site can apply its own
|
|
361
|
+
* per-page-type title precedence (e.g. "{City} Luxury Real Estate") and decide
|
|
362
|
+
* whether that title should win over VitePress's `titleTemplate`.
|
|
363
|
+
*/
|
|
364
|
+
interface ResolvedPageOverrides {
|
|
365
|
+
/**
|
|
366
|
+
* The page title. When `setPageTitle` is true this is written to
|
|
367
|
+
* `pageData.title` (VitePress then applies `titleTemplate`).
|
|
368
|
+
*/
|
|
369
|
+
title?: string;
|
|
370
|
+
/**
|
|
371
|
+
* When true, `title` is written back to `pageData.title`. Leave false for
|
|
372
|
+
* pages whose frontmatter title should remain authoritative (e.g. blog posts
|
|
373
|
+
* that already carry a good `<h1>`/title).
|
|
374
|
+
*/
|
|
375
|
+
setPageTitle?: boolean;
|
|
376
|
+
/** The meta description. Written to `pageData.description` when truthy. */
|
|
377
|
+
description?: string;
|
|
378
|
+
/** Open Graph type override (e.g. `'article'`, `'profile'`). */
|
|
379
|
+
ogType?: SeoOpenGraphConfig['type'];
|
|
380
|
+
/** Open Graph image URL override (e.g. a post's header image). */
|
|
381
|
+
ogImage?: string;
|
|
382
|
+
/** Keywords override (comma-separated) for the `keywords` meta. */
|
|
383
|
+
keywords?: string;
|
|
384
|
+
/**
|
|
385
|
+
* Open Graph title override. Defaults to `title` so og:title tracks <title>.
|
|
386
|
+
*/
|
|
387
|
+
ogTitle?: string;
|
|
388
|
+
/**
|
|
389
|
+
* Open Graph description override. Defaults to `description`.
|
|
390
|
+
*/
|
|
391
|
+
ogDescription?: string;
|
|
392
|
+
}
|
|
393
|
+
/** A single pluggable page-type rule: when `match` is true, emit `build`. */
|
|
394
|
+
interface SeoPageTypeRule {
|
|
395
|
+
/** Return true when this rule applies to the page (by route/slug/etc.). */
|
|
396
|
+
match: (ctx: SeoPageContext) => boolean;
|
|
397
|
+
/** Build the page-type JSON-LD objects to emit (already plain objects). */
|
|
398
|
+
build: (ctx: SeoPageContext) => Array<Record<string, unknown>>;
|
|
399
|
+
}
|
|
400
|
+
interface CreateSeoTransformPageDataOptions {
|
|
401
|
+
/** The parsed `.dcs/seo.yaml` (global graph + per-page meta). */
|
|
402
|
+
seoConfig: SeoConfiguration | undefined;
|
|
403
|
+
/**
|
|
404
|
+
* Pluggable page-type rules. Evaluated in order; **every** matching rule's
|
|
405
|
+
* `build` output is emitted (so a route can contribute both a primary schema
|
|
406
|
+
* and a BreadcrumbList from one rule, or be matched by several). The
|
|
407
|
+
* real-estate BlogPosting / Place / CollectionPage / Service / FAQPage logic
|
|
408
|
+
* is supplied here by the site — never hardcoded in this package.
|
|
409
|
+
*/
|
|
410
|
+
pageTypeRules?: SeoPageTypeRule[];
|
|
411
|
+
/**
|
|
412
|
+
* Optional hook to override per-page title / description / OG before tags are
|
|
413
|
+
* built — the site's title precedence and per-type description fallbacks.
|
|
414
|
+
* Receives the same context as the rules. Anything it omits falls back to the
|
|
415
|
+
* resolver / frontmatter defaults.
|
|
416
|
+
*/
|
|
417
|
+
resolvePage?: (ctx: SeoPageContext) => ResolvedPageOverrides | undefined;
|
|
418
|
+
/**
|
|
419
|
+
* Map a `relativePath` (+ params) to a route. Defaults to a VitePress-correct
|
|
420
|
+
* implementation: `index` becomes `/`, a trailing `/index` is dropped, `.md`
|
|
421
|
+
* is stripped, and dynamic `[name]` segments are substituted from
|
|
422
|
+
* `pageData.params`. Override only for unusual routing.
|
|
423
|
+
*/
|
|
424
|
+
relativePathToRoute?: (relativePath: string, params?: Record<string, unknown>) => string;
|
|
425
|
+
/**
|
|
426
|
+
* Emit a `<meta name="keywords">` from the resolved/overridden keywords.
|
|
427
|
+
* Default true (parity with the bespoke KDH emitter, which emitted keywords).
|
|
428
|
+
*/
|
|
429
|
+
includeKeywords?: boolean;
|
|
430
|
+
/** Enable debug logging of the emitted head per page. */
|
|
431
|
+
debug?: boolean;
|
|
432
|
+
}
|
|
433
|
+
/** Default VitePress route derivation (matches the bespoke KDH helper). */
|
|
434
|
+
declare function defaultRelativePathToRoute(relativePath: string, params?: Record<string, unknown>): string;
|
|
435
|
+
/**
|
|
436
|
+
* Build just the SEO head tuples for a page (no `pageData` mutation). Exposed
|
|
437
|
+
* separately so it is unit-testable without a VitePress `pageData` round-trip
|
|
438
|
+
* and reusable by callers that manage the `head`/`title` sinks themselves.
|
|
439
|
+
*
|
|
440
|
+
* @returns `{ head, title, description }` — the head tuples to append, and the
|
|
441
|
+
* final title/description (already overridden) the caller should write to
|
|
442
|
+
* `pageData` when `applyTitle`/`applyDescription` are appropriate.
|
|
443
|
+
*/
|
|
444
|
+
declare function buildVitePressSeoHead(pageData: VitePressPageData, options: CreateSeoTransformPageDataOptions): {
|
|
445
|
+
head: VitePressHeadConfig[];
|
|
446
|
+
title?: string;
|
|
447
|
+
description?: string;
|
|
448
|
+
setPageTitle: boolean;
|
|
449
|
+
};
|
|
450
|
+
/**
|
|
451
|
+
* Create a VitePress `transformPageData(pageData)` function that bakes DCS SEO
|
|
452
|
+
* (global meta/OG/Twitter/canonical + global JSON-LD graph + pluggable
|
|
453
|
+
* page-type JSON-LD) into the SSG `<head>`.
|
|
454
|
+
*
|
|
455
|
+
* Mutations performed on `pageData`:
|
|
456
|
+
* - **`frontmatter.head`** — the resolved tags are *appended* to any existing
|
|
457
|
+
* `head` (so site-level `head` config is preserved).
|
|
458
|
+
* - **`description`** — set to the resolved/overridden description so
|
|
459
|
+
* VitePress emits exactly one `description` meta (no duplicate; we do not
|
|
460
|
+
* push our own description meta).
|
|
461
|
+
* - **`title`** — set only when the site's `resolvePage` hook returns
|
|
462
|
+
* `setPageTitle: true` for this page, mirroring the bespoke behaviour where
|
|
463
|
+
* seo.yaml/per-type titles are authoritative but a post's frontmatter title
|
|
464
|
+
* is left intact.
|
|
465
|
+
*
|
|
466
|
+
* Defensive: never throws (a failure logs a warning and leaves `pageData`
|
|
467
|
+
* untouched), so SEO can never break a production VitePress build.
|
|
468
|
+
*/
|
|
469
|
+
declare function createSeoTransformPageData(options: CreateSeoTransformPageDataOptions): (pageData: VitePressPageData) => void;
|
|
470
|
+
|
|
471
|
+
export { type CreateSeoTransformPageDataOptions as C, type GlobalSeoConfig as G, type HeadOverrides as H, type PageSeoConfig as P, type ResolvedPageSeo as R, type SeoConfiguration as S, type UseSeoReturn as U, type VitePressPageData as V, type SeoSchemaConfig as a, type SeoOpenGraphConfig as b, type SeoTwitterConfig as c, createSeoTransformPageData as d, buildVitePressSeoHead as e, defaultRelativePathToRoute as f, type SeoPageContext as g, type SeoPageTypeRule as h, type ResolvedPageOverrides as i, type VitePressHeadConfig as j, type SeoAuthorConfig as k, type SeoSocialConfig as l, type SeoImagesConfig as m, type SeoAlternateConfig as n, type SeoVerificationConfig as o, type UseSeoConfig as p };
|