@duffcloudservices/cms 0.7.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-FUNIALH6.js → chunk-UPAMLKOQ.js} +33 -14
- package/dist/chunk-UPAMLKOQ.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 +245 -88
- package/dist/plugins/index.js.map +1 -1
- package/dist/seo/index.d.ts +2 -2
- package/dist/seo/index.js +1 -1
- package/dist/{vitepressTransform-y8Ru9elj.d.ts → vitepressTransform-DeEzgGWU.d.ts} +10 -2
- package/package.json +1 -1
- package/dist/chunk-FUNIALH6.js.map +0 -1
|
@@ -637,10 +637,13 @@ function parsePagesManifest(raw) {
|
|
|
637
637
|
if (typeof p !== "string" || p.length === 0) continue;
|
|
638
638
|
const slug = entry.slug;
|
|
639
639
|
const title = entry.title;
|
|
640
|
+
const lastmodRaw = entry.lastmod ?? entry.lastUpdated ?? entry.dateModified;
|
|
641
|
+
const lastmod = typeof lastmodRaw === "string" && lastmodRaw.length > 0 ? lastmodRaw : void 0;
|
|
640
642
|
routes.push({
|
|
641
643
|
slug: typeof slug === "string" ? slug : "",
|
|
642
644
|
path: p,
|
|
643
|
-
...typeof title === "string" && title.length > 0 ? { title } : {}
|
|
645
|
+
...typeof title === "string" && title.length > 0 ? { title } : {},
|
|
646
|
+
...lastmod ? { lastmod } : {}
|
|
644
647
|
});
|
|
645
648
|
}
|
|
646
649
|
return routes.length > 0 ? routes : null;
|
|
@@ -649,7 +652,15 @@ function parsePagesManifest(raw) {
|
|
|
649
652
|
// src/seo/sitemap.ts
|
|
650
653
|
var XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>';
|
|
651
654
|
var URLSET_NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
|
|
652
|
-
var AI_BOTS = [
|
|
655
|
+
var AI_BOTS = [
|
|
656
|
+
"GPTBot",
|
|
657
|
+
"ClaudeBot",
|
|
658
|
+
"PerplexityBot",
|
|
659
|
+
"Google-Extended",
|
|
660
|
+
"CCBot",
|
|
661
|
+
"OAI-SearchBot",
|
|
662
|
+
"Applebot-Extended"
|
|
663
|
+
];
|
|
653
664
|
function trimTrailingSlash(url) {
|
|
654
665
|
return url.replace(/\/+$/, "");
|
|
655
666
|
}
|
|
@@ -712,8 +723,9 @@ function buildSitemapXml(params) {
|
|
|
712
723
|
} = params;
|
|
713
724
|
const excludeSet = new Set(exclude);
|
|
714
725
|
const noindexSet = new Set(noindex);
|
|
726
|
+
const siteLastmod = validW3CLastmod(lastmod);
|
|
715
727
|
const seen = /* @__PURE__ */ new Set();
|
|
716
|
-
const
|
|
728
|
+
const entries = [];
|
|
717
729
|
for (const route of routes) {
|
|
718
730
|
if (!isRouteIndexable(route, seoConfig, { exclude: excludeSet, noindex: noindexSet, excludedGlobs })) {
|
|
719
731
|
continue;
|
|
@@ -722,15 +734,17 @@ function buildSitemapXml(params) {
|
|
|
722
734
|
if (!loc) continue;
|
|
723
735
|
if (seen.has(loc)) continue;
|
|
724
736
|
seen.add(loc);
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
737
|
+
const perPage = validW3CLastmod(route.lastmod);
|
|
738
|
+
entries.push({ loc, lastmod: perPage ?? siteLastmod });
|
|
739
|
+
}
|
|
740
|
+
if (entries.length === 0) return "";
|
|
741
|
+
const urls = entries.map(({ loc, lastmod: lm }) => {
|
|
742
|
+
const lastmodLine = lm ? `
|
|
743
|
+
<lastmod>${escapeXml(lm)}</lastmod>` : "";
|
|
744
|
+
return ` <url>
|
|
732
745
|
<loc>${escapeXml(loc)}</loc>${lastmodLine}
|
|
733
|
-
</url
|
|
746
|
+
</url>`;
|
|
747
|
+
}).join("\n");
|
|
734
748
|
return `${XML_HEADER}
|
|
735
749
|
<urlset xmlns="${URLSET_NS}">
|
|
736
750
|
${urls}
|
|
@@ -773,6 +787,11 @@ function buildRobotsTxt(params) {
|
|
|
773
787
|
}
|
|
774
788
|
return lines.join("\n") + "\n";
|
|
775
789
|
}
|
|
790
|
+
function isHandAuthoredRobotsAcceptable(text) {
|
|
791
|
+
if (!text || !text.trim()) return false;
|
|
792
|
+
if (/^\s*</.test(text)) return false;
|
|
793
|
+
return /^[ \t]*User-agent:[ \t]*\*[ \t]*$/im.test(text);
|
|
794
|
+
}
|
|
776
795
|
function buildLlmsTxt(params) {
|
|
777
796
|
const { routes, siteUrl, seoConfig, exclude = [], noindex = [], excludedGlobs = [] } = params;
|
|
778
797
|
const excludeSet = new Set(exclude);
|
|
@@ -998,6 +1017,6 @@ function createSeoTransformPageData(options) {
|
|
|
998
1017
|
};
|
|
999
1018
|
}
|
|
1000
1019
|
|
|
1001
|
-
export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, deriveSameAs, escapeJsonLd, filterRealFaq, filterRealReviews, findLocalBusinessSchema, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, graphAbsorbs, graphIds, isLocalBusinessType, isRouteIndexable, loadPagesManifest, matchesExcludedGlob, parsePagesManifest, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags };
|
|
1002
|
-
//# sourceMappingURL=chunk-
|
|
1003
|
-
//# sourceMappingURL=chunk-
|
|
1020
|
+
export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, deriveSameAs, escapeJsonLd, filterRealFaq, filterRealReviews, findLocalBusinessSchema, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, graphAbsorbs, graphIds, isHandAuthoredRobotsAcceptable, isLocalBusinessType, isRouteIndexable, loadPagesManifest, matchesExcludedGlob, parsePagesManifest, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags };
|
|
1021
|
+
//# sourceMappingURL=chunk-UPAMLKOQ.js.map
|
|
1022
|
+
//# sourceMappingURL=chunk-UPAMLKOQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/seo/schemaGraph.ts","../src/seo/headTags.ts","../src/seo/spliceHeadHtml.ts","../src/seo/pagesManifest.ts","../src/seo/sitemap.ts","../src/seo/vitepressTransform.ts"],"names":["path"],"mappings":"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","file":"chunk-UPAMLKOQ.js","sourcesContent":["/**\n * Framework-free schema.org JSON-LD builders shared by BOTH SEO emit paths.\n *\n * This is the single home for the structured-data \"completeness\" logic the DCS\n * SEO factory needs: a cross-linked `@graph` knowledge spine (Organization /\n * WebSite / LocalBusiness), `BreadcrumbList`, `BlogPosting`, `FAQPage`, and the\n * HONEST `Review` / `aggregateRating` injection. Both call sites consume it:\n *\n * - the Vue-SPA per-route emitter (`buildHeadTags` in `./headTags.ts`), and\n * - the VitePress `transformPageData` factory (`./vitepressTransform.ts`).\n *\n * Every export here is a PURE function of plain inputs (no Vue, no Vite, no\n * filesystem) returning plain `schema.org` objects, so the two paths stay\n * byte-identical and the builder logic is never duplicated.\n *\n * ── HONESTY (non-negotiable) ────────────────────────────────────────────────\n * `Review`, `aggregateRating`, and `FAQPage` are emitted ONLY from REAL data:\n * • Review/aggregateRating come from `.dcs/content.yaml` review items that\n * carry a numeric `rating`, a non-empty `text`, and an `authorName`.\n * • FAQPage comes from a structured Q&A source (frontmatter `faq: [{q,a}]` or\n * `.dcs/faq.yaml`) where each entry has a non-empty question AND answer.\n * When the real source is missing or empty the builder short-circuits to an\n * empty result — it NEVER synthesises a placeholder rating, review, or Q&A. A\n * fabricated rating is a legal + trust risk; this module makes fabrication\n * impossible by construction (no defaults, no invented counts).\n */\n\nimport type { GlobalSeoConfig, SeoSchemaConfig } from '../types/seo'\n\nconst SCHEMA_CONTEXT = 'https://schema.org'\n\n/** A plain schema.org object (already shaped for JSON-LD serialisation). */\nexport type SchemaObject = Record<string, unknown>\n\n/**\n * The schema.org `@type`s that, when present in `global.schemas`, are treated as\n * the site's LocalBusiness node and ABSORBED into the `@graph` spine (promoted\n * with an `@id` + `parentOrganization` ref) rather than emitted as a second,\n * unlinked copy. This is the union of `LocalBusiness` and its common subtypes\n * used across DCS sites (Iron Oak `HomeAndConstructionBusiness`, kept\n * `MedicalBusiness`, ktbraunlaw `LegalService`).\n */\nconst LOCAL_BUSINESS_TYPES = new Set<string>([\n 'LocalBusiness',\n 'HomeAndConstructionBusiness',\n 'GeneralContractor',\n 'HVACBusiness',\n 'Plumber',\n 'Electrician',\n 'RoofingContractor',\n 'HousePainter',\n 'MovingCompany',\n 'MedicalBusiness',\n 'Dentist',\n 'Physician',\n 'MedicalClinic',\n 'LegalService',\n 'Attorney',\n 'Notary',\n 'AccountingService',\n 'FinancialService',\n 'InsuranceAgency',\n 'RealEstateAgent',\n 'Store',\n 'ProfessionalService',\n 'AutomotiveBusiness',\n 'AutoRepair',\n 'BeautySalon',\n 'HairSalon',\n 'HealthAndBeautyBusiness',\n 'FoodEstablishment',\n 'Restaurant',\n])\n\n/** Return true when a schema `@type` is a LocalBusiness (sub)type. */\nexport function isLocalBusinessType(type: string | undefined): boolean {\n return !!type && LOCAL_BUSINESS_TYPES.has(type)\n}\n\n/**\n * A real review item, mirroring the shape stored in `.dcs/content.yaml` under a\n * `reviews.<key>.items` array (see `useReviewContent`). Only the fields the\n * honest Review/aggregateRating builder needs are typed here; extra fields are\n * ignored.\n */\nexport interface ReviewSource {\n rating?: unknown\n text?: unknown\n authorName?: unknown\n date?: unknown\n locationName?: unknown\n}\n\n/** A structured FAQ entry: `{ q, a }` (frontmatter) — honesty-gated. */\nexport interface FaqSource {\n q?: unknown\n a?: unknown\n /** Alternate keys some sources use (`question`/`answer`). */\n question?: unknown\n answer?: unknown\n}\n\n/** A single breadcrumb hop along the route to the current page. */\nexport interface BreadcrumbCrumb {\n /** Human-readable name (e.g. `Home`, `Services`, the post title). */\n name: string\n /** Absolute URL for this hop. */\n item: string\n}\n\n/** Blog-post metadata used to build a `BlogPosting`, from the SPA or VitePress. */\nexport interface BlogMeta {\n /** The headline / post title. */\n headline?: string\n /** ISO-ish publish date as authored (granularity preserved, e.g. `2026-01`). */\n datePublished?: string\n /** ISO-ish modified date, if distinct. */\n dateModified?: string\n /** Absolute canonical URL of the post (becomes `mainEntityOfPage`). */\n url?: string\n /** Header/social image URL for the post. */\n image?: string\n /** Short description / excerpt. */\n description?: string\n}\n\n// =============================================================================\n// @id derivation\n// =============================================================================\n\nfunction trimSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\n/** Stable `@id` anchors derived from the site URL (no new YAML required). */\nexport function graphIds(siteUrl: string): {\n organization: string\n website: string\n localBusiness: string\n} {\n const base = trimSlash(siteUrl)\n return {\n organization: `${base}/#organization`,\n website: `${base}/#website`,\n localBusiness: `${base}/#localbusiness`,\n }\n}\n\n// =============================================================================\n// social → sameAs\n// =============================================================================\n\n/**\n * Derive an `sameAs` array of absolute profile URLs from the global `social`\n * block. Returns `[]` when nothing is configured (so the key is omitted, never\n * emitted empty).\n */\nexport function deriveSameAs(global: GlobalSeoConfig): string[] {\n const s = global.social\n if (!s) return []\n const out: string[] = []\n const push = (u: string | undefined) => {\n if (u && u.trim()) out.push(u.trim())\n }\n // Each handle may already be a full URL or a bare slug/handle.\n if (s.facebook) push(asUrl(s.facebook, 'https://www.facebook.com/'))\n if (s.instagram) push(asUrl(stripAt(s.instagram), 'https://www.instagram.com/'))\n if (s.linkedin) push(asUrl(s.linkedin, 'https://www.linkedin.com/in/'))\n if (s.youtube) push(asUrl(s.youtube, 'https://www.youtube.com/'))\n if (s.github) push(asUrl(stripAt(s.github), 'https://github.com/'))\n if (s.twitter) push(asUrl(stripAt(s.twitter), 'https://twitter.com/'))\n return out\n}\n\nfunction stripAt(handle: string): string {\n return handle.startsWith('@') ? handle.slice(1) : handle\n}\n\nfunction asUrl(value: string, base: string): string {\n if (/^https?:\\/\\//i.test(value)) return value\n return `${base}${value.replace(/^\\/+/, '')}`\n}\n\n// =============================================================================\n// LocalBusiness extraction\n// =============================================================================\n\n/**\n * Find the LocalBusiness-subtype entry in `global.schemas`, if any. This is the\n * NAP-complete node a site already hand-authors; the graph builder absorbs it\n * (rather than letting `generateJsonLd` emit a second, unlinked copy).\n *\n * @returns the matching `SeoSchemaConfig` and its index, or `null`.\n */\nexport function findLocalBusinessSchema(\n global: GlobalSeoConfig\n): { schema: SeoSchemaConfig; index: number } | null {\n const schemas = global.schemas ?? []\n for (let i = 0; i < schemas.length; i++) {\n if (isLocalBusinessType(schemas[i]?.type)) {\n return { schema: schemas[i], index: i }\n }\n }\n return null\n}\n\n/**\n * True when `buildGlobalGraph` provides this schema canonically, so it must NOT\n * also be emitted standalone: the LocalBusiness subtype it folds in, or a global\n * `Organization` / `WebSite` (the graph emits cross-linked versions of both).\n *\n * Shared by both emit paths so the de-dup rule is identical.\n */\nexport function graphAbsorbs(schema: SeoSchemaConfig, global: GlobalSeoConfig): boolean {\n if (schema.type === 'Organization' || schema.type === 'WebSite') return true\n const found = findLocalBusinessSchema(global)\n return !!found && found.schema === schema\n}\n\n// =============================================================================\n// Honest Reviews / aggregateRating\n// =============================================================================\n\n/** One normalised, REAL review (passed the honesty gate). */\nexport interface NormalisedReview {\n rating: number\n text: string\n authorName: string\n date?: string\n locationName?: string\n}\n\n/**\n * Keep ONLY real review items: a numeric `rating`, a non-empty `text`, and a\n * non-empty `authorName`. Anything missing any of the three is dropped (never\n * back-filled). Returns `[]` when nothing qualifies.\n */\nexport function filterRealReviews(items: ReviewSource[] | undefined): NormalisedReview[] {\n if (!Array.isArray(items)) return []\n const out: NormalisedReview[] = []\n for (const item of items) {\n if (!item || typeof item !== 'object') continue\n const ratingNum = Number((item as ReviewSource).rating)\n const text = typeof item.text === 'string' ? item.text.trim() : ''\n const authorName = typeof item.authorName === 'string' ? item.authorName.trim() : ''\n if (!Number.isFinite(ratingNum) || ratingNum <= 0) continue\n if (!text) continue\n if (!authorName) continue\n out.push({\n rating: ratingNum,\n text,\n authorName,\n ...(typeof item.date === 'string' && item.date.trim() ? { date: item.date.trim() } : {}),\n ...(typeof item.locationName === 'string' && item.locationName.trim()\n ? { locationName: item.locationName.trim() }\n : {}),\n })\n }\n return out\n}\n\n/** A `Review` schema.org node array + an `aggregateRating`, both honesty-gated. */\nexport interface ReviewSchemaParts {\n /** `Review` nodes (one per real item). Empty when no real reviews. */\n review: SchemaObject[]\n /** `AggregateRating` node, or `undefined` when no real reviews. */\n aggregateRating?: SchemaObject\n}\n\n/** The schema.org `bestRating`/`worstRating` bounds for DCS review ratings. */\nconst BEST_RATING = 5\nconst WORST_RATING = 1\n\n/** Clamp a rating into `[WORST_RATING, BEST_RATING]` (F3). */\nfunction clampRating(value: number): number {\n if (value < WORST_RATING) return WORST_RATING\n if (value > BEST_RATING) return BEST_RATING\n return value\n}\n\n/**\n * Build `Review[]` + `aggregateRating` from REAL review items only.\n *\n * Each `ratingValue` is CLAMPED to `[worstRating, bestRating]` (F3): a source\n * rating of 7 (or 0/-1) would otherwise emit an out-of-range, schema-invalid\n * value (and skew the aggregate). The aggregate mean is computed from the SAME\n * clamped values so the headline rating matches the displayed reviews.\n *\n * `aggregateRating.ratingValue` is the mean (rounded to one decimal),\n * `reviewCount`/`ratingCount` equal the real item count. When there are zero\n * real items, BOTH are omitted — never an invented rating or count.\n */\nexport function buildReviewSchemaParts(items: ReviewSource[] | undefined): ReviewSchemaParts {\n const real = filterRealReviews(items)\n if (real.length === 0) return { review: [] }\n\n const clamped = real.map((r) => ({ ...r, rating: clampRating(r.rating) }))\n\n const review: SchemaObject[] = clamped.map((r) => ({\n '@type': 'Review',\n reviewRating: {\n '@type': 'Rating',\n ratingValue: r.rating,\n bestRating: BEST_RATING,\n worstRating: WORST_RATING,\n },\n author: { '@type': 'Person', name: r.authorName },\n reviewBody: r.text,\n ...(r.date ? { datePublished: r.date } : {}),\n }))\n\n const sum = clamped.reduce((acc, r) => acc + r.rating, 0)\n const mean = Math.round((sum / clamped.length) * 10) / 10\n\n const aggregateRating: SchemaObject = {\n '@type': 'AggregateRating',\n ratingValue: mean,\n reviewCount: clamped.length,\n ratingCount: clamped.length,\n bestRating: BEST_RATING,\n worstRating: WORST_RATING,\n }\n\n return { review, aggregateRating }\n}\n\n// =============================================================================\n// Global @graph spine\n// =============================================================================\n\n/**\n * Build the cross-linked global knowledge graph as ONE JSON-LD object carrying\n * a `@graph` array: `Organization`, `WebSite` (publisher → org), and the site's\n * `LocalBusiness` node (parentOrganization → org), auto-derived from\n * `global.siteName` / `global.siteUrl` / `global.images.logo` / `global.social`.\n * Requires NO new YAML.\n *\n * The LocalBusiness node ABSORBS the existing `global.schemas[*]` LocalBusiness\n * subtype (its NAP/geo/hours/offers are preserved verbatim) and is promoted with\n * an `@id`; honest `review` + `aggregateRating` are merged in when supplied.\n *\n * Returns `[]` when there is no `siteUrl` (no stable `@id` anchor possible), so\n * the existing per-schema emission is left untouched for un-configured sites.\n *\n * @param opts.reviews REAL review items (from content.yaml) for the business\n * node. Optional; when omitted/empty no Review/aggregateRating is added.\n */\nexport function buildGlobalGraph(\n global: GlobalSeoConfig,\n opts: { reviews?: ReviewSource[] } = {}\n): SchemaObject[] {\n const siteUrl = global.siteUrl ? trimSlash(global.siteUrl) : ''\n if (!siteUrl) return []\n\n const ids = graphIds(siteUrl)\n const name = global.siteName || ''\n const logo = global.images?.logo\n const sameAs = deriveSameAs(global)\n\n // ── Organization ─────────────────────────────────────────────────────────\n const organization: SchemaObject = {\n '@type': 'Organization',\n '@id': ids.organization,\n ...(name ? { name } : {}),\n url: `${siteUrl}/`,\n ...(logo ? { logo } : {}),\n ...(sameAs.length ? { sameAs } : {}),\n }\n\n // ── WebSite ────────────────────────────────────────────────────────────────\n const website: SchemaObject = {\n '@type': 'WebSite',\n '@id': ids.website,\n url: `${siteUrl}/`,\n ...(name ? { name } : {}),\n publisher: { '@id': ids.organization },\n }\n\n const graph: SchemaObject[] = [organization, website]\n\n // ── LocalBusiness (absorb the hand-authored NAP node, add @id + parent) ────\n const found = findLocalBusinessSchema(global)\n if (found) {\n const props = found.schema.properties ?? {}\n const localBusiness: SchemaObject = {\n '@type': found.schema.type,\n '@id': ids.localBusiness,\n // Preserve the hand-authored NAP/geo/hours/offers verbatim…\n ...props,\n // …but ensure the cross-link to the Organization is present.\n parentOrganization: { '@id': ids.organization },\n }\n // Default url to the site root when the node doesn't set its own.\n if (localBusiness.url == null) localBusiness.url = `${siteUrl}/`\n\n // Honest reviews → merge Review[] + aggregateRating into the business node.\n const reviewParts = buildReviewSchemaParts(opts.reviews)\n if (reviewParts.review.length > 0) {\n localBusiness.review = reviewParts.review\n if (reviewParts.aggregateRating) {\n localBusiness.aggregateRating = reviewParts.aggregateRating\n }\n }\n\n graph.push(localBusiness)\n }\n\n return [\n {\n '@context': SCHEMA_CONTEXT,\n '@graph': graph,\n },\n ]\n}\n\n// =============================================================================\n// BreadcrumbList\n// =============================================================================\n\n/**\n * Build a `BreadcrumbList` from an ordered trail of crumbs (Home → … → current).\n *\n * Honesty/cleanliness rules:\n * • The home page (a trail of length ≤ 1, i.e. just \"Home\") emits NOTHING —\n * a single-item breadcrumb is noise.\n * • Positions are 1-based and contiguous.\n *\n * @returns a single-element array `[BreadcrumbList]`, or `[]` for the home page.\n */\nexport function buildBreadcrumbList(trail: BreadcrumbCrumb[]): SchemaObject[] {\n if (!Array.isArray(trail) || trail.length <= 1) return []\n\n const itemListElement = trail.map((crumb, i) => ({\n '@type': 'ListItem',\n position: i + 1,\n name: crumb.name,\n item: crumb.item,\n }))\n\n return [\n {\n '@context': SCHEMA_CONTEXT,\n '@type': 'BreadcrumbList',\n itemListElement,\n },\n ]\n}\n\n/**\n * Derive a Home → … → current breadcrumb trail from a route path.\n *\n * Each path segment becomes a crumb; the segment label comes from\n * `titles[segmentPath]` (an absolute-route → title map) when present, else a\n * slug-derived Title Case of the segment (never blank).\n *\n * @param route the page route, e.g. `/`, `/services`, `/blog/my-post`.\n * @param siteUrl normalised site URL (no trailing slash).\n * @param titles optional map of route → human title for intermediate hops\n * AND the leaf (e.g. `{ '/': 'Home', '/blog': 'Blog',\n * '/blog/my-post': 'My Post' }`). Missing entries fall back to\n * a slug-derived title.\n * @param homeName label for the root crumb (default `Home`).\n */\nexport function breadcrumbTrailFromRoute(\n route: string,\n siteUrl: string,\n titles: Record<string, string> = {},\n homeName = 'Home'\n): BreadcrumbCrumb[] {\n const base = trimSlash(siteUrl)\n const home: BreadcrumbCrumb = {\n name: titles['/'] || homeName,\n item: `${base}/`,\n }\n const normalised = (route || '/').split('?')[0].split('#')[0]\n if (normalised === '/' || normalised === '') return [home]\n\n const segments = normalised.replace(/^\\/+/, '').replace(/\\/+$/, '').split('/')\n const trail: BreadcrumbCrumb[] = [home]\n let acc = ''\n for (const seg of segments) {\n acc += `/${seg}`\n trail.push({\n name: titles[acc] || slugToTitle(seg),\n item: `${base}${acc}`,\n })\n }\n return trail\n}\n\n/** Title-case a slug segment (`my-post` → `My Post`); never blank. */\nexport function slugToTitle(slug: string): string {\n const cleaned = (slug || '').replace(/[-_]+/g, ' ').trim()\n if (!cleaned) return slug || ''\n return cleaned\n .split(/\\s+/)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ')\n}\n\n// =============================================================================\n// BlogPosting\n// =============================================================================\n\n/**\n * Build a single `BlogPosting`.\n *\n * `author`/`publisher` are emitted as SELF-CONTAINED Organization nodes — each\n * carrying its `@id`, plus a concrete `name` and `url`. This is the F2 fix: a\n * bare `{ '@id': … }` ref DANGLES when `emitBlogPosting` runs WITHOUT the global\n * `@graph` (the VitePress path with `emitGraph: false`), because nothing then\n * defines the Organization node that `@id` points at. An inline node is valid\n * standalone AND still carries the `@id`, so when the `@graph` IS present a\n * consumer merges the two by `@id` (no duplication, no dangle) either way.\n *\n * `datePublished` granularity is preserved exactly as authored (e.g. a\n * year-month `2026-01` is NOT padded to a fabricated day).\n *\n * @param global Optional global config — supplies the publisher/author `name`.\n * When omitted (or no `siteName`), the inline node still carries `@id` + `url`\n * so the reference never dangles.\n * @returns `[BlogPosting]`, or `[]` when there is no headline (nothing to emit).\n */\nexport function buildBlogPosting(\n meta: BlogMeta,\n siteUrl: string,\n global?: GlobalSeoConfig\n): SchemaObject[] {\n const headline = typeof meta.headline === 'string' ? meta.headline.trim() : ''\n if (!headline) return []\n\n const ids = graphIds(siteUrl)\n const base = trimSlash(siteUrl)\n const name = global?.siteName?.trim()\n\n // Self-contained publisher/author node: keeps the @id (graph-mergeable) AND\n // inlines name/url so it never dangles when no @graph spine is emitted.\n const orgNode: SchemaObject = {\n '@type': 'Organization',\n '@id': ids.organization,\n ...(name ? { name } : {}),\n ...(base ? { url: `${base}/` } : {}),\n }\n\n const node: SchemaObject = {\n '@context': SCHEMA_CONTEXT,\n '@type': 'BlogPosting',\n headline,\n author: orgNode,\n publisher: orgNode,\n }\n if (meta.description) node.description = meta.description\n if (meta.datePublished) node.datePublished = meta.datePublished\n if (meta.dateModified) node.dateModified = meta.dateModified\n if (meta.image) node.image = meta.image\n if (meta.url) node.mainEntityOfPage = meta.url\n\n return [node]\n}\n\n// =============================================================================\n// FAQPage (honesty-gated)\n// =============================================================================\n\n/** One normalised, REAL FAQ pair (passed the honesty gate). */\nexport interface NormalisedFaq {\n question: string\n answer: string\n}\n\n/**\n * Keep ONLY real FAQ entries: a non-empty question AND a non-empty answer.\n * Tolerant of both `{ q, a }` and `{ question, answer }` shapes. Returns `[]`\n * when nothing qualifies (so no FAQPage is emitted).\n */\nexport function filterRealFaq(entries: FaqSource[] | undefined): NormalisedFaq[] {\n if (!Array.isArray(entries)) return []\n const out: NormalisedFaq[] = []\n for (const entry of entries) {\n if (!entry || typeof entry !== 'object') continue\n const q = pickString(entry.q ?? entry.question)\n const a = pickString(entry.a ?? entry.answer)\n if (!q || !a) continue\n out.push({ question: q, answer: a })\n }\n return out\n}\n\nfunction pickString(value: unknown): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * Build a `FAQPage` from a STRUCTURED Q&A source ONLY (frontmatter `faq:` or a\n * `.dcs/faq.yaml`). A `<FaqPage />` Vue component is NOT a valid source — there\n * is no machine-readable Q&A to read, so this correctly emits nothing.\n *\n * @returns `[FAQPage]` with one `Question` per real entry, or `[]` when there is\n * no valid structured Q&A.\n */\nexport function buildFaqPage(entries: FaqSource[] | undefined): SchemaObject[] {\n const real = filterRealFaq(entries)\n if (real.length === 0) return []\n\n return [\n {\n '@context': SCHEMA_CONTEXT,\n '@type': 'FAQPage',\n mainEntity: real.map((f) => ({\n '@type': 'Question',\n name: f.question,\n acceptedAnswer: { '@type': 'Answer', text: f.answer },\n })),\n },\n ]\n}\n\n// =============================================================================\n// content.yaml review lookup (tolerant of per-site key naming)\n// =============================================================================\n\n/**\n * The relevant slice of `.dcs/content.yaml`: flat dotted keys live under\n * `global` and per-page maps. Reviews are stored under `reviews.<key>.items`\n * (e.g. iron-oak `reviews.reviews.items`, kept `reviews.testimonials.items`),\n * so the key differs per site — `findReviewItemsForPage` scans tolerantly.\n */\nexport interface ContentConfig {\n global?: Record<string, unknown>\n pages?: Record<string, Record<string, unknown>>\n}\n\n/** A `reviews.<key>.items` array is the canonical real-review source. */\nfunction isReviewItemsKey(key: string): boolean {\n return /^reviews\\..+\\.items$/.test(key)\n}\n\n/**\n * Find the REAL review items for a page from `.dcs/content.yaml`, tolerant of\n * the per-site key naming (`reviews.reviews.items` vs `reviews.testimonials.\n * items`). Prefers a page-scoped block, then falls back to global; within a\n * block it picks the FIRST `reviews.*.items` array (sites carry one canonical\n * source). Returns `[]` when none is present — no synthesis.\n *\n * The honesty filter still runs downstream (`buildReviewSchemaParts`), so a\n * non-empty return here is NOT yet a guarantee of emission; items without a\n * rating/text/authorName are dropped there.\n */\nexport function findReviewItemsForPage(\n content: ContentConfig | undefined,\n pageSlug: string\n): ReviewSource[] {\n if (!content) return []\n const fromBlock = (block: Record<string, unknown> | undefined): ReviewSource[] | null => {\n if (!block) return null\n for (const key of Object.keys(block)) {\n if (isReviewItemsKey(key) && Array.isArray(block[key])) {\n return block[key] as ReviewSource[]\n }\n }\n return null\n }\n const page = content.pages?.[pageSlug]\n return fromBlock(page) ?? fromBlock(content.global) ?? []\n}\n\n// =============================================================================\n// OG-URL absolutization\n// =============================================================================\n\n/**\n * Guarantee an absolute URL. When `value` is already absolute (`http(s)://`) it\n * is returned unchanged; when it is a site-relative path it is joined onto the\n * (trailing-slash-trimmed) `siteUrl`; when it is empty the `fallback` (typically\n * the already-absolute canonical) is returned.\n */\nexport function absolutizeUrl(\n value: string | undefined,\n siteUrl: string | undefined,\n fallback = ''\n): string {\n const v = (value ?? '').trim()\n if (!v) return fallback\n if (/^https?:\\/\\//i.test(v)) return v\n const base = trimSlash(siteUrl ?? '')\n if (!base) return v\n return `${base}/${v.replace(/^\\/+/, '')}`\n}\n","/**\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'\nimport {\n buildGlobalGraph,\n buildBreadcrumbList,\n buildBlogPosting,\n buildFaqPage,\n graphAbsorbs,\n absolutizeUrl,\n type BreadcrumbCrumb,\n type ReviewSource,\n type FaqSource,\n type BlogMeta,\n} from './schemaGraph'\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 // ── Schema-completeness inputs (additive; OFF unless supplied) ─────────────\n // The build-time static-HTML emitter opts into these to bake the cross-linked\n // @graph spine + BreadcrumbList + (honest) BlogPosting/FAQPage/Review. The\n // runtime `useSEO` composable does NOT pass them, so its byte output is\n // unchanged. See `./schemaGraph.ts`.\n\n /**\n * Emit the global `@graph` spine (Organization + WebSite + the promoted\n * LocalBusiness node, cross-linked by `@id`) PREPENDED before the per-schema\n * JSON-LD. When true, the LocalBusiness subtype already present in\n * `global.schemas` is ABSORBED into the graph (not emitted a second time).\n * Default `false`.\n */\n emitGraph?: boolean\n /**\n * Ordered Home → … → current breadcrumb trail (absolute item URLs). When it\n * has more than one hop a `BreadcrumbList` is emitted; the home page (≤ 1 hop)\n * emits none.\n */\n breadcrumbTrail?: BreadcrumbCrumb[]\n /**\n * REAL review items (from `.dcs/content.yaml`) for the LocalBusiness node.\n * Honest Review[] + aggregateRating are emitted ONLY for items with a numeric\n * rating + non-empty text + authorName; empty/missing ⇒ nothing.\n */\n reviews?: ReviewSource[]\n /**\n * Structured FAQ pairs (frontmatter `faq:` / `.dcs/faq.yaml`). A `FAQPage` is\n * emitted ONLY when at least one entry has a non-empty question AND answer.\n */\n faq?: FaqSource[]\n /**\n * Blog-post metadata. When `headline` is present a `BlogPosting` is emitted —\n * UNLESS the page's own schemas already hand-author a `BlogPosting` (then the\n * builder defers to the authored copy to avoid duplication).\n */\n blogMeta?: BlogMeta\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 // og:url is absolutized: an already-absolute og.url is used verbatim, a\n // site-relative og.url is joined onto the site URL, and a missing og.url falls\n // back to the (already-absolute) canonical. This stops a relative per-page\n // `openGraph.url` leaking a non-absolute og:url into the baked <head>.\n tags.push({ property: 'og:url', content: absolutizeUrl(og.url, global.siteUrl, 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 // Guarantee the canonical is absolute even if a page set a relative one.\n link.push({ rel: 'canonical', href: absolutizeUrl(resolved.canonical, global.siteUrl, 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 = buildJsonLd(resolved, global, overrides)\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/**\n * Assemble the JSON-LD objects for a page, weaving in the schema-completeness\n * builders (`./schemaGraph.ts`) ONLY when the caller opts in via `overrides`.\n *\n * Order (when opted in): `@graph` spine → per-page/global schemas →\n * `BreadcrumbList` → `BlogPosting` → `FAQPage`. With no opt-in inputs this is\n * exactly the historical `overrides.schemas ?? generateJsonLd(...)`, so the\n * runtime composable's bytes are unchanged.\n *\n * Anti-duplication:\n * • When `emitGraph` absorbs the LocalBusiness subtype into the spine, that\n * subtype is excluded from the per-schema emission (no second copy).\n * • A builder `BlogPosting` is skipped when the page already hand-authors one.\n */\nfunction buildJsonLd(\n resolved: ResolvedPageSeo,\n global: GlobalSeoConfig,\n overrides?: HeadTagOverrides\n): object[] {\n // A full schema replacement still wins (back-compat with applyHead overrides).\n if (overrides?.schemas) return overrides.schemas\n\n const out: object[] = []\n\n const emitGraph = overrides?.emitGraph === true\n\n // Per-schema config to emit normally. When the graph absorbs the LocalBusiness\n // subtype, drop it here so it is not emitted twice.\n let perSchema: SeoSchemaConfig[] = resolved.schemas\n if (emitGraph) {\n const graph = buildGlobalGraph(global, { reviews: overrides?.reviews })\n if (graph.length > 0) {\n out.push(...graph)\n // Remove the schemas the @graph now provides canonically — the\n // LocalBusiness subtype it folded in, plus any global Organization/WebSite\n // (the graph emits cross-linked ones) — to avoid duplicate nodes.\n perSchema = resolved.schemas.filter((s) => !graphAbsorbs(s, global))\n }\n }\n\n out.push(...generateJsonLd(perSchema, global))\n\n // BreadcrumbList from the supplied trail (home ⇒ none).\n if (overrides?.breadcrumbTrail) {\n out.push(...buildBreadcrumbList(overrides.breadcrumbTrail))\n }\n\n // BlogPosting — only when the page does not already hand-author one.\n if (overrides?.blogMeta && global.siteUrl) {\n const alreadyAuthored = resolved.schemas.some((s) => s.type === 'BlogPosting')\n if (!alreadyAuthored) {\n out.push(...buildBlogPosting(overrides.blogMeta, global.siteUrl, global))\n }\n }\n\n // FAQPage — honesty-gated (only real structured Q&A).\n if (overrides?.faq) {\n out.push(...buildFaqPage(overrides.faq))\n }\n\n return out\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 a JSON-LD string for safe inclusion inside an HTML `<script>` element.\n *\n * A `<script type=\"application/ld+json\">` is a DATA block, but the HTML parser\n * still scans its raw text for `</script` (and `<!--`) — so untrusted CMS free\n * text (review bodies, FAQ Q&A, blog descriptions) carrying `</script>` could\n * break out of the script and inject markup (stored XSS).\n *\n * Per OWASP \"JSON in an HTML context\", we escape the three HTML-significant\n * characters as their `\\uXXXX` JSON escapes. Because these characters only ever\n * appear INSIDE JSON string literals (never as JSON structure), the result is\n * still byte-for-byte valid JSON that `JSON.parse` round-trips:\n * `<` → `<` (defeats `</script>` and `<!--` breakout)\n * `>` → `>`\n * `&` → `&`\n *\n * Exported so BOTH static-SEO sinks (the SPA `spliceHeadHtml` path AND the\n * VitePress `transformPageData` head emit) share one guard.\n */\nexport function escapeJsonLd(json: string): string {\n return json\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\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 * 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 * Per-page last-modification date for the sitemap `<lastmod>`. A W3C-datetime\n * string authored in `pages.yaml` (`lastmod` / `lastUpdated` / `dateModified`).\n * Validated + de-fabricated at emit time; an invalid or absent value falls\n * back to the site-wide lastmod. Never invented.\n */\n lastmod?: string\n}\n\n/** Shape of the relevant slice of `.dcs/pages.yaml`. */\ninterface RawPagesManifest {\n pages?: Array<{\n slug?: unknown\n path?: unknown\n title?: unknown\n lastmod?: unknown\n lastUpdated?: unknown\n dateModified?: unknown\n }>\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 const lastmodRaw =\n (entry as { lastmod?: unknown }).lastmod ??\n (entry as { lastUpdated?: unknown }).lastUpdated ??\n (entry as { dateModified?: unknown }).dateModified\n const lastmod =\n typeof lastmodRaw === 'string' && lastmodRaw.length > 0 ? lastmodRaw : undefined\n routes.push({\n slug: typeof slug === 'string' ? slug : '',\n path: p,\n ...(typeof title === 'string' && title.length > 0 ? { title } : {}),\n ...(lastmod ? { lastmod } : {}),\n })\n }\n\n return routes.length > 0 ? routes : null\n}\n","/**\n * Shared, PURE (fs-free) cores for the DCS site-file emitters:\n * `sitemap.xml`, `robots.txt`, and `llms.txt`.\n *\n * This is the SINGLE SOURCE OF TRUTH for the three site-wide static files. Both\n * cms's own `dcsSeoPlugin` (the \"factory\" emit path) and — as a follow-up on a\n * separate branch — kit-vite's `dcsSitemapPlugin` import these cores so their\n * outputs are byte-identical. The file name/shape deliberately mirror the\n * proven kit-vite emitter (`packages/kit-vite/src/sitemap.ts`) so the eventual\n * DRY port is a one-line re-export swap.\n *\n * These cores reuse cms's single SEO source of truth — `resolvePageSeo` for\n * each route's canonical + robots — so the `<loc>` written into `sitemap.xml`\n * (and the link written into `llms.txt`) is byte-identical to the\n * `<link rel=\"canonical\">` the SEO emitter bakes into each page's `<head>`. No\n * head-tag or manifest logic is forked here.\n *\n * Everything in this module is pure string-in / string-out and never throws.\n * The filesystem work lives in the plugin layer (`dcsSeoPlugin`), exactly like\n * the existing `headTags` / `pagesManifest` split — so these cores stay\n * Vue/Vite-free and unit-testable under jsdom.\n */\n\nimport { resolvePageSeo } from './headTags'\nimport type { PageRouteEntry } from './pagesManifest'\nimport type { SeoConfiguration } from '../types/seo'\n\n// =============================================================================\n// Shared helpers\n// =============================================================================\n\nconst XML_HEADER = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>'\nconst URLSET_NS = 'http://www.sitemaps.org/schemas/sitemap/0.9'\n\n/**\n * AI-crawler user agents emitted as explicit allow/deny groups in robots.txt so\n * AI discovery is opt-in-friendly (and explicitly gated off in preview). A site\n * can opt the whole tier out via `robots.aiBots: false`.\n */\nexport const AI_BOTS = [\n 'GPTBot',\n 'ClaudeBot',\n 'PerplexityBot',\n 'Google-Extended',\n 'CCBot',\n 'OAI-SearchBot',\n 'Applebot-Extended',\n] as const\n\n/** Trim a single trailing slash so joins never produce `//`. */\nfunction trimTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n\n/**\n * Sanitize a site-controlled string (title/description) before it is\n * interpolated into a single Markdown/text LINE of `llms.txt`.\n *\n * `llms.txt` is line-oriented Markdown; raw site free-text could carry newlines\n * or control chars that break the line structure (e.g. injecting a fake `##`\n * heading or a `- [x](javascript:...)` list item), or Markdown link/heading\n * sigils that hijack the line. We:\n * - collapse ALL newlines / carriage returns / control chars to a space,\n * - neutralize the line-leading Markdown sigils that would change block\n * structure (`#` heading, `>` blockquote, `-`/`*`/`+` list, `[` link),\n * - defang the `](` link sequence so `[text](javascript:...)` can't form an\n * active link,\n * then collapse runs of whitespace. Pure: string in, single-line string out.\n */\nfunction sanitizeLlmsText(value: string | undefined): string {\n if (!value) return ''\n return value\n // newlines, tabs, and other C0/C1 control chars -> space (kills breakout)\n .replace(/[\\u0000-\\u001f\\u007f-\\u009f]+/g, ' ')\n // defang any Markdown link target: `](` → `] (` so it can't resolve to a URL\n .replace(/\\]\\(/g, '] (')\n // neutralize the remaining link-open bracket so `[text]` isn't a link label\n .replace(/[[\\]]/g, ' ')\n // collapse whitespace runs and trim\n .replace(/\\s+/g, ' ')\n .trim()\n // neutralize a line-leading block sigil (heading/blockquote/list) the value\n // could still start with after trimming, so it can't open a new block\n .replace(/^([#>*+-]+)/, '\\\\$1')\n}\n\n/** XML-escape the five predefined entities for a `<loc>` value. */\nfunction escapeXml(value: string): string {\n return value\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n}\n\n/**\n * Convert a `pages.yaml` top-level `excluded:` glob (e.g. `/dev-*`, `/_*`,\n * `/404`) into a matcher. Only `*` is treated as a wildcard; everything else is\n * literal. Honors the coron8-shape top-level `excluded:` list, which cms's\n * manifest parser ignores.\n */\nfunction globToRegExp(glob: string): RegExp {\n const escaped = glob.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&').replace(/\\*/g, '.*')\n return new RegExp(`^${escaped}$`)\n}\n\n/**\n * True if `routePath` matches any glob in `excludedGlobs`. Exported so the\n * per-route HTML emitter applies the IDENTICAL exclusion semantics as the\n * sitemap/llms path (single source of truth for \"excluded\").\n */\nexport function matchesExcludedGlob(routePath: string, excludedGlobs: string[]): boolean {\n return excludedGlobs.some((g) => globToRegExp(g).test(routePath))\n}\n\n/**\n * W3C-datetime / ISO-8601 shapes the sitemaps spec accepts for `<lastmod>`:\n * `YYYY`, `YYYY-MM`, `YYYY-MM-DD`,\n * `YYYY-MM-DDThh:mmTZD`, `YYYY-MM-DDThh:mm:ssTZD`, `…ss.s+TZD`\n * where `TZD` is `Z` or `±hh:mm`. We accept the full-date and richer datetime\n * forms (the only ones sites actually author). Anything else is invalid.\n */\nconst W3C_DATETIME_RE =\n /^\\d{4}(-\\d{2}(-\\d{2}(T\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?(Z|[+-]\\d{2}:\\d{2})?)?)?)?$/\n\n/**\n * Validate a candidate `<lastmod>` value as a W3C datetime AND a real calendar\n * instant. Returns the trimmed value when valid, else `undefined` (caller omits\n * the `<lastmod>` rather than emitting garbage into the sitemap).\n *\n * Three gates:\n * 1. the lexical shape (W3C_DATETIME_RE),\n * 2. `Date.parse` is not NaN,\n * 3. for a `YYYY-MM-DD` prefix, the parsed UTC Y/M/D round-trips back to the\n * authored Y/M/D — this rejects shape-valid-but-impossible dates like\n * `2026-02-30`, which JS's lenient `Date.parse` would otherwise roll over\n * to March 2 (and thus silently corrupt the emitted lastmod).\n */\nfunction validW3CLastmod(value: string | undefined): string | undefined {\n if (typeof value !== 'string') return undefined\n const v = value.trim()\n if (!v) return undefined\n if (!W3C_DATETIME_RE.test(v)) return undefined\n\n const ms = Date.parse(v)\n if (Number.isNaN(ms)) return undefined\n\n // Strict calendar round-trip ONLY for a date-only `YYYY-MM-DD` value (parsed\n // as UTC midnight, so the UTC Y/M/D must equal the authored Y/M/D). This\n // rejects 02-30 / 04-31 etc. We skip this for datetimes carrying a time/zone,\n // where a timezone offset can legitimately shift the UTC calendar day.\n const dateOnly = v.match(/^(\\d{4})-(\\d{2})-(\\d{2})$/)\n if (dateOnly) {\n const [, y, m, d] = dateOnly\n const dt = new Date(ms)\n if (\n dt.getUTCFullYear() !== Number(y) ||\n dt.getUTCMonth() + 1 !== Number(m) ||\n dt.getUTCDate() !== Number(d)\n ) {\n return undefined\n }\n }\n\n return v\n}\n\n/**\n * The SINGLE indexability predicate shared by `buildSitemapXml` and\n * `buildLlmsTxt` so the two outputs always agree on which routes appear.\n *\n * A route is NOT indexable when: its path/slug is in `exclude`, OR its\n * path/slug is in `noindex`, OR its path matches an `excludedGlobs` entry, OR\n * its resolved robots (from `seo.yaml`) matches `/noindex/i`.\n */\nexport function isRouteIndexable(\n route: PageRouteEntry,\n seoConfig: SeoConfiguration | undefined,\n opts: { exclude?: Set<string>; noindex?: Set<string>; excludedGlobs?: string[] } = {}\n): boolean {\n const { exclude, noindex, excludedGlobs = [] } = opts\n\n if (exclude && (exclude.has(route.path) || (route.slug && exclude.has(route.slug)))) return false\n if (noindex && (noindex.has(route.path) || (route.slug && noindex.has(route.slug)))) return false\n if (matchesExcludedGlob(route.path, excludedGlobs)) return false\n\n const resolved = resolvePageSeo(route.slug, route.path, seoConfig, route.title)\n if (/noindex/i.test(resolved.robots)) return false\n\n return true\n}\n\n/**\n * Resolve the absolute `<loc>`/link for a route, preferring the canonical baked\n * by the shared cms resolver (single-source reuse) and falling back to\n * `siteUrl + path`. Returns `null` when neither is derivable.\n */\nfunction resolveLoc(\n route: PageRouteEntry,\n seoConfig: SeoConfiguration | undefined,\n siteUrl: string | undefined\n): string | null {\n const resolved = resolvePageSeo(route.slug, route.path, seoConfig, route.title)\n if (resolved.canonical) return resolved.canonical\n if (siteUrl) return `${trimTrailingSlash(siteUrl)}${route.path}`\n return null\n}\n\n// =============================================================================\n// sitemap.xml\n// =============================================================================\n\nexport interface BuildSitemapParams {\n /** Route list (from `loadPagesManifest`). */\n routes: PageRouteEntry[]\n /** Canonical production origin (preferred base when no per-page canonical). */\n siteUrl?: string\n /** cms seo config (passed straight through to `resolvePageSeo`). */\n seoConfig?: SeoConfiguration\n /** Routes to skip entirely (path or slug). */\n exclude?: string[]\n /** Routes forced to noindex / omitted (path or slug). */\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs (coron8 parity). */\n excludedGlobs?: string[]\n /** Optional single site-wide `<lastmod>` (ISO date) — no fabricated per-page dates. */\n lastmod?: string\n}\n\n/**\n * Build the sitemap XML string from a route list. Pure: routes + siteUrl +\n * seoConfig in, XML out.\n *\n * A route is omitted when it is not indexable (see {@link isRouteIndexable}) OR\n * no absolute `<loc>` is derivable (missing siteUrl + no canonical).\n *\n * Returns the empty string when no route yields an absolute `<loc>` (the caller\n * treats this as a no-op signal — a sitemap with no absolute base is worse than\n * none).\n */\nexport function buildSitemapXml(params: BuildSitemapParams): string {\n const {\n routes,\n siteUrl,\n seoConfig,\n exclude = [],\n noindex = [],\n excludedGlobs = [],\n lastmod,\n } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n // De-duplicate <loc> values: two distinct routes can resolve to the SAME\n // canonical (e.g. `/` and `/home`, or an alias path), and duplicate <loc>\n // entries are invalid/penalized in a sitemap. First-wins preserves order.\n // Validated site-wide fallback lastmod (per-page values take precedence).\n const siteLastmod = validW3CLastmod(lastmod)\n const seen = new Set<string>()\n const entries: Array<{ loc: string; lastmod?: string }> = []\n for (const route of routes) {\n if (!isRouteIndexable(route, seoConfig, { exclude: excludeSet, noindex: noindexSet, excludedGlobs })) {\n continue\n }\n const loc = resolveLoc(route, seoConfig, siteUrl)\n if (!loc) continue\n if (seen.has(loc)) continue\n seen.add(loc)\n // Per-page <lastmod> (validated) wins; else the validated site-wide value;\n // else none. Both validated, so an authored bad date is dropped, never\n // emitted (one bad date invalidates the whole sitemap). Never fabricated.\n const perPage = validW3CLastmod(route.lastmod)\n entries.push({ loc, lastmod: perPage ?? siteLastmod })\n }\n\n // No absolute base derivable for any route → no-op signal.\n if (entries.length === 0) return ''\n\n // Only emit <lastmod> when the value is a valid W3C datetime — never emit an\n // unvalidated site-authored string (a bad date invalidates the whole sitemap).\n const urls = entries\n .map(({ loc, lastmod: lm }) => {\n const lastmodLine = lm ? `\\n <lastmod>${escapeXml(lm)}</lastmod>` : ''\n return ` <url>\\n <loc>${escapeXml(loc)}</loc>${lastmodLine}\\n </url>`\n })\n .join('\\n')\n\n return `${XML_HEADER}\\n<urlset xmlns=\"${URLSET_NS}\">\\n${urls}\\n</urlset>\\n`\n}\n\n// =============================================================================\n// robots.txt\n// =============================================================================\n\n/** robots.txt override hooks. */\nexport interface DcsRobotsOptions {\n /** Emit a robots.txt at all (default `true`). */\n enabled?: boolean\n /** `Disallow:` lines to emit (default `[]`). */\n disallow?: string[]\n /** `Allow:` lines to emit (default `['/']` in production). */\n allow?: string[]\n /** Raw lines appended verbatim after the generated directives. */\n extra?: string[]\n /**\n * Emit explicit AI-crawler allow/deny groups (GPTBot, ClaudeBot,\n * PerplexityBot, Google-Extended, CCBot, OAI-SearchBot, Applebot-Extended).\n * Default `true` — each `Allow: /` in\n * production, each `Disallow: /` in preview. Set `false` to drop the tier.\n */\n aiBots?: boolean\n /**\n * Overwrite an existing `dist/robots.txt` (e.g. a hand-authored\n * `public/robots.txt` Vite already copied). Default `false` — do not clobber.\n */\n force?: boolean\n}\n\nexport interface BuildRobotsParams {\n /** Canonical production origin (required for the absolute `Sitemap:` line). */\n siteUrl?: string\n /** Preview / staging gate: `Disallow: /`, no `Sitemap:` line. */\n preview?: boolean\n /** robots.txt override hooks. */\n robots?: DcsRobotsOptions\n /** Whether a sitemap is being emitted (drives the `Sitemap:` line). */\n hasSitemap?: boolean\n}\n\n/**\n * Build the robots.txt string. Pure: siteUrl + options in, text out.\n *\n * - **Preview mode** (`preview: true`): emits the privacy gate `User-agent: *`\n * + `Disallow: /`, the same `Disallow: /` for each AI-bot tier (when\n * enabled), and OMITS the `Sitemap:` line.\n * - **Production**: `User-agent: *`, any `disallow`/`allow` lines (default\n * `Allow: /`), explicit AI-bot allow groups (when enabled), a blank line,\n * then an absolute `Sitemap: {siteUrl}/sitemap.xml` (trailing slash trimmed).\n * The `Sitemap:` line is omitted when no sitemap is emitted or no `siteUrl`\n * is known.\n * - `extra` lines are appended verbatim.\n */\nexport function buildRobotsTxt(params: BuildRobotsParams): string {\n const { siteUrl, preview = false, robots = {}, hasSitemap = true } = params\n const { disallow = [], allow, extra = [], aiBots = true } = robots\n\n const lines: string[] = ['User-agent: *']\n\n if (preview) {\n lines.push('Disallow: /')\n if (aiBots) {\n for (const bot of AI_BOTS) {\n lines.push('')\n lines.push(`User-agent: ${bot}`)\n lines.push('Disallow: /')\n }\n }\n if (extra.length > 0) lines.push(...extra)\n return lines.join('\\n') + '\\n'\n }\n\n // Allow directives (default `Allow: /`).\n const allowLines = allow ?? ['/']\n for (const a of allowLines) lines.push(`Allow: ${a}`)\n // Disallow directives.\n for (const d of disallow) lines.push(`Disallow: ${d}`)\n\n // Explicit AI-crawler groups (opt-in-friendly discovery). Each group MUST\n // replay the operator's `disallow` (and `allow`) directives so a per-bot\n // group never becomes LESS restricted than `User-agent: *` — otherwise an\n // `Allow: /`-only AI group would re-open a path the operator disallowed for\n // everyone (e.g. `/admin`). robots.txt groups do NOT inherit, so the global\n // directives are repeated verbatim inside each AI group.\n if (aiBots) {\n for (const bot of AI_BOTS) {\n lines.push('')\n lines.push(`User-agent: ${bot}`)\n for (const a of allowLines) lines.push(`Allow: ${a}`)\n for (const d of disallow) lines.push(`Disallow: ${d}`)\n }\n }\n\n // Absolute Sitemap line (spec requires scheme+host).\n if (hasSitemap && siteUrl) {\n lines.push('')\n lines.push(`Sitemap: ${trimTrailingSlash(siteUrl)}/sitemap.xml`)\n }\n\n if (extra.length > 0) {\n lines.push(...extra)\n }\n\n return lines.join('\\n') + '\\n'\n}\n\n/**\n * Decide whether an EXISTING (hand-authored) `robots.txt` in `dist/` (one Vite\n * copied from `public/`) should be KEPT rather than overwritten by the factory.\n * It is kept when it is a REAL robots file: it carries a `User-agent: *` group\n * and is not an SPA-fallback HTML page. A bare `Sitemap:`-only stub (the\n * just-posh case) or an HTML shell fails and is OVERWRITTEN with the complete\n * factory robots (User-agent groups + the AI-bot tier + Sitemap), so AI\n * discovery is guaranteed even on a site that shipped a broken stub.\n *\n * Minimal-overwrite by design: a deliberate human robots (kduff's rich file, or\n * one with custom Disallow rules) is preserved — the factory never silently\n * drops hand-authored directives. `force` still overwrites unconditionally.\n * The \"factory-as-single-source, validated override\" gate (D6).\n *\n * Pure: string in, boolean out.\n */\nexport function isHandAuthoredRobotsAcceptable(text: string): boolean {\n if (!text || !text.trim()) return false\n // An SPA-fallback HTML page (the \"worse than a 404\" failure) starts with markup.\n if (/^\\s*</.test(text)) return false\n // Must carry the global group — a bare `Sitemap:`-only stub (just-posh) has none.\n return /^[ \\t]*User-agent:[ \\t]*\\*[ \\t]*$/im.test(text)\n}\n\n// =============================================================================\n// llms.txt\n// =============================================================================\n\nexport interface BuildLlmsParams {\n /** Route list (from `loadPagesManifest`). */\n routes: PageRouteEntry[]\n /** Canonical production origin (preferred base when no per-page canonical). */\n siteUrl?: string\n /** cms seo config (passed straight through to `resolvePageSeo`). */\n seoConfig?: SeoConfiguration\n /** Routes to skip entirely (path or slug). */\n exclude?: string[]\n /** Routes forced to noindex / omitted (path or slug). */\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs (coron8 parity). */\n excludedGlobs?: string[]\n}\n\n/**\n * Build the `llms.txt` plain-text body following the emerging llms.txt\n * convention (Markdown-ish): an H1 `# {siteName}`, a one-line `> {summary}`\n * blockquote, then a `## Pages` section listing each INDEXABLE route as a\n * Markdown link `- [{title}]({canonical}): {description}`.\n *\n * Sourced from the SAME inputs as the sitemap (so there is zero new resolution\n * path): `siteName`/`defaultDescription` from `seoConfig.global`, the URL list\n * filtered through the IDENTICAL {@link isRouteIndexable} predicate, and per-page\n * title/description/canonical from `resolvePageSeo`.\n *\n * Returns the empty string (no-op) when there is no `siteName` AND no indexable\n * route with a derivable link.\n */\nexport function buildLlmsTxt(params: BuildLlmsParams): string {\n const { routes, siteUrl, seoConfig, exclude = [], noindex = [], excludedGlobs = [] } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n const global = seoConfig?.global ?? {}\n // siteName/summary are site-controlled free text → sanitize before they reach\n // a Markdown line (newline/heading/link-injection guard). `siteName` keeps its\n // emptiness semantics for the no-op check below, so derive a separate display.\n const siteName = global.siteName\n const summary = sanitizeLlmsText(global.defaultDescription)\n\n type Entry = { title: string; canonical: string; description: string }\n const entries: Entry[] = []\n for (const route of routes) {\n if (!isRouteIndexable(route, seoConfig, { exclude: excludeSet, noindex: noindexSet, excludedGlobs })) {\n continue\n }\n const loc = resolveLoc(route, seoConfig, siteUrl)\n if (!loc) continue\n const resolved = resolvePageSeo(route.slug, route.path, seoConfig, route.title)\n entries.push({\n // title + description are site-controlled free text → sanitize per-line.\n title: sanitizeLlmsText(resolved.title),\n canonical: loc,\n description: sanitizeLlmsText(resolved.description),\n })\n }\n\n // No-op when there is nothing meaningful to publish.\n if (!siteName && entries.length === 0) return ''\n\n const out: string[] = []\n out.push(`# ${sanitizeLlmsText(siteName) || entries[0]?.title || 'Site'}`)\n if (summary) {\n out.push('')\n out.push(`> ${summary}`)\n }\n\n if (entries.length > 0) {\n out.push('')\n out.push('## Pages')\n out.push('')\n for (const e of entries) {\n const desc = e.description ? `: ${e.description}` : ''\n out.push(`- [${e.title}](${e.canonical})${desc}`)\n }\n }\n\n return out.join('\\n') + '\\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'\nimport {\n buildGlobalGraph,\n buildBreadcrumbList,\n buildBlogPosting,\n buildFaqPage,\n breadcrumbTrailFromRoute,\n graphAbsorbs,\n slugToTitle,\n type ReviewSource,\n type FaqSource,\n type BlogMeta,\n} from './schemaGraph'\nimport { escapeJsonLd } from './spliceHeadHtml'\nimport type { SeoSchemaConfig } from '../types/seo'\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\n // ── Schema-completeness (shared with the SPA path via ./schemaGraph.ts) ────\n\n /**\n * Emit the cross-linked global `@graph` spine (Organization + WebSite + the\n * promoted LocalBusiness node) in place of the flat per-schema global JSON-LD.\n * When ON, the LocalBusiness subtype in `global.schemas` is ABSORBED into the\n * graph (not emitted twice); other global schemas (Person, etc.) are still\n * emitted standalone. Default `false`, so existing sites (which supply their\n * own graph via `global.schemas`) are unchanged until they opt in.\n */\n emitGraph?: boolean\n /**\n * Emit an automatic `BreadcrumbList` derived from the route depth on every\n * non-home page. Default `false` (sites that already emit breadcrumbs via\n * `pageTypeRules` should leave this off to avoid duplicates). Intermediate /\n * leaf crumb titles come from `breadcrumbTitles(ctx)` when provided, else a\n * slug-derived Title Case.\n */\n emitBreadcrumbs?: boolean\n /**\n * Map a context to a `{ route → title }` map for breadcrumb hop labels (e.g.\n * `{ '/': 'Home', '/blogs': 'Blog' }`). Missing entries fall back to a\n * slug-derived title. Only consulted when `emitBreadcrumbs` is true.\n */\n breadcrumbTitles?: (ctx: SeoPageContext) => Record<string, string> | undefined\n /**\n * Emit an automatic `BlogPosting` (author/publisher as `@id` refs) for blog\n * routes, derived from frontmatter (`title`/`date`/`image`/`description`).\n * `blogMatch` decides which routes are posts; default off. Skipped when a\n * `pageTypeRule` already emitted a `BlogPosting` for the page.\n */\n emitBlogPosting?: boolean\n /** Predicate selecting blog-post routes for `emitBlogPosting`. */\n blogMatch?: (ctx: SeoPageContext) => boolean\n /**\n * Emit an automatic, honesty-gated `FAQPage` from `frontmatter.faq` (an array\n * of `{ q, a }` / `{ question, answer }`). Emits nothing when the frontmatter\n * carries no structured Q&A. Default `false`.\n */\n emitFaq?: boolean\n /**\n * Provide REAL review items for the LocalBusiness node in the `@graph` (only\n * used when `emitGraph` is true). Honesty-gated downstream — items lacking a\n * rating/text/authorName are dropped, and an empty result emits no Review or\n * aggregateRating. Default: none.\n */\n resolveReviews?: (ctx: SeoPageContext) => ReviewSource[] | undefined\n\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/**\n * A `<script type=\"application/ld+json\">` VitePress head tuple.\n *\n * VitePress's `renderHead` writes this third tuple element into the script's\n * innerHTML WITHOUT escaping it for the HTML script context. Untrusted CMS free\n * text (review bodies, FAQ Q&A, blog descriptions) carrying `</script>` would\n * therefore break out and inject markup (stored XSS). We escape the serialized\n * JSON-LD via the SAME shared guard the SPA emit path uses (`escapeJsonLd`),\n * which `\\u`-escapes `<`/`>`/`&` while keeping the payload valid JSON.\n */\nfunction ldScript(obj: Record<string, unknown>): VitePressHeadConfig {\n return ['script', { type: 'application/ld+json' }, escapeJsonLd(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 emitGraph = false,\n emitBreadcrumbs = false,\n breadcrumbTitles,\n emitBlogPosting = false,\n blogMatch,\n emitFaq = false,\n resolveReviews,\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 //\n // When `emitGraph` is on, the shared spine (Organization + WebSite + the\n // promoted LocalBusiness node) is emitted as a single `@graph` block and the\n // LocalBusiness subtype is removed from the flat per-schema emission so it is\n // not duplicated. Other global schemas (Person, etc.) still emit standalone.\n let globalSchemas: SeoSchemaConfig[] = resolved.schemas\n if (emitGraph) {\n const reviews = resolveReviews?.(ctx)\n const graph = buildGlobalGraph(global, { reviews })\n if (graph.length > 0) {\n for (const obj of graph) head.push(ldScript(obj))\n // Drop the schemas the @graph now provides canonically (LocalBusiness +\n // any Organization/WebSite) so they are not emitted twice.\n globalSchemas = resolved.schemas.filter((s) => !graphAbsorbs(s, global))\n }\n }\n for (const obj of generateJsonLd(globalSchemas, global)) {\n head.push(ldScript(obj as Record<string, unknown>))\n }\n\n // ── Shared schema-completeness builders (./schemaGraph.ts) ────────────────\n // Run BEFORE the site's pageTypeRules so vertical-specific extras (Place /\n // CollectionPage) layer on top of the shared Breadcrumb/Blog/FAQ base.\n //\n // BreadcrumbList from route depth (home ⇒ none).\n if (emitBreadcrumbs) {\n const titles = breadcrumbTitles?.(ctx) ?? {}\n const trail = breadcrumbTrailFromRoute(route, siteUrl, titles)\n for (const obj of buildBreadcrumbList(trail)) head.push(ldScript(obj))\n }\n\n // BlogPosting from frontmatter — only when the site marks this a blog route\n // and no pageTypeRule will also emit one (it would be a dup). We detect a\n // rule-emitted BlogPosting by checking the head we are about to add below, so\n // here we simply defer to the site: if it wired a BlogPosting rule, it should\n // leave `emitBlogPosting` off. (See README/option docs.)\n if (emitBlogPosting && (blogMatch ? blogMatch(ctx) : false)) {\n const blogMeta: BlogMeta = {\n headline: typeof fm.title === 'string' ? fm.title : slugToTitle(slug),\n datePublished: typeof fm.date === 'string' ? fm.date : undefined,\n dateModified: typeof fm.lastUpdated === 'string' ? fm.lastUpdated : undefined,\n url: canonical,\n image: typeof fm.headerImage === 'string' ? fm.headerImage : (typeof fm.image === 'string' ? fm.image : undefined),\n description: typeof fm.description === 'string' ? fm.description : undefined,\n }\n for (const obj of buildBlogPosting(blogMeta, siteUrl, global)) head.push(ldScript(obj))\n }\n\n // FAQPage from frontmatter.faq (honesty-gated; component-only FAQ ⇒ nothing).\n if (emitFaq) {\n const faq = Array.isArray(fm.faq) ? (fm.faq as FaqSource[]) : undefined\n for (const obj of buildFaqPage(faq)) head.push(ldScript(obj))\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"]}
|
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 } from './vitepressTransform-
|
|
4
|
-
export { A as AI_BOTS, E as BlogMeta, z as BreadcrumbCrumb, x as BuildLlmsParams, w as BuildRobotsParams, B as BuildSitemapParams, C as ContentConfig, a as CreateSeoTransformPageDataOptions, D as DcsRobotsOptions, H as FaqSource, Y as HeadOverrides, I as PageSeoConfig, R as ResolvedPageOverrides, W as ResolvedPageSeo, F as ReviewSource, y as SchemaObject, Q as SeoAlternateConfig, J as SeoAuthorConfig, S as SeoConfiguration, L as SeoImagesConfig, M as SeoOpenGraphConfig, e as SeoPageContext, f as SeoPageTypeRule, O as SeoSchemaConfig, K as SeoSocialConfig, N as SeoTwitterConfig, T as SeoVerificationConfig, X as UseSeoConfig, g as VitePressHeadConfig, V as VitePressPageData, u as absolutizeUrl, n as breadcrumbTrailFromRoute, o as buildBlogPosting, m as buildBreadcrumbList, p as buildFaqPage, l as buildGlobalGraph, j as buildLlmsTxt, q as buildReviewSchemaParts, i as buildRobotsTxt, h as buildSitemapXml, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute, s as filterRealFaq, r as filterRealReviews, t as findReviewItemsForPage, k as isRouteIndexable, v as slugToTitle } from './vitepressTransform-
|
|
3
|
+
import { U as UseSeoReturn, G as GlobalSeoConfig } from './vitepressTransform-DeEzgGWU.js';
|
|
4
|
+
export { A as AI_BOTS, E as BlogMeta, z as BreadcrumbCrumb, x as BuildLlmsParams, w as BuildRobotsParams, B as BuildSitemapParams, C as ContentConfig, a as CreateSeoTransformPageDataOptions, D as DcsRobotsOptions, H as FaqSource, Y as HeadOverrides, I as PageSeoConfig, R as ResolvedPageOverrides, W as ResolvedPageSeo, F as ReviewSource, y as SchemaObject, Q as SeoAlternateConfig, J as SeoAuthorConfig, S as SeoConfiguration, L as SeoImagesConfig, M as SeoOpenGraphConfig, e as SeoPageContext, f as SeoPageTypeRule, O as SeoSchemaConfig, K as SeoSocialConfig, N as SeoTwitterConfig, T as SeoVerificationConfig, X as UseSeoConfig, g as VitePressHeadConfig, V as VitePressPageData, u as absolutizeUrl, n as breadcrumbTrailFromRoute, o as buildBlogPosting, m as buildBreadcrumbList, p as buildFaqPage, l as buildGlobalGraph, j as buildLlmsTxt, q as buildReviewSchemaParts, i as buildRobotsTxt, h as buildSitemapXml, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute, s as filterRealFaq, r as filterRealReviews, t as findReviewItemsForPage, k as isRouteIndexable, v as slugToTitle } from './vitepressTransform-DeEzgGWU.js';
|
|
5
5
|
export { HeadLinkTag, HeadMetaTag, HeadScriptTag, HeadTagOverrides, ResolvedHeadTags, buildHeadTags, escapeJsonLd, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, renderHeadTags, resolvePageSeo, spliceHeadHtml, stripManagedHeadTags } from './seo/index.js';
|
|
6
6
|
import { ImageContext, ResponsiveImageResult } from '@duffcloudservices/cms-core';
|
|
7
7
|
export { ImageContext, ResponsiveImageOptions, ResponsiveImageResult, ResponsiveSource, isCdnAssetUrl, resolveResponsiveImage } from '@duffcloudservices/cms-core';
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import './chunk-KCWMS7P4.js';
|
|
2
|
-
import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-
|
|
3
|
-
export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, escapeJsonLd, filterRealFaq, filterRealReviews, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, isRouteIndexable, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags } from './chunk-
|
|
2
|
+
import { resolvePageSeo, generateJsonLd, buildHeadTags } from './chunk-UPAMLKOQ.js';
|
|
3
|
+
export { AI_BOTS, absolutizeUrl, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSitemapXml, buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute, escapeJsonLd, filterRealFaq, filterRealReviews, findReviewItemsForPage, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, isRouteIndexable, renderHeadTags, resolvePageSeo, slugToTitle, spliceHeadHtml, stripManagedHeadTags } from './chunk-UPAMLKOQ.js';
|
|
4
4
|
import { shallowRef, ref, computed, onMounted, readonly, toValue, onUnmounted } from 'vue';
|
|
5
5
|
import { useHead } from '@unhead/vue';
|
|
6
6
|
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 { D as DcsRobotsOptions, P as PageRouteEntry, S as SeoConfiguration, C as ContentConfig } from '../vitepressTransform-
|
|
3
|
-
export { a as CreateSeoTransformPageDataOptions, R as ResolvedPageOverrides, e as SeoPageContext, f as SeoPageTypeRule, g as VitePressHeadConfig, V as VitePressPageData, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute } from '../vitepressTransform-
|
|
2
|
+
import { D as DcsRobotsOptions, P as PageRouteEntry, S as SeoConfiguration, C as ContentConfig } from '../vitepressTransform-DeEzgGWU.js';
|
|
3
|
+
export { a as CreateSeoTransformPageDataOptions, R as ResolvedPageOverrides, e as SeoPageContext, f as SeoPageTypeRule, g as VitePressHeadConfig, V as VitePressPageData, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute } from '../vitepressTransform-DeEzgGWU.js';
|
|
4
4
|
import { Component, Plugin as Plugin$1 } from 'vue';
|
|
5
5
|
import MarkdownIt from 'markdown-it';
|
|
6
6
|
|