@duffcloudservices/cms 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TIGZ7RKI.js → chunk-JJK7OGC2.js} +5 -2
- package/dist/chunk-JJK7OGC2.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/index.js +6 -4
- package/dist/plugins/index.js.map +1 -1
- package/dist/{vitepressTransform-DAhmD_YQ.d.ts → vitepressTransform-D9P9D7So.d.ts} +8 -0
- package/package.json +2 -1
- package/src/components/ManagedImage.test.ts +75 -0
- package/src/components/ManagedImage.vue +19 -3
- package/src/components/ResponsiveImage.test.ts +47 -0
- package/src/components/ResponsiveImage.vue +15 -3
- package/dist/chunk-TIGZ7RKI.js.map +0 -1
|
@@ -316,6 +316,9 @@ function buildVitePressSeoHead(pageData, options) {
|
|
|
316
316
|
const pageOg = seoConfig?.pages?.[slug]?.openGraph;
|
|
317
317
|
const ogConfig = {
|
|
318
318
|
...pageOg,
|
|
319
|
+
// Site-supplied extra OG fields (publishedTime/section/tags/…) — lower
|
|
320
|
+
// precedence than the explicit type/title/desc/image resolved below.
|
|
321
|
+
...overrides.og,
|
|
319
322
|
type: ogType,
|
|
320
323
|
title: overrides.ogTitle || templated,
|
|
321
324
|
description: overrides.ogDescription || pageOg?.description || description,
|
|
@@ -452,5 +455,5 @@ function parsePagesManifest(raw) {
|
|
|
452
455
|
}
|
|
453
456
|
|
|
454
457
|
export { buildHeadTags, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, loadPagesManifest, renderHeadTags, resolvePageSeo, spliceHeadHtml, stripManagedHeadTags };
|
|
455
|
-
//# sourceMappingURL=chunk-
|
|
456
|
-
//# sourceMappingURL=chunk-
|
|
458
|
+
//# sourceMappingURL=chunk-JJK7OGC2.js.map
|
|
459
|
+
//# sourceMappingURL=chunk-JJK7OGC2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/headTags.ts","../src/seo/spliceHeadHtml.ts","../src/seo/vitepressTransform.ts","../src/seo/pagesManifest.ts"],"names":["path"],"mappings":"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","file":"chunk-JJK7OGC2.js","sourcesContent":["/**\n * Framework-agnostic SEO head-tag resolution.\n *\n * This module is the single source of truth for turning a\n * (`pageSlug`, `pagePath`, `SeoConfiguration`) triple into a plain,\n * serialisable description of the `<head>` tags a page should carry:\n * resolved title, meta[], link[], and JSON-LD script[].\n *\n * It is consumed by:\n * - `useSEO` (runtime, via `@unhead/vue`) — see `../composables/useSEO.ts`\n * - `dcsSeoPlugin`'s build-time static-HTML emitter — see\n * `../plugins/dcsSeoPlugin.ts`\n *\n * Keeping the resolution here (rather than inside the Vue composable) means\n * the runtime and the build-time emitter produce byte-identical tags from the\n * same `seo.yaml`, with no Vue/unhead dependency required at build time.\n *\n * Runtime behaviour is intentionally identical to the previous in-composable\n * logic **except** for one corrected bug: `og:title` now falls back to the\n * fully-resolved (template-applied) page title instead of the raw, untemplated\n * `page.title`. Previously `og:title` could diverge from the `<title>` element\n * (e.g. `<title>Iron Oak Contractors | Our Services</title>` but\n * `og:title = \"Our Services\"`).\n */\n\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n SeoOpenGraphConfig,\n SeoTwitterConfig,\n SeoSchemaConfig,\n ResolvedPageSeo,\n} from '../types/seo'\n\n// =============================================================================\n// Plain head-tag structures (no framework types)\n// =============================================================================\n\n/** A `<meta>` tag — either a `name=`/`content=` or `property=`/`content=` pair. */\nexport interface HeadMetaTag {\n name?: string\n property?: string\n content: string\n}\n\n/** A `<link>` tag (canonical, alternate, etc.). */\nexport interface HeadLinkTag {\n rel: string\n href: string\n hreflang?: string\n}\n\n/** A `<script type=\"application/ld+json\">` tag carrying serialised JSON-LD. */\nexport interface HeadScriptTag {\n type: string\n /** Pre-serialised JSON-LD string (already `JSON.stringify`-ed). */\n children: string\n}\n\n/**\n * The complete, framework-agnostic set of resolved `<head>` tags for a page.\n *\n * - `title` is the final, template-applied title (what goes in `<title>`).\n * - `meta` covers description, keywords, robots, verification, OG, Twitter.\n * - `link` covers canonical + hreflang alternates.\n * - `script` covers JSON-LD (global + page schemas).\n * - `jsonLd` is the same JSON-LD as parsed objects, for callers that want the\n * structured form (e.g. `useSEO().getSchema()`).\n */\nexport interface ResolvedHeadTags {\n title: string\n meta: HeadMetaTag[]\n link: HeadLinkTag[]\n script: HeadScriptTag[]\n jsonLd: object[]\n /** The fully-resolved page SEO (merged global + page) used to build tags. */\n resolved: ResolvedPageSeo\n}\n\n/** Optional overrides applied on top of the resolved config when building tags. */\nexport interface HeadTagOverrides {\n /** Override the resolved `<title>`. */\n title?: string\n /**\n * Page-specific title fallback used when `seo.yaml` has no `title` for this\n * page (e.g. the route `title` from `pages.yaml`). Unlike `global.defaultTitle`\n * this IS run through `titleTemplate`, so un-configured routes (blog posts,\n * etc.) get unique titles rather than the global default.\n */\n fallbackTitle?: string\n /** Override the resolved meta description. */\n description?: string\n /** Override the meta keywords value. */\n keywords?: string\n /**\n * Force the robots directive (e.g. `'noindex, nofollow'`). When supplied this\n * wins over both page- and global-level robots.\n */\n robots?: string\n /** Replace the JSON-LD schema objects entirely (already-built objects). */\n schemas?: object[]\n /** Extra meta tags appended after the generated ones. */\n meta?: HeadMetaTag[]\n /**\n * Emit a `<meta name=\"keywords\">` tag from the page's `keywords` field.\n *\n * Defaults to `false` so the `useSEO` runtime path stays byte-identical to\n * its historical output (which never emitted keywords). The static-HTML\n * emitter opts in (`true`) to surface page keywords in the baked `<head>`.\n */\n includeKeywords?: boolean\n}\n\n// =============================================================================\n// Open Graph / Twitter / JSON-LD generation\n// =============================================================================\n\n/**\n * Generate Open Graph meta tags from config.\n *\n * @param resolvedTitle - the final, template-applied page title. Used as the\n * `og:title` fallback so OG stays consistent with `<title>`.\n */\nexport function generateOpenGraphMeta(\n og: SeoOpenGraphConfig,\n global: GlobalSeoConfig,\n resolvedTitle: string,\n pageDescription: string,\n canonical: string\n): Array<{ property: string; content: string }> {\n const tags: Array<{ property: string; content: string }> = []\n\n // og:title falls back to the *resolved* (templated) title — see module\n // docblock. `og.title` is already pre-resolved in `resolvePageSeo` to\n // `page.openGraph.title || resolvedTitle`, so this is consistent either way.\n tags.push({ property: 'og:title', content: og.title || resolvedTitle })\n tags.push({ property: 'og:description', content: og.description || pageDescription })\n tags.push({ property: 'og:url', content: og.url || canonical })\n tags.push({ property: 'og:type', content: og.type || 'website' })\n\n const image = og.image || global.images?.ogDefault\n if (image) {\n tags.push({ property: 'og:image', content: image })\n if (og.imageAlt || resolvedTitle) {\n tags.push({ property: 'og:image:alt', content: og.imageAlt || resolvedTitle })\n }\n if (og.imageWidth) {\n tags.push({ property: 'og:image:width', content: String(og.imageWidth) })\n }\n if (og.imageHeight) {\n tags.push({ property: 'og:image:height', content: String(og.imageHeight) })\n }\n }\n\n if (global.siteName) {\n tags.push({ property: 'og:site_name', content: global.siteName })\n }\n\n if (global.locale) {\n tags.push({ property: 'og:locale', content: global.locale })\n }\n\n // Article-specific tags\n if (og.type === 'article') {\n if (og.publishedTime) {\n tags.push({ property: 'article:published_time', content: og.publishedTime })\n }\n if (og.modifiedTime) {\n tags.push({ property: 'article:modified_time', content: og.modifiedTime })\n }\n if (og.author) {\n tags.push({ property: 'article:author', content: og.author })\n }\n if (og.section) {\n tags.push({ property: 'article:section', content: og.section })\n }\n if (og.tags) {\n og.tags.forEach((tag) => {\n tags.push({ property: 'article:tag', content: tag })\n })\n }\n }\n\n return tags\n}\n\n/**\n * Generate Twitter Card meta tags from config.\n *\n * @param resolvedTitle - the final, template-applied page title (Twitter title\n * fallback), mirroring the OG behaviour.\n */\nexport function generateTwitterMeta(\n twitter: SeoTwitterConfig,\n global: GlobalSeoConfig,\n resolvedTitle: string,\n pageDescription: string\n): Array<{ name: string; content: string }> {\n const tags: Array<{ name: string; content: string }> = []\n\n tags.push({ name: 'twitter:card', content: twitter.card || 'summary_large_image' })\n tags.push({ name: 'twitter:title', content: twitter.title || resolvedTitle })\n tags.push({ name: 'twitter:description', content: twitter.description || pageDescription })\n\n const image = twitter.image || global.images?.twitterDefault\n if (image) {\n tags.push({ name: 'twitter:image', content: image })\n if (twitter.imageAlt || resolvedTitle) {\n tags.push({ name: 'twitter:image:alt', content: twitter.imageAlt || resolvedTitle })\n }\n }\n\n const site = twitter.site || global.social?.twitter\n if (site) {\n tags.push({ name: 'twitter:site', content: site.startsWith('@') ? site : `@${site}` })\n }\n\n if (twitter.creator) {\n tags.push({\n name: 'twitter:creator',\n content: twitter.creator.startsWith('@') ? twitter.creator : `@${twitter.creator}`,\n })\n }\n\n return tags\n}\n\n/**\n * Generate JSON-LD schema objects from schema configs, auto-populating common\n * WebSite properties from global config.\n */\nexport function generateJsonLd(schemas: SeoSchemaConfig[], global: GlobalSeoConfig): object[] {\n return schemas.map((schema) => {\n const base: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': schema.type,\n }\n\n // Merge properties\n if (schema.properties) {\n Object.assign(base, schema.properties)\n }\n\n // Auto-populate common properties from global config\n if (schema.type === 'WebSite' && global.siteUrl && !base.url) {\n base.url = global.siteUrl\n }\n if (schema.type === 'WebSite' && global.siteName && !base.name) {\n base.name = global.siteName\n }\n\n return base\n })\n}\n\n/**\n * Resolve page SEO by merging global defaults with page-specific config.\n *\n * Behavioural changes from the historical in-composable version (both bug\n * fixes):\n * 1. `openGraph.title` falls back to the **template-applied** page title, not\n * the raw `page.title`, so `og:title` matches `<title>`.\n * 2. The `titleTemplate` is applied **only** to a page-specific title\n * (`page.title`, or the `fallbackTitle` arg). It is no longer applied to\n * `global.defaultTitle`, which is already the complete brand title —\n * templating it produced `\"Brand | Default Title | Brand\"` doubling on any\n * page without its own `seo.yaml` entry.\n *\n * @param fallbackTitle - a page-specific title to use when `seo.yaml` has no\n * `title` for this page (e.g. the route `title` from `pages.yaml`). It IS run\n * through `titleTemplate`; `global.defaultTitle` is the last resort and is not.\n */\nexport function resolvePageSeo(\n pageSlug: string,\n pagePath: string | undefined,\n seoConfig: SeoConfiguration | undefined,\n fallbackTitle?: string\n): ResolvedPageSeo {\n const global = seoConfig?.global ?? {}\n const page = seoConfig?.pages?.[pageSlug] ?? {}\n\n // Build canonical URL\n let canonical = page.canonical || ''\n if (!canonical && global.siteUrl) {\n const path = pagePath ?? (pageSlug === 'home' ? '/' : `/${pageSlug}`)\n canonical = `${global.siteUrl.replace(/\\/$/, '')}${path}`\n }\n\n // Build title. The `%s` slot is for a PAGE-SPECIFIC title only (a seo.yaml\n // `page.title` or a `pages.yaml` route title). `global.defaultTitle` is\n // already brand-complete, so it is used verbatim — never re-templated.\n const pageSpecificTitle = page.title || fallbackTitle\n let title: string\n if (pageSpecificTitle) {\n title =\n page.noTitleTemplate || !global.titleTemplate\n ? pageSpecificTitle\n : global.titleTemplate.replace('%s', pageSpecificTitle)\n } else {\n title = global.defaultTitle || pageSlug\n }\n\n // Merge Open Graph. og:title now falls back to the *resolved* (templated)\n // title rather than the raw page.title, keeping it consistent with <title>.\n const openGraph: ResolvedPageSeo['openGraph'] = {\n type: page.openGraph?.type || 'website',\n title: page.openGraph?.title || title,\n description: page.openGraph?.description || page.description || global.defaultDescription || '',\n ...page.openGraph,\n }\n\n // Merge Twitter\n const twitter: ResolvedPageSeo['twitter'] = {\n card: page.twitter?.card || 'summary_large_image',\n ...page.twitter,\n }\n\n // Combine schemas (global + page)\n const schemas = [...(global.schemas ?? []), ...(page.schemas ?? [])]\n\n return {\n title,\n description: page.description || global.defaultDescription || '',\n canonical,\n robots: page.robots || global.robots || 'index, follow',\n openGraph,\n twitter,\n schemas,\n alternates: page.alternates ?? [],\n // Surface keywords on the resolved object so both runtime and emitter can\n // emit the meta tag without re-reading the raw page config.\n keywords: page.keywords,\n }\n}\n\n// =============================================================================\n// Unified head-tag builder (consumed by runtime + emitter)\n// =============================================================================\n\n/**\n * Build the complete, framework-agnostic set of `<head>` tags for a page.\n *\n * This is the function both the `useSEO` runtime and the build-time emitter\n * call, guaranteeing identical output. Pass `overrides` to mirror the\n * composable's `applyHead(overrides)` behaviour, or to force `robots` (used by\n * the emitter's `noindex` option).\n */\nexport function buildHeadTags(\n pageSlug: string,\n pagePath: string | undefined,\n seoConfig: SeoConfiguration | undefined,\n overrides?: HeadTagOverrides\n): ResolvedHeadTags {\n const resolved = resolvePageSeo(pageSlug, pagePath, seoConfig, overrides?.fallbackTitle)\n const global = seoConfig?.global ?? {}\n\n const title = overrides?.title ?? resolved.title\n const description = overrides?.description ?? resolved.description\n const robots = overrides?.robots ?? resolved.robots\n const keywords = overrides?.keywords ?? resolved.keywords\n\n // ── meta ────────────────────────────────────────────────────────────────\n const meta: HeadMetaTag[] = []\n\n meta.push({ name: 'description', content: description })\n\n // Keywords are opt-in (default off) so the runtime composable output is\n // unchanged; the static-HTML emitter passes includeKeywords: true.\n if (keywords && overrides?.includeKeywords) {\n meta.push({ name: 'keywords', content: keywords })\n }\n\n if (robots) {\n meta.push({ name: 'robots', content: robots })\n }\n\n // Verification codes\n if (global.verification?.google) {\n meta.push({ name: 'google-site-verification', content: global.verification.google })\n }\n if (global.verification?.bing) {\n meta.push({ name: 'msvalidate.01', content: global.verification.bing })\n }\n\n // Open Graph\n const ogMeta = generateOpenGraphMeta(resolved.openGraph, global, title, description, resolved.canonical)\n meta.push(...ogMeta.map((t) => ({ property: t.property, content: t.content })))\n\n // Twitter\n const twitterMeta = generateTwitterMeta(resolved.twitter, global, title, description)\n meta.push(...twitterMeta.map((t) => ({ name: t.name, content: t.content })))\n\n // Caller-supplied extra meta\n if (overrides?.meta) {\n meta.push(...overrides.meta)\n }\n\n // ── link ──────────────────────────────────────────────────────────────\n const link: HeadLinkTag[] = []\n\n if (resolved.canonical) {\n link.push({ rel: 'canonical', href: resolved.canonical })\n }\n\n resolved.alternates.forEach((alt) => {\n link.push({ rel: 'alternate', href: alt.href, hreflang: alt.hreflang })\n })\n\n // ── script (JSON-LD) ──────────────────────────────────────────────────\n const jsonLd = overrides?.schemas ?? generateJsonLd(resolved.schemas, global)\n const script: HeadScriptTag[] = jsonLd.map((schema) => ({\n type: 'application/ld+json',\n children: JSON.stringify(schema),\n }))\n\n return { title, meta, link, script, jsonLd, resolved }\n}\n","/**\n * Pure, framework-free `<head>` splicing for the build-time SEO emitter.\n *\n * Given a built `index.html` shell and a set of resolved head tags (from\n * `buildHeadTags`), this produces a new HTML string where the SEO-managed\n * tags — `<title>`, `description`, `keywords`, `robots`, `canonical`,\n * verification, all `og:*` / `article:*` properties, all `twitter:*` names,\n * and `application/ld+json` scripts — have been **replaced** (not duplicated)\n * with the resolved set.\n *\n * Design goals:\n * - **Idempotent**: running it twice yields the same output (it strips the\n * managed tags first, then re-inserts the canonical set).\n * - **Deterministic**: tag order is fixed by `renderHeadTags`.\n * - **Conservative**: only tags we own are touched. Charset, viewport, CSP,\n * theme-color, favicons, stylesheets, and the app script are left intact.\n *\n * This is intentionally regex-based (no DOM dependency) to mirror the existing\n * `dcsCdnImagePlugin` post-build HTML rewriting and to keep the emitter free of\n * heavy parser deps at build time.\n */\n\nimport type { ResolvedHeadTags } from './headTags'\n\n/** Escape a string for safe inclusion in a double-quoted HTML attribute. */\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n/**\n * Escape JSON-LD content for safe inclusion inside a `<script>` element.\n * Only `</` sequences are dangerous (they can prematurely close the script);\n * the rest of the JSON is already a valid string literal.\n */\nfunction escapeJsonLd(json: string): string {\n return json.replace(/<\\//g, '<\\\\/')\n}\n\n/** `name=` meta tags that the emitter owns and will replace. */\nconst MANAGED_META_NAMES = new Set([\n 'description',\n 'keywords',\n 'robots',\n 'google-site-verification',\n 'msvalidate.01',\n])\n\n/**\n * Property/name prefixes the emitter owns. Any meta whose `property` starts\n * with one of these (og:, article:) or whose `name` starts with `twitter:` is\n * considered managed and stripped before re-insertion.\n */\nconst MANAGED_PROPERTY_PREFIXES = ['og:', 'article:']\nconst MANAGED_NAME_PREFIXES = ['twitter:']\n\n/**\n * Render the resolved head tags to a deterministic HTML fragment.\n * Order: title, meta (in the order produced by buildHeadTags), link, script.\n */\nexport function renderHeadTags(tags: ResolvedHeadTags, indent = ' '): string {\n const lines: string[] = []\n\n lines.push(`${indent}<title>${escapeAttr(tags.title)}</title>`)\n\n for (const m of tags.meta) {\n if (m.property !== undefined) {\n lines.push(`${indent}<meta property=\"${escapeAttr(m.property)}\" content=\"${escapeAttr(m.content)}\" />`)\n } else if (m.name !== undefined) {\n lines.push(`${indent}<meta name=\"${escapeAttr(m.name)}\" content=\"${escapeAttr(m.content)}\" />`)\n }\n }\n\n for (const l of tags.link) {\n const hreflang = l.hreflang ? ` hreflang=\"${escapeAttr(l.hreflang)}\"` : ''\n lines.push(`${indent}<link rel=\"${escapeAttr(l.rel)}\" href=\"${escapeAttr(l.href)}\"${hreflang} />`)\n }\n\n for (const s of tags.script) {\n lines.push(`${indent}<script type=\"${escapeAttr(s.type)}\">${escapeJsonLd(s.children)}</script>`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Strip the SEO-managed tags from a `<head>` block so they can be re-inserted\n * without duplication. Operates only within `<head>...</head>` to avoid\n * touching body content.\n */\nexport function stripManagedHeadTags(html: string): string {\n const headMatch = html.match(/<head[^>]*>([\\s\\S]*?)<\\/head>/i)\n if (!headMatch) return html\n\n let head = headMatch[1]\n\n // Remove existing <title>…</title>\n head = head.replace(/[ \\t]*<title>[\\s\\S]*?<\\/title>[ \\t]*\\r?\\n?/gi, '')\n\n // Remove existing JSON-LD scripts\n head = head.replace(\n /[ \\t]*<script[^>]*type=[\"']application\\/ld\\+json[\"'][^>]*>[\\s\\S]*?<\\/script>[ \\t]*\\r?\\n?/gi,\n ''\n )\n\n // Remove managed <meta name=\"…\"> and <meta property=\"…\"> tags.\n // Matches any <meta ...> tag, inspects its name/property, drops it if managed.\n head = head.replace(/[ \\t]*<meta\\b[^>]*>[ \\t]*\\r?\\n?/gi, (tag) => {\n const nameMatch = tag.match(/\\bname=[\"']([^\"']*)[\"']/i)\n const propMatch = tag.match(/\\bproperty=[\"']([^\"']*)[\"']/i)\n const name = nameMatch?.[1]?.toLowerCase()\n const property = propMatch?.[1]?.toLowerCase()\n\n if (name) {\n if (MANAGED_META_NAMES.has(name)) return ''\n if (MANAGED_NAME_PREFIXES.some((p) => name.startsWith(p))) return ''\n }\n if (property) {\n if (MANAGED_PROPERTY_PREFIXES.some((p) => property.startsWith(p))) return ''\n }\n return tag\n })\n\n // Remove existing canonical link (alternates are also managed)\n head = head.replace(\n /[ \\t]*<link\\b[^>]*\\brel=[\"'](?:canonical|alternate)[\"'][^>]*>[ \\t]*\\r?\\n?/gi,\n ''\n )\n\n return html.slice(0, headMatch.index! + headMatch[0].indexOf(headMatch[1])) +\n head +\n html.slice(headMatch.index! + headMatch[0].indexOf(headMatch[1]) + headMatch[1].length)\n}\n\n/**\n * Splice resolved SEO head tags into an HTML document.\n *\n * Strips the existing managed tags, then inserts the rendered canonical set\n * immediately before `</head>`. If no `<head>` is present the HTML is returned\n * unchanged (defensive — the emitter logs and no-ops in that case).\n *\n * Idempotent: applying twice produces identical output.\n */\nexport function spliceHeadHtml(html: string, tags: ResolvedHeadTags): string {\n if (!/<\\/head>/i.test(html)) {\n return html\n }\n\n const stripped = stripManagedHeadTags(html)\n const fragment = renderHeadTags(tags)\n\n // Insert before </head>, preserving the indentation of the closing tag.\n return stripped.replace(/([ \\t]*)<\\/head>/i, (_m, indent) => {\n return `${fragment}\\n${indent}</head>`\n })\n}\n","/**\n * Build-time SEO for VitePress static-site generation.\n *\n * VitePress 1.6 does **not** use `unhead`, so the runtime `useSEO`/`applyHead`\n * composable is a no-op against the SSG HTML. The SSG-correct sink is the\n * `transformPageData(pageData)` build hook: writing `<meta>`/`<link>`/JSON-LD\n * into `pageData.frontmatter.head` (VitePress bakes those into the rendered\n * `<head>`) and overwriting `pageData.title` / `pageData.description` (VitePress\n * renders the `<title>` — via `titleTemplate` — and the `description` meta from\n * those two fields).\n *\n * This factory generalises the bespoke `buildSeoHead`/`transformPageData` that\n * shipped inline in a site's `.vitepress/config.ts`. It reuses the shared,\n * framework-agnostic resolver (`resolvePageSeo`, `generateOpenGraphMeta`,\n * `generateTwitterMeta`, `generateJsonLd`) for global + page meta / OG / Twitter\n * / canonical and the global JSON-LD knowledge graph, and delegates **page-type\n * JSON-LD** (e.g. Article / Place / CollectionPage / Service / FAQPage +\n * BreadcrumbList) to a *pluggable* rule set the site supplies. None of the\n * real-estate (or any other vertical's) schema logic lives in this package — it\n * is all site CONFIG.\n *\n * It is the VitePress counterpart to the Vue-SPA per-route emitter in\n * `dcsSeoPlugin({ emitStaticHtml: true })`; both produce identical global\n * meta/OG/Twitter/JSON-LD from the same `seo.yaml` via the shared resolver.\n *\n * @example\n * ```ts\n * // docs/.vitepress/config.ts\n * import { createSeoTransformPageData } from '@duffcloudservices/cms/plugins'\n * import seoConfig from '../../.dcs/seo.yaml'\n *\n * export default defineConfig({\n * transformPageData: createSeoTransformPageData({\n * seoConfig,\n * pageTypeRules: [\n * { match: (ctx) => ctx.route.startsWith('/blogs/'), build: (ctx) => [ ... ] },\n * // ...Place / CollectionPage / Service / FAQPage rules\n * ],\n * }),\n * })\n * ```\n */\n\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n SeoOpenGraphConfig,\n} from '../types/seo'\nimport {\n resolvePageSeo,\n generateOpenGraphMeta,\n generateTwitterMeta,\n generateJsonLd,\n} from './headTags'\n\n/**\n * A VitePress `head` entry. Mirrors VitePress's `HeadConfig` without taking a\n * dependency on the `vitepress` package (which is not a dependency of this\n * library). The tuple forms are:\n * ['meta', { name|property, content }]\n * ['link', { rel, href, ... }]\n * ['script', { type: 'application/ld+json' }, '<serialised json>']\n */\nexport type VitePressHeadConfig =\n | [string, Record<string, string>]\n | [string, Record<string, string>, string]\n\n/**\n * The minimal slice of VitePress's `PageData` this factory reads and mutates.\n * Typed structurally so callers can pass VitePress's real `PageData` without a\n * cast and without this package importing `vitepress`.\n */\nexport interface VitePressPageData {\n /** Source-relative path, e.g. `index.md`, `blogs/my-post.md`. */\n relativePath: string\n /** Dynamic-route params (e.g. `{ topic: 'home-buying' }`). */\n params?: Record<string, unknown>\n /** Page frontmatter; `head` is appended to here. */\n frontmatter: Record<string, any>\n /** VitePress page title (drives `<title>` via `titleTemplate`). */\n title?: string\n /** VitePress page description (drives the `description` meta). */\n description?: string\n [key: string]: unknown\n}\n\n/**\n * Context handed to the site's page-type rules and resolver hooks. Everything a\n * site needs to derive its title/description/og/schemas for one page, computed\n * once per page by the factory.\n */\nexport interface SeoPageContext {\n /** Route path, e.g. `/`, `/blogs/my-post`, `/locations/birmingham`. */\n route: string\n /** Slug: `'home'` for `/`, otherwise the route without its leading slash. */\n slug: string\n /** Absolute canonical URL for this route. */\n canonical: string\n /** Normalised site base URL (no trailing slash), e.g. `https://example.com`. */\n siteUrl: string\n /** The page's frontmatter (read-only convenience; same object as pageData). */\n frontmatter: Record<string, any>\n /** The resolved global SEO config block. */\n global: GlobalSeoConfig\n /** The full VitePress page data (for rules that need more than the above). */\n pageData: VitePressPageData\n}\n\n/**\n * The resolved per-page title / description / OG type a site may override.\n * Returned by the optional `resolvePage` hook so a site can apply its own\n * per-page-type title precedence (e.g. \"{City} Luxury Real Estate\") and decide\n * whether that title should win over VitePress's `titleTemplate`.\n */\nexport interface ResolvedPageOverrides {\n /**\n * The page title. When `setPageTitle` is true this is written to\n * `pageData.title` (VitePress then applies `titleTemplate`).\n */\n title?: string\n /**\n * When true, `title` is written back to `pageData.title`. Leave false for\n * pages whose frontmatter title should remain authoritative (e.g. blog posts\n * that already carry a good `<h1>`/title).\n */\n setPageTitle?: boolean\n /** The meta description. Written to `pageData.description` when truthy. */\n description?: string\n /** Open Graph type override (e.g. `'article'`, `'profile'`). */\n ogType?: SeoOpenGraphConfig['type']\n /** Open Graph image URL override (e.g. a post's header image). */\n ogImage?: string\n /**\n * Extra Open Graph fields to merge into the OG config (e.g. `publishedTime`,\n * `modifiedTime`, `section`, `tags`). The shared OG generator emits the\n * matching `article:*` tags when `ogType === 'article'`. This is how a site\n * supplies article metadata (date/category) without that logic living in the\n * package. Lower precedence than `ogType`/`ogImage`/`ogTitle`/`ogDescription`.\n */\n og?: Partial<SeoOpenGraphConfig>\n /** Keywords override (comma-separated) for the `keywords` meta. */\n keywords?: string\n /**\n * Open Graph title override. Defaults to `title` so og:title tracks <title>.\n */\n ogTitle?: string\n /**\n * Open Graph description override. Defaults to `description`.\n */\n ogDescription?: string\n}\n\n/** A single pluggable page-type rule: when `match` is true, emit `build`. */\nexport interface SeoPageTypeRule {\n /** Return true when this rule applies to the page (by route/slug/etc.). */\n match: (ctx: SeoPageContext) => boolean\n /** Build the page-type JSON-LD objects to emit (already plain objects). */\n build: (ctx: SeoPageContext) => Array<Record<string, unknown>>\n}\n\nexport interface CreateSeoTransformPageDataOptions {\n /** The parsed `.dcs/seo.yaml` (global graph + per-page meta). */\n seoConfig: SeoConfiguration | undefined\n /**\n * Pluggable page-type rules. Evaluated in order; **every** matching rule's\n * `build` output is emitted (so a route can contribute both a primary schema\n * and a BreadcrumbList from one rule, or be matched by several). The\n * real-estate BlogPosting / Place / CollectionPage / Service / FAQPage logic\n * is supplied here by the site — never hardcoded in this package.\n */\n pageTypeRules?: SeoPageTypeRule[]\n /**\n * Optional hook to override per-page title / description / OG before tags are\n * built — the site's title precedence and per-type description fallbacks.\n * Receives the same context as the rules. Anything it omits falls back to the\n * resolver / frontmatter defaults.\n */\n resolvePage?: (ctx: SeoPageContext) => ResolvedPageOverrides | undefined\n /**\n * Map a `relativePath` (+ params) to a route. Defaults to a VitePress-correct\n * implementation: `index` becomes `/`, a trailing `/index` is dropped, `.md`\n * is stripped, and dynamic `[name]` segments are substituted from\n * `pageData.params`. Override only for unusual routing.\n */\n relativePathToRoute?: (relativePath: string, params?: Record<string, unknown>) => string\n /**\n * Emit a `<meta name=\"keywords\">` from the resolved/overridden keywords.\n * Default true (parity with the bespoke KDH emitter, which emitted keywords).\n */\n includeKeywords?: boolean\n /** Enable debug logging of the emitted head per page. */\n debug?: boolean\n}\n\n/** Default VitePress route derivation (matches the bespoke KDH helper). */\nexport function defaultRelativePathToRoute(\n relativePath: string,\n params?: Record<string, unknown>\n): string {\n let stem = (relativePath ?? '').replace(/\\.md$/i, '')\n // Substitute dynamic [param] segments (e.g. topics/[topic] → topics/home-buying).\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue\n stem = stem.replace(`[${key}]`, String(value))\n }\n }\n if (stem === 'index') return '/'\n if (stem.endsWith('/index')) stem = stem.slice(0, -'/index'.length)\n return `/${stem}`\n}\n\nfunction normaliseSiteUrl(siteUrl: string | undefined): string {\n return (siteUrl ?? '').replace(/\\/$/, '')\n}\n\n/** A `<script type=\"application/ld+json\">` VitePress head tuple. */\nfunction ldScript(obj: Record<string, unknown>): VitePressHeadConfig {\n return ['script', { type: 'application/ld+json' }, JSON.stringify(obj)]\n}\n\n/**\n * Build just the SEO head tuples for a page (no `pageData` mutation). Exposed\n * separately so it is unit-testable without a VitePress `pageData` round-trip\n * and reusable by callers that manage the `head`/`title` sinks themselves.\n *\n * @returns `{ head, title, description }` — the head tuples to append, and the\n * final title/description (already overridden) the caller should write to\n * `pageData` when `applyTitle`/`applyDescription` are appropriate.\n */\nexport function buildVitePressSeoHead(\n pageData: VitePressPageData,\n options: CreateSeoTransformPageDataOptions\n): { head: VitePressHeadConfig[]; title?: string; description?: string; setPageTitle: boolean } {\n const {\n seoConfig,\n pageTypeRules = [],\n resolvePage,\n relativePathToRoute = defaultRelativePathToRoute,\n includeKeywords = true,\n } = options\n\n const global = seoConfig?.global ?? {}\n const siteUrl = normaliseSiteUrl(global.siteUrl)\n\n const fm = pageData.frontmatter ?? {}\n const route = relativePathToRoute(pageData.relativePath, pageData.params)\n const slug = route === '/' ? 'home' : route.slice(1)\n const canonical = route === '/' ? `${siteUrl}/` : `${siteUrl}${route}`\n\n const ctx: SeoPageContext = {\n route,\n slug,\n canonical,\n siteUrl,\n frontmatter: fm,\n global,\n pageData,\n }\n\n // Resolve global + per-page SEO from the shared resolver. We pass the page's\n // frontmatter title as the fallback so un-configured pages still get a\n // sensible (templated) title; seo.yaml `pages.<slug>.title` still wins.\n const resolved = resolvePageSeo(slug, route, seoConfig, fm.title)\n\n // Site-provided per-page overrides (title precedence + per-type description).\n const overrides = resolvePage?.(ctx) ?? {}\n\n // ── Title handling ────────────────────────────────────────────────────────\n // Two titles matter for VitePress:\n // • `rawTitle` → written to `pageData.title` (VitePress applies its OWN\n // `titleTemplate`, so the <title> element is templated once).\n // • `templated` → used for og:title / twitter:title so they match the final\n // <title> element (the shared resolver's og:title fix).\n // A site override (`overrides.title`) is the RAW per-type title; we template\n // it ourselves for OG/Twitter using the same precedence as the resolver.\n const rawTitle =\n overrides.title ?? pageSpecificSeoTitle(slug, seoConfig) ?? fm.title ?? resolved.title\n const templated = applyTitleTemplate(rawTitle, global, resolved)\n\n // Description precedence mirrors the bespoke emitter: a site per-type override\n // → the explicit seo.yaml `page.description` → the page frontmatter → the\n // global default. (The shared resolver collapses frontmatter and the global\n // default, so we read the explicit page description directly to keep\n // frontmatter ahead of the global fallback.)\n const pageSeoDescription = seoConfig?.pages?.[slug]?.description\n const description =\n overrides.description ||\n pageSeoDescription ||\n fm.description ||\n global.defaultDescription ||\n ''\n const keywords =\n overrides.keywords ||\n seoConfig?.pages?.[slug]?.keywords ||\n (Array.isArray(fm.tags) ? fm.tags.join(', ') : '')\n\n // Open Graph type/image: site override → seo.yaml → frontmatter → default.\n const ogType =\n overrides.ogType || resolved.openGraph.type\n const ogImage =\n overrides.ogImage || resolved.openGraph.image || fm.headerImage || fm.image || global.images?.ogDefault\n\n const head: VitePressHeadConfig[] = []\n\n // Keywords (parity: the bespoke emitter pushed keywords when present).\n if (includeKeywords && keywords) {\n head.push(['meta', { name: 'keywords', content: keywords }])\n }\n\n // Robots.\n head.push(['meta', { name: 'robots', content: resolved.robots }])\n\n // Canonical.\n head.push(['link', { rel: 'canonical', href: canonical }])\n\n // Verification.\n if (global.verification?.google) {\n head.push(['meta', { name: 'google-site-verification', content: global.verification.google }])\n }\n if (global.verification?.bing) {\n head.push(['meta', { name: 'msvalidate.01', content: global.verification.bing }])\n }\n\n // Open Graph — reuse the shared generator, with our resolved title/desc/og\n // overrides applied via a synthetic OG config so the output matches the SPA\n // emitter byte-for-byte (article:* fields included when type === 'article').\n // og:title tracks the TEMPLATED title (the resolver's og:title fix), so it\n // matches the final <title> element rather than the raw per-type title.\n //\n // We DON'T spread `resolved.openGraph` wholesale: for an un-configured page\n // its `description` is the global default, which would wrongly win over this\n // page's actual `description`. We instead keep only the page's *explicit* OG\n // fields (from seo.yaml) and let `description`/`title` fall through correctly.\n const pageOg = seoConfig?.pages?.[slug]?.openGraph\n const ogConfig: SeoOpenGraphConfig = {\n ...pageOg,\n // Site-supplied extra OG fields (publishedTime/section/tags/…) — lower\n // precedence than the explicit type/title/desc/image resolved below.\n ...overrides.og,\n type: ogType,\n title: overrides.ogTitle || templated,\n description: overrides.ogDescription || pageOg?.description || description,\n ...(ogImage ? { image: ogImage } : {}),\n url: canonical,\n }\n for (const t of generateOpenGraphMeta(ogConfig, global, templated, description, canonical)) {\n head.push(['meta', { property: t.property, content: t.content }])\n }\n\n // Twitter — shared generator (twitter:title also tracks the templated title).\n for (const t of generateTwitterMeta(resolved.twitter, global, templated, description)) {\n head.push(['meta', { name: t.name, content: t.content }])\n }\n\n // JSON-LD: the global knowledge graph (RealEstateAgent / Person / WebSite /\n // Organization / …) on every page, via the shared generator.\n for (const obj of generateJsonLd(resolved.schemas, global)) {\n head.push(ldScript(obj as Record<string, unknown>))\n }\n\n // Page-type JSON-LD: every matching site rule contributes its objects.\n for (const rule of pageTypeRules) {\n let matched = false\n try {\n matched = rule.match(ctx)\n } catch (err) {\n console.warn('[dcs-seo] pageTypeRule.match threw; skipping rule:', err)\n continue\n }\n if (!matched) continue\n let objects: Array<Record<string, unknown>> = []\n try {\n objects = rule.build(ctx) ?? []\n } catch (err) {\n console.warn('[dcs-seo] pageTypeRule.build threw; skipping rule output:', err)\n continue\n }\n for (const obj of objects) {\n if (obj) head.push(ldScript(obj))\n }\n }\n\n return {\n // Hand back the RAW title for `pageData.title`; VitePress applies its own\n // `titleTemplate` to produce the templated <title> element.\n title: rawTitle,\n head,\n description: description || undefined,\n setPageTitle: overrides.setPageTitle ?? false,\n }\n}\n\n/**\n * Compute the page-specific (un-templated) `seo.yaml` title for a slug, if any.\n * This is the title VitePress should template — it must NOT come pre-templated.\n */\nfunction pageSpecificSeoTitle(\n slug: string,\n seoConfig: SeoConfiguration | undefined\n): string | undefined {\n return seoConfig?.pages?.[slug]?.title || undefined\n}\n\n/**\n * Apply the global `titleTemplate` to a raw page title, matching the shared\n * resolver's precedence: a page that opts out (`noTitleTemplate`) or a\n * brand-complete global default is used verbatim; otherwise `%s` is filled.\n *\n * When `rawTitle` is the resolver's already-final title (e.g. the global\n * `defaultTitle`, used when there is no page-specific title), we return it\n * unchanged to avoid double-templating.\n */\nfunction applyTitleTemplate(\n rawTitle: string,\n global: GlobalSeoConfig,\n resolved: ResolvedPageSeoLike\n): string {\n // If rawTitle is exactly the resolver's final title, it is either already\n // templated (page-specific) or a brand-complete default — use as-is.\n if (rawTitle === resolved.title) return rawTitle\n if (!global.titleTemplate) return rawTitle\n return global.titleTemplate.replace('%s', rawTitle)\n}\n\n/** Structural subset of `ResolvedPageSeo` used by the title helper. */\ninterface ResolvedPageSeoLike {\n title: string\n}\n\n/**\n * Create a VitePress `transformPageData(pageData)` function that bakes DCS SEO\n * (global meta/OG/Twitter/canonical + global JSON-LD graph + pluggable\n * page-type JSON-LD) into the SSG `<head>`.\n *\n * Mutations performed on `pageData`:\n * - **`frontmatter.head`** — the resolved tags are *appended* to any existing\n * `head` (so site-level `head` config is preserved).\n * - **`description`** — set to the resolved/overridden description so\n * VitePress emits exactly one `description` meta (no duplicate; we do not\n * push our own description meta).\n * - **`title`** — set only when the site's `resolvePage` hook returns\n * `setPageTitle: true` for this page, mirroring the bespoke behaviour where\n * seo.yaml/per-type titles are authoritative but a post's frontmatter title\n * is left intact.\n *\n * Defensive: never throws (a failure logs a warning and leaves `pageData`\n * untouched), so SEO can never break a production VitePress build.\n */\nexport function createSeoTransformPageData(\n options: CreateSeoTransformPageDataOptions\n): (pageData: VitePressPageData) => void {\n return function transformPageData(pageData: VitePressPageData): void {\n try {\n const { head, title, description, setPageTitle } = buildVitePressSeoHead(pageData, options)\n\n pageData.frontmatter = pageData.frontmatter ?? {}\n const existing = (pageData.frontmatter.head as VitePressHeadConfig[] | undefined) ?? []\n pageData.frontmatter.head = [...existing, ...head]\n\n // Drive VitePress's own description meta from our resolved value so it is\n // not duplicated by the site-level default. (VitePress reads\n // pageData.description BEFORE transformPageData and renders the meta from\n // it, so overwriting the field here is the correct sink.)\n if (description) {\n pageData.description = description\n }\n\n // Make seo.yaml / per-type titles authoritative for <title> when the site\n // asks for it; VitePress then appends its titleTemplate.\n if (setPageTitle && title) {\n pageData.title = title\n }\n\n if (options.debug) {\n console.log(\n `[dcs-seo] transformPageData ${pageData.relativePath} → +${head.length} head tag(s)` +\n (setPageTitle && title ? ` (title=\"${title}\")` : '')\n )\n }\n } catch (err) {\n console.warn(\n `[dcs-seo] createSeoTransformPageData failed for ${pageData?.relativePath}; head left unchanged:`,\n err\n )\n }\n }\n}\n","/**\n * Loader for the `.dcs/pages.yaml` route manifest.\n *\n * `pages.yaml` is the canonical page registry maintained by the DCS portal and\n * by Copilot when scaffolding pages. For the build-time SEO emitter we only\n * need each route's `slug` and `path` (e.g. `{ slug: 'home', path: '/' }`).\n *\n * The parser is intentionally defensive: any missing/unparseable file or\n * malformed entry yields `null` (caller logs + no-ops) so a bad manifest can\n * never break a production build.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\n\n/** A single route extracted from `.dcs/pages.yaml`. */\nexport interface PageRouteEntry {\n /** Page slug, matching an entry in `seo.yaml` `pages.<slug>` (may be absent). */\n slug: string\n /** Route path, e.g. `/`, `/services`, `/blog/my-post`. */\n path: string\n /**\n * Human title from the manifest (e.g. \"Kitchen Cabinet Refresh\"). Used as the\n * per-route title fallback when `seo.yaml` has no entry for this page, so\n * un-configured routes (e.g. blog posts) get unique titles. Optional.\n */\n title?: string\n}\n\n/** Shape of the relevant slice of `.dcs/pages.yaml`. */\ninterface RawPagesManifest {\n pages?: Array<{ slug?: unknown; path?: unknown; title?: unknown }>\n}\n\n/**\n * Resolve and parse `.dcs/pages.yaml` from one of the usual locations.\n *\n * Mirrors the `.dcs` path resolution used elsewhere in the package (project\n * root, parent dir for VitePress-style nesting, and cwd).\n *\n * @returns the list of `{ slug, path }` routes, or `null` if the file is\n * absent, unreadable, unparseable, or contains no usable page entries.\n */\nexport function loadPagesManifest(\n projectRoot: string,\n relativePagesPath: string,\n debug = false\n): PageRouteEntry[] | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativePagesPath),\n path.resolve(projectRoot, '..', relativePagesPath),\n path.resolve(process.cwd(), relativePagesPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.warn('[dcs-seo] No pages.yaml found at:')\n possiblePaths.forEach((p) => console.warn(` - ${p}`))\n }\n return null\n }\n\n let raw: RawPagesManifest\n try {\n raw = yaml.load(fs.readFileSync(foundPath, 'utf8')) as RawPagesManifest\n } catch (error) {\n console.warn(`[dcs-seo] Failed to parse ${foundPath}:`, error)\n return null\n }\n\n const entries = parsePagesManifest(raw)\n if (!entries) {\n console.warn(`[dcs-seo] pages.yaml at ${foundPath} has no usable page entries`)\n return null\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${entries.length} routes from ${foundPath}`)\n }\n return entries\n}\n\n/**\n * Extract `{ slug, path }` routes from an already-parsed manifest object.\n * Exposed separately so tests can exercise it without touching the filesystem.\n *\n * Entries missing a string `path` are skipped; a missing slug falls back to the\n * empty string (so the route still gets baked, using global SEO defaults).\n */\nexport function parsePagesManifest(raw: unknown): PageRouteEntry[] | null {\n if (!raw || typeof raw !== 'object') return null\n const pages = (raw as RawPagesManifest).pages\n if (!Array.isArray(pages)) return null\n\n const routes: PageRouteEntry[] = []\n for (const entry of pages) {\n if (!entry || typeof entry !== 'object') continue\n const p = (entry as { path?: unknown }).path\n if (typeof p !== 'string' || p.length === 0) continue\n const slug = (entry as { slug?: unknown }).slug\n const title = (entry as { title?: unknown }).title\n routes.push({\n slug: typeof slug === 'string' ? slug : '',\n path: p,\n ...(typeof title === 'string' && title.length > 0 ? { title } : {}),\n })\n }\n\n return routes.length > 0 ? routes : null\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as vue from 'vue';
|
|
2
2
|
import { ComputedRef, MaybeRefOrGetter } from 'vue';
|
|
3
|
-
import { U as UseSeoReturn, G as GlobalSeoConfig, S as SeoConfiguration, R as ResolvedPageSeo, a as SeoSchemaConfig, b as SeoOpenGraphConfig, c as SeoTwitterConfig } from './vitepressTransform-
|
|
4
|
-
export { C as CreateSeoTransformPageDataOptions, H as HeadOverrides, P as PageSeoConfig, i as ResolvedPageOverrides, n as SeoAlternateConfig, k as SeoAuthorConfig, m as SeoImagesConfig, g as SeoPageContext, h as SeoPageTypeRule, l as SeoSocialConfig, o as SeoVerificationConfig, p as UseSeoConfig, j as VitePressHeadConfig, V as VitePressPageData, e as buildVitePressSeoHead, d as createSeoTransformPageData, f as defaultRelativePathToRoute } from './vitepressTransform-
|
|
3
|
+
import { U as UseSeoReturn, G as GlobalSeoConfig, S as SeoConfiguration, R as ResolvedPageSeo, a as SeoSchemaConfig, b as SeoOpenGraphConfig, c as SeoTwitterConfig } from './vitepressTransform-D9P9D7So.js';
|
|
4
|
+
export { C as CreateSeoTransformPageDataOptions, H as HeadOverrides, P as PageSeoConfig, i as ResolvedPageOverrides, n as SeoAlternateConfig, k as SeoAuthorConfig, m as SeoImagesConfig, g as SeoPageContext, h as SeoPageTypeRule, l as SeoSocialConfig, o as SeoVerificationConfig, p as UseSeoConfig, j as VitePressHeadConfig, V as VitePressPageData, e as buildVitePressSeoHead, d as createSeoTransformPageData, f as defaultRelativePathToRoute } from './vitepressTransform-D9P9D7So.js';
|
|
5
5
|
import { ImageContext, ResponsiveImageResult } from '@duffcloudservices/cms-core';
|
|
6
6
|
export { ImageContext, ResponsiveImageOptions, ResponsiveImageResult, ResponsiveSource, isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
|
7
7
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-
|
|
2
|
-
export { buildHeadTags, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, renderHeadTags, resolvePageSeo, spliceHeadHtml, stripManagedHeadTags } from './chunk-
|
|
1
|
+
import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-JJK7OGC2.js';
|
|
2
|
+
export { buildHeadTags, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, renderHeadTags, resolvePageSeo, spliceHeadHtml, stripManagedHeadTags } from './chunk-JJK7OGC2.js';
|
|
3
3
|
import { shallowRef, ref, computed, onMounted, readonly, toValue, onUnmounted } from 'vue';
|
|
4
4
|
import { useHead } from '@unhead/vue';
|
|
5
5
|
import { isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
-
import { S as SeoConfiguration } from '../vitepressTransform-
|
|
3
|
-
export { C as CreateSeoTransformPageDataOptions, i as ResolvedPageOverrides, g as SeoPageContext, h as SeoPageTypeRule, j as VitePressHeadConfig, V as VitePressPageData, e as buildVitePressSeoHead, d as createSeoTransformPageData, f as defaultRelativePathToRoute } from '../vitepressTransform-
|
|
2
|
+
import { S as SeoConfiguration } from '../vitepressTransform-D9P9D7So.js';
|
|
3
|
+
export { C as CreateSeoTransformPageDataOptions, i as ResolvedPageOverrides, g as SeoPageContext, h as SeoPageTypeRule, j as VitePressHeadConfig, V as VitePressPageData, e as buildVitePressSeoHead, d as createSeoTransformPageData, f as defaultRelativePathToRoute } from '../vitepressTransform-D9P9D7So.js';
|
|
4
4
|
import { Component, Plugin as Plugin$1 } from 'vue';
|
|
5
5
|
import MarkdownIt from 'markdown-it';
|
|
6
6
|
|
package/dist/plugins/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-
|
|
2
|
-
export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-
|
|
1
|
+
import { buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-JJK7OGC2.js';
|
|
2
|
+
export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-JJK7OGC2.js';
|
|
3
3
|
import fs2 from 'fs';
|
|
4
4
|
import path2 from 'path';
|
|
5
5
|
import yaml from 'js-yaml';
|
|
@@ -629,7 +629,7 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
629
629
|
}
|
|
630
630
|
|
|
631
631
|
// src/plugins/responsiveImagePlugin.ts
|
|
632
|
-
var CDN_PATTERN = /^(https?:\/\/files\.[^/]+\/[^/]+\/assets\/(?:[^/]+\/)*)([a-f0-9-]+)\.(\w+)$/;
|
|
632
|
+
var CDN_PATTERN = /^(https?:\/\/files\.[^/]+\/(?:content\/)?[^/]+\/assets\/(?:[^/]+\/)*)([a-f0-9-]+)\.(\w+)$/;
|
|
633
633
|
function escapeHtml2(str) {
|
|
634
634
|
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
635
635
|
}
|
|
@@ -653,10 +653,12 @@ function responsiveImagePlugin(md) {
|
|
|
653
653
|
`${basePath}${uuid}-lg.webp 1920w`
|
|
654
654
|
].join(", ");
|
|
655
655
|
const escapedAlt = escapeHtml2(alt);
|
|
656
|
+
const escapedOriginal = escapeHtml2(src);
|
|
657
|
+
const onError = `this.onerror=null;this.src='${escapedOriginal}';this.closest('picture')?.querySelectorAll('source').forEach(s=>s.remove())`;
|
|
656
658
|
return [
|
|
657
659
|
"<picture>",
|
|
658
660
|
` <source srcset="${srcset}" type="image/webp" sizes="(max-width: 1024px) 100vw, 1024px" />`,
|
|
659
|
-
` <img src="${basePath}${uuid}-md.webp" alt="${escapedAlt}" loading="lazy" decoding="async" />`,
|
|
661
|
+
` <img src="${basePath}${uuid}-md.webp" alt="${escapedAlt}" loading="lazy" decoding="async" onerror="${onError}" />`,
|
|
660
662
|
"</picture>"
|
|
661
663
|
].join("\n");
|
|
662
664
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/plugins/dcsContentPlugin.ts","../../src/plugins/dcsSeoPlugin.ts","../../src/plugins/dcsEditorPlugin.ts","../../src/plugins/dcsPreviewPlugin.ts","../../src/plugins/dcsCdnImagePlugin.ts","../../src/plugins/responsiveImagePlugin.ts"],"names":["path","fs","yaml","escapeHtml"],"mappings":";;;;;;;AAqDO,SAAS,gBAAA,CAAiB,OAAA,GAAmC,EAAC,EAAW;AAC9E,EAAA,MAAM,EAAE,WAAA,GAAc,mBAAA,EAAqB,KAAA,GAAQ,OAAM,GAAI,OAAA;AAE7D,EAAA,IAAI,cAAA;AAEJ,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,aAAA;AAAA,IAEN,eAAe,MAAA,EAAQ;AACrB,MAAA,cAAA,GAAiB,MAAA;AAAA,IACnB,CAAA;AAAA,IAEA,OAAO,MAAA,EAAQ;AACb,MAAA,MAAM,WAAA,GAAc,MAAA,CAAO,IAAA,IAAQ,OAAA,CAAQ,GAAA,EAAI;AAI/C,MAAA,MAAM,aAAA,GAAgB;AAAA,QACpBA,KAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,WAAW,CAAA;AAAA,QACrCA,KAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,IAAA,EAAM,WAAW,CAAA;AAAA,QAC3CA,KAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,WAAW;AAAA,OACzC;AAEA,MAAA,IAAI,SAAA;AACJ,MAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,QAAA,IAAIC,GAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC3B,UAAA,SAAA,GAAY,QAAA;AACZ,UAAA;AAAA,QACF;AAAA,MACF;AAEA,MAAA,IAAI,CAAC,SAAA,EAAW;AACd,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,IAAI,yCAAyC,CAAA;AACrD,UAAA,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAA,KAAM,OAAA,CAAQ,IAAI,CAAA,IAAA,EAAO,CAAC,EAAE,CAAC,CAAA;AACpD,UAAA,OAAA,CAAQ,IAAI,mCAAmC,CAAA;AAAA,QACjD;AACA,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ;AAAA,YACN,eAAA,EAAiB;AAAA;AACnB,SACF;AAAA,MACF;AAEA,MAAA,IAAI;AACF,QAAA,MAAM,WAAA,GAAcA,GAAA,CAAG,YAAA,CAAa,SAAA,EAAW,MAAM,CAAA;AACrD,QAAA,MAAM,OAAA,GAAU,IAAA,CAAK,IAAA,CAAK,WAAW,CAAA;AAErC,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,SAAS,CAAA,CAAE,CAAA;AAC/C,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,uBAAA,EAA0B,OAAA,CAAQ,OAAO,CAAA,CAAE,CAAA;AACvD,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,qBAAA,EAAwB,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,KAAA,IAAS,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,IAAK,QAAQ,CAAA,CAAE,CAAA;AAC7F,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,2BAAA,EAA8B,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,UAAU,EAAE,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA;AAAA,QACtF;AAEA,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ;AAAA,YACN,eAAA,EAAiB,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA;AACzC,SACF;AAAA,MACF,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,IAAA,CAAK,+CAA+C,KAAK,CAAA;AACjE,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ;AAAA,YACN,eAAA,EAAiB;AAAA;AACnB,SACF;AAAA,MACF;AAAA,IACF,CAAA;AAAA;AAAA,IAGA,gBAAgB,MAAA,EAAQ;AACtB,MAAA,MAAM,WAAA,GAAc,cAAA,EAAgB,IAAA,IAAQ,OAAA,CAAQ,GAAA,EAAI;AAExD,MAAA,MAAM,UAAA,GAAa;AAAA,QACjBD,KAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,WAAW,CAAA;AAAA,QACrCA,KAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,IAAA,EAAM,WAAW;AAAA,OAC7C;AAEA,MAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,KAAc;AAChC,QAAA,IAAIC,GAAA,CAAG,UAAA,CAAW,SAAS,CAAA,EAAG;AAC5B,UAAA,MAAA,CAAO,OAAA,CAAQ,IAAI,SAAS,CAAA;AAE5B,UAAA,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,CAAC,WAAA,KAAgB;AAC3C,YAAA,IAAI,gBAAgB,SAAA,EAAW;AAC7B,cAAA,IAAI,KAAA,EAAO;AACT,gBAAA,OAAA,CAAQ,IAAI,uDAAuD,CAAA;AAAA,cACrE;AACA,cAAA,MAAA,CAAO,OAAA,EAAQ;AAAA,YACjB;AAAA,UACF,CAAC,CAAA;AAAA,QACH;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF;AC3DA,SAAS,cAAc,MAAA,EAAgC;AACrD,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,KAAA,EAAO,MAAA,IAAU,MAAA;AACpC,EAAA,OAAOD,KAAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,GAAA,GAAMA,KAAAA,CAAK,OAAA,CAAQ,MAAA,CAAO,IAAA,IAAQ,OAAA,CAAQ,GAAA,EAAI,EAAG,GAAG,CAAA;AACpF;AAMA,SAAS,aAAA,CACP,WAAA,EACA,OAAA,EACA,KAAA,EACwD;AACxD,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpBA,KAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,OAAO,CAAA;AAAA,IACjCA,KAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,IAAA,EAAM,OAAO,CAAA;AAAA,IACvCA,KAAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,OAAO;AAAA,GACrC;AAEA,EAAA,IAAI,SAAA;AACJ,EAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,IAAA,IAAIC,GAAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC3B,MAAA,SAAA,GAAY,QAAA;AACZ,MAAA;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,OAAA,CAAQ,IAAI,iCAAiC,CAAA;AAC7C,MAAA,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAA,KAAM,OAAA,CAAQ,IAAI,CAAA,IAAA,EAAO,CAAC,EAAE,CAAC,CAAA;AACpD,MAAA,OAAA,CAAQ,IAAI,+BAA+B,CAAA;AAAA,IAC7C;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,WAAA,GAAcA,GAAAA,CAAG,YAAA,CAAa,SAAA,EAAW,MAAM,CAAA;AACrD,IAAA,MAAM,MAAA,GAASC,IAAAA,CAAK,IAAA,CAAK,WAAW,CAAA;AACpC,IAAA,OAAO,EAAE,QAAQ,SAAA,EAAU;AAAA,EAC7B,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAK,uCAAuC,KAAK,CAAA;AACzD,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AASA,SAAS,iBAAA,CAAkB,QAAgB,SAAA,EAA2B;AACpE,EAAA,MAAM,OAAA,GAAU,UAAU,OAAA,CAAQ,MAAA,EAAQ,EAAE,CAAA,CAAE,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAChE,EAAA,IAAI,YAAY,EAAA,EAAI,OAAOF,KAAAA,CAAK,IAAA,CAAK,QAAQ,YAAY,CAAA;AACzD,EAAA,OAAOA,KAAAA,CAAK,KAAK,MAAA,EAAQ,GAAG,QAAQ,KAAA,CAAM,GAAG,GAAG,YAAY,CAAA;AAC9D;AASO,SAAS,kBAAkB,MAAA,EAQvB;AACT,EAAA,MAAM,EAAE,MAAA,EAAQ,SAAA,EAAW,MAAA,EAAQ,SAAA,EAAW,OAAA,GAAU,EAAC,EAAG,OAAA,GAAU,EAAC,EAAG,KAAA,GAAQ,OAAM,GAAI,MAAA;AAE5F,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,OAAO,CAAA;AAClC,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,OAAO,CAAA;AAElC,EAAA,IAAI,OAAA,GAAU,CAAA;AACd,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,IAAA,IAAI,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,IAAM,KAAA,CAAM,IAAA,IAAQ,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,EAAI;AAC5E,MAAA,IAAI,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAA,2BAAA,EAA8B,KAAA,CAAM,IAAI,CAAA,CAAE,CAAA;AACjE,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,YAAA,GACJ,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA,IAAM,KAAA,CAAM,IAAA,IAAQ,UAAA,CAAW,GAAA,CAAI,KAAA,CAAM,IAAI,CAAA;AAExE,IAAA,MAAM,OAAO,aAAA,CAAc,KAAA,CAAM,IAAA,EAAM,KAAA,CAAM,MAAM,SAAA,EAAW;AAAA,MAC5D,eAAA,EAAiB,IAAA;AAAA,MACjB,eAAe,KAAA,CAAM,KAAA;AAAA,MACrB,MAAA,EAAQ,eAAe,mBAAA,GAAsB;AAAA,KAC9C,CAAA;AAED,IAAA,MAAM,IAAA,GAAO,cAAA,CAAe,SAAA,EAAW,IAAI,CAAA;AAC3C,IAAA,MAAM,OAAA,GAAU,iBAAA,CAAkB,MAAA,EAAQ,KAAA,CAAM,IAAI,CAAA;AAEpD,IAAAC,GAAAA,CAAG,UAAUD,KAAAA,CAAK,OAAA,CAAQ,OAAO,CAAA,EAAG,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AACvD,IAAAC,GAAAA,CAAG,aAAA,CAAc,OAAA,EAAS,IAAA,EAAM,MAAM,CAAA;AACtC,IAAA,OAAA,EAAA;AAEA,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,CAAA,gBAAA,EAAmBD,KAAAA,CAAK,QAAA,CAAS,MAAA,EAAQ,OAAO,CAAC,CAAA,SAAA,EAAY,IAAA,CAAK,KAAK,CAAA,CAAA,EACrE,YAAA,GAAe,WAAA,GAAc,EAC/B,CAAA,CAAA;AAAA,OACF;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,OAAA;AACT;AASO,SAAS,YAAA,CAAa,OAAA,GAA+B,EAAC,EAAW;AACtE,EAAA,MAAM;AAAA,IACJ,OAAA,GAAU,eAAA;AAAA,IACV,KAAA,GAAQ,KAAA;AAAA,IACR,cAAA,GAAiB,KAAA;AAAA,IACjB,SAAA,GAAY,iBAAA;AAAA,IACZ,UAAU,EAAC;AAAA,IACX,UAAU;AAAC,GACb,GAAI,OAAA;AAEJ,EAAA,IAAI,cAAA;AAEJ,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IAEN,eAAe,MAAA,EAAQ;AACrB,MAAA,cAAA,GAAiB,MAAA;AAAA,IACnB,CAAA;AAAA,IAEA,OAAO,MAAA,EAAQ;AACb,MAAA,MAAM,WAAA,GAAc,MAAA,CAAO,IAAA,IAAQ,OAAA,CAAQ,GAAA,EAAI;AAE/C,MAAA,MAAM,MAAA,GAAS,aAAA,CAAc,WAAA,EAAa,OAAA,EAAS,KAAK,CAAA;AACxD,MAAA,IAAI,CAAC,MAAA,EAAQ;AACX,QAAA,OAAO;AAAA,UACL,MAAA,EAAQ;AAAA,YACN,WAAA,EAAa;AAAA;AACf,SACF;AAAA,MACF;AAEA,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,iBAAA,EAAoB,MAAA,CAAO,SAAS,CAAA,CAAE,CAAA;AAClD,QAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,mBAAA,EAAsB,MAAA,CAAO,MAAA,CAAO,OAAO,CAAA,CAAE,CAAA;AACzD,QAAA,OAAA,CAAQ,IAAI,CAAA,qBAAA,EAAwB,MAAA,CAAO,OAAO,MAAA,EAAQ,QAAA,IAAY,WAAW,CAAA,CAAE,CAAA;AACnF,QAAA,OAAA,CAAQ,GAAA;AAAA,UACN,CAAA,iBAAA,EAAoB,MAAA,CAAO,IAAA,CAAK,MAAA,CAAO,MAAA,CAAO,KAAA,IAAS,EAAE,CAAA,CAAE,IAAA,CAAK,IAAI,CAAA,IAAK,QAAQ,CAAA;AAAA,SACnF;AAAA,MACF;AAEA,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ;AAAA,UACN,WAAA,EAAa,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,MAAM;AAAA;AAC3C,OACF;AAAA,IACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYA,WAAA,GAAc;AACZ,MAAA,IAAI,CAAC,cAAA,EAAgB;AAErB,MAAA,IAAI;AACF,QAAA,MAAM,WAAA,GAAc,cAAA,EAAgB,IAAA,IAAQ,OAAA,CAAQ,GAAA,EAAI;AACxD,QAAA,MAAM,MAAA,GAAS,cAAc,cAAc,CAAA;AAE3C,QAAA,IAAI,CAACC,GAAAA,CAAG,UAAA,CAAW,MAAM,CAAA,EAAG;AAC1B,UAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,4CAAA,EAA+C,MAAM,CAAA,WAAA,CAAa,CAAA;AAC/E,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,SAAA,GAAYD,KAAAA,CAAK,IAAA,CAAK,MAAA,EAAQ,YAAY,CAAA;AAChD,QAAA,IAAI,CAACC,GAAAA,CAAG,UAAA,CAAW,SAAS,CAAA,EAAG;AAC7B,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,8CAA8C,MAAM,CAAA,iBAAA;AAAA,WACtD;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,MAAA,GAAS,iBAAA,CAAkB,WAAA,EAAa,SAAA,EAAW,KAAK,CAAA;AAC9D,QAAA,IAAI,CAAC,MAAA,EAAQ;AACX,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,6BAA6B,SAAS,CAAA,8CAAA;AAAA,WACxC;AACA,UAAA;AAAA,QACF;AAEA,QAAA,MAAM,MAAA,GAAS,aAAA,CAAc,WAAA,EAAa,OAAA,EAAS,KAAK,CAAA;AACxD,QAAA,IAAI,CAAC,MAAA,EAAQ;AAIX,UAAA,OAAA,CAAQ,IAAA;AAAA,YACN,6BAA6B,OAAO,CAAA,oDAAA;AAAA,WACtC;AAAA,QACF;AAEA,QAAA,MAAM,SAAA,GAAYA,GAAAA,CAAG,YAAA,CAAa,SAAA,EAAW,MAAM,CAAA;AAEnD,QAAA,MAAM,UAAU,iBAAA,CAAkB;AAAA,UAChC,MAAA;AAAA,UACA,SAAA;AAAA,UACA,MAAA;AAAA,UACA,WAAW,MAAA,EAAQ,MAAA;AAAA,UACnB,OAAA;AAAA,UACA,OAAA;AAAA,UACA;AAAA,SACD,CAAA;AAED,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,gCAAA,EAAmC,OAAO,CAAA,uBAAA,CAAyB,CAAA;AAAA,QACjF;AAAA,MACF,SAAS,KAAA,EAAO;AAEd,QAAA,OAAA,CAAQ,IAAA,CAAK,iEAAiE,KAAK,CAAA;AAAA,MACrF;AAAA,IACF,CAAA;AAAA;AAAA,IAGA,gBAAgB,MAAA,EAAQ;AACtB,MAAA,MAAM,WAAA,GAAc,cAAA,EAAgB,IAAA,IAAQ,OAAA,CAAQ,GAAA,EAAI;AAExD,MAAA,MAAM,UAAA,GAAa;AAAA,QACjBD,KAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,OAAO,CAAA;AAAA,QACjCA,KAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,IAAA,EAAM,OAAO;AAAA,OACzC;AAEA,MAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,KAAc;AAChC,QAAA,IAAIC,GAAAA,CAAG,UAAA,CAAW,SAAS,CAAA,EAAG;AAC5B,UAAA,MAAA,CAAO,OAAA,CAAQ,IAAI,SAAS,CAAA;AAE5B,UAAA,MAAA,CAAO,OAAA,CAAQ,EAAA,CAAG,QAAA,EAAU,CAAC,WAAA,KAAgB;AAC3C,YAAA,IAAI,gBAAgB,SAAA,EAAW;AAC7B,cAAA,IAAI,KAAA,EAAO;AACT,gBAAA,OAAA,CAAQ,IAAI,+CAA+C,CAAA;AAAA,cAC7D;AACA,cAAA,MAAA,CAAO,OAAA,EAAQ;AAAA,YACjB;AAAA,UACF,CAAC,CAAA;AAAA,QACH;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF;;;ACnTO,SAAS,eAAA,CAAgB,OAAA,GAAkC,EAAC,EAAW;AAC5E,EAAA,MAAM,EAAE,KAAA,GAAQ,KAAA,EAAM,GAAI,OAAA;AAC1B,EAAA,MAAM,YAAA,GAAe,yBAAA;AACrB,EAAA,MAAM,mBAAA,GAAsB,0BAAA;AAG5B,EAAA,IAAI,KAAA,GAAQ,KAAA;AACZ,EAAA,IAAI,QAAA,GAAW,GAAA;AAEf,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,mBAAA;AAAA,IAEN,eAAe,MAAA,EAAQ;AACrB,MAAA,KAAA,GAAQ,OAAO,OAAA,KAAY,OAAA;AAC3B,MAAA,QAAA,GAAW,OAAO,IAAA,IAAQ,GAAA;AAAA,IAC5B,CAAA;AAAA,IAEA,UAAU,EAAA,EAAI;AACZ,MAAA,IAAI,EAAA,KAAO,YAAA,IAAgB,EAAA,KAAO,mBAAA,EAAqB;AACrD,QAAA,OAAO,EAAA;AAAA,MACT;AAAA,IACF,CAAA;AAAA,IAEA,KAAK,EAAA,EAAI;AACP,MAAA,IAAI,OAAO,YAAA,EAAc;AACvB,QAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,MAWT;AAEA,MAAA,IAAI,OAAO,mBAAA,EAAqB;AAC9B,QAAA,OAAO;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,MAoBT;AAAA,IACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAiBA,kBAAA,EAAoB;AAAA,MAClB,KAAA,EAAO,KAAA;AAAA,MACP,QAAQ,IAAA,EAAM;AACZ,QAAA,IAAI,CAAC,KAAA,IAAS,QAAA,KAAa,GAAA,EAAK,OAAO,IAAA;AAEvC,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,IAAI,mEAAmE,CAAA;AAAA,QACjF;AAGA,QAAA,MAAM,SAAA,GAAY,8BAA8B,YAAY,CAAA,WAAA,CAAA;AAC5D,QAAA,MAAM,SAAA,GAAY,8BAA8B,mBAAmB,CAAA,WAAA,CAAA;AAGnE,QAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,SAAA,EAAW,CAAA,EAAG,SAAS;AAAA,EAAK,SAAS;AAAA,OAAA,CAAW,CAAA;AAAA,MACtE;AAAA;AACF,GACF;AACF;AC5FO,SAAS,gBAAA,CACd,eAAA,EACA,OAAA,GAAmC,EAAC,EAC5B;AACR,EAAA,MAAM,UAAU,eAAA,CAAgB;AAAA,IAC9B,IAAA,EAAM,kBAAA;AAAA,IACN,KAAA,GAAQ;AACN,MAAA,OAAO,MAAM,EAAE,eAAA,EAAiB,EAAE,SAAS,OAAA,CAAQ,OAAA,IAAW,MAAM,CAAA;AAAA,IACtE;AAAA,GACD,CAAA;AAED,EAAA,OAAO;AAAA,IACL,QAAQ,GAAA,EAAU;AAChB,MAAA,GAAA,CAAI,SAAA,CAAU,oBAAoB,OAAO,CAAA;AAEzC,MAAA,IAAI,OAAA,CAAQ,YAAY,MAAA,EAAW;AACjC,QAAA,GAAA,CAAI,OAAA,CAAQ,yBAAA,EAA2B,OAAA,CAAQ,OAAO,CAAA;AAAA,MACxD;AAAA,IACF;AAAA,GACF;AACF;AC2CA,SAAS,WAAW,GAAA,EAAqB;AACvC,EAAA,OAAO,GAAA,CACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,EACrB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,QAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,MAAM,MAAM,CAAA;AACzB;AAMA,SAAS,mBAAA,CACP,KAAA,EACA,GAAA,EACA,UAAA,EACA,KAAA,EACQ;AACR,EAAA,MAAM,QAAA,GAAW,KAAA,CAAM,QAAA,IAAY,EAAC;AAGpC,EAAA,MAAM,kBAAkB,QAAA,CAAS,MAAA;AAAA,IAC/B,CAAC,MAAM,CAAA,CAAE,MAAA,KAAW,SAAS,CAAA,CAAE,MAAA,KAAW,KAAA,IAAS,CAAA,CAAE,MAAA,KAAW;AAAA,GAClE;AAEA,EAAA,IAAI,eAAA,CAAgB,WAAW,CAAA,EAAG;AAEhC,IAAA,OAAO,CAAA,UAAA,EAAa,UAAA,CAAW,KAAA,CAAM,MAAM,CAAC,UAAU,UAAA,CAAW,GAAG,CAAC,CAAA,CAAA,EAAI,UAAU,CAAA,mCAAA,CAAA;AAAA,EACrF;AAEA,EAAA,MAAM,MAAA,GAAS,eAAA,CACZ,GAAA,CAAI,CAAC,MAAM,CAAA,EAAG,CAAA,CAAE,MAAM,CAAA,CAAA,EAAI,CAAA,CAAE,KAAK,CAAA,CAAA,CAAG,CAAA,CACpC,KAAK,IAAI,CAAA;AAGZ,EAAA,MAAM,YAAY,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,WAAW,KAAK,CAAA;AACzD,EAAA,MAAM,WAAA,GAAc,SAAA,GAAY,SAAA,CAAU,MAAA,GAAS,KAAA,CAAM,MAAA;AAEzD,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,CAAA,kBAAA,EAAqB,MAAM,CAAA,2BAAA,EAA8B,UAAA,CAAW,KAAK,CAAC,CAAA,IAAA,CAAA;AAAA,IAC1E,CAAA,YAAA,EAAe,WAAW,WAAW,CAAC,UAAU,UAAA,CAAW,GAAG,CAAC,CAAA,CAAA,EAAI,UAAU,CAAA,mCAAA,CAAA;AAAA,IAC7E;AAAA,GACF,CAAE,KAAK,IAAI,CAAA;AACb;AAUA,SAAS,eAAA,CACP,WAAA,EACA,eAAA,EACA,KAAA,EACsC;AACtC,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpBD,KAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,eAAe,CAAA;AAAA,IACzCA,KAAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,IAAA,EAAM,eAAe,CAAA;AAAA,IAC/CA,KAAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,eAAe;AAAA,GAC7C;AAEA,EAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,IAAA,IAAIC,GAAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC3B,MAAA,IAAI;AACF,QAAA,MAAM,GAAA,GAAMA,GAAAA,CAAG,YAAA,CAAa,QAAA,EAAU,MAAM,CAAA;AAC5C,QAAA,MAAM,IAAA,GAAoB,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA;AAExC,QAAA,MAAM,GAAA,uBAAU,GAAA,EAA8B;AAC9C,QAAA,KAAA,MAAW,KAAA,IAAS,KAAK,MAAA,EAAQ;AAE/B,UAAA,GAAA,CAAI,GAAA,CAAI,KAAA,CAAM,SAAA,EAAW,KAAK,CAAA;AAC9B,UAAA,IAAI,CAAC,KAAA,CAAM,SAAA,CAAU,UAAA,CAAW,GAAG,CAAA,EAAG;AACpC,YAAA,GAAA,CAAI,GAAA,CAAI,GAAA,GAAM,KAAA,CAAM,SAAA,EAAW,KAAK,CAAA;AAAA,UACtC;AAAA,QACF;AAEA,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,IAAI,CAAA,uBAAA,EAA0B,IAAA,CAAK,OAAO,MAAM,CAAA,cAAA,EAAiB,QAAQ,CAAA,CAAE,CAAA;AAAA,QACrF;AACA,QAAA,OAAO,GAAA;AAAA,MACT,SAAS,KAAA,EAAO;AACd,QAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,gCAAA,EAAmC,QAAQ,CAAA,CAAA,CAAA,EAAK,KAAK,CAAA;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,OAAA,CAAQ,IAAI,iDAAiD,CAAA;AAC7D,IAAA,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAA,KAAM,OAAA,CAAQ,IAAI,CAAA,IAAA,EAAO,CAAC,EAAE,CAAC,CAAA;AAAA,EACtD;AACA,EAAA,OAAO,IAAA;AACT;AASO,SAAS,iBAAA,CAAkB,OAAA,GAAoC,EAAC,EAAW;AAChF,EAAA,MAAM;AAAA,IACJ,OAAA,GAAU,yBAAA;AAAA,IACV,YAAA,GAAe,CAAC,UAAU,CAAA;AAAA,IAC1B,YAAA,GAAe,mCAAA;AAAA,IACf,KAAA,GAAQ;AAAA,GACV,GAAI,OAAA;AAGJ,EAAA,IAAI,QAAA,GAAiD,IAAA;AACrD,EAAA,IAAI,YAAA,GAAe,KAAA;AAMnB,EAAA,SAAS,mBAAmB,IAAA,EAAuB;AACjD,IAAA,OAAO,aAAa,IAAA,CAAK,CAAC,WAAW,IAAA,CAAK,QAAA,CAAS,MAAM,CAAC,CAAA;AAAA,EAC5D;AAMA,EAAA,SAAS,kBAAkB,GAAA,EAAqB;AAC9C,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,IAAA,KAAS,GAAG,OAAO,GAAA;AAC7C,IAAA,IAAI,MAAA,GAAS,GAAA;AACb,IAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AAGjC,MAAA,MAAM,QAAQ,IAAI,MAAA;AAAA,QAChB,CAAA,SAAA,EAAY,WAAA,CAAY,MAAM,CAAC,CAAA,YAAA,CAAA;AAAA,QAC/B;AAAA,OACF;AACA,MAAA,MAAA,GAAS,OAAO,OAAA,CAAQ,KAAA,EAAO,CAAC,KAAA,EAAO,OAAO,OAAA,KAAY;AACxD,QAAA,MAAM,SAAA,GAAY,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAAI,OAAA;AAC9C,QAAA,MAAM,KAAA,GAAQ,SAAU,GAAA,CAAI,SAAS,KAAK,QAAA,CAAU,GAAA,CAAI,MAAM,SAAS,CAAA;AACvE,QAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,QAAA,OAAO,CAAA,EAAG,KAAK,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA,CAAA;AAAA,MAChC,CAAC,CAAA;AAAA,IACH;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAYA,EAAA,SAAS,cAAc,IAAA,EAAsB;AAC3C,IAAA,IAAI,CAAC,QAAA,IAAY,QAAA,CAAS,IAAA,KAAS,GAAG,OAAO,IAAA;AAI7C,IAAA,MAAM,WAAA,GAAc,8DAAA;AACpB,IAAA,IAAA,GAAO,IAAA,CAAK,QAAQ,WAAA,EAAa,CAAC,QAAQ,SAAA,EAAW,aAAA,EAAe,WAAW,QAAA,KAAa;AAC1F,MAAA,MAAM,KAAA,GAAQ,SAAU,GAAA,CAAI,SAAS,KAAK,QAAA,CAAU,GAAA,CAAI,MAAM,SAAS,CAAA;AACvE,MAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAGnB,MAAA,MAAM,QAAA,GAAA,CAAY,SAAA,GAAY,QAAA,EAAU,KAAA,CAAM,sBAAsB,CAAA;AACpE,MAAA,MAAM,GAAA,GAAM,QAAA,GAAW,QAAA,CAAS,CAAC,CAAA,GAAI,EAAA;AAGrC,MAAA,MAAM,UAAA,GAAA,CAAc,SAAA,GAAY,QAAA,EAC7B,OAAA,CAAQ,0BAA0B,EAAE,CAAA,CACpC,OAAA,CAAQ,4BAAA,EAA8B,EAAE,CAAA,CACxC,OAAA,CAAQ,6BAAA,EAA+B,EAAE,EACzC,IAAA,EAAK;AAER,MAAA,MAAM,UAAA,GAAa,UAAA,GAAa,GAAA,GAAM,UAAA,GAAa,EAAA;AAGnD,MAAA,MAAM,UAAA,GAAA,CAAc,SAAA,GAAY,QAAA,EAAU,KAAA,CAAM,6BAA6B,CAAA;AAC7E,MAAA,MAAM,KAAA,GAAQ,UAAA,GAAa,UAAA,CAAW,CAAC,CAAA,GAAI,YAAA;AAG3C,MAAA,IAAI,CAAC,KAAA,CAAM,QAAA,IAAY,KAAA,CAAM,QAAA,CAAS,WAAW,CAAA,EAAG;AAClD,QAAA,OAAO,CAAA,UAAA,EAAa,UAAA,CAAW,KAAA,CAAM,MAAM,CAAC,UAAU,UAAA,CAAW,GAAG,CAAC,CAAA,CAAA,EAAI,UAAU,CAAA,mCAAA,CAAA;AAAA,MACrF;AAGA,MAAA,OAAO,mBAAA,CAAoB,KAAA,EAAO,GAAA,EAAK,UAAA,EAAY,KAAK,CAAA;AAAA,IAC1D,CAAC,CAAA;AAGD,IAAA,IAAA,GAAO,kBAAkB,IAAI,CAAA;AAE7B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,eAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IAET,eAAe,MAAA,EAAQ;AACrB,MAAA,YAAA,GAAe,OAAO,OAAA,KAAY,OAAA;AAClC,MAAA,IAAI,CAAC,YAAA,EAAc;AACjB,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,IAAI,+EAA0E,CAAA;AAAA,QACxF;AACA,QAAA;AAAA,MACF;AACA,MAAA,QAAA,GAAW,eAAA,CAAgB,MAAA,CAAO,IAAA,EAAM,OAAA,EAAS,KAAK,CAAA;AAAA,IACxD,CAAA;AAAA,IAEA,SAAA,CAAU,MAAM,EAAA,EAAI;AAElB,MAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,QAAA,EAAU;AAGhC,MAAA,IAAI,CAAC,gDAAA,CAAiD,IAAA,CAAK,EAAE,CAAA,EAAG;AAGhE,MAAA,IAAI,CAAC,kBAAA,CAAmB,IAAI,CAAA,EAAG;AAE/B,MAAA,MAAM,WAAA,GAAc,cAAc,IAAI,CAAA;AACtC,MAAA,IAAI,gBAAgB,IAAA,EAAM;AACxB,QAAA,OAAO,EAAE,IAAA,EAAM,WAAA,EAAa,GAAA,EAAK,IAAA,EAAK;AAAA,MACxC;AAAA,IACF,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,WAAA,CAAY,MAAM,MAAA,EAAQ;AACxB,MAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,QAAA,EAAU,OAAO,IAAA;AACvC,MAAA,IAAI,CAAC,kBAAA,CAAmB,IAAI,CAAA,EAAG,OAAO,IAAA;AAEtC,MAAA,MAAM,QAAA,GAAW,kBAAkB,IAAI,CAAA;AACvC,MAAA,IAAI,aAAa,IAAA,EAAM;AACrB,QAAA,IAAI,KAAA,EAAO;AACT,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,oDAAA,EAAuD,MAAA,CAAO,QAAQ,CAAA,CAAE,CAAA;AAAA,QACtF;AACA,QAAA,OAAO,EAAE,IAAA,EAAM,QAAA,EAAU,GAAA,EAAK,IAAA,EAAK;AAAA,MACrC;AACA,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,mBAAmB,IAAA,EAAM;AACvB,MAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,QAAA,EAAU,OAAO,IAAA;AACvC,MAAA,IAAI,CAAC,kBAAA,CAAmB,IAAI,CAAA,EAAG,OAAO,IAAA;AAGtC,MAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,QAAA,MAAM,YAAY,IAAI,MAAA;AAAA,UACpB,CAAA,2BAAA,EAA8B,WAAA,CAAY,MAAM,CAAC,CAAA,cAAA,CAAA;AAAA,UACjD;AAAA,SACF;AACA,QAAA,IAAA,GAAO,KAAK,OAAA,CAAQ,SAAA,EAAW,CAAC,KAAA,EAAO,GAAA,EAAK,SAAS,IAAA,KAAS;AAC5D,UAAA,MAAM,SAAA,GAAY,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAAI,OAAA;AAC9C,UAAA,MAAM,KAAA,GAAQ,SAAU,GAAA,CAAI,SAAS,KAAK,QAAA,CAAU,GAAA,CAAI,MAAM,SAAS,CAAA;AACvE,UAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,UAAA,OAAO,GAAG,GAAG,CAAA,EAAG,KAAA,CAAM,MAAM,GAAG,IAAI,CAAA,CAAA;AAAA,QACrC,CAAC,CAAA;AAAA,MACH;AAEA,MAAA,OAAO,IAAA;AAAA,IACT;AAAA,GACF;AACF;AAKA,SAAS,YAAY,GAAA,EAAqB;AACxC,EAAA,OAAO,GAAA,CAAI,OAAA,CAAQ,qBAAA,EAAuB,MAAM,CAAA;AAClD;AAiDO,SAAS,cAAA,CAAe,OAAA,GAAiC,EAAC,EAAG;AAClE,EAAA,MAAM;AAAA,IACJ,OAAA,GAAU,yBAAA;AAAA,IACV,YAAA,GAAe,CAAC,UAAU,CAAA;AAAA,IAC1B,KAAA,GAAQ,KAAA;AAAA,IACR,UAAA,GAAa,CAAC,OAAA,EAAS,KAAK;AAAA,GAC9B,GAAI,OAAA;AAEJ,EAAA,OAAO,OAAO,UAAA,KAAiD;AAC7D,IAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,UAAA,CAAW,IAAA,EAAM,SAAS,KAAK,CAAA;AAChE,IAAA,IAAI,CAAC,QAAA,EAAU;AACb,MAAA,IAAI,KAAA,EAAO;AACT,QAAA,OAAA,CAAQ,IAAI,8CAA8C,CAAA;AAAA,MAC5D;AACA,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,SAAS,UAAA,CAAW,MAAA;AAC1B,IAAA,IAAI,CAACA,GAAAA,CAAG,UAAA,CAAW,MAAM,CAAA,EAAG;AAE5B,IAAA,IAAI,cAAA,GAAiB,CAAA;AACrB,IAAA,IAAI,YAAA,GAAe,CAAA;AAEnB,IAAA,SAAS,QAAQ,GAAA,EAAa;AAC5B,MAAA,MAAM,UAAUA,GAAAA,CAAG,WAAA,CAAY,KAAK,EAAE,aAAA,EAAe,MAAM,CAAA;AAC3D,MAAA,KAAA,MAAW,SAAS,OAAA,EAAS;AAC3B,QAAA,MAAM,QAAA,GAAWD,KAAAA,CAAK,IAAA,CAAK,GAAA,EAAK,MAAM,IAAI,CAAA;AAC1C,QAAA,IAAI,KAAA,CAAM,aAAY,EAAG;AACvB,UAAA,OAAA,CAAQ,QAAQ,CAAA;AAAA,QAClB,CAAA,MAAA,IAAW,UAAA,CAAW,IAAA,CAAK,CAAC,GAAA,KAAQ,MAAM,IAAA,CAAK,QAAA,CAAS,GAAG,CAAC,CAAA,EAAG;AAC7D,UAAA,WAAA,CAAY,QAAQ,CAAA;AAAA,QACtB;AAAA,MACF;AAAA,IACF;AAEA,IAAA,SAAS,YAAY,QAAA,EAAkB;AACrC,MAAA,IAAI,OAAA,GAAUC,GAAAA,CAAG,YAAA,CAAa,QAAA,EAAU,MAAM,CAAA;AAC9C,MAAA,IAAI,CAAC,aAAa,IAAA,CAAK,CAAC,MAAM,OAAA,CAAQ,QAAA,CAAS,CAAC,CAAC,CAAA,EAAG;AAEpD,MAAA,IAAI,OAAA,GAAU,KAAA;AAEd,MAAA,IAAI,QAAA,CAAS,QAAA,CAAS,OAAO,CAAA,EAAG;AAE9B,QAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,UAAA,MAAM,YAAY,IAAI,MAAA;AAAA,YACpB,CAAA,2BAAA,EAA8B,WAAA,CAAY,MAAM,CAAC,CAAA,cAAA,CAAA;AAAA,YACjD;AAAA,WACF;AACA,UAAA,OAAA,GAAU,QAAQ,OAAA,CAAQ,SAAA,EAAW,CAAC,KAAA,EAAO,GAAA,EAAK,SAAS,IAAA,KAAS;AAClE,YAAA,MAAM,SAAA,GAAY,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAAI,OAAA;AAC9C,YAAA,MAAM,QAAA,GAAW,SAAU,GAAA,CAAI,SAAS,KAAK,QAAA,CAAU,GAAA,CAAI,MAAM,SAAS,CAAA;AAC1E,YAAA,IAAI,CAAC,UAAU,OAAO,KAAA;AACtB,YAAA,OAAA,GAAU,IAAA;AACV,YAAA,YAAA,EAAA;AACA,YAAA,OAAO,GAAG,GAAG,CAAA,EAAG,QAAA,CAAS,MAAM,GAAG,IAAI,CAAA,CAAA;AAAA,UACxC,CAAC,CAAA;AAAA,QACH;AAAA,MACF,CAAA,MAAO;AAEL,QAAA,KAAA,MAAW,UAAU,YAAA,EAAc;AACjC,UAAA,MAAM,QAAQ,IAAI,MAAA;AAAA,YAChB,CAAA,SAAA,EAAY,WAAA,CAAY,MAAM,CAAC,CAAA,YAAA,CAAA;AAAA,YAC/B;AAAA,WACF;AACA,UAAA,OAAA,GAAU,QAAQ,OAAA,CAAQ,KAAA,EAAO,CAAC,KAAA,EAAO,OAAO,OAAA,KAAY;AAC1D,YAAA,MAAM,SAAA,GAAY,MAAA,CAAO,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA,GAAI,OAAA;AAC9C,YAAA,MAAM,KAAA,GAAQ,SAAU,GAAA,CAAI,SAAS,KAAK,QAAA,CAAU,GAAA,CAAI,MAAM,SAAS,CAAA;AACvE,YAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AACnB,YAAA,OAAA,GAAU,IAAA;AACV,YAAA,YAAA,EAAA;AACA,YAAA,OAAO,CAAA,EAAG,KAAK,CAAA,EAAG,KAAA,CAAM,MAAM,CAAA,CAAA;AAAA,UAChC,CAAC,CAAA;AAAA,QACH;AAAA,MACF;AAEA,MAAA,IAAI,OAAA,EAAS;AACX,QAAAA,GAAAA,CAAG,aAAA,CAAc,QAAA,EAAU,OAAA,EAAS,MAAM,CAAA;AAC1C,QAAA,cAAA,EAAA;AAAA,MACF;AAAA,IACF;AAEA,IAAA,OAAA,CAAQ,MAAM,CAAA;AAEd,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,OAAA,CAAQ,GAAA;AAAA,QACN,CAAA,yCAAA,EAA4C,cAAc,CAAA,iBAAA,EAAoB,YAAY,CAAA,WAAA;AAAA,OAC5F;AAAA,IACF;AAAA,EACF,CAAA;AACF;;;ACnfA,IAAM,WAAA,GAAc,6EAAA;AAMpB,SAASE,YAAW,GAAA,EAAqB;AACvC,EAAA,OAAO,GAAA,CACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,EACrB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,QAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,MAAM,MAAM,CAAA;AACzB;AAEO,SAAS,sBAAsB,EAAA,EAAsB;AAC1D,EAAA,MAAM,oBAAA,GAAuB,EAAA,CAAG,QAAA,CAAS,KAAA,CAAM,KAAA;AAE/C,EAAA,EAAA,CAAG,QAAA,CAAS,MAAM,KAAA,GAAQ,CAAC,QAAQ,GAAA,EAAK,OAAA,EAAS,KAAK,IAAA,KAAS;AAC7D,IAAA,MAAM,KAAA,GAAQ,OAAO,GAAG,CAAA;AACxB,IAAA,MAAM,GAAA,GAAM,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,EAAA;AACpC,IAAA,MAAM,GAAA,GAAM,MAAM,OAAA,IAAW,EAAA;AAE7B,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,KAAA,CAAM,WAAW,CAAA;AACnC,IAAA,IAAI,CAAC,KAAA,EAAO;AAEV,MAAA,IAAI,oBAAA,EAAsB;AACxB,QAAA,OAAO,oBAAA,CAAqB,MAAA,EAAQ,GAAA,EAAK,OAAA,EAAS,KAAK,IAAI,CAAA;AAAA,MAC7D;AACA,MAAA,OAAO,IAAA,CAAK,WAAA,CAAY,MAAA,EAAQ,GAAA,EAAK,OAAO,CAAA;AAAA,IAC9C;AAEA,IAAA,MAAM,GAAG,QAAA,EAAU,IAAI,CAAA,GAAI,KAAA;AAE3B,IAAA,MAAM,MAAA,GAAS;AAAA,MACb,CAAA,EAAG,QAAQ,CAAA,EAAG,IAAI,CAAA,aAAA,CAAA;AAAA,MAClB,CAAA,EAAG,QAAQ,CAAA,EAAG,IAAI,CAAA,cAAA,CAAA;AAAA,MAClB,CAAA,EAAG,QAAQ,CAAA,EAAG,IAAI,CAAA,cAAA;AAAA,KACpB,CAAE,KAAK,IAAI,CAAA;AAEX,IAAA,MAAM,UAAA,GAAaA,YAAW,GAAG,CAAA;AAEjC,IAAA,OAAO;AAAA,MACL,WAAA;AAAA,MACA,qBAAqB,MAAM,CAAA,gEAAA,CAAA;AAAA,MAC3B,CAAA,YAAA,EAAe,QAAQ,CAAA,EAAG,IAAI,kBAAkB,UAAU,CAAA,oCAAA,CAAA;AAAA,MAC1D;AAAA,KACF,CAAE,KAAK,IAAI,CAAA;AAAA,EACb,CAAA;AACF","file":"index.js","sourcesContent":["/**\n * DCS Content Plugin for Vite\n *\n * Reads `.dcs/content.yaml` at build time and injects content\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsContentPlugin({ debug: true })\n * ]\n * })\n * ```\n *\n * For VitePress:\n * ```typescript\n * // .vitepress/config.ts\n * import { defineConfig } from 'vitepress'\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsContentPlugin()\n * ]\n * }\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { DcsContentFile } from '../types/content'\n\nexport interface DcsContentPluginOptions {\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\n contentPath?: string\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects .dcs/content.yaml at build time.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\n const { contentPath = '.dcs/content.yaml', debug = false } = options\n\n let resolvedConfig: ResolvedConfig\n\n return {\n name: 'dcs-content',\n\n configResolved(config) {\n resolvedConfig = config\n },\n\n config(config) {\n const projectRoot = config.root || process.cwd()\n\n // Try to find content.yaml in multiple locations\n // VitePress projects have the docs folder as root, so check parent too\n const possiblePaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n path.resolve(process.cwd(), contentPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-content] No content.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-content] Using defaults only')\n }\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const content = yaml.load(fileContent) as DcsContentFile\n\n if (debug) {\n console.log(`[dcs-content] Loaded ${foundPath}`)\n console.log(`[dcs-content] Version: ${content.version}`)\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\n }\n\n return {\n define: {\n __DCS_CONTENT__: JSON.stringify(content),\n },\n }\n } catch (error) {\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n },\n\n // Watch for changes in development\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n ]\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n\n server.watcher.on('change', (changedPath) => {\n if (changedPath === watchPath) {\n if (debug) {\n console.log('[dcs-content] content.yaml changed, triggering reload')\n }\n server.restart()\n }\n })\n }\n })\n },\n }\n}\n","/**\n * DCS SEO Plugin for Vite\n *\n * Two responsibilities, both driven by `.dcs/seo.yaml`:\n *\n * 1. **Build-time define** (always on): reads `.dcs/seo.yaml` and injects it as\n * the `__DCS_SEO__` global for the runtime `useSEO` composable.\n *\n * 2. **Static `<head>` emitter** (opt-in via `emitStaticHtml: true`, default\n * OFF): after the bundle is written, reads the SPA's built `index.html`,\n * and for every route in `.dcs/pages.yaml` writes a per-route\n * `dist/<path>/index.html` whose `<head>` carries the resolved title, meta,\n * canonical, Open Graph, Twitter, and JSON-LD tags. This gives a Vue SPA\n * per-route static SEO **without** vite-ssg.\n *\n * VitePress sites already bake SEO via their own config, so they leave this\n * option OFF and are completely unaffected.\n *\n * @example Runtime define only (default — safe for VitePress)\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [dcsSeoPlugin({ debug: true })]\n * })\n * ```\n *\n * @example Vue SPA with per-route static <head> emission\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsSeoPlugin({\n * emitStaticHtml: true, // turn the emitter ON\n * pagesPath: '.dcs/pages.yaml', // route manifest (default)\n * noindex: ['account', 'projects'], // robots: noindex,nofollow\n * exclude: ['/preview'], // skip these routes entirely\n * })\n * ]\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { SeoConfiguration } from '../types/seo'\nimport { buildHeadTags } from '../seo/headTags'\nimport { spliceHeadHtml } from '../seo/spliceHeadHtml'\nimport { loadPagesManifest, type PageRouteEntry } from '../seo/pagesManifest'\n\nexport interface DcsSeoPluginOptions {\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\n seoPath?: string\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * Opt-in: emit per-route static `<head>` (meta + JSON-LD) into the built\n * `dist/` at the end of the build. Default `false` — VitePress sites and any\n * site that bakes its own SEO are unaffected when this is off.\n */\n emitStaticHtml?: boolean\n\n /**\n * Path to the route manifest relative to project root, used only when\n * `emitStaticHtml` is true. Default `'.dcs/pages.yaml'`.\n */\n pagesPath?: string\n\n /**\n * Routes to skip entirely (no per-route HTML written). Matched against the\n * route `path` (e.g. `'/preview'`) OR the route `slug` (e.g. `'account'`).\n */\n exclude?: string[]\n\n /**\n * Routes that should receive `robots: noindex, nofollow`. Matched against the\n * route `path` OR `slug`. The home route (`/`) still overwrites\n * `dist/index.html`.\n */\n noindex?: string[]\n}\n\n/** Resolve the SPA output directory from Vite config (absolute path). */\nfunction resolveOutDir(config: ResolvedConfig): string {\n const out = config.build?.outDir || 'dist'\n return path.isAbsolute(out) ? out : path.resolve(config.root || process.cwd(), out)\n}\n\n/**\n * Load + parse seo.yaml from the usual `.dcs` locations.\n * Returns `{ config, foundPath }` or `null` (missing/unparseable).\n */\nfunction loadSeoConfig(\n projectRoot: string,\n seoPath: string,\n debug: boolean\n): { config: SeoConfiguration; foundPath: string } | null {\n const possiblePaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n path.resolve(process.cwd(), seoPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-seo] No seo.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-seo] Using defaults only')\n }\n return null\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const config = yaml.load(fileContent) as SeoConfiguration\n return { config, foundPath }\n } catch (error) {\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\n return null\n }\n}\n\n/**\n * Normalise a route path to the on-disk output file:\n * '/' -> '<outDir>/index.html'\n * '/services' -> '<outDir>/services/index.html'\n * '/blog/my-post' -> '<outDir>/blog/my-post/index.html'\n * '/services/' -> '<outDir>/services/index.html'\n */\nfunction routeToOutputFile(outDir: string, routePath: string): string {\n const trimmed = routePath.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (trimmed === '') return path.join(outDir, 'index.html')\n return path.join(outDir, ...trimmed.split('/'), 'index.html')\n}\n\n/**\n * Core emitter, separated from Vite so it is unit-testable.\n *\n * For each route: build the head tags from the shared resolver, splice them\n * into the shell HTML, and write `dist/<path>/index.html`. Returns the number\n * of files written. Pure aside from `fs` writes — deterministic + idempotent.\n */\nexport function emitStaticSeoHtml(params: {\n outDir: string\n shellHtml: string\n routes: PageRouteEntry[]\n seoConfig: SeoConfiguration | undefined\n exclude?: string[]\n noindex?: string[]\n debug?: boolean\n}): number {\n const { outDir, shellHtml, routes, seoConfig, exclude = [], noindex = [], debug = false } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n let written = 0\n for (const route of routes) {\n if (excludeSet.has(route.path) || (route.slug && excludeSet.has(route.slug))) {\n if (debug) console.log(`[dcs-seo] skip (excluded): ${route.path}`)\n continue\n }\n\n const forceNoindex =\n noindexSet.has(route.path) || (route.slug && noindexSet.has(route.slug))\n\n const tags = buildHeadTags(route.slug, route.path, seoConfig, {\n includeKeywords: true,\n fallbackTitle: route.title,\n robots: forceNoindex ? 'noindex, nofollow' : undefined,\n })\n\n const html = spliceHeadHtml(shellHtml, tags)\n const outFile = routeToOutputFile(outDir, route.path)\n\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, html, 'utf8')\n written++\n\n if (debug) {\n console.log(\n `[dcs-seo] wrote ${path.relative(outDir, outFile)} (title=\"${tags.title}\"${\n forceNoindex ? ', noindex' : ''\n })`\n )\n }\n }\n\n return written\n}\n\n/**\n * Vite plugin that injects .dcs/seo.yaml at build time and, optionally, emits\n * per-route static `<head>` HTML for SPA SEO.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\n const {\n seoPath = '.dcs/seo.yaml',\n debug = false,\n emitStaticHtml = false,\n pagesPath = '.dcs/pages.yaml',\n exclude = [],\n noindex = [],\n } = options\n\n let resolvedConfig: ResolvedConfig\n\n return {\n name: 'dcs-seo',\n\n configResolved(config) {\n resolvedConfig = config\n },\n\n config(config) {\n const projectRoot = config.root || process.cwd()\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n return {\n define: {\n __DCS_SEO__: 'undefined',\n },\n }\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${loaded.foundPath}`)\n console.log(`[dcs-seo] Version: ${loaded.config.version}`)\n console.log(`[dcs-seo] Site Name: ${loaded.config.global?.siteName || '(not set)'}`)\n console.log(\n `[dcs-seo] Pages: ${Object.keys(loaded.config.pages ?? {}).join(', ') || '(none)'}`\n )\n }\n\n return {\n define: {\n __DCS_SEO__: JSON.stringify(loaded.config),\n },\n }\n },\n\n /**\n * Build-time static `<head>` emitter (opt-in via `emitStaticHtml`).\n *\n * Runs after the bundle is written so the built `index.html` shell exists\n * on disk. For an SPA, Vite emits a single `index.html`; we fan it out to\n * per-route files with route-specific `<head>` content.\n *\n * Fully defensive: any missing/unparseable input logs a warning and\n * no-ops. Never throws (so it can never break a production build).\n */\n writeBundle() {\n if (!emitStaticHtml) return\n\n try {\n const projectRoot = resolvedConfig?.root || process.cwd()\n const outDir = resolveOutDir(resolvedConfig)\n\n if (!fs.existsSync(outDir)) {\n console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`)\n return\n }\n\n const shellPath = path.join(outDir, 'index.html')\n if (!fs.existsSync(shellPath)) {\n console.warn(\n `[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`\n )\n return\n }\n\n const routes = loadPagesManifest(projectRoot, pagesPath, debug)\n if (!routes) {\n console.warn(\n `[dcs-seo] emitStaticHtml: ${pagesPath} missing or empty; skipping per-route emission`\n )\n return\n }\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n // seo.yaml is optional for emission — routes still get baked with\n // global/runtime defaults (which here means just the shell title and\n // a derived canonical-less head). Warn so the operator knows.\n console.warn(\n `[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`\n )\n }\n\n const shellHtml = fs.readFileSync(shellPath, 'utf8')\n\n const written = emitStaticSeoHtml({\n outDir,\n shellHtml,\n routes,\n seoConfig: loaded?.config,\n exclude,\n noindex,\n debug,\n })\n\n if (debug) {\n console.log(`[dcs-seo] emitStaticHtml: wrote ${written} per-route HTML file(s)`)\n }\n } catch (error) {\n // Never break the build on SEO emission failure.\n console.warn('[dcs-seo] emitStaticHtml failed; build output left unchanged:', error)\n }\n },\n\n // Watch for changes in development\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n ]\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n\n server.watcher.on('change', (changedPath) => {\n if (changedPath === watchPath) {\n if (debug) {\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\n }\n server.restart()\n }\n })\n }\n })\n },\n }\n}\n","/**\n * DCS Editor Plugin for Vite\n *\n * Injects the editor bridge script into customer sites when running\n * in portal preview mode (inside the visual editor iframe).\n *\n * The bridge enables:\n * - Inline text editing via contenteditable\n * - Section hover highlights and AI ✨ buttons\n * - postMessage communication with the portal\n *\n * This plugin should only be active in development/preview mode.\n * It's safe to include in production builds — it does nothing unless\n * the page detects it's inside an iframe.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsEditorPlugin()\n * ]\n * })\n * ```\n */\n\nimport type { Plugin } from 'vite'\n\nexport interface DcsEditorPluginOptions {\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects the editor bridge script for portal preview integration.\n *\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\n *\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\n * resolved through Vite's module graph rather than hitting the browser's native ESM\n * resolver, which can't handle bare specifiers.\n */\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\n const { debug = false } = options\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\n\n // Captured during configResolved — used to gate HTML injection.\n let isDev = false\n let basePath = '/'\n\n return {\n name: 'dcs-editor-bridge',\n\n configResolved(config) {\n isDev = config.command === 'serve'\n basePath = config.base ?? '/'\n },\n\n resolveId(id) {\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\n return id\n }\n },\n\n load(id) {\n if (id === VIRTUAL_PATH) {\n return `\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\n// The bridge only activates inside an iframe (portal preview)\nif (window.parent !== window) {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\n } else {\n setTimeout(initEditorBridge, 200)\n }\n}\n`\n }\n\n if (id === RIBBON_VIRTUAL_PATH) {\n return `\nimport { createApp } from 'vue'\nimport PreviewRibbon from '@duffcloudservices/cms/components'\n\nfunction mountRibbon() {\n // Create a detached mount point\n const el = document.createElement('div')\n el.id = 'dcs-preview-ribbon-root'\n document.body.appendChild(el)\n\n const app = createApp(PreviewRibbon)\n app.mount(el)\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', mountRibbon)\n} else {\n mountRibbon()\n}\n`\n }\n },\n\n /**\n * Only inject the editor bridge and ribbon scripts when:\n * 1. Running in dev mode (Vite dev server), AND\n * 2. The app is served at the root path (base === '/')\n *\n * These are virtual modules resolved via absolute paths (e.g.\n * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a\n * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT\n * routed to the Vite dev server by the reverse proxy — they hit the main\n * DCS server instead and get redirected to portal.duffcloudservices.com\n * (no CORS headers), causing browser console errors.\n *\n * In production builds, App.vue imports and mounts PreviewRibbon\n * directly, so virtual script injection is always redundant anyway.\n */\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n if (!isDev || basePath !== '/') return html\n\n if (debug) {\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\n }\n\n // Inject both the editor bridge and preview ribbon scripts\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\n\n // Insert before closing </body> tag\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\n },\n },\n }\n}\n","/**\n * DCS Preview Plugin for Vue\n *\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\n * component. The ribbon handles its own visibility — it only renders on\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\n * production domains, and inside the visual page editor iframe).\n *\n * Why does the caller pass the component in? Because this file is compiled\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\n * import out of the compiled plugins bundle avoids the build error while\n * still letting consumer code (which *does* run through Vite) resolve the\n * SFC at dev/build time.\n *\n * @example VitePress theme/index.ts\n * ```typescript\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\n * import PreviewRibbon from '@duffcloudservices/cms/components'\n *\n * export default {\n * Layout,\n * enhanceApp({ app }) {\n * app.use(dcsPreviewPlugin(PreviewRibbon))\n * }\n * }\n * ```\n */\n\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\n\nexport interface DcsPreviewPluginOptions {\n /**\n * Override the version string displayed on the ribbon.\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\n */\n version?: string | null\n}\n\n/**\n * Creates and returns the DCS Preview plugin.\n *\n * When installed, it registers the supplied ribbon component as a global\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\n * `transformIndexHtml`.\n *\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\n * @param options - Optional configuration\n */\nexport function dcsPreviewPlugin(\n ribbonComponent: Component,\n options: DcsPreviewPluginOptions = {},\n): Plugin {\n const Wrapper = defineComponent({\n name: 'DcsPreviewRibbon',\n setup() {\n return () => h(ribbonComponent, { version: options.version ?? null })\n },\n })\n\n return {\n install(app: App) {\n app.component('DcsPreviewRibbon', Wrapper)\n\n if (options.version !== undefined) {\n app.provide('__dcs_preview_version__', options.version)\n }\n },\n }\n}\n","/**\n * DCS CDN Image Plugin for Vite\n *\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\n * generated by the `image-migrate adopt` CLI command.\n *\n * For raster images with WebP variants, `<img>` elements are transformed into\n * responsive `<picture>` elements with `srcset` for optimised delivery.\n * SVGs receive a simple URL swap with no variant handling.\n *\n * **In development mode this plugin is a no-op** — local `/images/` paths\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\n *\n * The plugin handles two in-pipeline replacement vectors:\n *\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\n *\n * A third vector — **post-build file processing** — is handled by the\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\n * VitePress config. VitePress generates HTML *after* both Vite builds\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\n * `service-worker.js`.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsCdnImagePlugin()\n * ]\n * },\n * buildEnd: dcsCdnBuildEnd()\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { Plugin } from 'vite'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface CdnImageMapVariant {\n suffix: string\n blobPath: string\n cdnUrl: string\n width: number\n height: number\n size: number\n}\n\nexport interface CdnImageMapEntry {\n localPath: string\n blobPath: string\n cdnUrl: string\n fileName: string\n contentType: string\n originalSize: number\n width?: number\n height?: number\n hash: string\n variants?: CdnImageMapVariant[]\n}\n\nexport interface CdnImageMap {\n generated: string\n site: string\n cdnBase: string\n images: CdnImageMapEntry[]\n}\n\nexport interface DcsCdnImagePluginOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\n * mapping file is compared *without* a leading slash.\n *\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /**\n * Default `sizes` attribute for responsive `<picture>` elements.\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\n *\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\n */\n defaultSizes?: string\n\n /** Enable debug logging */\n debug?: boolean\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n/**\n * Build a responsive `<picture>` element for a raster image that has\n * WebP variants.\n */\nfunction buildPictureElement(\n entry: CdnImageMapEntry,\n alt: string,\n extraAttrs: string,\n sizes: string\n): string {\n const variants = entry.variants ?? []\n\n // Collect only the display-relevant variants (not thumb, not og)\n const displayVariants = variants.filter(\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\n )\n\n if (displayVariants.length === 0) {\n // No display variants — fall back to original CDN URL as plain <img>\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n const srcset = displayVariants\n .map((v) => `${v.cdnUrl} ${v.width}w`)\n .join(', ')\n\n // Pick the -md variant as the default src, or fall through to original\n const mdVariant = variants.find((v) => v.suffix === '-md')\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\n '</picture>',\n ].join('\\n')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Shared helpers\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Try to find and load the CDN image map from common locations.\n * VitePress nests docs under a subfolder, so we check parent dirs too.\n */\nfunction loadCdnImageMap(\n projectRoot: string,\n relativeMapPath: string,\n debug: boolean\n): Map<string, CdnImageMapEntry> | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativeMapPath),\n path.resolve(projectRoot, '..', relativeMapPath),\n path.resolve(process.cwd(), relativeMapPath),\n ]\n\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n try {\n const raw = fs.readFileSync(testPath, 'utf8')\n const data: CdnImageMap = JSON.parse(raw)\n\n const map = new Map<string, CdnImageMapEntry>()\n for (const entry of data.images) {\n // Store both with and without leading slash for flexible matching\n map.set(entry.localPath, entry)\n if (!entry.localPath.startsWith('/')) {\n map.set('/' + entry.localPath, entry)\n }\n }\n\n if (debug) {\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\n }\n return map\n } catch (error) {\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\n }\n }\n }\n\n if (debug) {\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n }\n return null\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Vite Plugin\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\n */\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\n debug = false,\n } = options\n\n // Lookup map built from the JSON file: key = localPath (no leading slash)\n let imageMap: Map<string, CdnImageMapEntry> | null = null\n let isProduction = false\n\n /**\n * Check whether a source code string contains any of the path prefixes\n * we care about. Quick bail-out for files that don't reference images.\n */\n function hasImageReferences(code: string): boolean {\n return pathPrefixes.some((prefix) => code.includes(prefix))\n }\n\n /**\n * Replace image path references in any string value with CDN URLs.\n * Used for code transforms and renderChunk post-processing.\n */\n function replaceImagePaths(str: string): string {\n if (!imageMap || imageMap.size === 0) return str\n let result = str\n for (const prefix of pathPrefixes) {\n // Global replace: matches prefix followed by a relative path bounded\n // by quotes, parens, or whitespace. Allows spaces in filenames.\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n result = result.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${quote}${entry.cdnUrl}`\n })\n }\n return result\n }\n\n /**\n * Replace standalone URL references in JS/TS string literals and Vue template\n * attributes. This handles patterns like:\n * src=\"/images/foo.jpg\"\n * { src: '/images/foo.jpg' }\n * url(/images/foo.svg)\n *\n * For `<img>` tags with raster images that have variants, the entire tag is\n * replaced with a `<picture>` element.\n */\n function transformCode(code: string): string {\n if (!imageMap || imageMap.size === 0) return code\n\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return _match\n\n // Extract alt from the existing attributes\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\n const alt = altMatch ? altMatch[1] : ''\n\n // Collect other attributes (not src, alt, loading, decoding — we set those)\n const otherAttrs = (beforeSrc + afterSrc)\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\n .trim()\n\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\n\n // Extract custom sizes if specified\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\n\n // SVGs and images without variants → simple URL swap <img>\n if (!entry.variants || entry.variants.length === 0) {\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n // Raster with variants → <picture>\n return buildPictureElement(entry, alt, extraAttrs, sizes)\n })\n\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\n code = replaceImagePaths(code)\n\n return code\n }\n\n return {\n name: 'dcs-cdn-image',\n enforce: 'pre',\n\n configResolved(config) {\n isProduction = config.command === 'build'\n if (!isProduction) {\n if (debug) {\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\n }\n return\n }\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\n },\n\n transform(code, id) {\n // Only run in production builds\n if (!isProduction || !imageMap) return\n\n // Only process relevant file types\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\n\n // Quick bail — skip files with no image path references\n if (!hasImageReferences(code)) return\n\n const transformed = transformCode(code)\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n\n /**\n * Replace image paths in final rendered JS/CSS chunks.\n *\n * This fires AFTER Vite's `define` substitution, catching paths that were\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\n * `transform` hook because `define` replacement happens at bundle time.\n */\n renderChunk(code, _chunk) {\n if (!isProduction || !imageMap) return null\n if (!hasImageReferences(code)) return null\n\n const replaced = replaceImagePaths(code)\n if (replaced !== code) {\n if (debug) {\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\n }\n return { code: replaced, map: null }\n }\n return null\n },\n\n /**\n * Transform HTML output to catch references injected outside the module\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\n *\n * Note: In VitePress, this hook fires for the `index.html` template but\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\n * coverage of generated HTML files.\n */\n transformIndexHtml(html) {\n if (!isProduction || !imageMap) return html\n if (!hasImageReferences(html)) return html\n\n // Replace href/src/content attributes that reference mapped images\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${pre}${entry.cdnUrl}${post}`\n })\n }\n\n return html\n },\n }\n}\n\n/**\n * Escape a string for use in a RegExp.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// VitePress buildEnd hook\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface DcsCdnBuildEndOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement.\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * File extensions to process in the output directory.\n * Default: `['.html', '.js']`\n */\n extensions?: string[]\n}\n\n/**\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\n * static files in the output directory to replace remaining `/images/` paths\n * with CDN URLs.\n *\n * VitePress generates HTML **after** both Vite builds complete, so Vite\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\n * HTML files are written to disk.\n *\n * Also rewrites static files (e.g. `service-worker.js`) copied from\n * `public/` that are not part of Vite's module pipeline.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * buildEnd: dcsCdnBuildEnd({ debug: true })\n * })\n * ```\n */\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n debug = false,\n extensions = ['.html', '.js'],\n } = options\n\n return async (siteConfig: { root: string; outDir: string }) => {\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\n if (!imageMap) {\n if (debug) {\n console.log('[dcs-cdn-image] buildEnd: no image map found')\n }\n return\n }\n\n const outDir = siteConfig.outDir\n if (!fs.existsSync(outDir)) return\n\n let filesProcessed = 0\n let refsReplaced = 0\n\n function walkDir(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n walkDir(fullPath)\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\n processFile(fullPath)\n }\n }\n }\n\n function processFile(filePath: string) {\n let content = fs.readFileSync(filePath, 'utf8')\n if (!pathPrefixes.some((p) => content.includes(p))) return\n\n let changed = false\n\n if (filePath.endsWith('.html')) {\n // HTML: replace href/src/content attribute values\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!mapEntry) return match\n changed = true\n refsReplaced++\n return `${pre}${mapEntry.cdnUrl}${post}`\n })\n }\n } else {\n // JS/other: replace quoted string paths\n for (const prefix of pathPrefixes) {\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n content = content.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n changed = true\n refsReplaced++\n return `${quote}${entry.cdnUrl}`\n })\n }\n }\n\n if (changed) {\n fs.writeFileSync(filePath, content, 'utf8')\n filesProcessed++\n }\n }\n\n walkDir(outDir)\n\n if (debug) {\n console.log(\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\n )\n }\n }\n}","/**\n * markdown-it plugin that transforms standard `` image syntax\n * into responsive `<picture>` elements when the URL matches the DCS CDN\n * asset pattern.\n *\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * markdown: {\n * config: (md) => {\n * md.use(responsiveImagePlugin)\n * },\n * },\n * })\n * ```\n *\n * Input markdown:\n * ```md\n * \n * ```\n *\n * Rendered HTML:\n * ```html\n * <picture>\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\n * type=\"image/webp\"\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\n * </picture>\n * ```\n */\n\nimport type MarkdownIt from 'markdown-it'\n\n/** Matches DCS CDN asset URLs: base path / UUID . extension */\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\n\n/**\n * Escapes HTML special characters for safe attribute embedding.\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\nexport function responsiveImagePlugin(md: MarkdownIt): void {\n const defaultImageRenderer = md.renderer.rules.image\n\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\n const token = tokens[idx]\n const src = token.attrGet('src') ?? ''\n const alt = token.content ?? ''\n\n const match = src.match(CDN_PATTERN)\n if (!match) {\n // Not a CDN image — render normally\n if (defaultImageRenderer) {\n return defaultImageRenderer(tokens, idx, options, env, self)\n }\n return self.renderToken(tokens, idx, options)\n }\n\n const [, basePath, uuid] = match\n\n const srcset = [\n `${basePath}${uuid}-sm.webp 640w`,\n `${basePath}${uuid}-md.webp 1024w`,\n `${basePath}${uuid}-lg.webp 1920w`,\n ].join(', ')\n\n const escapedAlt = escapeHtml(alt)\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" />`,\n '</picture>',\n ].join('\\n')\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/plugins/dcsContentPlugin.ts","../../src/plugins/dcsSeoPlugin.ts","../../src/plugins/dcsEditorPlugin.ts","../../src/plugins/dcsPreviewPlugin.ts","../../src/plugins/dcsCdnImagePlugin.ts","../../src/plugins/responsiveImagePlugin.ts"],"names":["path","fs","yaml","escapeHtml"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * DCS Content Plugin for Vite\n *\n * Reads `.dcs/content.yaml` at build time and injects content\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsContentPlugin({ debug: true })\n * ]\n * })\n * ```\n *\n * For VitePress:\n * ```typescript\n * // .vitepress/config.ts\n * import { defineConfig } from 'vitepress'\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsContentPlugin()\n * ]\n * }\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { DcsContentFile } from '../types/content'\n\nexport interface DcsContentPluginOptions {\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\n contentPath?: string\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects .dcs/content.yaml at build time.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\n const { contentPath = '.dcs/content.yaml', debug = false } = options\n\n let resolvedConfig: ResolvedConfig\n\n return {\n name: 'dcs-content',\n\n configResolved(config) {\n resolvedConfig = config\n },\n\n config(config) {\n const projectRoot = config.root || process.cwd()\n\n // Try to find content.yaml in multiple locations\n // VitePress projects have the docs folder as root, so check parent too\n const possiblePaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n path.resolve(process.cwd(), contentPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-content] No content.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-content] Using defaults only')\n }\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const content = yaml.load(fileContent) as DcsContentFile\n\n if (debug) {\n console.log(`[dcs-content] Loaded ${foundPath}`)\n console.log(`[dcs-content] Version: ${content.version}`)\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\n }\n\n return {\n define: {\n __DCS_CONTENT__: JSON.stringify(content),\n },\n }\n } catch (error) {\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n },\n\n // Watch for changes in development\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n ]\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n\n server.watcher.on('change', (changedPath) => {\n if (changedPath === watchPath) {\n if (debug) {\n console.log('[dcs-content] content.yaml changed, triggering reload')\n }\n server.restart()\n }\n })\n }\n })\n },\n }\n}\n","/**\n * DCS SEO Plugin for Vite\n *\n * Two responsibilities, both driven by `.dcs/seo.yaml`:\n *\n * 1. **Build-time define** (always on): reads `.dcs/seo.yaml` and injects it as\n * the `__DCS_SEO__` global for the runtime `useSEO` composable.\n *\n * 2. **Static `<head>` emitter** (opt-in via `emitStaticHtml: true`, default\n * OFF): after the bundle is written, reads the SPA's built `index.html`,\n * and for every route in `.dcs/pages.yaml` writes a per-route\n * `dist/<path>/index.html` whose `<head>` carries the resolved title, meta,\n * canonical, Open Graph, Twitter, and JSON-LD tags. This gives a Vue SPA\n * per-route static SEO **without** vite-ssg.\n *\n * VitePress sites already bake SEO via their own config, so they leave this\n * option OFF and are completely unaffected.\n *\n * @example Runtime define only (default — safe for VitePress)\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [dcsSeoPlugin({ debug: true })]\n * })\n * ```\n *\n * @example Vue SPA with per-route static <head> emission\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsSeoPlugin({\n * emitStaticHtml: true, // turn the emitter ON\n * pagesPath: '.dcs/pages.yaml', // route manifest (default)\n * noindex: ['account', 'projects'], // robots: noindex,nofollow\n * exclude: ['/preview'], // skip these routes entirely\n * })\n * ]\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { SeoConfiguration } from '../types/seo'\nimport { buildHeadTags } from '../seo/headTags'\nimport { spliceHeadHtml } from '../seo/spliceHeadHtml'\nimport { loadPagesManifest, type PageRouteEntry } from '../seo/pagesManifest'\n\nexport interface DcsSeoPluginOptions {\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\n seoPath?: string\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * Opt-in: emit per-route static `<head>` (meta + JSON-LD) into the built\n * `dist/` at the end of the build. Default `false` — VitePress sites and any\n * site that bakes its own SEO are unaffected when this is off.\n */\n emitStaticHtml?: boolean\n\n /**\n * Path to the route manifest relative to project root, used only when\n * `emitStaticHtml` is true. Default `'.dcs/pages.yaml'`.\n */\n pagesPath?: string\n\n /**\n * Routes to skip entirely (no per-route HTML written). Matched against the\n * route `path` (e.g. `'/preview'`) OR the route `slug` (e.g. `'account'`).\n */\n exclude?: string[]\n\n /**\n * Routes that should receive `robots: noindex, nofollow`. Matched against the\n * route `path` OR `slug`. The home route (`/`) still overwrites\n * `dist/index.html`.\n */\n noindex?: string[]\n}\n\n/** Resolve the SPA output directory from Vite config (absolute path). */\nfunction resolveOutDir(config: ResolvedConfig): string {\n const out = config.build?.outDir || 'dist'\n return path.isAbsolute(out) ? out : path.resolve(config.root || process.cwd(), out)\n}\n\n/**\n * Load + parse seo.yaml from the usual `.dcs` locations.\n * Returns `{ config, foundPath }` or `null` (missing/unparseable).\n */\nfunction loadSeoConfig(\n projectRoot: string,\n seoPath: string,\n debug: boolean\n): { config: SeoConfiguration; foundPath: string } | null {\n const possiblePaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n path.resolve(process.cwd(), seoPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-seo] No seo.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-seo] Using defaults only')\n }\n return null\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const config = yaml.load(fileContent) as SeoConfiguration\n return { config, foundPath }\n } catch (error) {\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\n return null\n }\n}\n\n/**\n * Normalise a route path to the on-disk output file:\n * '/' -> '<outDir>/index.html'\n * '/services' -> '<outDir>/services/index.html'\n * '/blog/my-post' -> '<outDir>/blog/my-post/index.html'\n * '/services/' -> '<outDir>/services/index.html'\n */\nfunction routeToOutputFile(outDir: string, routePath: string): string {\n const trimmed = routePath.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (trimmed === '') return path.join(outDir, 'index.html')\n return path.join(outDir, ...trimmed.split('/'), 'index.html')\n}\n\n/**\n * Core emitter, separated from Vite so it is unit-testable.\n *\n * For each route: build the head tags from the shared resolver, splice them\n * into the shell HTML, and write `dist/<path>/index.html`. Returns the number\n * of files written. Pure aside from `fs` writes — deterministic + idempotent.\n */\nexport function emitStaticSeoHtml(params: {\n outDir: string\n shellHtml: string\n routes: PageRouteEntry[]\n seoConfig: SeoConfiguration | undefined\n exclude?: string[]\n noindex?: string[]\n debug?: boolean\n}): number {\n const { outDir, shellHtml, routes, seoConfig, exclude = [], noindex = [], debug = false } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n let written = 0\n for (const route of routes) {\n if (excludeSet.has(route.path) || (route.slug && excludeSet.has(route.slug))) {\n if (debug) console.log(`[dcs-seo] skip (excluded): ${route.path}`)\n continue\n }\n\n const forceNoindex =\n noindexSet.has(route.path) || (route.slug && noindexSet.has(route.slug))\n\n const tags = buildHeadTags(route.slug, route.path, seoConfig, {\n includeKeywords: true,\n fallbackTitle: route.title,\n robots: forceNoindex ? 'noindex, nofollow' : undefined,\n })\n\n const html = spliceHeadHtml(shellHtml, tags)\n const outFile = routeToOutputFile(outDir, route.path)\n\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, html, 'utf8')\n written++\n\n if (debug) {\n console.log(\n `[dcs-seo] wrote ${path.relative(outDir, outFile)} (title=\"${tags.title}\"${\n forceNoindex ? ', noindex' : ''\n })`\n )\n }\n }\n\n return written\n}\n\n/**\n * Vite plugin that injects .dcs/seo.yaml at build time and, optionally, emits\n * per-route static `<head>` HTML for SPA SEO.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\n const {\n seoPath = '.dcs/seo.yaml',\n debug = false,\n emitStaticHtml = false,\n pagesPath = '.dcs/pages.yaml',\n exclude = [],\n noindex = [],\n } = options\n\n let resolvedConfig: ResolvedConfig\n\n return {\n name: 'dcs-seo',\n\n configResolved(config) {\n resolvedConfig = config\n },\n\n config(config) {\n const projectRoot = config.root || process.cwd()\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n return {\n define: {\n __DCS_SEO__: 'undefined',\n },\n }\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${loaded.foundPath}`)\n console.log(`[dcs-seo] Version: ${loaded.config.version}`)\n console.log(`[dcs-seo] Site Name: ${loaded.config.global?.siteName || '(not set)'}`)\n console.log(\n `[dcs-seo] Pages: ${Object.keys(loaded.config.pages ?? {}).join(', ') || '(none)'}`\n )\n }\n\n return {\n define: {\n __DCS_SEO__: JSON.stringify(loaded.config),\n },\n }\n },\n\n /**\n * Build-time static `<head>` emitter (opt-in via `emitStaticHtml`).\n *\n * Runs after the bundle is written so the built `index.html` shell exists\n * on disk. For an SPA, Vite emits a single `index.html`; we fan it out to\n * per-route files with route-specific `<head>` content.\n *\n * Fully defensive: any missing/unparseable input logs a warning and\n * no-ops. Never throws (so it can never break a production build).\n */\n writeBundle() {\n if (!emitStaticHtml) return\n\n try {\n const projectRoot = resolvedConfig?.root || process.cwd()\n const outDir = resolveOutDir(resolvedConfig)\n\n if (!fs.existsSync(outDir)) {\n console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`)\n return\n }\n\n const shellPath = path.join(outDir, 'index.html')\n if (!fs.existsSync(shellPath)) {\n console.warn(\n `[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`\n )\n return\n }\n\n const routes = loadPagesManifest(projectRoot, pagesPath, debug)\n if (!routes) {\n console.warn(\n `[dcs-seo] emitStaticHtml: ${pagesPath} missing or empty; skipping per-route emission`\n )\n return\n }\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n // seo.yaml is optional for emission — routes still get baked with\n // global/runtime defaults (which here means just the shell title and\n // a derived canonical-less head). Warn so the operator knows.\n console.warn(\n `[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`\n )\n }\n\n const shellHtml = fs.readFileSync(shellPath, 'utf8')\n\n const written = emitStaticSeoHtml({\n outDir,\n shellHtml,\n routes,\n seoConfig: loaded?.config,\n exclude,\n noindex,\n debug,\n })\n\n if (debug) {\n console.log(`[dcs-seo] emitStaticHtml: wrote ${written} per-route HTML file(s)`)\n }\n } catch (error) {\n // Never break the build on SEO emission failure.\n console.warn('[dcs-seo] emitStaticHtml failed; build output left unchanged:', error)\n }\n },\n\n // Watch for changes in development\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n ]\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n\n server.watcher.on('change', (changedPath) => {\n if (changedPath === watchPath) {\n if (debug) {\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\n }\n server.restart()\n }\n })\n }\n })\n },\n }\n}\n","/**\n * DCS Editor Plugin for Vite\n *\n * Injects the editor bridge script into customer sites when running\n * in portal preview mode (inside the visual editor iframe).\n *\n * The bridge enables:\n * - Inline text editing via contenteditable\n * - Section hover highlights and AI ✨ buttons\n * - postMessage communication with the portal\n *\n * This plugin should only be active in development/preview mode.\n * It's safe to include in production builds — it does nothing unless\n * the page detects it's inside an iframe.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsEditorPlugin()\n * ]\n * })\n * ```\n */\n\nimport type { Plugin } from 'vite'\n\nexport interface DcsEditorPluginOptions {\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects the editor bridge script for portal preview integration.\n *\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\n *\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\n * resolved through Vite's module graph rather than hitting the browser's native ESM\n * resolver, which can't handle bare specifiers.\n */\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\n const { debug = false } = options\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\n\n // Captured during configResolved — used to gate HTML injection.\n let isDev = false\n let basePath = '/'\n\n return {\n name: 'dcs-editor-bridge',\n\n configResolved(config) {\n isDev = config.command === 'serve'\n basePath = config.base ?? '/'\n },\n\n resolveId(id) {\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\n return id\n }\n },\n\n load(id) {\n if (id === VIRTUAL_PATH) {\n return `\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\n// The bridge only activates inside an iframe (portal preview)\nif (window.parent !== window) {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\n } else {\n setTimeout(initEditorBridge, 200)\n }\n}\n`\n }\n\n if (id === RIBBON_VIRTUAL_PATH) {\n return `\nimport { createApp } from 'vue'\nimport PreviewRibbon from '@duffcloudservices/cms/components'\n\nfunction mountRibbon() {\n // Create a detached mount point\n const el = document.createElement('div')\n el.id = 'dcs-preview-ribbon-root'\n document.body.appendChild(el)\n\n const app = createApp(PreviewRibbon)\n app.mount(el)\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', mountRibbon)\n} else {\n mountRibbon()\n}\n`\n }\n },\n\n /**\n * Only inject the editor bridge and ribbon scripts when:\n * 1. Running in dev mode (Vite dev server), AND\n * 2. The app is served at the root path (base === '/')\n *\n * These are virtual modules resolved via absolute paths (e.g.\n * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a\n * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT\n * routed to the Vite dev server by the reverse proxy — they hit the main\n * DCS server instead and get redirected to portal.duffcloudservices.com\n * (no CORS headers), causing browser console errors.\n *\n * In production builds, App.vue imports and mounts PreviewRibbon\n * directly, so virtual script injection is always redundant anyway.\n */\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n if (!isDev || basePath !== '/') return html\n\n if (debug) {\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\n }\n\n // Inject both the editor bridge and preview ribbon scripts\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\n\n // Insert before closing </body> tag\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\n },\n },\n }\n}\n","/**\n * DCS Preview Plugin for Vue\n *\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\n * component. The ribbon handles its own visibility — it only renders on\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\n * production domains, and inside the visual page editor iframe).\n *\n * Why does the caller pass the component in? Because this file is compiled\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\n * import out of the compiled plugins bundle avoids the build error while\n * still letting consumer code (which *does* run through Vite) resolve the\n * SFC at dev/build time.\n *\n * @example VitePress theme/index.ts\n * ```typescript\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\n * import PreviewRibbon from '@duffcloudservices/cms/components'\n *\n * export default {\n * Layout,\n * enhanceApp({ app }) {\n * app.use(dcsPreviewPlugin(PreviewRibbon))\n * }\n * }\n * ```\n */\n\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\n\nexport interface DcsPreviewPluginOptions {\n /**\n * Override the version string displayed on the ribbon.\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\n */\n version?: string | null\n}\n\n/**\n * Creates and returns the DCS Preview plugin.\n *\n * When installed, it registers the supplied ribbon component as a global\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\n * `transformIndexHtml`.\n *\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\n * @param options - Optional configuration\n */\nexport function dcsPreviewPlugin(\n ribbonComponent: Component,\n options: DcsPreviewPluginOptions = {},\n): Plugin {\n const Wrapper = defineComponent({\n name: 'DcsPreviewRibbon',\n setup() {\n return () => h(ribbonComponent, { version: options.version ?? null })\n },\n })\n\n return {\n install(app: App) {\n app.component('DcsPreviewRibbon', Wrapper)\n\n if (options.version !== undefined) {\n app.provide('__dcs_preview_version__', options.version)\n }\n },\n }\n}\n","/**\n * DCS CDN Image Plugin for Vite\n *\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\n * generated by the `image-migrate adopt` CLI command.\n *\n * For raster images with WebP variants, `<img>` elements are transformed into\n * responsive `<picture>` elements with `srcset` for optimised delivery.\n * SVGs receive a simple URL swap with no variant handling.\n *\n * **In development mode this plugin is a no-op** — local `/images/` paths\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\n *\n * The plugin handles two in-pipeline replacement vectors:\n *\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\n *\n * A third vector — **post-build file processing** — is handled by the\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\n * VitePress config. VitePress generates HTML *after* both Vite builds\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\n * `service-worker.js`.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsCdnImagePlugin()\n * ]\n * },\n * buildEnd: dcsCdnBuildEnd()\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { Plugin } from 'vite'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface CdnImageMapVariant {\n suffix: string\n blobPath: string\n cdnUrl: string\n width: number\n height: number\n size: number\n}\n\nexport interface CdnImageMapEntry {\n localPath: string\n blobPath: string\n cdnUrl: string\n fileName: string\n contentType: string\n originalSize: number\n width?: number\n height?: number\n hash: string\n variants?: CdnImageMapVariant[]\n}\n\nexport interface CdnImageMap {\n generated: string\n site: string\n cdnBase: string\n images: CdnImageMapEntry[]\n}\n\nexport interface DcsCdnImagePluginOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\n * mapping file is compared *without* a leading slash.\n *\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /**\n * Default `sizes` attribute for responsive `<picture>` elements.\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\n *\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\n */\n defaultSizes?: string\n\n /** Enable debug logging */\n debug?: boolean\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n/**\n * Build a responsive `<picture>` element for a raster image that has\n * WebP variants.\n */\nfunction buildPictureElement(\n entry: CdnImageMapEntry,\n alt: string,\n extraAttrs: string,\n sizes: string\n): string {\n const variants = entry.variants ?? []\n\n // Collect only the display-relevant variants (not thumb, not og)\n const displayVariants = variants.filter(\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\n )\n\n if (displayVariants.length === 0) {\n // No display variants — fall back to original CDN URL as plain <img>\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n const srcset = displayVariants\n .map((v) => `${v.cdnUrl} ${v.width}w`)\n .join(', ')\n\n // Pick the -md variant as the default src, or fall through to original\n const mdVariant = variants.find((v) => v.suffix === '-md')\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\n '</picture>',\n ].join('\\n')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Shared helpers\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Try to find and load the CDN image map from common locations.\n * VitePress nests docs under a subfolder, so we check parent dirs too.\n */\nfunction loadCdnImageMap(\n projectRoot: string,\n relativeMapPath: string,\n debug: boolean\n): Map<string, CdnImageMapEntry> | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativeMapPath),\n path.resolve(projectRoot, '..', relativeMapPath),\n path.resolve(process.cwd(), relativeMapPath),\n ]\n\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n try {\n const raw = fs.readFileSync(testPath, 'utf8')\n const data: CdnImageMap = JSON.parse(raw)\n\n const map = new Map<string, CdnImageMapEntry>()\n for (const entry of data.images) {\n // Store both with and without leading slash for flexible matching\n map.set(entry.localPath, entry)\n if (!entry.localPath.startsWith('/')) {\n map.set('/' + entry.localPath, entry)\n }\n }\n\n if (debug) {\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\n }\n return map\n } catch (error) {\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\n }\n }\n }\n\n if (debug) {\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n }\n return null\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Vite Plugin\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\n */\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\n debug = false,\n } = options\n\n // Lookup map built from the JSON file: key = localPath (no leading slash)\n let imageMap: Map<string, CdnImageMapEntry> | null = null\n let isProduction = false\n\n /**\n * Check whether a source code string contains any of the path prefixes\n * we care about. Quick bail-out for files that don't reference images.\n */\n function hasImageReferences(code: string): boolean {\n return pathPrefixes.some((prefix) => code.includes(prefix))\n }\n\n /**\n * Replace image path references in any string value with CDN URLs.\n * Used for code transforms and renderChunk post-processing.\n */\n function replaceImagePaths(str: string): string {\n if (!imageMap || imageMap.size === 0) return str\n let result = str\n for (const prefix of pathPrefixes) {\n // Global replace: matches prefix followed by a relative path bounded\n // by quotes, parens, or whitespace. Allows spaces in filenames.\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n result = result.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${quote}${entry.cdnUrl}`\n })\n }\n return result\n }\n\n /**\n * Replace standalone URL references in JS/TS string literals and Vue template\n * attributes. This handles patterns like:\n * src=\"/images/foo.jpg\"\n * { src: '/images/foo.jpg' }\n * url(/images/foo.svg)\n *\n * For `<img>` tags with raster images that have variants, the entire tag is\n * replaced with a `<picture>` element.\n */\n function transformCode(code: string): string {\n if (!imageMap || imageMap.size === 0) return code\n\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return _match\n\n // Extract alt from the existing attributes\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\n const alt = altMatch ? altMatch[1] : ''\n\n // Collect other attributes (not src, alt, loading, decoding — we set those)\n const otherAttrs = (beforeSrc + afterSrc)\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\n .trim()\n\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\n\n // Extract custom sizes if specified\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\n\n // SVGs and images without variants → simple URL swap <img>\n if (!entry.variants || entry.variants.length === 0) {\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n // Raster with variants → <picture>\n return buildPictureElement(entry, alt, extraAttrs, sizes)\n })\n\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\n code = replaceImagePaths(code)\n\n return code\n }\n\n return {\n name: 'dcs-cdn-image',\n enforce: 'pre',\n\n configResolved(config) {\n isProduction = config.command === 'build'\n if (!isProduction) {\n if (debug) {\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\n }\n return\n }\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\n },\n\n transform(code, id) {\n // Only run in production builds\n if (!isProduction || !imageMap) return\n\n // Only process relevant file types\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\n\n // Quick bail — skip files with no image path references\n if (!hasImageReferences(code)) return\n\n const transformed = transformCode(code)\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n\n /**\n * Replace image paths in final rendered JS/CSS chunks.\n *\n * This fires AFTER Vite's `define` substitution, catching paths that were\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\n * `transform` hook because `define` replacement happens at bundle time.\n */\n renderChunk(code, _chunk) {\n if (!isProduction || !imageMap) return null\n if (!hasImageReferences(code)) return null\n\n const replaced = replaceImagePaths(code)\n if (replaced !== code) {\n if (debug) {\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\n }\n return { code: replaced, map: null }\n }\n return null\n },\n\n /**\n * Transform HTML output to catch references injected outside the module\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\n *\n * Note: In VitePress, this hook fires for the `index.html` template but\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\n * coverage of generated HTML files.\n */\n transformIndexHtml(html) {\n if (!isProduction || !imageMap) return html\n if (!hasImageReferences(html)) return html\n\n // Replace href/src/content attributes that reference mapped images\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${pre}${entry.cdnUrl}${post}`\n })\n }\n\n return html\n },\n }\n}\n\n/**\n * Escape a string for use in a RegExp.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// VitePress buildEnd hook\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface DcsCdnBuildEndOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement.\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * File extensions to process in the output directory.\n * Default: `['.html', '.js']`\n */\n extensions?: string[]\n}\n\n/**\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\n * static files in the output directory to replace remaining `/images/` paths\n * with CDN URLs.\n *\n * VitePress generates HTML **after** both Vite builds complete, so Vite\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\n * HTML files are written to disk.\n *\n * Also rewrites static files (e.g. `service-worker.js`) copied from\n * `public/` that are not part of Vite's module pipeline.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * buildEnd: dcsCdnBuildEnd({ debug: true })\n * })\n * ```\n */\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n debug = false,\n extensions = ['.html', '.js'],\n } = options\n\n return async (siteConfig: { root: string; outDir: string }) => {\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\n if (!imageMap) {\n if (debug) {\n console.log('[dcs-cdn-image] buildEnd: no image map found')\n }\n return\n }\n\n const outDir = siteConfig.outDir\n if (!fs.existsSync(outDir)) return\n\n let filesProcessed = 0\n let refsReplaced = 0\n\n function walkDir(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n walkDir(fullPath)\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\n processFile(fullPath)\n }\n }\n }\n\n function processFile(filePath: string) {\n let content = fs.readFileSync(filePath, 'utf8')\n if (!pathPrefixes.some((p) => content.includes(p))) return\n\n let changed = false\n\n if (filePath.endsWith('.html')) {\n // HTML: replace href/src/content attribute values\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!mapEntry) return match\n changed = true\n refsReplaced++\n return `${pre}${mapEntry.cdnUrl}${post}`\n })\n }\n } else {\n // JS/other: replace quoted string paths\n for (const prefix of pathPrefixes) {\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n content = content.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n changed = true\n refsReplaced++\n return `${quote}${entry.cdnUrl}`\n })\n }\n }\n\n if (changed) {\n fs.writeFileSync(filePath, content, 'utf8')\n filesProcessed++\n }\n }\n\n walkDir(outDir)\n\n if (debug) {\n console.log(\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\n )\n }\n }\n}","/**\n * markdown-it plugin that transforms standard `` image syntax\n * into responsive `<picture>` elements when the URL matches the DCS CDN\n * asset pattern.\n *\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * markdown: {\n * config: (md) => {\n * md.use(responsiveImagePlugin)\n * },\n * },\n * })\n * ```\n *\n * Input markdown:\n * ```md\n * \n * ```\n *\n * Rendered HTML:\n * ```html\n * <picture>\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\n * type=\"image/webp\"\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\n * </picture>\n * ```\n */\n\nimport type MarkdownIt from 'markdown-it'\n\n/**\n * Matches DCS CDN asset URLs: base path / UUID . extension.\n *\n * Aligned with `@duffcloudservices/cms-core` `responsive-image.ts`: the\n * `(?:content\\/)?` group covers the content-container shape\n * `https://files.duffcloudservices.com/content/{slug}/assets/...` in addition\n * to the branding-container shape `.../{slug}/assets/...`. The group is\n * non-capturing, so the captured base path / UUID / extension are unchanged.\n */\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/(?:content\\/)?[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\n\n/**\n * Escapes HTML special characters for safe attribute embedding.\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\nexport function responsiveImagePlugin(md: MarkdownIt): void {\n const defaultImageRenderer = md.renderer.rules.image\n\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\n const token = tokens[idx]\n const src = token.attrGet('src') ?? ''\n const alt = token.content ?? ''\n\n const match = src.match(CDN_PATTERN)\n if (!match) {\n // Not a CDN image — render normally\n if (defaultImageRenderer) {\n return defaultImageRenderer(tokens, idx, options, env, self)\n }\n return self.renderToken(tokens, idx, options)\n }\n\n const [, basePath, uuid] = match\n\n const srcset = [\n `${basePath}${uuid}-sm.webp 640w`,\n `${basePath}${uuid}-md.webp 1024w`,\n `${basePath}${uuid}-lg.webp 1920w`,\n ].join(', ')\n\n const escapedAlt = escapeHtml(alt)\n\n // Self-heal: this plugin emits a STATIC HTML string at build time (no Vue\n // runtime), so the fallback must be an inline onerror. A fit-variant\n // (`-md`/`-lg`) may legitimately be absent (internal/imaging variants.go\n // skips variants by DIMENSION). On the first error, swap the `<img>` to the\n // ORIGINAL url AND strip the sibling `<source>` elements — a `<picture>`\n // does NOT re-resolve after a `<source>` 404s, so the variant set must be\n // removed for the original to display. (`this.onerror=null` prevents loops.)\n const escapedOriginal = escapeHtml(src)\n const onError =\n `this.onerror=null;this.src='${escapedOriginal}';` +\n `this.closest('picture')?.querySelectorAll('source').forEach(s=>s.remove())`\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" onerror=\"${onError}\" />`,\n '</picture>',\n ].join('\\n')\n }\n}\n"]}
|
|
@@ -379,6 +379,14 @@ interface ResolvedPageOverrides {
|
|
|
379
379
|
ogType?: SeoOpenGraphConfig['type'];
|
|
380
380
|
/** Open Graph image URL override (e.g. a post's header image). */
|
|
381
381
|
ogImage?: string;
|
|
382
|
+
/**
|
|
383
|
+
* Extra Open Graph fields to merge into the OG config (e.g. `publishedTime`,
|
|
384
|
+
* `modifiedTime`, `section`, `tags`). The shared OG generator emits the
|
|
385
|
+
* matching `article:*` tags when `ogType === 'article'`. This is how a site
|
|
386
|
+
* supplies article metadata (date/category) without that logic living in the
|
|
387
|
+
* package. Lower precedence than `ogType`/`ogImage`/`ogTitle`/`ogDescription`.
|
|
388
|
+
*/
|
|
389
|
+
og?: Partial<SeoOpenGraphConfig>;
|
|
382
390
|
/** Keywords override (comma-separated) for the `keywords` meta. */
|
|
383
391
|
keywords?: string;
|
|
384
392
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duffcloudservices/cms",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Vue 3 composables and Vite plugins for DCS CMS integration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"@types/js-yaml": "^4.0.9",
|
|
58
58
|
"@types/markdown-it": "^14.1.0",
|
|
59
59
|
"@types/node": "^20.11.0",
|
|
60
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
60
61
|
"@vue/test-utils": "^2.4.0",
|
|
61
62
|
"markdown-it": "^14.0.0",
|
|
62
63
|
"tsup": "^8.0.0",
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import ManagedImage from './ManagedImage.vue'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A CDN URL whose UUID/extension triggers responsive variant resolution
|
|
7
|
+
* (so a `<picture>` with `<source>` renders by default).
|
|
8
|
+
*/
|
|
9
|
+
const ORIGINAL = 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg'
|
|
10
|
+
|
|
11
|
+
describe('ManagedImage self-healing fallback', () => {
|
|
12
|
+
it('renders a <picture> with <source> for a CDN image by default', () => {
|
|
13
|
+
const wrapper = mount(ManagedImage, {
|
|
14
|
+
props: {
|
|
15
|
+
pageSlug: 'home',
|
|
16
|
+
imageKey: 'hero.image',
|
|
17
|
+
fallbackSrc: ORIGINAL,
|
|
18
|
+
fallbackAlt: 'Hero',
|
|
19
|
+
context: 'hero',
|
|
20
|
+
},
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(wrapper.find('picture').exists()).toBe(true)
|
|
24
|
+
expect(wrapper.find('source').exists()).toBe(true)
|
|
25
|
+
// Default <img> src points at a generated variant, not the original.
|
|
26
|
+
expect(wrapper.find('img').attributes('src')).toContain('abc-123-md.webp')
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('swaps to the original and removes <source> when the variant errors', async () => {
|
|
30
|
+
const wrapper = mount(ManagedImage, {
|
|
31
|
+
props: {
|
|
32
|
+
pageSlug: 'home',
|
|
33
|
+
imageKey: 'hero.image',
|
|
34
|
+
fallbackSrc: ORIGINAL,
|
|
35
|
+
fallbackAlt: 'Hero',
|
|
36
|
+
context: 'hero',
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Sanity: starts as a <picture> with variant <source>.
|
|
41
|
+
expect(wrapper.find('picture').exists()).toBe(true)
|
|
42
|
+
expect(wrapper.find('source').exists()).toBe(true)
|
|
43
|
+
|
|
44
|
+
// The 404'd fit-variant fires `error`.
|
|
45
|
+
await wrapper.find('img').trigger('error')
|
|
46
|
+
|
|
47
|
+
// The <picture>/<source> branch must drop out — a <picture> does NOT
|
|
48
|
+
// re-resolve after a <source> 404s, so the variant set has to be removed.
|
|
49
|
+
expect(wrapper.find('picture').exists()).toBe(false)
|
|
50
|
+
expect(wrapper.find('source').exists()).toBe(false)
|
|
51
|
+
|
|
52
|
+
// A plain <img> now renders the ORIGINAL url.
|
|
53
|
+
const img = wrapper.find('img')
|
|
54
|
+
expect(img.exists()).toBe(true)
|
|
55
|
+
expect(img.attributes('src')).toBe(ORIGINAL)
|
|
56
|
+
// The original is still exposed for the visual editor.
|
|
57
|
+
expect(img.attributes('data-dcs-image-url')).toBe(ORIGINAL)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('renders a plain <img> at the original when `original` is set (no variants)', () => {
|
|
61
|
+
const wrapper = mount(ManagedImage, {
|
|
62
|
+
props: {
|
|
63
|
+
pageSlug: 'home',
|
|
64
|
+
imageKey: 'hero.image',
|
|
65
|
+
fallbackSrc: ORIGINAL,
|
|
66
|
+
fallbackAlt: 'Hero',
|
|
67
|
+
context: 'hero',
|
|
68
|
+
original: true,
|
|
69
|
+
},
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(wrapper.find('picture').exists()).toBe(false)
|
|
73
|
+
expect(wrapper.find('img').attributes('src')).toBe(ORIGINAL)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import { computed, useAttrs } from 'vue'
|
|
2
|
+
import { computed, ref, useAttrs } from 'vue'
|
|
3
3
|
import { useResponsiveImage } from '../composables/useResponsiveImage'
|
|
4
4
|
import { useTextContent } from '../composables/useTextContent'
|
|
5
5
|
import type { ImageContext } from '@duffcloudservices/cms-core'
|
|
@@ -42,9 +42,25 @@ const image = useResponsiveImage({
|
|
|
42
42
|
original: () => props.original,
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
+
// Self-heal: a fit-variant (`-md`/`-lg`) may legitimately be absent when the
|
|
46
|
+
// source is smaller than the target dimension (see internal/imaging variants.go,
|
|
47
|
+
// which skips variants by DIMENSION). When the rendered variant 404s, fall back
|
|
48
|
+
// to the ORIGINAL url and drop the `<picture>`/`<source>` branch — a `<picture>`
|
|
49
|
+
// does NOT re-resolve after a `<source>` 404s, so the variant set must be removed,
|
|
50
|
+
// not just the `<img>` src swapped.
|
|
51
|
+
const failed = ref(false)
|
|
52
|
+
function onVariantError() {
|
|
53
|
+
if (!failed.value) {
|
|
54
|
+
failed.value = true
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
const imgAttrs = computed(() => ({
|
|
46
59
|
...image.imgProps,
|
|
47
60
|
...attrs,
|
|
61
|
+
// When a variant has failed, render the original (also exposed as
|
|
62
|
+
// data-dcs-image-url for the visual editor) instead of the 404'd variant.
|
|
63
|
+
...(failed.value ? { src: imageSrc.value } : {}),
|
|
48
64
|
'data-dcs-image-key': props.imageKey,
|
|
49
65
|
'data-dcs-image-url': imageSrc.value,
|
|
50
66
|
'data-dcs-image-alt': imageAlt.value,
|
|
@@ -52,7 +68,7 @@ const imgAttrs = computed(() => ({
|
|
|
52
68
|
</script>
|
|
53
69
|
|
|
54
70
|
<template>
|
|
55
|
-
<picture v-if="image.hasVariants && !original">
|
|
71
|
+
<picture v-if="image.hasVariants && !original && !failed">
|
|
56
72
|
<source
|
|
57
73
|
v-for="source in image.sources"
|
|
58
74
|
:key="source.type"
|
|
@@ -60,7 +76,7 @@ const imgAttrs = computed(() => ({
|
|
|
60
76
|
:type="source.type"
|
|
61
77
|
:sizes="source.sizes"
|
|
62
78
|
/>
|
|
63
|
-
<img v-bind="imgAttrs" />
|
|
79
|
+
<img v-bind="imgAttrs" @error="onVariantError" />
|
|
64
80
|
</picture>
|
|
65
81
|
<img v-else v-bind="imgAttrs" />
|
|
66
82
|
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import ResponsiveImage from './ResponsiveImage.vue'
|
|
4
|
+
|
|
5
|
+
const ORIGINAL = 'https://files.duffcloudservices.com/kept/assets/hero/abc-123.jpg'
|
|
6
|
+
|
|
7
|
+
describe('ResponsiveImage self-healing fallback', () => {
|
|
8
|
+
it('renders a <picture> with <source> for a CDN image by default', () => {
|
|
9
|
+
const wrapper = mount(ResponsiveImage, {
|
|
10
|
+
props: { src: ORIGINAL, alt: 'Hero', context: 'hero' },
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
expect(wrapper.find('picture').exists()).toBe(true)
|
|
14
|
+
expect(wrapper.find('source').exists()).toBe(true)
|
|
15
|
+
expect(wrapper.find('img').attributes('src')).toContain('abc-123-md.webp')
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it('swaps to props.src and removes <source> when the variant errors', async () => {
|
|
19
|
+
const wrapper = mount(ResponsiveImage, {
|
|
20
|
+
props: { src: ORIGINAL, alt: 'Hero', context: 'hero', class: 'object-cover' },
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
expect(wrapper.find('picture').exists()).toBe(true)
|
|
24
|
+
expect(wrapper.find('source').exists()).toBe(true)
|
|
25
|
+
|
|
26
|
+
await wrapper.find('img').trigger('error')
|
|
27
|
+
|
|
28
|
+
// <picture>/<source> must be neutralized so the original can display.
|
|
29
|
+
expect(wrapper.find('picture').exists()).toBe(false)
|
|
30
|
+
expect(wrapper.find('source').exists()).toBe(false)
|
|
31
|
+
|
|
32
|
+
const img = wrapper.find('img')
|
|
33
|
+
expect(img.exists()).toBe(true)
|
|
34
|
+
expect(img.attributes('src')).toBe(ORIGINAL)
|
|
35
|
+
// The class binding survives the fallback render.
|
|
36
|
+
expect(img.classes()).toContain('object-cover')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('renders a plain <img> at props.src when `original` is set', () => {
|
|
40
|
+
const wrapper = mount(ResponsiveImage, {
|
|
41
|
+
props: { src: ORIGINAL, alt: 'Hero', context: 'hero', original: true },
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
expect(wrapper.find('picture').exists()).toBe(false)
|
|
45
|
+
expect(wrapper.find('img').attributes('src')).toBe(ORIGINAL)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* />
|
|
14
14
|
* ```
|
|
15
15
|
*/
|
|
16
|
-
import { computed, useAttrs } from 'vue'
|
|
16
|
+
import { computed, ref, useAttrs } from 'vue'
|
|
17
17
|
import { useResponsiveImage } from '../composables/useResponsiveImage'
|
|
18
18
|
import type { ImageContext } from '@duffcloudservices/cms-core'
|
|
19
19
|
|
|
@@ -42,15 +42,27 @@ const image = useResponsiveImage({
|
|
|
42
42
|
original: () => props.original,
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
+
// Self-heal: when the rendered fit-variant (`-md`/`-lg`) 404s — legitimately
|
|
46
|
+
// absent for sources smaller than the target dimension — fall back to the
|
|
47
|
+
// ORIGINAL `props.src` and drop the `<picture>`/`<source>` branch. A `<picture>`
|
|
48
|
+
// does NOT re-resolve after a `<source>` 404s, so the variant set must be removed.
|
|
49
|
+
const failed = ref(false)
|
|
50
|
+
function onVariantError() {
|
|
51
|
+
if (!failed.value) {
|
|
52
|
+
failed.value = true
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
const attrs = useAttrs()
|
|
46
57
|
const imgAttrs = computed(() => ({
|
|
47
58
|
...image.imgProps,
|
|
48
59
|
...attrs,
|
|
60
|
+
...(failed.value ? { src: props.src } : {}),
|
|
49
61
|
}))
|
|
50
62
|
</script>
|
|
51
63
|
|
|
52
64
|
<template>
|
|
53
|
-
<picture v-if="image.hasVariants && !original">
|
|
65
|
+
<picture v-if="image.hasVariants && !original && !failed">
|
|
54
66
|
<source
|
|
55
67
|
v-for="source in image.sources"
|
|
56
68
|
:key="source.type"
|
|
@@ -58,7 +70,7 @@ const imgAttrs = computed(() => ({
|
|
|
58
70
|
:type="source.type"
|
|
59
71
|
:sizes="source.sizes"
|
|
60
72
|
/>
|
|
61
|
-
<img v-bind="imgAttrs" :class="props.class" />
|
|
73
|
+
<img v-bind="imgAttrs" :class="props.class" @error="onVariantError" />
|
|
62
74
|
</picture>
|
|
63
75
|
<img v-else v-bind="imgAttrs" :class="props.class" />
|
|
64
76
|
</template>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/seo/headTags.ts","../src/seo/spliceHeadHtml.ts","../src/seo/vitepressTransform.ts","../src/seo/pagesManifest.ts"],"names":["path"],"mappings":";;;;;AA2HO,SAAS,qBAAA,CACd,EAAA,EACA,MAAA,EACA,aAAA,EACA,iBACA,SAAA,EAC8C;AAC9C,EAAA,MAAM,OAAqD,EAAC;AAK5D,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,UAAA,EAAY,SAAS,EAAA,CAAG,KAAA,IAAS,eAAe,CAAA;AACtE,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,gBAAA,EAAkB,SAAS,EAAA,CAAG,WAAA,IAAe,iBAAiB,CAAA;AACpF,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,QAAA,EAAU,SAAS,EAAA,CAAG,GAAA,IAAO,WAAW,CAAA;AAC9D,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,SAAA,EAAW,SAAS,EAAA,CAAG,IAAA,IAAQ,WAAW,CAAA;AAEhE,EAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,KAAA,IAAS,MAAA,CAAO,MAAA,EAAQ,SAAA;AACzC,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,UAAA,EAAY,OAAA,EAAS,OAAO,CAAA;AAClD,IAAA,IAAI,EAAA,CAAG,YAAY,aAAA,EAAe;AAChC,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,cAAA,EAAgB,SAAS,EAAA,CAAG,QAAA,IAAY,eAAe,CAAA;AAAA,IAC/E;AACA,IAAA,IAAI,GAAG,UAAA,EAAY;AACjB,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,gBAAA,EAAkB,SAAS,MAAA,CAAO,EAAA,CAAG,UAAU,CAAA,EAAG,CAAA;AAAA,IAC1E;AACA,IAAA,IAAI,GAAG,WAAA,EAAa;AAClB,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,QAAA,EAAU,iBAAA,EAAmB,SAAS,MAAA,CAAO,EAAA,CAAG,WAAW,CAAA,EAAG,CAAA;AAAA,IAC5E;AAAA,EACF;AAEA,EAAA,IAAI,OAAO,QAAA,EAAU;AACnB,IAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,gBAAgB,OAAA,EAAS,MAAA,CAAO,UAAU,CAAA;AAAA,EAClE;AAEA,EAAA,IAAI,OAAO,MAAA,EAAQ;AACjB,IAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,aAAa,OAAA,EAAS,MAAA,CAAO,QAAQ,CAAA;AAAA,EAC7D;AAGA,EAAA,IAAI,EAAA,CAAG,SAAS,SAAA,EAAW;AACzB,IAAA,IAAI,GAAG,aAAA,EAAe;AACpB,MAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,0BAA0B,OAAA,EAAS,EAAA,CAAG,eAAe,CAAA;AAAA,IAC7E;AACA,IAAA,IAAI,GAAG,YAAA,EAAc;AACnB,MAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,yBAAyB,OAAA,EAAS,EAAA,CAAG,cAAc,CAAA;AAAA,IAC3E;AACA,IAAA,IAAI,GAAG,MAAA,EAAQ;AACb,MAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,kBAAkB,OAAA,EAAS,EAAA,CAAG,QAAQ,CAAA;AAAA,IAC9D;AACA,IAAA,IAAI,GAAG,OAAA,EAAS;AACd,MAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,mBAAmB,OAAA,EAAS,EAAA,CAAG,SAAS,CAAA;AAAA,IAChE;AACA,IAAA,IAAI,GAAG,IAAA,EAAM;AACX,MAAA,EAAA,CAAG,IAAA,CAAK,OAAA,CAAQ,CAAC,GAAA,KAAQ;AACvB,QAAA,IAAA,CAAK,KAAK,EAAE,QAAA,EAAU,aAAA,EAAe,OAAA,EAAS,KAAK,CAAA;AAAA,MACrD,CAAC,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAQO,SAAS,mBAAA,CACd,OAAA,EACA,MAAA,EACA,aAAA,EACA,eAAA,EAC0C;AAC1C,EAAA,MAAM,OAAiD,EAAC;AAExD,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,cAAA,EAAgB,SAAS,OAAA,CAAQ,IAAA,IAAQ,uBAAuB,CAAA;AAClF,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,eAAA,EAAiB,SAAS,OAAA,CAAQ,KAAA,IAAS,eAAe,CAAA;AAC5E,EAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,qBAAA,EAAuB,SAAS,OAAA,CAAQ,WAAA,IAAe,iBAAiB,CAAA;AAE1F,EAAA,MAAM,KAAA,GAAQ,OAAA,CAAQ,KAAA,IAAS,MAAA,CAAO,MAAA,EAAQ,cAAA;AAC9C,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,IAAA,CAAK,KAAK,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,OAAO,CAAA;AACnD,IAAA,IAAI,OAAA,CAAQ,YAAY,aAAA,EAAe;AACrC,MAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,mBAAA,EAAqB,SAAS,OAAA,CAAQ,QAAA,IAAY,eAAe,CAAA;AAAA,IACrF;AAAA,EACF;AAEA,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,IAAA,IAAQ,MAAA,CAAO,MAAA,EAAQ,OAAA;AAC5C,EAAA,IAAI,IAAA,EAAM;AACR,IAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,cAAA,EAAgB,OAAA,EAAS,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,GAAI,IAAA,GAAO,CAAA,CAAA,EAAI,IAAI,IAAI,CAAA;AAAA,EACvF;AAEA,EAAA,IAAI,QAAQ,OAAA,EAAS;AACnB,IAAA,IAAA,CAAK,IAAA,CAAK;AAAA,MACR,IAAA,EAAM,iBAAA;AAAA,MACN,OAAA,EAAS,OAAA,CAAQ,OAAA,CAAQ,UAAA,CAAW,GAAG,IAAI,OAAA,CAAQ,OAAA,GAAU,CAAA,CAAA,EAAI,OAAA,CAAQ,OAAO,CAAA;AAAA,KACjF,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,IAAA;AACT;AAMO,SAAS,cAAA,CAAe,SAA4B,MAAA,EAAmC;AAC5F,EAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,KAAW;AAC7B,IAAA,MAAM,IAAA,GAAgC;AAAA,MACpC,UAAA,EAAY,oBAAA;AAAA,MACZ,SAAS,MAAA,CAAO;AAAA,KAClB;AAGA,IAAA,IAAI,OAAO,UAAA,EAAY;AACrB,MAAA,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,MAAA,CAAO,UAAU,CAAA;AAAA,IACvC;AAGA,IAAA,IAAI,OAAO,IAAA,KAAS,SAAA,IAAa,OAAO,OAAA,IAAW,CAAC,KAAK,GAAA,EAAK;AAC5D,MAAA,IAAA,CAAK,MAAM,MAAA,CAAO,OAAA;AAAA,IACpB;AACA,IAAA,IAAI,OAAO,IAAA,KAAS,SAAA,IAAa,OAAO,QAAA,IAAY,CAAC,KAAK,IAAA,EAAM;AAC9D,MAAA,IAAA,CAAK,OAAO,MAAA,CAAO,QAAA;AAAA,IACrB;AAEA,IAAA,OAAO,IAAA;AAAA,EACT,CAAC,CAAA;AACH;AAmBO,SAAS,cAAA,CACd,QAAA,EACA,QAAA,EACA,SAAA,EACA,aAAA,EACiB;AACjB,EAAA,MAAM,MAAA,GAAS,SAAA,EAAW,MAAA,IAAU,EAAC;AACrC,EAAA,MAAM,IAAA,GAAO,SAAA,EAAW,KAAA,GAAQ,QAAQ,KAAK,EAAC;AAG9C,EAAA,IAAI,SAAA,GAAY,KAAK,SAAA,IAAa,EAAA;AAClC,EAAA,IAAI,CAAC,SAAA,IAAa,MAAA,CAAO,OAAA,EAAS;AAChC,IAAA,MAAMA,QAAO,QAAA,KAAa,QAAA,KAAa,MAAA,GAAS,GAAA,GAAM,IAAI,QAAQ,CAAA,CAAA,CAAA;AAClE,IAAA,SAAA,GAAY,CAAA,EAAG,OAAO,OAAA,CAAQ,OAAA,CAAQ,OAAO,EAAE,CAAC,GAAGA,KAAI,CAAA,CAAA;AAAA,EACzD;AAKA,EAAA,MAAM,iBAAA,GAAoB,KAAK,KAAA,IAAS,aAAA;AACxC,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,iBAAA,EAAmB;AACrB,IAAA,KAAA,GACE,IAAA,CAAK,eAAA,IAAmB,CAAC,MAAA,CAAO,aAAA,GAC5B,oBACA,MAAA,CAAO,aAAA,CAAc,OAAA,CAAQ,IAAA,EAAM,iBAAiB,CAAA;AAAA,EAC5D,CAAA,MAAO;AACL,IAAA,KAAA,GAAQ,OAAO,YAAA,IAAgB,QAAA;AAAA,EACjC;AAIA,EAAA,MAAM,SAAA,GAA0C;AAAA,IAC9C,IAAA,EAAM,IAAA,CAAK,SAAA,EAAW,IAAA,IAAQ,SAAA;AAAA,IAC9B,KAAA,EAAO,IAAA,CAAK,SAAA,EAAW,KAAA,IAAS,KAAA;AAAA,IAChC,aAAa,IAAA,CAAK,SAAA,EAAW,eAAe,IAAA,CAAK,WAAA,IAAe,OAAO,kBAAA,IAAsB,EAAA;AAAA,IAC7F,GAAG,IAAA,CAAK;AAAA,GACV;AAGA,EAAA,MAAM,OAAA,GAAsC;AAAA,IAC1C,IAAA,EAAM,IAAA,CAAK,OAAA,EAAS,IAAA,IAAQ,qBAAA;AAAA,IAC5B,GAAG,IAAA,CAAK;AAAA,GACV;AAGA,EAAA,MAAM,OAAA,GAAU,CAAC,GAAI,MAAA,CAAO,OAAA,IAAW,EAAC,EAAI,GAAI,IAAA,CAAK,OAAA,IAAW,EAAG,CAAA;AAEnE,EAAA,OAAO;AAAA,IACL,KAAA;AAAA,IACA,WAAA,EAAa,IAAA,CAAK,WAAA,IAAe,MAAA,CAAO,kBAAA,IAAsB,EAAA;AAAA,IAC9D,SAAA;AAAA,IACA,MAAA,EAAQ,IAAA,CAAK,MAAA,IAAU,MAAA,CAAO,MAAA,IAAU,eAAA;AAAA,IACxC,SAAA;AAAA,IACA,OAAA;AAAA,IACA,OAAA;AAAA,IACA,UAAA,EAAY,IAAA,CAAK,UAAA,IAAc,EAAC;AAAA;AAAA;AAAA,IAGhC,UAAU,IAAA,CAAK;AAAA,GACjB;AACF;AAcO,SAAS,aAAA,CACd,QAAA,EACA,QAAA,EACA,SAAA,EACA,SAAA,EACkB;AAClB,EAAA,MAAM,WAAW,cAAA,CAAe,QAAA,EAAU,QAAA,EAAU,SAAA,EAAW,WAAW,aAAa,CAAA;AACvF,EAAA,MAAM,MAAA,GAAS,SAAA,EAAW,MAAA,IAAU,EAAC;AAErC,EAAA,MAAM,KAAA,GAAQ,SAAA,EAAW,KAAA,IAAS,QAAA,CAAS,KAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,SAAA,EAAW,WAAA,IAAe,QAAA,CAAS,WAAA;AACvD,EAAA,MAAM,MAAA,GAAS,SAAA,EAAW,MAAA,IAAU,QAAA,CAAS,MAAA;AAC7C,EAAA,MAAM,QAAA,GAAW,SAAA,EAAW,QAAA,IAAY,QAAA,CAAS,QAAA;AAGjD,EAAA,MAAM,OAAsB,EAAC;AAE7B,EAAA,IAAA,CAAK,KAAK,EAAE,IAAA,EAAM,aAAA,EAAe,OAAA,EAAS,aAAa,CAAA;AAIvD,EAAA,IAAI,QAAA,IAAY,WAAW,eAAA,EAAiB;AAC1C,IAAA,IAAA,CAAK,KAAK,EAAE,IAAA,EAAM,UAAA,EAAY,OAAA,EAAS,UAAU,CAAA;AAAA,EACnD;AAEA,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,IAAA,CAAK,KAAK,EAAE,IAAA,EAAM,QAAA,EAAU,OAAA,EAAS,QAAQ,CAAA;AAAA,EAC/C;AAGA,EAAA,IAAI,MAAA,CAAO,cAAc,MAAA,EAAQ;AAC/B,IAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,0BAAA,EAA4B,SAAS,MAAA,CAAO,YAAA,CAAa,QAAQ,CAAA;AAAA,EACrF;AACA,EAAA,IAAI,MAAA,CAAO,cAAc,IAAA,EAAM;AAC7B,IAAA,IAAA,CAAK,IAAA,CAAK,EAAE,IAAA,EAAM,eAAA,EAAiB,SAAS,MAAA,CAAO,YAAA,CAAa,MAAM,CAAA;AAAA,EACxE;AAGA,EAAA,MAAM,MAAA,GAAS,sBAAsB,QAAA,CAAS,SAAA,EAAW,QAAQ,KAAA,EAAO,WAAA,EAAa,SAAS,SAAS,CAAA;AACvG,EAAA,IAAA,CAAK,IAAA,CAAK,GAAG,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,QAAA,EAAU,CAAA,CAAE,QAAA,EAAU,OAAA,EAAS,CAAA,CAAE,OAAA,GAAU,CAAC,CAAA;AAG9E,EAAA,MAAM,cAAc,mBAAA,CAAoB,QAAA,CAAS,OAAA,EAAS,MAAA,EAAQ,OAAO,WAAW,CAAA;AACpF,EAAA,IAAA,CAAK,IAAA,CAAK,GAAG,WAAA,CAAY,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,OAAA,EAAS,CAAA,CAAE,OAAA,GAAU,CAAC,CAAA;AAG3E,EAAA,IAAI,WAAW,IAAA,EAAM;AACnB,IAAA,IAAA,CAAK,IAAA,CAAK,GAAG,SAAA,CAAU,IAAI,CAAA;AAAA,EAC7B;AAGA,EAAA,MAAM,OAAsB,EAAC;AAE7B,EAAA,IAAI,SAAS,SAAA,EAAW;AACtB,IAAA,IAAA,CAAK,KAAK,EAAE,GAAA,EAAK,aAAa,IAAA,EAAM,QAAA,CAAS,WAAW,CAAA;AAAA,EAC1D;AAEA,EAAA,QAAA,CAAS,UAAA,CAAW,OAAA,CAAQ,CAAC,GAAA,KAAQ;AACnC,IAAA,IAAA,CAAK,IAAA,CAAK,EAAE,GAAA,EAAK,WAAA,EAAa,IAAA,EAAM,IAAI,IAAA,EAAM,QAAA,EAAU,GAAA,CAAI,QAAA,EAAU,CAAA;AAAA,EACxE,CAAC,CAAA;AAGD,EAAA,MAAM,SAAS,SAAA,EAAW,OAAA,IAAW,cAAA,CAAe,QAAA,CAAS,SAAS,MAAM,CAAA;AAC5E,EAAA,MAAM,MAAA,GAA0B,MAAA,CAAO,GAAA,CAAI,CAAC,MAAA,MAAY;AAAA,IACtD,IAAA,EAAM,qBAAA;AAAA,IACN,QAAA,EAAU,IAAA,CAAK,SAAA,CAAU,MAAM;AAAA,GACjC,CAAE,CAAA;AAEF,EAAA,OAAO,EAAE,KAAA,EAAO,IAAA,EAAM,IAAA,EAAM,MAAA,EAAQ,QAAQ,QAAA,EAAS;AACvD;;;ACvYA,SAAS,WAAW,KAAA,EAAuB;AACzC,EAAA,OAAO,KAAA,CACJ,OAAA,CAAQ,IAAA,EAAM,OAAO,EACrB,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA,CACtB,QAAQ,IAAA,EAAM,MAAM,CAAA,CACpB,OAAA,CAAQ,MAAM,MAAM,CAAA;AACzB;AAOA,SAAS,aAAa,IAAA,EAAsB;AAC1C,EAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAQ,MAAM,CAAA;AACpC;AAGA,IAAM,kBAAA,uBAAyB,GAAA,CAAI;AAAA,EACjC,aAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,0BAAA;AAAA,EACA;AACF,CAAC,CAAA;AAOD,IAAM,yBAAA,GAA4B,CAAC,KAAA,EAAO,UAAU,CAAA;AACpD,IAAM,qBAAA,GAAwB,CAAC,UAAU,CAAA;AAMlC,SAAS,cAAA,CAAe,IAAA,EAAwB,MAAA,GAAS,MAAA,EAAgB;AAC9E,EAAA,MAAM,QAAkB,EAAC;AAEzB,EAAA,KAAA,CAAM,IAAA,CAAK,GAAG,MAAM,CAAA,OAAA,EAAU,WAAW,IAAA,CAAK,KAAK,CAAC,CAAA,QAAA,CAAU,CAAA;AAE9D,EAAA,KAAA,MAAW,CAAA,IAAK,KAAK,IAAA,EAAM;AACzB,IAAA,IAAI,CAAA,CAAE,aAAa,MAAA,EAAW;AAC5B,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,MAAM,CAAA,gBAAA,EAAmB,UAAA,CAAW,CAAA,CAAE,QAAQ,CAAC,CAAA,WAAA,EAAc,UAAA,CAAW,CAAA,CAAE,OAAO,CAAC,CAAA,IAAA,CAAM,CAAA;AAAA,IACxG,CAAA,MAAA,IAAW,CAAA,CAAE,IAAA,KAAS,MAAA,EAAW;AAC/B,MAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,MAAM,CAAA,YAAA,EAAe,UAAA,CAAW,CAAA,CAAE,IAAI,CAAC,CAAA,WAAA,EAAc,UAAA,CAAW,CAAA,CAAE,OAAO,CAAC,CAAA,IAAA,CAAM,CAAA;AAAA,IAChG;AAAA,EACF;AAEA,EAAA,KAAA,MAAW,CAAA,IAAK,KAAK,IAAA,EAAM;AACzB,IAAA,MAAM,QAAA,GAAW,EAAE,QAAA,GAAW,CAAA,WAAA,EAAc,WAAW,CAAA,CAAE,QAAQ,CAAC,CAAA,CAAA,CAAA,GAAM,EAAA;AACxE,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,MAAM,CAAA,WAAA,EAAc,WAAW,CAAA,CAAE,GAAG,CAAC,CAAA,QAAA,EAAW,WAAW,CAAA,CAAE,IAAI,CAAC,CAAA,CAAA,EAAI,QAAQ,CAAA,GAAA,CAAK,CAAA;AAAA,EACnG;AAEA,EAAA,KAAA,MAAW,CAAA,IAAK,KAAK,MAAA,EAAQ;AAC3B,IAAA,KAAA,CAAM,IAAA,CAAK,CAAA,EAAG,MAAM,CAAA,cAAA,EAAiB,UAAA,CAAW,CAAA,CAAE,IAAI,CAAC,CAAA,EAAA,EAAK,YAAA,CAAa,CAAA,CAAE,QAAQ,CAAC,CAAA,SAAA,CAAW,CAAA;AAAA,EACjG;AAEA,EAAA,OAAO,KAAA,CAAM,KAAK,IAAI,CAAA;AACxB;AAOO,SAAS,qBAAqB,IAAA,EAAsB;AACzD,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,gCAAgC,CAAA;AAC7D,EAAA,IAAI,CAAC,WAAW,OAAO,IAAA;AAEvB,EAAA,IAAI,IAAA,GAAO,UAAU,CAAC,CAAA;AAGtB,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,8CAAA,EAAgD,EAAE,CAAA;AAGtE,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA;AAAA,IACV,4FAAA;AAAA,IACA;AAAA,GACF;AAIA,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA,CAAQ,mCAAA,EAAqC,CAAC,GAAA,KAAQ;AAChE,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,KAAA,CAAM,0BAA0B,CAAA;AACtD,IAAA,MAAM,SAAA,GAAY,GAAA,CAAI,KAAA,CAAM,8BAA8B,CAAA;AAC1D,IAAA,MAAM,IAAA,GAAO,SAAA,GAAY,CAAC,CAAA,EAAG,WAAA,EAAY;AACzC,IAAA,MAAM,QAAA,GAAW,SAAA,GAAY,CAAC,CAAA,EAAG,WAAA,EAAY;AAE7C,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,IAAI,kBAAA,CAAmB,GAAA,CAAI,IAAI,CAAA,EAAG,OAAO,EAAA;AACzC,MAAA,IAAI,qBAAA,CAAsB,KAAK,CAAC,CAAA,KAAM,KAAK,UAAA,CAAW,CAAC,CAAC,CAAA,EAAG,OAAO,EAAA;AAAA,IACpE;AACA,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,IAAI,yBAAA,CAA0B,KAAK,CAAC,CAAA,KAAM,SAAS,UAAA,CAAW,CAAC,CAAC,CAAA,EAAG,OAAO,EAAA;AAAA,IAC5E;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAC,CAAA;AAGD,EAAA,IAAA,GAAO,IAAA,CAAK,OAAA;AAAA,IACV,6EAAA;AAAA,IACA;AAAA,GACF;AAEA,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,CAAA,EAAG,SAAA,CAAU,KAAA,GAAS,SAAA,CAAU,CAAC,CAAA,CAAE,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAC,CAAC,CAAA,GACxE,IAAA,GACA,IAAA,CAAK,KAAA,CAAM,SAAA,CAAU,KAAA,GAAS,SAAA,CAAU,CAAC,CAAA,CAAE,OAAA,CAAQ,SAAA,CAAU,CAAC,CAAC,CAAA,GAAI,SAAA,CAAU,CAAC,EAAE,MAAM,CAAA;AAC1F;AAWO,SAAS,cAAA,CAAe,MAAc,IAAA,EAAgC;AAC3E,EAAA,IAAI,CAAC,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,EAAG;AAC3B,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,QAAA,GAAW,qBAAqB,IAAI,CAAA;AAC1C,EAAA,MAAM,QAAA,GAAW,eAAe,IAAI,CAAA;AAGpC,EAAA,OAAO,QAAA,CAAS,OAAA,CAAQ,mBAAA,EAAqB,CAAC,IAAI,MAAA,KAAW;AAC3D,IAAA,OAAO,GAAG,QAAQ;AAAA,EAAK,MAAM,CAAA,OAAA,CAAA;AAAA,EAC/B,CAAC,CAAA;AACH;;;AC6BO,SAAS,0BAAA,CACd,cACA,MAAA,EACQ;AACR,EAAA,IAAI,IAAA,GAAA,CAAQ,YAAA,IAAgB,EAAA,EAAI,OAAA,CAAQ,UAAU,EAAE,CAAA;AAEpD,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,EAAG;AACjD,MAAA,IAAI,SAAS,IAAA,EAAM;AACnB,MAAA,IAAA,GAAO,KAAK,OAAA,CAAQ,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA,CAAA,EAAK,MAAA,CAAO,KAAK,CAAC,CAAA;AAAA,IAC/C;AAAA,EACF;AACA,EAAA,IAAI,IAAA,KAAS,SAAS,OAAO,GAAA;AAC7B,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,EAAG,IAAA,GAAO,KAAK,KAAA,CAAM,CAAA,EAAG,CAAC,QAAA,CAAS,MAAM,CAAA;AAClE,EAAA,OAAO,IAAI,IAAI,CAAA,CAAA;AACjB;AAEA,SAAS,iBAAiB,OAAA,EAAqC;AAC7D,EAAA,OAAA,CAAQ,OAAA,IAAW,EAAA,EAAI,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAC1C;AAGA,SAAS,SAAS,GAAA,EAAmD;AACnE,EAAA,OAAO,CAAC,UAAU,EAAE,IAAA,EAAM,uBAAsB,EAAG,IAAA,CAAK,SAAA,CAAU,GAAG,CAAC,CAAA;AACxE;AAWO,SAAS,qBAAA,CACd,UACA,OAAA,EAC8F;AAC9F,EAAA,MAAM;AAAA,IACJ,SAAA;AAAA,IACA,gBAAgB,EAAC;AAAA,IACjB,WAAA;AAAA,IACA,mBAAA,GAAsB,0BAAA;AAAA,IACtB,eAAA,GAAkB;AAAA,GACpB,GAAI,OAAA;AAEJ,EAAA,MAAM,MAAA,GAAS,SAAA,EAAW,MAAA,IAAU,EAAC;AACrC,EAAA,MAAM,OAAA,GAAU,gBAAA,CAAiB,MAAA,CAAO,OAAO,CAAA;AAE/C,EAAA,MAAM,EAAA,GAAK,QAAA,CAAS,WAAA,IAAe,EAAC;AACpC,EAAA,MAAM,KAAA,GAAQ,mBAAA,CAAoB,QAAA,CAAS,YAAA,EAAc,SAAS,MAAM,CAAA;AACxE,EAAA,MAAM,OAAO,KAAA,KAAU,GAAA,GAAM,MAAA,GAAS,KAAA,CAAM,MAAM,CAAC,CAAA;AACnD,EAAA,MAAM,SAAA,GAAY,UAAU,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,CAAA,CAAA,GAAM,CAAA,EAAG,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA;AAEpE,EAAA,MAAM,GAAA,GAAsB;AAAA,IAC1B,KAAA;AAAA,IACA,IAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA;AAAA,IACA,WAAA,EAAa,EAAA;AAAA,IACb,MAAA;AAAA,IACA;AAAA,GACF;AAKA,EAAA,MAAM,WAAW,cAAA,CAAe,IAAA,EAAM,KAAA,EAAO,SAAA,EAAW,GAAG,KAAK,CAAA;AAGhE,EAAA,MAAM,SAAA,GAAY,WAAA,GAAc,GAAG,CAAA,IAAK,EAAC;AAUzC,EAAA,MAAM,QAAA,GACJ,UAAU,KAAA,IAAS,oBAAA,CAAqB,MAAM,SAAS,CAAA,IAAK,EAAA,CAAG,KAAA,IAAS,QAAA,CAAS,KAAA;AACnF,EAAA,MAAM,SAAA,GAAY,kBAAA,CAAmB,QAAA,EAAU,MAAA,EAAQ,QAAQ,CAAA;AAO/D,EAAA,MAAM,kBAAA,GAAqB,SAAA,EAAW,KAAA,GAAQ,IAAI,CAAA,EAAG,WAAA;AACrD,EAAA,MAAM,cACJ,SAAA,CAAU,WAAA,IACV,sBACA,EAAA,CAAG,WAAA,IACH,OAAO,kBAAA,IACP,EAAA;AACF,EAAA,MAAM,WACJ,SAAA,CAAU,QAAA,IACV,SAAA,EAAW,KAAA,GAAQ,IAAI,CAAA,EAAG,QAAA,KACzB,KAAA,CAAM,OAAA,CAAQ,GAAG,IAAI,CAAA,GAAI,GAAG,IAAA,CAAK,IAAA,CAAK,IAAI,CAAA,GAAI,EAAA,CAAA;AAGjD,EAAA,MAAM,MAAA,GACJ,SAAA,CAAU,MAAA,IAAU,QAAA,CAAS,SAAA,CAAU,IAAA;AACzC,EAAA,MAAM,OAAA,GACJ,SAAA,CAAU,OAAA,IAAW,QAAA,CAAS,SAAA,CAAU,KAAA,IAAS,EAAA,CAAG,WAAA,IAAe,EAAA,CAAG,KAAA,IAAS,MAAA,CAAO,MAAA,EAAQ,SAAA;AAEhG,EAAA,MAAM,OAA8B,EAAC;AAGrC,EAAA,IAAI,mBAAmB,QAAA,EAAU;AAC/B,IAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,MAAM,UAAA,EAAY,OAAA,EAAS,QAAA,EAAU,CAAC,CAAA;AAAA,EAC7D;AAGA,EAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,IAAA,EAAM,UAAU,OAAA,EAAS,QAAA,CAAS,MAAA,EAAQ,CAAC,CAAA;AAGhE,EAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,KAAK,WAAA,EAAa,IAAA,EAAM,SAAA,EAAW,CAAC,CAAA;AAGzD,EAAA,IAAI,MAAA,CAAO,cAAc,MAAA,EAAQ;AAC/B,IAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,IAAA,EAAM,0BAAA,EAA4B,OAAA,EAAS,MAAA,CAAO,YAAA,CAAa,MAAA,EAAQ,CAAC,CAAA;AAAA,EAC/F;AACA,EAAA,IAAI,MAAA,CAAO,cAAc,IAAA,EAAM;AAC7B,IAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,IAAA,EAAM,eAAA,EAAiB,OAAA,EAAS,MAAA,CAAO,YAAA,CAAa,IAAA,EAAM,CAAC,CAAA;AAAA,EAClF;AAYA,EAAA,MAAM,MAAA,GAAS,SAAA,EAAW,KAAA,GAAQ,IAAI,CAAA,EAAG,SAAA;AACzC,EAAA,MAAM,QAAA,GAA+B;AAAA,IACnC,GAAG,MAAA;AAAA,IACH,IAAA,EAAM,MAAA;AAAA,IACN,KAAA,EAAO,UAAU,OAAA,IAAW,SAAA;AAAA,IAC5B,WAAA,EAAa,SAAA,CAAU,aAAA,IAAiB,MAAA,EAAQ,WAAA,IAAe,WAAA;AAAA,IAC/D,GAAI,OAAA,GAAU,EAAE,KAAA,EAAO,OAAA,KAAY,EAAC;AAAA,IACpC,GAAA,EAAK;AAAA,GACP;AACA,EAAA,KAAA,MAAW,KAAK,qBAAA,CAAsB,QAAA,EAAU,QAAQ,SAAA,EAAW,WAAA,EAAa,SAAS,CAAA,EAAG;AAC1F,IAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,QAAA,EAAU,CAAA,CAAE,QAAA,EAAU,OAAA,EAAS,CAAA,CAAE,OAAA,EAAS,CAAC,CAAA;AAAA,EAClE;AAGA,EAAA,KAAA,MAAW,KAAK,mBAAA,CAAoB,QAAA,CAAS,SAAS,MAAA,EAAQ,SAAA,EAAW,WAAW,CAAA,EAAG;AACrF,IAAA,IAAA,CAAK,IAAA,CAAK,CAAC,MAAA,EAAQ,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,OAAA,EAAS,CAAA,CAAE,OAAA,EAAS,CAAC,CAAA;AAAA,EAC1D;AAIA,EAAA,KAAA,MAAW,GAAA,IAAO,cAAA,CAAe,QAAA,CAAS,OAAA,EAAS,MAAM,CAAA,EAAG;AAC1D,IAAA,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,GAA8B,CAAC,CAAA;AAAA,EACpD;AAGA,EAAA,KAAA,MAAW,QAAQ,aAAA,EAAe;AAChC,IAAA,IAAI,OAAA,GAAU,KAAA;AACd,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,IAC1B,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,IAAA,CAAK,sDAAsD,GAAG,CAAA;AACtE,MAAA;AAAA,IACF;AACA,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,IAAI,UAA0C,EAAC;AAC/C,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,IAAK,EAAC;AAAA,IAChC,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,IAAA,CAAK,6DAA6D,GAAG,CAAA;AAC7E,MAAA;AAAA,IACF;AACA,IAAA,KAAA,MAAW,OAAO,OAAA,EAAS;AACzB,MAAA,IAAI,GAAA,EAAK,IAAA,CAAK,IAAA,CAAK,QAAA,CAAS,GAAG,CAAC,CAAA;AAAA,IAClC;AAAA,EACF;AAEA,EAAA,OAAO;AAAA;AAAA;AAAA,IAGL,KAAA,EAAO,QAAA;AAAA,IACP,IAAA;AAAA,IACA,aAAa,WAAA,IAAe,MAAA;AAAA,IAC5B,YAAA,EAAc,UAAU,YAAA,IAAgB;AAAA,GAC1C;AACF;AAMA,SAAS,oBAAA,CACP,MACA,SAAA,EACoB;AACpB,EAAA,OAAO,SAAA,EAAW,KAAA,GAAQ,IAAI,CAAA,EAAG,KAAA,IAAS,MAAA;AAC5C;AAWA,SAAS,kBAAA,CACP,QAAA,EACA,MAAA,EACA,QAAA,EACQ;AAGR,EAAA,IAAI,QAAA,KAAa,QAAA,CAAS,KAAA,EAAO,OAAO,QAAA;AACxC,EAAA,IAAI,CAAC,MAAA,CAAO,aAAA,EAAe,OAAO,QAAA;AAClC,EAAA,OAAO,MAAA,CAAO,aAAA,CAAc,OAAA,CAAQ,IAAA,EAAM,QAAQ,CAAA;AACpD;AA0BO,SAAS,2BACd,OAAA,EACuC;AACvC,EAAA,OAAO,SAAS,kBAAkB,QAAA,EAAmC;AACnE,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,MAAM,KAAA,EAAO,WAAA,EAAa,cAAa,GAAI,qBAAA,CAAsB,UAAU,OAAO,CAAA;AAE1F,MAAA,QAAA,CAAS,WAAA,GAAc,QAAA,CAAS,WAAA,IAAe,EAAC;AAChD,MAAA,MAAM,QAAA,GAAY,QAAA,CAAS,WAAA,CAAY,IAAA,IAA8C,EAAC;AACtF,MAAA,QAAA,CAAS,YAAY,IAAA,GAAO,CAAC,GAAG,QAAA,EAAU,GAAG,IAAI,CAAA;AAMjD,MAAA,IAAI,WAAA,EAAa;AACf,QAAA,QAAA,CAAS,WAAA,GAAc,WAAA;AAAA,MACzB;AAIA,MAAA,IAAI,gBAAgB,KAAA,EAAO;AACzB,QAAA,QAAA,CAAS,KAAA,GAAQ,KAAA;AAAA,MACnB;AAEA,MAAA,IAAI,QAAQ,KAAA,EAAO;AACjB,QAAA,OAAA,CAAQ,GAAA;AAAA,UACN,CAAA,4BAAA,EAA+B,QAAA,CAAS,YAAY,CAAA,SAAA,EAAO,IAAA,CAAK,MAAM,CAAA,YAAA,CAAA,IACnE,YAAA,IAAgB,KAAA,GAAQ,CAAA,SAAA,EAAY,KAAK,CAAA,EAAA,CAAA,GAAO,EAAA;AAAA,SACrD;AAAA,MACF;AAAA,IACF,SAAS,GAAA,EAAK;AACZ,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,CAAA,gDAAA,EAAmD,UAAU,YAAY,CAAA,sBAAA,CAAA;AAAA,QACzE;AAAA,OACF;AAAA,IACF;AAAA,EACF,CAAA;AACF;AChbO,SAAS,iBAAA,CACd,WAAA,EACA,iBAAA,EACA,KAAA,GAAQ,KAAA,EACiB;AACzB,EAAA,MAAM,aAAA,GAAgB;AAAA,IACpB,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,iBAAiB,CAAA;AAAA,IAC3C,IAAA,CAAK,OAAA,CAAQ,WAAA,EAAa,IAAA,EAAM,iBAAiB,CAAA;AAAA,IACjD,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,iBAAiB;AAAA,GAC/C;AAEA,EAAA,IAAI,SAAA;AACJ,EAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,IAAA,IAAI,EAAA,CAAG,UAAA,CAAW,QAAQ,CAAA,EAAG;AAC3B,MAAA,SAAA,GAAY,QAAA;AACZ,MAAA;AAAA,IACF;AAAA,EACF;AAEA,EAAA,IAAI,CAAC,SAAA,EAAW;AACd,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,OAAA,CAAQ,KAAK,mCAAmC,CAAA;AAChD,MAAA,aAAA,CAAc,OAAA,CAAQ,CAAC,CAAA,KAAM,OAAA,CAAQ,KAAK,CAAA,IAAA,EAAO,CAAC,EAAE,CAAC,CAAA;AAAA,IACvD;AACA,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,KAAK,IAAA,CAAK,EAAA,CAAG,YAAA,CAAa,SAAA,EAAW,MAAM,CAAC,CAAA;AAAA,EACpD,SAAS,KAAA,EAAO;AACd,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,0BAAA,EAA6B,SAAS,CAAA,CAAA,CAAA,EAAK,KAAK,CAAA;AAC7D,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,GAAU,mBAAmB,GAAG,CAAA;AACtC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,OAAA,CAAQ,IAAA,CAAK,CAAA,wBAAA,EAA2B,SAAS,CAAA,2BAAA,CAA6B,CAAA;AAC9E,IAAA,OAAO,IAAA;AAAA,EACT;AAEA,EAAA,IAAI,KAAA,EAAO;AACT,IAAA,OAAA,CAAQ,IAAI,CAAA,iBAAA,EAAoB,OAAA,CAAQ,MAAM,CAAA,aAAA,EAAgB,SAAS,CAAA,CAAE,CAAA;AAAA,EAC3E;AACA,EAAA,OAAO,OAAA;AACT;AASO,SAAS,mBAAmB,GAAA,EAAuC;AACxE,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AAC5C,EAAA,MAAM,QAAS,GAAA,CAAyB,KAAA;AACxC,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,GAAG,OAAO,IAAA;AAElC,EAAA,MAAM,SAA2B,EAAC;AAClC,EAAA,KAAA,MAAW,SAAS,KAAA,EAAO;AACzB,IAAA,IAAI,CAAC,KAAA,IAAS,OAAO,KAAA,KAAU,QAAA,EAAU;AACzC,IAAA,MAAM,IAAK,KAAA,CAA6B,IAAA;AACxC,IAAA,IAAI,OAAO,CAAA,KAAM,QAAA,IAAY,CAAA,CAAE,WAAW,CAAA,EAAG;AAC7C,IAAA,MAAM,OAAQ,KAAA,CAA6B,IAAA;AAC3C,IAAA,MAAM,QAAS,KAAA,CAA8B,KAAA;AAC7C,IAAA,MAAA,CAAO,IAAA,CAAK;AAAA,MACV,IAAA,EAAM,OAAO,IAAA,KAAS,QAAA,GAAW,IAAA,GAAO,EAAA;AAAA,MACxC,IAAA,EAAM,CAAA;AAAA,MACN,GAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,SAAS,CAAA,GAAI,EAAE,KAAA,EAAM,GAAI;AAAC,KAClE,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,MAAA,CAAO,MAAA,GAAS,CAAA,GAAI,MAAA,GAAS,IAAA;AACtC","file":"chunk-TIGZ7RKI.js","sourcesContent":["/**\n * Framework-agnostic SEO head-tag resolution.\n *\n * This module is the single source of truth for turning a\n * (`pageSlug`, `pagePath`, `SeoConfiguration`) triple into a plain,\n * serialisable description of the `<head>` tags a page should carry:\n * resolved title, meta[], link[], and JSON-LD script[].\n *\n * It is consumed by:\n * - `useSEO` (runtime, via `@unhead/vue`) — see `../composables/useSEO.ts`\n * - `dcsSeoPlugin`'s build-time static-HTML emitter — see\n * `../plugins/dcsSeoPlugin.ts`\n *\n * Keeping the resolution here (rather than inside the Vue composable) means\n * the runtime and the build-time emitter produce byte-identical tags from the\n * same `seo.yaml`, with no Vue/unhead dependency required at build time.\n *\n * Runtime behaviour is intentionally identical to the previous in-composable\n * logic **except** for one corrected bug: `og:title` now falls back to the\n * fully-resolved (template-applied) page title instead of the raw, untemplated\n * `page.title`. Previously `og:title` could diverge from the `<title>` element\n * (e.g. `<title>Iron Oak Contractors | Our Services</title>` but\n * `og:title = \"Our Services\"`).\n */\n\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n SeoOpenGraphConfig,\n SeoTwitterConfig,\n SeoSchemaConfig,\n ResolvedPageSeo,\n} from '../types/seo'\n\n// =============================================================================\n// Plain head-tag structures (no framework types)\n// =============================================================================\n\n/** A `<meta>` tag — either a `name=`/`content=` or `property=`/`content=` pair. */\nexport interface HeadMetaTag {\n name?: string\n property?: string\n content: string\n}\n\n/** A `<link>` tag (canonical, alternate, etc.). */\nexport interface HeadLinkTag {\n rel: string\n href: string\n hreflang?: string\n}\n\n/** A `<script type=\"application/ld+json\">` tag carrying serialised JSON-LD. */\nexport interface HeadScriptTag {\n type: string\n /** Pre-serialised JSON-LD string (already `JSON.stringify`-ed). */\n children: string\n}\n\n/**\n * The complete, framework-agnostic set of resolved `<head>` tags for a page.\n *\n * - `title` is the final, template-applied title (what goes in `<title>`).\n * - `meta` covers description, keywords, robots, verification, OG, Twitter.\n * - `link` covers canonical + hreflang alternates.\n * - `script` covers JSON-LD (global + page schemas).\n * - `jsonLd` is the same JSON-LD as parsed objects, for callers that want the\n * structured form (e.g. `useSEO().getSchema()`).\n */\nexport interface ResolvedHeadTags {\n title: string\n meta: HeadMetaTag[]\n link: HeadLinkTag[]\n script: HeadScriptTag[]\n jsonLd: object[]\n /** The fully-resolved page SEO (merged global + page) used to build tags. */\n resolved: ResolvedPageSeo\n}\n\n/** Optional overrides applied on top of the resolved config when building tags. */\nexport interface HeadTagOverrides {\n /** Override the resolved `<title>`. */\n title?: string\n /**\n * Page-specific title fallback used when `seo.yaml` has no `title` for this\n * page (e.g. the route `title` from `pages.yaml`). Unlike `global.defaultTitle`\n * this IS run through `titleTemplate`, so un-configured routes (blog posts,\n * etc.) get unique titles rather than the global default.\n */\n fallbackTitle?: string\n /** Override the resolved meta description. */\n description?: string\n /** Override the meta keywords value. */\n keywords?: string\n /**\n * Force the robots directive (e.g. `'noindex, nofollow'`). When supplied this\n * wins over both page- and global-level robots.\n */\n robots?: string\n /** Replace the JSON-LD schema objects entirely (already-built objects). */\n schemas?: object[]\n /** Extra meta tags appended after the generated ones. */\n meta?: HeadMetaTag[]\n /**\n * Emit a `<meta name=\"keywords\">` tag from the page's `keywords` field.\n *\n * Defaults to `false` so the `useSEO` runtime path stays byte-identical to\n * its historical output (which never emitted keywords). The static-HTML\n * emitter opts in (`true`) to surface page keywords in the baked `<head>`.\n */\n includeKeywords?: boolean\n}\n\n// =============================================================================\n// Open Graph / Twitter / JSON-LD generation\n// =============================================================================\n\n/**\n * Generate Open Graph meta tags from config.\n *\n * @param resolvedTitle - the final, template-applied page title. Used as the\n * `og:title` fallback so OG stays consistent with `<title>`.\n */\nexport function generateOpenGraphMeta(\n og: SeoOpenGraphConfig,\n global: GlobalSeoConfig,\n resolvedTitle: string,\n pageDescription: string,\n canonical: string\n): Array<{ property: string; content: string }> {\n const tags: Array<{ property: string; content: string }> = []\n\n // og:title falls back to the *resolved* (templated) title — see module\n // docblock. `og.title` is already pre-resolved in `resolvePageSeo` to\n // `page.openGraph.title || resolvedTitle`, so this is consistent either way.\n tags.push({ property: 'og:title', content: og.title || resolvedTitle })\n tags.push({ property: 'og:description', content: og.description || pageDescription })\n tags.push({ property: 'og:url', content: og.url || canonical })\n tags.push({ property: 'og:type', content: og.type || 'website' })\n\n const image = og.image || global.images?.ogDefault\n if (image) {\n tags.push({ property: 'og:image', content: image })\n if (og.imageAlt || resolvedTitle) {\n tags.push({ property: 'og:image:alt', content: og.imageAlt || resolvedTitle })\n }\n if (og.imageWidth) {\n tags.push({ property: 'og:image:width', content: String(og.imageWidth) })\n }\n if (og.imageHeight) {\n tags.push({ property: 'og:image:height', content: String(og.imageHeight) })\n }\n }\n\n if (global.siteName) {\n tags.push({ property: 'og:site_name', content: global.siteName })\n }\n\n if (global.locale) {\n tags.push({ property: 'og:locale', content: global.locale })\n }\n\n // Article-specific tags\n if (og.type === 'article') {\n if (og.publishedTime) {\n tags.push({ property: 'article:published_time', content: og.publishedTime })\n }\n if (og.modifiedTime) {\n tags.push({ property: 'article:modified_time', content: og.modifiedTime })\n }\n if (og.author) {\n tags.push({ property: 'article:author', content: og.author })\n }\n if (og.section) {\n tags.push({ property: 'article:section', content: og.section })\n }\n if (og.tags) {\n og.tags.forEach((tag) => {\n tags.push({ property: 'article:tag', content: tag })\n })\n }\n }\n\n return tags\n}\n\n/**\n * Generate Twitter Card meta tags from config.\n *\n * @param resolvedTitle - the final, template-applied page title (Twitter title\n * fallback), mirroring the OG behaviour.\n */\nexport function generateTwitterMeta(\n twitter: SeoTwitterConfig,\n global: GlobalSeoConfig,\n resolvedTitle: string,\n pageDescription: string\n): Array<{ name: string; content: string }> {\n const tags: Array<{ name: string; content: string }> = []\n\n tags.push({ name: 'twitter:card', content: twitter.card || 'summary_large_image' })\n tags.push({ name: 'twitter:title', content: twitter.title || resolvedTitle })\n tags.push({ name: 'twitter:description', content: twitter.description || pageDescription })\n\n const image = twitter.image || global.images?.twitterDefault\n if (image) {\n tags.push({ name: 'twitter:image', content: image })\n if (twitter.imageAlt || resolvedTitle) {\n tags.push({ name: 'twitter:image:alt', content: twitter.imageAlt || resolvedTitle })\n }\n }\n\n const site = twitter.site || global.social?.twitter\n if (site) {\n tags.push({ name: 'twitter:site', content: site.startsWith('@') ? site : `@${site}` })\n }\n\n if (twitter.creator) {\n tags.push({\n name: 'twitter:creator',\n content: twitter.creator.startsWith('@') ? twitter.creator : `@${twitter.creator}`,\n })\n }\n\n return tags\n}\n\n/**\n * Generate JSON-LD schema objects from schema configs, auto-populating common\n * WebSite properties from global config.\n */\nexport function generateJsonLd(schemas: SeoSchemaConfig[], global: GlobalSeoConfig): object[] {\n return schemas.map((schema) => {\n const base: Record<string, unknown> = {\n '@context': 'https://schema.org',\n '@type': schema.type,\n }\n\n // Merge properties\n if (schema.properties) {\n Object.assign(base, schema.properties)\n }\n\n // Auto-populate common properties from global config\n if (schema.type === 'WebSite' && global.siteUrl && !base.url) {\n base.url = global.siteUrl\n }\n if (schema.type === 'WebSite' && global.siteName && !base.name) {\n base.name = global.siteName\n }\n\n return base\n })\n}\n\n/**\n * Resolve page SEO by merging global defaults with page-specific config.\n *\n * Behavioural changes from the historical in-composable version (both bug\n * fixes):\n * 1. `openGraph.title` falls back to the **template-applied** page title, not\n * the raw `page.title`, so `og:title` matches `<title>`.\n * 2. The `titleTemplate` is applied **only** to a page-specific title\n * (`page.title`, or the `fallbackTitle` arg). It is no longer applied to\n * `global.defaultTitle`, which is already the complete brand title —\n * templating it produced `\"Brand | Default Title | Brand\"` doubling on any\n * page without its own `seo.yaml` entry.\n *\n * @param fallbackTitle - a page-specific title to use when `seo.yaml` has no\n * `title` for this page (e.g. the route `title` from `pages.yaml`). It IS run\n * through `titleTemplate`; `global.defaultTitle` is the last resort and is not.\n */\nexport function resolvePageSeo(\n pageSlug: string,\n pagePath: string | undefined,\n seoConfig: SeoConfiguration | undefined,\n fallbackTitle?: string\n): ResolvedPageSeo {\n const global = seoConfig?.global ?? {}\n const page = seoConfig?.pages?.[pageSlug] ?? {}\n\n // Build canonical URL\n let canonical = page.canonical || ''\n if (!canonical && global.siteUrl) {\n const path = pagePath ?? (pageSlug === 'home' ? '/' : `/${pageSlug}`)\n canonical = `${global.siteUrl.replace(/\\/$/, '')}${path}`\n }\n\n // Build title. The `%s` slot is for a PAGE-SPECIFIC title only (a seo.yaml\n // `page.title` or a `pages.yaml` route title). `global.defaultTitle` is\n // already brand-complete, so it is used verbatim — never re-templated.\n const pageSpecificTitle = page.title || fallbackTitle\n let title: string\n if (pageSpecificTitle) {\n title =\n page.noTitleTemplate || !global.titleTemplate\n ? pageSpecificTitle\n : global.titleTemplate.replace('%s', pageSpecificTitle)\n } else {\n title = global.defaultTitle || pageSlug\n }\n\n // Merge Open Graph. og:title now falls back to the *resolved* (templated)\n // title rather than the raw page.title, keeping it consistent with <title>.\n const openGraph: ResolvedPageSeo['openGraph'] = {\n type: page.openGraph?.type || 'website',\n title: page.openGraph?.title || title,\n description: page.openGraph?.description || page.description || global.defaultDescription || '',\n ...page.openGraph,\n }\n\n // Merge Twitter\n const twitter: ResolvedPageSeo['twitter'] = {\n card: page.twitter?.card || 'summary_large_image',\n ...page.twitter,\n }\n\n // Combine schemas (global + page)\n const schemas = [...(global.schemas ?? []), ...(page.schemas ?? [])]\n\n return {\n title,\n description: page.description || global.defaultDescription || '',\n canonical,\n robots: page.robots || global.robots || 'index, follow',\n openGraph,\n twitter,\n schemas,\n alternates: page.alternates ?? [],\n // Surface keywords on the resolved object so both runtime and emitter can\n // emit the meta tag without re-reading the raw page config.\n keywords: page.keywords,\n }\n}\n\n// =============================================================================\n// Unified head-tag builder (consumed by runtime + emitter)\n// =============================================================================\n\n/**\n * Build the complete, framework-agnostic set of `<head>` tags for a page.\n *\n * This is the function both the `useSEO` runtime and the build-time emitter\n * call, guaranteeing identical output. Pass `overrides` to mirror the\n * composable's `applyHead(overrides)` behaviour, or to force `robots` (used by\n * the emitter's `noindex` option).\n */\nexport function buildHeadTags(\n pageSlug: string,\n pagePath: string | undefined,\n seoConfig: SeoConfiguration | undefined,\n overrides?: HeadTagOverrides\n): ResolvedHeadTags {\n const resolved = resolvePageSeo(pageSlug, pagePath, seoConfig, overrides?.fallbackTitle)\n const global = seoConfig?.global ?? {}\n\n const title = overrides?.title ?? resolved.title\n const description = overrides?.description ?? resolved.description\n const robots = overrides?.robots ?? resolved.robots\n const keywords = overrides?.keywords ?? resolved.keywords\n\n // ── meta ────────────────────────────────────────────────────────────────\n const meta: HeadMetaTag[] = []\n\n meta.push({ name: 'description', content: description })\n\n // Keywords are opt-in (default off) so the runtime composable output is\n // unchanged; the static-HTML emitter passes includeKeywords: true.\n if (keywords && overrides?.includeKeywords) {\n meta.push({ name: 'keywords', content: keywords })\n }\n\n if (robots) {\n meta.push({ name: 'robots', content: robots })\n }\n\n // Verification codes\n if (global.verification?.google) {\n meta.push({ name: 'google-site-verification', content: global.verification.google })\n }\n if (global.verification?.bing) {\n meta.push({ name: 'msvalidate.01', content: global.verification.bing })\n }\n\n // Open Graph\n const ogMeta = generateOpenGraphMeta(resolved.openGraph, global, title, description, resolved.canonical)\n meta.push(...ogMeta.map((t) => ({ property: t.property, content: t.content })))\n\n // Twitter\n const twitterMeta = generateTwitterMeta(resolved.twitter, global, title, description)\n meta.push(...twitterMeta.map((t) => ({ name: t.name, content: t.content })))\n\n // Caller-supplied extra meta\n if (overrides?.meta) {\n meta.push(...overrides.meta)\n }\n\n // ── link ──────────────────────────────────────────────────────────────\n const link: HeadLinkTag[] = []\n\n if (resolved.canonical) {\n link.push({ rel: 'canonical', href: resolved.canonical })\n }\n\n resolved.alternates.forEach((alt) => {\n link.push({ rel: 'alternate', href: alt.href, hreflang: alt.hreflang })\n })\n\n // ── script (JSON-LD) ──────────────────────────────────────────────────\n const jsonLd = overrides?.schemas ?? generateJsonLd(resolved.schemas, global)\n const script: HeadScriptTag[] = jsonLd.map((schema) => ({\n type: 'application/ld+json',\n children: JSON.stringify(schema),\n }))\n\n return { title, meta, link, script, jsonLd, resolved }\n}\n","/**\n * Pure, framework-free `<head>` splicing for the build-time SEO emitter.\n *\n * Given a built `index.html` shell and a set of resolved head tags (from\n * `buildHeadTags`), this produces a new HTML string where the SEO-managed\n * tags — `<title>`, `description`, `keywords`, `robots`, `canonical`,\n * verification, all `og:*` / `article:*` properties, all `twitter:*` names,\n * and `application/ld+json` scripts — have been **replaced** (not duplicated)\n * with the resolved set.\n *\n * Design goals:\n * - **Idempotent**: running it twice yields the same output (it strips the\n * managed tags first, then re-inserts the canonical set).\n * - **Deterministic**: tag order is fixed by `renderHeadTags`.\n * - **Conservative**: only tags we own are touched. Charset, viewport, CSP,\n * theme-color, favicons, stylesheets, and the app script are left intact.\n *\n * This is intentionally regex-based (no DOM dependency) to mirror the existing\n * `dcsCdnImagePlugin` post-build HTML rewriting and to keep the emitter free of\n * heavy parser deps at build time.\n */\n\nimport type { ResolvedHeadTags } from './headTags'\n\n/** Escape a string for safe inclusion in a double-quoted HTML attribute. */\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/\"/g, '"')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\n/**\n * Escape JSON-LD content for safe inclusion inside a `<script>` element.\n * Only `</` sequences are dangerous (they can prematurely close the script);\n * the rest of the JSON is already a valid string literal.\n */\nfunction escapeJsonLd(json: string): string {\n return json.replace(/<\\//g, '<\\\\/')\n}\n\n/** `name=` meta tags that the emitter owns and will replace. */\nconst MANAGED_META_NAMES = new Set([\n 'description',\n 'keywords',\n 'robots',\n 'google-site-verification',\n 'msvalidate.01',\n])\n\n/**\n * Property/name prefixes the emitter owns. Any meta whose `property` starts\n * with one of these (og:, article:) or whose `name` starts with `twitter:` is\n * considered managed and stripped before re-insertion.\n */\nconst MANAGED_PROPERTY_PREFIXES = ['og:', 'article:']\nconst MANAGED_NAME_PREFIXES = ['twitter:']\n\n/**\n * Render the resolved head tags to a deterministic HTML fragment.\n * Order: title, meta (in the order produced by buildHeadTags), link, script.\n */\nexport function renderHeadTags(tags: ResolvedHeadTags, indent = ' '): string {\n const lines: string[] = []\n\n lines.push(`${indent}<title>${escapeAttr(tags.title)}</title>`)\n\n for (const m of tags.meta) {\n if (m.property !== undefined) {\n lines.push(`${indent}<meta property=\"${escapeAttr(m.property)}\" content=\"${escapeAttr(m.content)}\" />`)\n } else if (m.name !== undefined) {\n lines.push(`${indent}<meta name=\"${escapeAttr(m.name)}\" content=\"${escapeAttr(m.content)}\" />`)\n }\n }\n\n for (const l of tags.link) {\n const hreflang = l.hreflang ? ` hreflang=\"${escapeAttr(l.hreflang)}\"` : ''\n lines.push(`${indent}<link rel=\"${escapeAttr(l.rel)}\" href=\"${escapeAttr(l.href)}\"${hreflang} />`)\n }\n\n for (const s of tags.script) {\n lines.push(`${indent}<script type=\"${escapeAttr(s.type)}\">${escapeJsonLd(s.children)}</script>`)\n }\n\n return lines.join('\\n')\n}\n\n/**\n * Strip the SEO-managed tags from a `<head>` block so they can be re-inserted\n * without duplication. Operates only within `<head>...</head>` to avoid\n * touching body content.\n */\nexport function stripManagedHeadTags(html: string): string {\n const headMatch = html.match(/<head[^>]*>([\\s\\S]*?)<\\/head>/i)\n if (!headMatch) return html\n\n let head = headMatch[1]\n\n // Remove existing <title>…</title>\n head = head.replace(/[ \\t]*<title>[\\s\\S]*?<\\/title>[ \\t]*\\r?\\n?/gi, '')\n\n // Remove existing JSON-LD scripts\n head = head.replace(\n /[ \\t]*<script[^>]*type=[\"']application\\/ld\\+json[\"'][^>]*>[\\s\\S]*?<\\/script>[ \\t]*\\r?\\n?/gi,\n ''\n )\n\n // Remove managed <meta name=\"…\"> and <meta property=\"…\"> tags.\n // Matches any <meta ...> tag, inspects its name/property, drops it if managed.\n head = head.replace(/[ \\t]*<meta\\b[^>]*>[ \\t]*\\r?\\n?/gi, (tag) => {\n const nameMatch = tag.match(/\\bname=[\"']([^\"']*)[\"']/i)\n const propMatch = tag.match(/\\bproperty=[\"']([^\"']*)[\"']/i)\n const name = nameMatch?.[1]?.toLowerCase()\n const property = propMatch?.[1]?.toLowerCase()\n\n if (name) {\n if (MANAGED_META_NAMES.has(name)) return ''\n if (MANAGED_NAME_PREFIXES.some((p) => name.startsWith(p))) return ''\n }\n if (property) {\n if (MANAGED_PROPERTY_PREFIXES.some((p) => property.startsWith(p))) return ''\n }\n return tag\n })\n\n // Remove existing canonical link (alternates are also managed)\n head = head.replace(\n /[ \\t]*<link\\b[^>]*\\brel=[\"'](?:canonical|alternate)[\"'][^>]*>[ \\t]*\\r?\\n?/gi,\n ''\n )\n\n return html.slice(0, headMatch.index! + headMatch[0].indexOf(headMatch[1])) +\n head +\n html.slice(headMatch.index! + headMatch[0].indexOf(headMatch[1]) + headMatch[1].length)\n}\n\n/**\n * Splice resolved SEO head tags into an HTML document.\n *\n * Strips the existing managed tags, then inserts the rendered canonical set\n * immediately before `</head>`. If no `<head>` is present the HTML is returned\n * unchanged (defensive — the emitter logs and no-ops in that case).\n *\n * Idempotent: applying twice produces identical output.\n */\nexport function spliceHeadHtml(html: string, tags: ResolvedHeadTags): string {\n if (!/<\\/head>/i.test(html)) {\n return html\n }\n\n const stripped = stripManagedHeadTags(html)\n const fragment = renderHeadTags(tags)\n\n // Insert before </head>, preserving the indentation of the closing tag.\n return stripped.replace(/([ \\t]*)<\\/head>/i, (_m, indent) => {\n return `${fragment}\\n${indent}</head>`\n })\n}\n","/**\n * Build-time SEO for VitePress static-site generation.\n *\n * VitePress 1.6 does **not** use `unhead`, so the runtime `useSEO`/`applyHead`\n * composable is a no-op against the SSG HTML. The SSG-correct sink is the\n * `transformPageData(pageData)` build hook: writing `<meta>`/`<link>`/JSON-LD\n * into `pageData.frontmatter.head` (VitePress bakes those into the rendered\n * `<head>`) and overwriting `pageData.title` / `pageData.description` (VitePress\n * renders the `<title>` — via `titleTemplate` — and the `description` meta from\n * those two fields).\n *\n * This factory generalises the bespoke `buildSeoHead`/`transformPageData` that\n * shipped inline in a site's `.vitepress/config.ts`. It reuses the shared,\n * framework-agnostic resolver (`resolvePageSeo`, `generateOpenGraphMeta`,\n * `generateTwitterMeta`, `generateJsonLd`) for global + page meta / OG / Twitter\n * / canonical and the global JSON-LD knowledge graph, and delegates **page-type\n * JSON-LD** (e.g. Article / Place / CollectionPage / Service / FAQPage +\n * BreadcrumbList) to a *pluggable* rule set the site supplies. None of the\n * real-estate (or any other vertical's) schema logic lives in this package — it\n * is all site CONFIG.\n *\n * It is the VitePress counterpart to the Vue-SPA per-route emitter in\n * `dcsSeoPlugin({ emitStaticHtml: true })`; both produce identical global\n * meta/OG/Twitter/JSON-LD from the same `seo.yaml` via the shared resolver.\n *\n * @example\n * ```ts\n * // docs/.vitepress/config.ts\n * import { createSeoTransformPageData } from '@duffcloudservices/cms/plugins'\n * import seoConfig from '../../.dcs/seo.yaml'\n *\n * export default defineConfig({\n * transformPageData: createSeoTransformPageData({\n * seoConfig,\n * pageTypeRules: [\n * { match: (ctx) => ctx.route.startsWith('/blogs/'), build: (ctx) => [ ... ] },\n * // ...Place / CollectionPage / Service / FAQPage rules\n * ],\n * }),\n * })\n * ```\n */\n\nimport type {\n SeoConfiguration,\n GlobalSeoConfig,\n SeoOpenGraphConfig,\n} from '../types/seo'\nimport {\n resolvePageSeo,\n generateOpenGraphMeta,\n generateTwitterMeta,\n generateJsonLd,\n} from './headTags'\n\n/**\n * A VitePress `head` entry. Mirrors VitePress's `HeadConfig` without taking a\n * dependency on the `vitepress` package (which is not a dependency of this\n * library). The tuple forms are:\n * ['meta', { name|property, content }]\n * ['link', { rel, href, ... }]\n * ['script', { type: 'application/ld+json' }, '<serialised json>']\n */\nexport type VitePressHeadConfig =\n | [string, Record<string, string>]\n | [string, Record<string, string>, string]\n\n/**\n * The minimal slice of VitePress's `PageData` this factory reads and mutates.\n * Typed structurally so callers can pass VitePress's real `PageData` without a\n * cast and without this package importing `vitepress`.\n */\nexport interface VitePressPageData {\n /** Source-relative path, e.g. `index.md`, `blogs/my-post.md`. */\n relativePath: string\n /** Dynamic-route params (e.g. `{ topic: 'home-buying' }`). */\n params?: Record<string, unknown>\n /** Page frontmatter; `head` is appended to here. */\n frontmatter: Record<string, any>\n /** VitePress page title (drives `<title>` via `titleTemplate`). */\n title?: string\n /** VitePress page description (drives the `description` meta). */\n description?: string\n [key: string]: unknown\n}\n\n/**\n * Context handed to the site's page-type rules and resolver hooks. Everything a\n * site needs to derive its title/description/og/schemas for one page, computed\n * once per page by the factory.\n */\nexport interface SeoPageContext {\n /** Route path, e.g. `/`, `/blogs/my-post`, `/locations/birmingham`. */\n route: string\n /** Slug: `'home'` for `/`, otherwise the route without its leading slash. */\n slug: string\n /** Absolute canonical URL for this route. */\n canonical: string\n /** Normalised site base URL (no trailing slash), e.g. `https://example.com`. */\n siteUrl: string\n /** The page's frontmatter (read-only convenience; same object as pageData). */\n frontmatter: Record<string, any>\n /** The resolved global SEO config block. */\n global: GlobalSeoConfig\n /** The full VitePress page data (for rules that need more than the above). */\n pageData: VitePressPageData\n}\n\n/**\n * The resolved per-page title / description / OG type a site may override.\n * Returned by the optional `resolvePage` hook so a site can apply its own\n * per-page-type title precedence (e.g. \"{City} Luxury Real Estate\") and decide\n * whether that title should win over VitePress's `titleTemplate`.\n */\nexport interface ResolvedPageOverrides {\n /**\n * The page title. When `setPageTitle` is true this is written to\n * `pageData.title` (VitePress then applies `titleTemplate`).\n */\n title?: string\n /**\n * When true, `title` is written back to `pageData.title`. Leave false for\n * pages whose frontmatter title should remain authoritative (e.g. blog posts\n * that already carry a good `<h1>`/title).\n */\n setPageTitle?: boolean\n /** The meta description. Written to `pageData.description` when truthy. */\n description?: string\n /** Open Graph type override (e.g. `'article'`, `'profile'`). */\n ogType?: SeoOpenGraphConfig['type']\n /** Open Graph image URL override (e.g. a post's header image). */\n ogImage?: string\n /** Keywords override (comma-separated) for the `keywords` meta. */\n keywords?: string\n /**\n * Open Graph title override. Defaults to `title` so og:title tracks <title>.\n */\n ogTitle?: string\n /**\n * Open Graph description override. Defaults to `description`.\n */\n ogDescription?: string\n}\n\n/** A single pluggable page-type rule: when `match` is true, emit `build`. */\nexport interface SeoPageTypeRule {\n /** Return true when this rule applies to the page (by route/slug/etc.). */\n match: (ctx: SeoPageContext) => boolean\n /** Build the page-type JSON-LD objects to emit (already plain objects). */\n build: (ctx: SeoPageContext) => Array<Record<string, unknown>>\n}\n\nexport interface CreateSeoTransformPageDataOptions {\n /** The parsed `.dcs/seo.yaml` (global graph + per-page meta). */\n seoConfig: SeoConfiguration | undefined\n /**\n * Pluggable page-type rules. Evaluated in order; **every** matching rule's\n * `build` output is emitted (so a route can contribute both a primary schema\n * and a BreadcrumbList from one rule, or be matched by several). The\n * real-estate BlogPosting / Place / CollectionPage / Service / FAQPage logic\n * is supplied here by the site — never hardcoded in this package.\n */\n pageTypeRules?: SeoPageTypeRule[]\n /**\n * Optional hook to override per-page title / description / OG before tags are\n * built — the site's title precedence and per-type description fallbacks.\n * Receives the same context as the rules. Anything it omits falls back to the\n * resolver / frontmatter defaults.\n */\n resolvePage?: (ctx: SeoPageContext) => ResolvedPageOverrides | undefined\n /**\n * Map a `relativePath` (+ params) to a route. Defaults to a VitePress-correct\n * implementation: `index` becomes `/`, a trailing `/index` is dropped, `.md`\n * is stripped, and dynamic `[name]` segments are substituted from\n * `pageData.params`. Override only for unusual routing.\n */\n relativePathToRoute?: (relativePath: string, params?: Record<string, unknown>) => string\n /**\n * Emit a `<meta name=\"keywords\">` from the resolved/overridden keywords.\n * Default true (parity with the bespoke KDH emitter, which emitted keywords).\n */\n includeKeywords?: boolean\n /** Enable debug logging of the emitted head per page. */\n debug?: boolean\n}\n\n/** Default VitePress route derivation (matches the bespoke KDH helper). */\nexport function defaultRelativePathToRoute(\n relativePath: string,\n params?: Record<string, unknown>\n): string {\n let stem = (relativePath ?? '').replace(/\\.md$/i, '')\n // Substitute dynamic [param] segments (e.g. topics/[topic] → topics/home-buying).\n if (params) {\n for (const [key, value] of Object.entries(params)) {\n if (value == null) continue\n stem = stem.replace(`[${key}]`, String(value))\n }\n }\n if (stem === 'index') return '/'\n if (stem.endsWith('/index')) stem = stem.slice(0, -'/index'.length)\n return `/${stem}`\n}\n\nfunction normaliseSiteUrl(siteUrl: string | undefined): string {\n return (siteUrl ?? '').replace(/\\/$/, '')\n}\n\n/** A `<script type=\"application/ld+json\">` VitePress head tuple. */\nfunction ldScript(obj: Record<string, unknown>): VitePressHeadConfig {\n return ['script', { type: 'application/ld+json' }, JSON.stringify(obj)]\n}\n\n/**\n * Build just the SEO head tuples for a page (no `pageData` mutation). Exposed\n * separately so it is unit-testable without a VitePress `pageData` round-trip\n * and reusable by callers that manage the `head`/`title` sinks themselves.\n *\n * @returns `{ head, title, description }` — the head tuples to append, and the\n * final title/description (already overridden) the caller should write to\n * `pageData` when `applyTitle`/`applyDescription` are appropriate.\n */\nexport function buildVitePressSeoHead(\n pageData: VitePressPageData,\n options: CreateSeoTransformPageDataOptions\n): { head: VitePressHeadConfig[]; title?: string; description?: string; setPageTitle: boolean } {\n const {\n seoConfig,\n pageTypeRules = [],\n resolvePage,\n relativePathToRoute = defaultRelativePathToRoute,\n includeKeywords = true,\n } = options\n\n const global = seoConfig?.global ?? {}\n const siteUrl = normaliseSiteUrl(global.siteUrl)\n\n const fm = pageData.frontmatter ?? {}\n const route = relativePathToRoute(pageData.relativePath, pageData.params)\n const slug = route === '/' ? 'home' : route.slice(1)\n const canonical = route === '/' ? `${siteUrl}/` : `${siteUrl}${route}`\n\n const ctx: SeoPageContext = {\n route,\n slug,\n canonical,\n siteUrl,\n frontmatter: fm,\n global,\n pageData,\n }\n\n // Resolve global + per-page SEO from the shared resolver. We pass the page's\n // frontmatter title as the fallback so un-configured pages still get a\n // sensible (templated) title; seo.yaml `pages.<slug>.title` still wins.\n const resolved = resolvePageSeo(slug, route, seoConfig, fm.title)\n\n // Site-provided per-page overrides (title precedence + per-type description).\n const overrides = resolvePage?.(ctx) ?? {}\n\n // ── Title handling ────────────────────────────────────────────────────────\n // Two titles matter for VitePress:\n // • `rawTitle` → written to `pageData.title` (VitePress applies its OWN\n // `titleTemplate`, so the <title> element is templated once).\n // • `templated` → used for og:title / twitter:title so they match the final\n // <title> element (the shared resolver's og:title fix).\n // A site override (`overrides.title`) is the RAW per-type title; we template\n // it ourselves for OG/Twitter using the same precedence as the resolver.\n const rawTitle =\n overrides.title ?? pageSpecificSeoTitle(slug, seoConfig) ?? fm.title ?? resolved.title\n const templated = applyTitleTemplate(rawTitle, global, resolved)\n\n // Description precedence mirrors the bespoke emitter: a site per-type override\n // → the explicit seo.yaml `page.description` → the page frontmatter → the\n // global default. (The shared resolver collapses frontmatter and the global\n // default, so we read the explicit page description directly to keep\n // frontmatter ahead of the global fallback.)\n const pageSeoDescription = seoConfig?.pages?.[slug]?.description\n const description =\n overrides.description ||\n pageSeoDescription ||\n fm.description ||\n global.defaultDescription ||\n ''\n const keywords =\n overrides.keywords ||\n seoConfig?.pages?.[slug]?.keywords ||\n (Array.isArray(fm.tags) ? fm.tags.join(', ') : '')\n\n // Open Graph type/image: site override → seo.yaml → frontmatter → default.\n const ogType =\n overrides.ogType || resolved.openGraph.type\n const ogImage =\n overrides.ogImage || resolved.openGraph.image || fm.headerImage || fm.image || global.images?.ogDefault\n\n const head: VitePressHeadConfig[] = []\n\n // Keywords (parity: the bespoke emitter pushed keywords when present).\n if (includeKeywords && keywords) {\n head.push(['meta', { name: 'keywords', content: keywords }])\n }\n\n // Robots.\n head.push(['meta', { name: 'robots', content: resolved.robots }])\n\n // Canonical.\n head.push(['link', { rel: 'canonical', href: canonical }])\n\n // Verification.\n if (global.verification?.google) {\n head.push(['meta', { name: 'google-site-verification', content: global.verification.google }])\n }\n if (global.verification?.bing) {\n head.push(['meta', { name: 'msvalidate.01', content: global.verification.bing }])\n }\n\n // Open Graph — reuse the shared generator, with our resolved title/desc/og\n // overrides applied via a synthetic OG config so the output matches the SPA\n // emitter byte-for-byte (article:* fields included when type === 'article').\n // og:title tracks the TEMPLATED title (the resolver's og:title fix), so it\n // matches the final <title> element rather than the raw per-type title.\n //\n // We DON'T spread `resolved.openGraph` wholesale: for an un-configured page\n // its `description` is the global default, which would wrongly win over this\n // page's actual `description`. We instead keep only the page's *explicit* OG\n // fields (from seo.yaml) and let `description`/`title` fall through correctly.\n const pageOg = seoConfig?.pages?.[slug]?.openGraph\n const ogConfig: SeoOpenGraphConfig = {\n ...pageOg,\n type: ogType,\n title: overrides.ogTitle || templated,\n description: overrides.ogDescription || pageOg?.description || description,\n ...(ogImage ? { image: ogImage } : {}),\n url: canonical,\n }\n for (const t of generateOpenGraphMeta(ogConfig, global, templated, description, canonical)) {\n head.push(['meta', { property: t.property, content: t.content }])\n }\n\n // Twitter — shared generator (twitter:title also tracks the templated title).\n for (const t of generateTwitterMeta(resolved.twitter, global, templated, description)) {\n head.push(['meta', { name: t.name, content: t.content }])\n }\n\n // JSON-LD: the global knowledge graph (RealEstateAgent / Person / WebSite /\n // Organization / …) on every page, via the shared generator.\n for (const obj of generateJsonLd(resolved.schemas, global)) {\n head.push(ldScript(obj as Record<string, unknown>))\n }\n\n // Page-type JSON-LD: every matching site rule contributes its objects.\n for (const rule of pageTypeRules) {\n let matched = false\n try {\n matched = rule.match(ctx)\n } catch (err) {\n console.warn('[dcs-seo] pageTypeRule.match threw; skipping rule:', err)\n continue\n }\n if (!matched) continue\n let objects: Array<Record<string, unknown>> = []\n try {\n objects = rule.build(ctx) ?? []\n } catch (err) {\n console.warn('[dcs-seo] pageTypeRule.build threw; skipping rule output:', err)\n continue\n }\n for (const obj of objects) {\n if (obj) head.push(ldScript(obj))\n }\n }\n\n return {\n // Hand back the RAW title for `pageData.title`; VitePress applies its own\n // `titleTemplate` to produce the templated <title> element.\n title: rawTitle,\n head,\n description: description || undefined,\n setPageTitle: overrides.setPageTitle ?? false,\n }\n}\n\n/**\n * Compute the page-specific (un-templated) `seo.yaml` title for a slug, if any.\n * This is the title VitePress should template — it must NOT come pre-templated.\n */\nfunction pageSpecificSeoTitle(\n slug: string,\n seoConfig: SeoConfiguration | undefined\n): string | undefined {\n return seoConfig?.pages?.[slug]?.title || undefined\n}\n\n/**\n * Apply the global `titleTemplate` to a raw page title, matching the shared\n * resolver's precedence: a page that opts out (`noTitleTemplate`) or a\n * brand-complete global default is used verbatim; otherwise `%s` is filled.\n *\n * When `rawTitle` is the resolver's already-final title (e.g. the global\n * `defaultTitle`, used when there is no page-specific title), we return it\n * unchanged to avoid double-templating.\n */\nfunction applyTitleTemplate(\n rawTitle: string,\n global: GlobalSeoConfig,\n resolved: ResolvedPageSeoLike\n): string {\n // If rawTitle is exactly the resolver's final title, it is either already\n // templated (page-specific) or a brand-complete default — use as-is.\n if (rawTitle === resolved.title) return rawTitle\n if (!global.titleTemplate) return rawTitle\n return global.titleTemplate.replace('%s', rawTitle)\n}\n\n/** Structural subset of `ResolvedPageSeo` used by the title helper. */\ninterface ResolvedPageSeoLike {\n title: string\n}\n\n/**\n * Create a VitePress `transformPageData(pageData)` function that bakes DCS SEO\n * (global meta/OG/Twitter/canonical + global JSON-LD graph + pluggable\n * page-type JSON-LD) into the SSG `<head>`.\n *\n * Mutations performed on `pageData`:\n * - **`frontmatter.head`** — the resolved tags are *appended* to any existing\n * `head` (so site-level `head` config is preserved).\n * - **`description`** — set to the resolved/overridden description so\n * VitePress emits exactly one `description` meta (no duplicate; we do not\n * push our own description meta).\n * - **`title`** — set only when the site's `resolvePage` hook returns\n * `setPageTitle: true` for this page, mirroring the bespoke behaviour where\n * seo.yaml/per-type titles are authoritative but a post's frontmatter title\n * is left intact.\n *\n * Defensive: never throws (a failure logs a warning and leaves `pageData`\n * untouched), so SEO can never break a production VitePress build.\n */\nexport function createSeoTransformPageData(\n options: CreateSeoTransformPageDataOptions\n): (pageData: VitePressPageData) => void {\n return function transformPageData(pageData: VitePressPageData): void {\n try {\n const { head, title, description, setPageTitle } = buildVitePressSeoHead(pageData, options)\n\n pageData.frontmatter = pageData.frontmatter ?? {}\n const existing = (pageData.frontmatter.head as VitePressHeadConfig[] | undefined) ?? []\n pageData.frontmatter.head = [...existing, ...head]\n\n // Drive VitePress's own description meta from our resolved value so it is\n // not duplicated by the site-level default. (VitePress reads\n // pageData.description BEFORE transformPageData and renders the meta from\n // it, so overwriting the field here is the correct sink.)\n if (description) {\n pageData.description = description\n }\n\n // Make seo.yaml / per-type titles authoritative for <title> when the site\n // asks for it; VitePress then appends its titleTemplate.\n if (setPageTitle && title) {\n pageData.title = title\n }\n\n if (options.debug) {\n console.log(\n `[dcs-seo] transformPageData ${pageData.relativePath} → +${head.length} head tag(s)` +\n (setPageTitle && title ? ` (title=\"${title}\")` : '')\n )\n }\n } catch (err) {\n console.warn(\n `[dcs-seo] createSeoTransformPageData failed for ${pageData?.relativePath}; head left unchanged:`,\n err\n )\n }\n }\n}\n","/**\n * Loader for the `.dcs/pages.yaml` route manifest.\n *\n * `pages.yaml` is the canonical page registry maintained by the DCS portal and\n * by Copilot when scaffolding pages. For the build-time SEO emitter we only\n * need each route's `slug` and `path` (e.g. `{ slug: 'home', path: '/' }`).\n *\n * The parser is intentionally defensive: any missing/unparseable file or\n * malformed entry yields `null` (caller logs + no-ops) so a bad manifest can\n * never break a production build.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\n\n/** A single route extracted from `.dcs/pages.yaml`. */\nexport interface PageRouteEntry {\n /** Page slug, matching an entry in `seo.yaml` `pages.<slug>` (may be absent). */\n slug: string\n /** Route path, e.g. `/`, `/services`, `/blog/my-post`. */\n path: string\n /**\n * Human title from the manifest (e.g. \"Kitchen Cabinet Refresh\"). Used as the\n * per-route title fallback when `seo.yaml` has no entry for this page, so\n * un-configured routes (e.g. blog posts) get unique titles. Optional.\n */\n title?: string\n}\n\n/** Shape of the relevant slice of `.dcs/pages.yaml`. */\ninterface RawPagesManifest {\n pages?: Array<{ slug?: unknown; path?: unknown; title?: unknown }>\n}\n\n/**\n * Resolve and parse `.dcs/pages.yaml` from one of the usual locations.\n *\n * Mirrors the `.dcs` path resolution used elsewhere in the package (project\n * root, parent dir for VitePress-style nesting, and cwd).\n *\n * @returns the list of `{ slug, path }` routes, or `null` if the file is\n * absent, unreadable, unparseable, or contains no usable page entries.\n */\nexport function loadPagesManifest(\n projectRoot: string,\n relativePagesPath: string,\n debug = false\n): PageRouteEntry[] | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativePagesPath),\n path.resolve(projectRoot, '..', relativePagesPath),\n path.resolve(process.cwd(), relativePagesPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.warn('[dcs-seo] No pages.yaml found at:')\n possiblePaths.forEach((p) => console.warn(` - ${p}`))\n }\n return null\n }\n\n let raw: RawPagesManifest\n try {\n raw = yaml.load(fs.readFileSync(foundPath, 'utf8')) as RawPagesManifest\n } catch (error) {\n console.warn(`[dcs-seo] Failed to parse ${foundPath}:`, error)\n return null\n }\n\n const entries = parsePagesManifest(raw)\n if (!entries) {\n console.warn(`[dcs-seo] pages.yaml at ${foundPath} has no usable page entries`)\n return null\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${entries.length} routes from ${foundPath}`)\n }\n return entries\n}\n\n/**\n * Extract `{ slug, path }` routes from an already-parsed manifest object.\n * Exposed separately so tests can exercise it without touching the filesystem.\n *\n * Entries missing a string `path` are skipped; a missing slug falls back to the\n * empty string (so the route still gets baked, using global SEO defaults).\n */\nexport function parsePagesManifest(raw: unknown): PageRouteEntry[] | null {\n if (!raw || typeof raw !== 'object') return null\n const pages = (raw as RawPagesManifest).pages\n if (!Array.isArray(pages)) return null\n\n const routes: PageRouteEntry[] = []\n for (const entry of pages) {\n if (!entry || typeof entry !== 'object') continue\n const p = (entry as { path?: unknown }).path\n if (typeof p !== 'string' || p.length === 0) continue\n const slug = (entry as { slug?: unknown }).slug\n const title = (entry as { title?: unknown }).title\n routes.push({\n slug: typeof slug === 'string' ? slug : '',\n path: p,\n ...(typeof title === 'string' && title.length > 0 ? { title } : {}),\n })\n }\n\n return routes.length > 0 ? routes : null\n}\n"]}
|