@duffcloudservices/cms 0.13.1 → 0.13.3

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.
@@ -85,6 +85,17 @@ function graphAbsorbs(schema, global) {
85
85
  const found = findLocalBusinessSchema(global);
86
86
  return !!found && found.schema === schema;
87
87
  }
88
+ function findGlobalSchemasOfType(global, type) {
89
+ return (global.schemas ?? []).filter((s) => s?.type === type);
90
+ }
91
+ var GRAPH_OWNED_KEYS = /* @__PURE__ */ new Set(["@context", "@id", "@type"]);
92
+ function absorbSchemaProps(node, props) {
93
+ if (!props) return;
94
+ for (const [key, value] of Object.entries(props)) {
95
+ if (GRAPH_OWNED_KEYS.has(key)) continue;
96
+ node[key] = value;
97
+ }
98
+ }
88
99
  function filterRealReviews(items) {
89
100
  if (!Array.isArray(items)) return [];
90
101
  const out = [];
@@ -166,13 +177,19 @@ function buildGlobalGraph(global, opts = {}) {
166
177
  ...logo ? { logo } : {},
167
178
  ...sameAs.length ? { sameAs } : {}
168
179
  };
180
+ for (const schema of findGlobalSchemasOfType(global, "Organization")) {
181
+ absorbSchemaProps(organization, schema.properties);
182
+ }
169
183
  const website = {
170
184
  "@type": "WebSite",
171
185
  "@id": ids.website,
172
186
  url: `${siteUrl}/`,
173
- ...name ? { name } : {},
174
- publisher: { "@id": ids.organization }
187
+ ...name ? { name } : {}
175
188
  };
189
+ for (const schema of findGlobalSchemasOfType(global, "WebSite")) {
190
+ absorbSchemaProps(website, schema.properties);
191
+ }
192
+ website.publisher = { "@id": ids.organization };
176
193
  const graph = [organization, website];
177
194
  const found = findLocalBusinessSchema(global);
178
195
  if (found) {
@@ -562,6 +579,22 @@ var MANAGED_META_NAMES = /* @__PURE__ */ new Set([
562
579
  ]);
563
580
  var MANAGED_PROPERTY_PREFIXES = ["og:", "article:"];
564
581
  var MANAGED_NAME_PREFIXES = ["twitter:"];
582
+ var MANAGED_LINK_RELS = /* @__PURE__ */ new Set(["canonical", "alternate"]);
583
+ function isManagedMetaTag(name, property) {
584
+ const n = typeof name === "string" ? name.toLowerCase() : void 0;
585
+ const p = typeof property === "string" ? property.toLowerCase() : void 0;
586
+ if (n) {
587
+ if (MANAGED_META_NAMES.has(n)) return true;
588
+ if (MANAGED_NAME_PREFIXES.some((prefix) => n.startsWith(prefix))) return true;
589
+ }
590
+ if (p) {
591
+ if (MANAGED_PROPERTY_PREFIXES.some((prefix) => p.startsWith(prefix))) return true;
592
+ }
593
+ return false;
594
+ }
595
+ function isManagedLinkRel(rel) {
596
+ return typeof rel === "string" && MANAGED_LINK_RELS.has(rel.toLowerCase());
597
+ }
565
598
  function renderHeadTags(tags, indent = " ") {
566
599
  const lines = [];
567
600
  lines.push(`${indent}<title>${escapeAttr(tags.title)}</title>`);
@@ -593,21 +626,12 @@ function stripManagedHeadTags(html) {
593
626
  head = head.replace(/[ \t]*<meta\b[^>]*>[ \t]*\r?\n?/gi, (tag) => {
594
627
  const nameMatch = tag.match(/\bname=["']([^"']*)["']/i);
595
628
  const propMatch = tag.match(/\bproperty=["']([^"']*)["']/i);
596
- const name = nameMatch?.[1]?.toLowerCase();
597
- const property = propMatch?.[1]?.toLowerCase();
598
- if (name) {
599
- if (MANAGED_META_NAMES.has(name)) return "";
600
- if (MANAGED_NAME_PREFIXES.some((p) => name.startsWith(p))) return "";
601
- }
602
- if (property) {
603
- if (MANAGED_PROPERTY_PREFIXES.some((p) => property.startsWith(p))) return "";
604
- }
605
- return tag;
629
+ return isManagedMetaTag(nameMatch?.[1], propMatch?.[1]) ? "" : tag;
630
+ });
631
+ head = head.replace(/[ \t]*<link\b[^>]*>[ \t]*\r?\n?/gi, (tag) => {
632
+ const relMatch = tag.match(/\brel=["']([^"']*)["']/i);
633
+ return isManagedLinkRel(relMatch?.[1]) ? "" : tag;
606
634
  });
607
- head = head.replace(
608
- /[ \t]*<link\b[^>]*\brel=["'](?:canonical|alternate)["'][^>]*>[ \t]*\r?\n?/gi,
609
- ""
610
- );
611
635
  return html.slice(0, headMatch.index + headMatch[0].indexOf(headMatch[1])) + head + html.slice(headMatch.index + headMatch[0].indexOf(headMatch[1]) + headMatch[1].length);
612
636
  }
613
637
  function spliceHeadHtml(html, tags) {
@@ -622,6 +646,69 @@ ${indent}</head>`;
622
646
  });
623
647
  }
624
648
 
649
+ // src/seo/sweepBakedHeadTags.ts
650
+ function metaIdentity(name, property) {
651
+ if (typeof property === "string" && property !== "") {
652
+ return `meta|property|${property.toLowerCase()}`;
653
+ }
654
+ if (typeof name === "string" && name !== "") {
655
+ return `meta|name|${name.toLowerCase()}`;
656
+ }
657
+ return null;
658
+ }
659
+ function linkIdentity(rel, hreflang) {
660
+ if (typeof rel !== "string" || rel === "") return null;
661
+ const r = rel.toLowerCase();
662
+ if (r === "alternate") {
663
+ return `link|alternate|${typeof hreflang === "string" ? hreflang.toLowerCase() : ""}`;
664
+ }
665
+ return `link|${r}`;
666
+ }
667
+ function headInputIdentities(input) {
668
+ const ids = /* @__PURE__ */ new Set();
669
+ for (const m of input.meta ?? []) {
670
+ if (!m) continue;
671
+ const id = metaIdentity(m.name, m.property);
672
+ if (id) ids.add(id);
673
+ }
674
+ for (const l of input.link ?? []) {
675
+ if (!l) continue;
676
+ const id = linkIdentity(l.rel, l.hreflang);
677
+ if (id) ids.add(id);
678
+ }
679
+ return ids;
680
+ }
681
+ function snapshotBakedManagedHeadTags(doc) {
682
+ const d = doc ?? (typeof document !== "undefined" ? document : void 0);
683
+ if (!d || !d.head) return [];
684
+ const out = [];
685
+ for (const el of Array.from(d.head.querySelectorAll("meta"))) {
686
+ if (isManagedMetaTag(el.getAttribute("name"), el.getAttribute("property"))) {
687
+ out.push(el);
688
+ }
689
+ }
690
+ for (const el of Array.from(d.head.querySelectorAll("link"))) {
691
+ if (isManagedLinkRel(el.getAttribute("rel"))) {
692
+ out.push(el);
693
+ }
694
+ }
695
+ return out;
696
+ }
697
+ function sweepBakedManagedHeadTags(snapshot, input) {
698
+ if (snapshot.length === 0) return 0;
699
+ const declared = headInputIdentities(input);
700
+ let removed = 0;
701
+ for (const el of snapshot) {
702
+ if (!el || !el.isConnected) continue;
703
+ const tag = el.tagName.toUpperCase();
704
+ const identity = tag === "META" ? metaIdentity(el.getAttribute("name"), el.getAttribute("property")) : tag === "LINK" ? linkIdentity(el.getAttribute("rel"), el.getAttribute("hreflang")) : null;
705
+ if (!identity || declared.has(identity)) continue;
706
+ el.remove();
707
+ removed++;
708
+ }
709
+ return removed;
710
+ }
711
+
625
712
  // src/seo/sitemap.ts
626
713
  var XML_HEADER = '<?xml version="1.0" encoding="UTF-8"?>';
627
714
  var URLSET_NS = "http://www.sitemaps.org/schemas/sitemap/0.9";
@@ -956,11 +1043,20 @@ function installSeoHead(router, options) {
956
1043
  }
957
1044
  let entry = null;
958
1045
  let initialNavigationPending = true;
1046
+ let bakedManagedTags = snapshotBakedManagedHeadTags();
1047
+ let bakedSweepPending = true;
959
1048
  return router.afterEach((to, from, failure) => {
960
1049
  const path4 = to && typeof to.path === "string" ? to.path : "";
961
1050
  const normalizedPath = normalizeSeoHeadPath(path4);
962
1051
  if (failure) {
963
- onResolve?.({ path: path4, normalizedPath, matched: null, reason: "navigation-failed", title: null });
1052
+ onResolve?.({
1053
+ path: path4,
1054
+ normalizedPath,
1055
+ matched: null,
1056
+ reason: "navigation-failed",
1057
+ title: null,
1058
+ sweptBakedTags: 0
1059
+ });
964
1060
  return;
965
1061
  }
966
1062
  if (initialNavigationPending) {
@@ -972,18 +1068,33 @@ function installSeoHead(router, options) {
972
1068
  normalizedPath,
973
1069
  matched: null,
974
1070
  reason: "initial-navigation",
975
- title: null
1071
+ title: null,
1072
+ sweptBakedTags: 0
976
1073
  });
977
1074
  return;
978
1075
  }
979
1076
  }
980
1077
  if (!routeMap) {
981
- onResolve?.({ path: path4, normalizedPath, matched: null, reason: "no-manifest", title: null });
1078
+ onResolve?.({
1079
+ path: path4,
1080
+ normalizedPath,
1081
+ matched: null,
1082
+ reason: "no-manifest",
1083
+ title: null,
1084
+ sweptBakedTags: 0
1085
+ });
982
1086
  return;
983
1087
  }
984
1088
  const matched = routeMap.get(normalizedPath);
985
1089
  if (!matched) {
986
- onResolve?.({ path: path4, normalizedPath, matched: null, reason: "no-match", title: null });
1090
+ onResolve?.({
1091
+ path: path4,
1092
+ normalizedPath,
1093
+ matched: null,
1094
+ reason: "no-match",
1095
+ title: null,
1096
+ sweptBakedTags: 0
1097
+ });
987
1098
  return;
988
1099
  }
989
1100
  const forceNoindex = noindexSet.has(matched.path) || !!matched.slug && noindexSet.has(matched.slug);
@@ -1000,9 +1111,22 @@ function installSeoHead(router, options) {
1000
1111
  robots: forceNoindex ? "noindex, nofollow" : void 0
1001
1112
  });
1002
1113
  const input = { title: tags.title, meta: tags.meta, link: tags.link };
1114
+ let sweptBakedTags = 0;
1115
+ if (bakedSweepPending) {
1116
+ bakedSweepPending = false;
1117
+ sweptBakedTags = sweepBakedManagedHeadTags(bakedManagedTags, input);
1118
+ bakedManagedTags = [];
1119
+ }
1003
1120
  if (entry) entry.patch(input);
1004
1121
  else entry = head.push(input);
1005
- onResolve?.({ path: path4, normalizedPath, matched, reason: "applied", title: tags.title });
1122
+ onResolve?.({
1123
+ path: path4,
1124
+ normalizedPath,
1125
+ matched,
1126
+ reason: "applied",
1127
+ title: tags.title,
1128
+ sweptBakedTags
1129
+ });
1006
1130
  });
1007
1131
  }
1008
1132
 
@@ -2062,6 +2186,6 @@ function createSeoTransformPageData(options) {
2062
2186
  };
2063
2187
  }
2064
2188
 
2065
- export { AI_BOTS, BodyPrerenderError, CANONICAL_CHARSET_META, CHARSET_BUDGET_BYTES, CHARSET_HEADROOM_WARN_BYTES, CharsetBudgetError, EmittedUrlError, EmittedUrlNotRunError, HeadHonestyError, HeadHonestyNotRunError, SEO_HEAD_ROUTE_HAS_NO_META, absolutizeUrl, assertHeadHonesty, assertHeadHonestyExecuted, auditCheckedTotal, auditSameOriginAssets, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHasCredential, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSeoHeadRouteMap, buildSitemapXml, buildVitePressSeoHead, checkCharsetBudget, checkRemoteAssets, checkSameOriginAssets, classifyUrl, collectEmittedUrlsFromFiles, collectUrlsFromHtml, collectUrlsFromLlmsTxt, collectUrlsFromSitemap, createSeoTransformPageData, decodeHtmlEntities, dedupeUrls, defaultRelativePathToRoute, deriveSameAs, escapeJsonLd, extractBakedHead, filterRealFaq, filterRealReviews, findBusinessLicense, findCharsetByteOffset, findDuplicateNormalizedPaths, findHeadHonestyExecutionFaults, findHeadHonestyViolations, findLocalBusinessSchema, findReviewItemsForPage, formatCharsetHeadroomWarning, formatDuplicateNormalizedPaths, formatEmittedUrlAudit, formatEmittedUrlReport, formatHeadHonestyExecutionReport, formatHeadHonestyReport, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, graphAbsorbs, graphIds, hoistCharsetMeta, installSeoHead, isBodyPrerenderEnabled, isHandAuthoredRobotsAcceptable, isLocalBusinessType, isRouteEmitted, isRouteIndexable, loadPagesManifest, normalizeHeadText, normalizeSeoHeadPath, parsePagesManifest, prerenderBodies, renderHeadTags, resolveHonestyMode, resolvePageSeo, routeExclusionReason, routeToOutputFile, slugToTitle, spliceBodyHtml, spliceHeadHtml, stripManagedHeadTags };
2066
- //# sourceMappingURL=chunk-HVSF23P7.js.map
2067
- //# sourceMappingURL=chunk-HVSF23P7.js.map
2189
+ export { AI_BOTS, BodyPrerenderError, CANONICAL_CHARSET_META, CHARSET_BUDGET_BYTES, CHARSET_HEADROOM_WARN_BYTES, CharsetBudgetError, EmittedUrlError, EmittedUrlNotRunError, HeadHonestyError, HeadHonestyNotRunError, SEO_HEAD_ROUTE_HAS_NO_META, absolutizeUrl, assertHeadHonesty, assertHeadHonestyExecuted, auditCheckedTotal, auditSameOriginAssets, breadcrumbTrailFromRoute, buildBlogPosting, buildBreadcrumbList, buildFaqPage, buildGlobalGraph, buildHasCredential, buildHeadTags, buildLlmsTxt, buildReviewSchemaParts, buildRobotsTxt, buildSeoHeadRouteMap, buildSitemapXml, buildVitePressSeoHead, checkCharsetBudget, checkRemoteAssets, checkSameOriginAssets, classifyUrl, collectEmittedUrlsFromFiles, collectUrlsFromHtml, collectUrlsFromLlmsTxt, collectUrlsFromSitemap, createSeoTransformPageData, decodeHtmlEntities, dedupeUrls, defaultRelativePathToRoute, deriveSameAs, escapeJsonLd, extractBakedHead, filterRealFaq, filterRealReviews, findBusinessLicense, findCharsetByteOffset, findDuplicateNormalizedPaths, findGlobalSchemasOfType, findHeadHonestyExecutionFaults, findHeadHonestyViolations, findLocalBusinessSchema, findReviewItemsForPage, formatCharsetHeadroomWarning, formatDuplicateNormalizedPaths, formatEmittedUrlAudit, formatEmittedUrlReport, formatHeadHonestyExecutionReport, formatHeadHonestyReport, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, graphAbsorbs, graphIds, headInputIdentities, hoistCharsetMeta, installSeoHead, isBodyPrerenderEnabled, isHandAuthoredRobotsAcceptable, isLocalBusinessType, isManagedLinkRel, isManagedMetaTag, isRouteEmitted, isRouteIndexable, loadPagesManifest, normalizeHeadText, normalizeSeoHeadPath, parsePagesManifest, prerenderBodies, renderHeadTags, resolveHonestyMode, resolvePageSeo, routeExclusionReason, routeToOutputFile, slugToTitle, snapshotBakedManagedHeadTags, spliceBodyHtml, spliceHeadHtml, stripManagedHeadTags, sweepBakedManagedHeadTags };
2190
+ //# sourceMappingURL=chunk-64BUBTW7.js.map
2191
+ //# sourceMappingURL=chunk-64BUBTW7.js.map