@duffcloudservices/cms 0.13.2 → 0.13.4

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.
@@ -1,9 +1,9 @@
1
- import { H as HeadMetaTag, l as HeadLinkTag } from '../installSeoHead-EE0Z7UmK.js';
2
- export { m as HeadScriptTag, o as HeadTagOverrides, I as InstallSeoHeadOptions, R as ResolvedHeadTags, B as SEO_HEAD_ROUTE_HAS_NO_META, q as SeoHeadClientLike, t as SeoHeadEntryLike, u as SeoHeadInput, v as SeoHeadPageRoute, w as SeoHeadPagesManifest, x as SeoHeadResolution, y as SeoHeadResolutionReason, C as SeoHeadRouteHasNoMeta, p as SeoHeadRouteLike, S as SeoHeadRouterLike, b as buildHeadTags, k as buildSeoHeadRouteMap, f as escapeJsonLd, z as findDuplicateNormalizedPaths, A as formatDuplicateNormalizedPaths, g as generateJsonLd, a as generateOpenGraphMeta, c as generateTwitterMeta, j as installSeoHead, h as isManagedLinkRel, i as isManagedMetaTag, n as normalizeSeoHeadPath, d as renderHeadTags, r as resolvePageSeo, s as spliceHeadHtml, e as stripManagedHeadTags } from '../installSeoHead-EE0Z7UmK.js';
3
- import { Z as PageRouteEntry } from '../vitepressTransform-Ds5Hff8t.js';
4
- export { A as AI_BOTS, y as BlogMeta, x as BreadcrumbCrumb, v as BuildLlmsParams, u as BuildRobotsParams, B as BuildSitemapParams, E as ContentConfig, C as CreateSeoTransformPageDataOptions, D as DcsRobotsOptions, F as FaqSource, ad as NormalisedFaq, ac as NormalisedReview, R as ResolvedPageOverrides, ae as ReviewSchemaParts, z as ReviewSource, a2 as RouteEmissionRules, a3 as RouteExclusionReason, w as SchemaObject, S as SeoPageContext, r as SeoPageTypeRule, t as VitePressHeadConfig, V as VitePressPageData, q as absolutizeUrl, j as breadcrumbTrailFromRoute, k as buildBlogPosting, h as buildBreadcrumbList, l as buildFaqPage, g as buildGlobalGraph, a4 as buildHasCredential, f as buildLlmsTxt, m as buildReviewSchemaParts, e as buildRobotsTxt, a as buildSitemapXml, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute, a9 as deriveSameAs, o as filterRealFaq, n as filterRealReviews, a8 as findBusinessLicense, a6 as findGlobalSchemasOfType, a5 as findLocalBusinessSchema, p as findReviewItemsForPage, a7 as graphAbsorbs, aa as graphIds, ab as isLocalBusinessType, a0 as isRouteEmitted, i as isRouteIndexable, _ as loadPagesManifest, $ as parsePagesManifest, a1 as routeExclusionReason, s as slugToTitle } from '../vitepressTransform-Ds5Hff8t.js';
5
- import { a as HeadObservation } from '../headHonesty-OzxvLuwd.js';
6
- export { B as BakedHead, g as HeadHonestyError, l as HeadHonestyExecution, k as HeadHonestyNotRunError, p as HeadHonestyOptions, o as HeadHonestyViolation, H as HonestyMode, m as HonestyModeInput, b as assertHeadHonesty, h as assertHeadHonestyExecuted, d as decodeHtmlEntities, e as extractBakedHead, i as findHeadHonestyExecutionFaults, f as findHeadHonestyViolations, j as formatHeadHonestyExecutionReport, c as formatHeadHonestyReport, n as normalizeHeadText, r as resolveHonestyMode } from '../headHonesty-OzxvLuwd.js';
1
+ import { H as HeadMetaTag, l as HeadLinkTag } from '../installSeoHead-CuVdVCis.js';
2
+ export { m as HeadScriptTag, o as HeadTagOverrides, I as InstallSeoHeadOptions, R as ResolvedHeadTags, B as SEO_HEAD_ROUTE_HAS_NO_META, q as SeoHeadClientLike, t as SeoHeadEntryLike, u as SeoHeadInput, v as SeoHeadPageRoute, w as SeoHeadPagesManifest, x as SeoHeadResolution, y as SeoHeadResolutionReason, C as SeoHeadRouteHasNoMeta, p as SeoHeadRouteLike, S as SeoHeadRouterLike, b as buildHeadTags, k as buildSeoHeadRouteMap, f as escapeJsonLd, z as findDuplicateNormalizedPaths, A as formatDuplicateNormalizedPaths, g as generateJsonLd, a as generateOpenGraphMeta, c as generateTwitterMeta, j as installSeoHead, h as isManagedLinkRel, i as isManagedMetaTag, n as normalizeSeoHeadPath, d as renderHeadTags, r as resolvePageSeo, s as spliceHeadHtml, e as stripManagedHeadTags } from '../installSeoHead-CuVdVCis.js';
3
+ import { Z as PageRouteEntry } from '../vitepressTransform-CZ_IB3dq.js';
4
+ export { A as AI_BOTS, y as BlogMeta, x as BreadcrumbCrumb, v as BuildLlmsParams, u as BuildRobotsParams, B as BuildSitemapParams, E as ContentConfig, C as CreateSeoTransformPageDataOptions, D as DcsRobotsOptions, F as FaqSource, ag as NormalisedFaq, af as NormalisedReview, ai as PostalAddressGap, ab as REQUIRED_POSTAL_ADDRESS_FIELDS, R as ResolvedPageOverrides, ah as ReviewSchemaParts, z as ReviewSource, a2 as RouteEmissionRules, a3 as RouteExclusionReason, w as SchemaObject, S as SeoPageContext, r as SeoPageTypeRule, t as VitePressHeadConfig, V as VitePressPageData, q as absolutizeUrl, j as breadcrumbTrailFromRoute, k as buildBlogPosting, h as buildBreadcrumbList, l as buildFaqPage, g as buildGlobalGraph, a4 as buildHasCredential, f as buildLlmsTxt, m as buildReviewSchemaParts, e as buildRobotsTxt, a as buildSitemapXml, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute, ac as deriveSameAs, o as filterRealFaq, n as filterRealReviews, a8 as findBusinessLicense, a6 as findGlobalSchemasOfType, a5 as findLocalBusinessSchema, a9 as findPostalAddressGaps, p as findReviewItemsForPage, aa as formatPostalAddressGapReport, a7 as graphAbsorbs, ad as graphIds, ae as isLocalBusinessType, a0 as isRouteEmitted, i as isRouteIndexable, _ as loadPagesManifest, $ as parsePagesManifest, a1 as routeExclusionReason, s as slugToTitle } from '../vitepressTransform-CZ_IB3dq.js';
5
+ import { a as HeadObservation, H as HonestyMode } from '../headHonesty-OzxvLuwd.js';
6
+ export { B as BakedHead, g as HeadHonestyError, l as HeadHonestyExecution, k as HeadHonestyNotRunError, p as HeadHonestyOptions, o as HeadHonestyViolation, m as HonestyModeInput, b as assertHeadHonesty, h as assertHeadHonestyExecuted, d as decodeHtmlEntities, e as extractBakedHead, i as findHeadHonestyExecutionFaults, f as findHeadHonestyViolations, j as formatHeadHonestyExecutionReport, c as formatHeadHonestyReport, n as normalizeHeadText, r as resolveHonestyMode } from '../headHonesty-OzxvLuwd.js';
7
7
  import 'vue';
8
8
 
9
9
  /**
@@ -344,6 +344,131 @@ interface BodyPrerenderEnableInput {
344
344
  */
345
345
  declare function isBodyPrerenderEnabled(input: BodyPrerenderEnableInput): boolean;
346
346
 
347
+ /**
348
+ * FAQ body-honesty rail — a `FAQPage` node must describe copy the page RENDERS.
349
+ *
350
+ * ## The defect class
351
+ *
352
+ * `buildFaqPage` (`./schemaGraph.ts`) emits a `FAQPage` from any well-formed
353
+ * `{q,a}` pair it finds in `.dcs/content.yaml`. That gate enforces the SHAPE of
354
+ * the honesty rule (no invented Q&A) but it cannot enforce its SUBSTANCE: the
355
+ * builder is pure and has no idea whether the page it decorates renders those
356
+ * questions anywhere a human — or a crawler — can see them. Seeding
357
+ * `pages.<slug>.faq` without also rendering a visible FAQ section therefore
358
+ * ships structured data for content that does not exist on the page. Google's
359
+ * FAQPage guidance requires the Q&A be visible to the user; markup for invisible
360
+ * content is a spam signal, and it is the exact fabrication class the rest of
361
+ * this module tree exists to prevent.
362
+ *
363
+ * Measured origin: the 2026-08-30 Iron Oak drafting pass
364
+ * (`.docs/analysis/iron-oak-seo-drafts-2026-08-30.md` §3, "The honesty condition
365
+ * on all of the above") — Iron Oak has Q&A-shaped copy and no FAQ section, so
366
+ * applying the FAQPage finding as a one-step content seed would have produced
367
+ * exactly this defect on a live customer site.
368
+ *
369
+ * ## The rail
370
+ *
371
+ * Given an emitted document, take every `Question.name` out of every `FAQPage`
372
+ * JSON-LD node and require each one to appear in the PRERENDERED BODY TEXT.
373
+ *
374
+ * ## The vacuity trap this rail is built around
375
+ *
376
+ * The question text is, by construction, already present in the document — it is
377
+ * sitting in the `<script type="application/ld+json">` block being audited.
378
+ * `dcsSeoPlugin` bakes that block into `<head>`, which `extractPrerenderedBodyText`
379
+ * never reads, but not every emitter does (vite-ssg and hand-rolled prerenders
380
+ * put JSON-LD in the body), and a body extractor that keeps `<script>` content
381
+ * would then find every question in every document — green against a site with
382
+ * no FAQ section at all. `extractPrerenderedBodyText` therefore strips
383
+ * `<script>`, `<style>`, `<noscript>` and comments CONTENT-AND-ALL, and
384
+ * `faqBodyHonesty.test.ts` exercises the body-placed case explicitly.
385
+ */
386
+
387
+ /** One emitted document to audit. */
388
+ interface FaqBodyObservation {
389
+ /** Route path as it appears in `pages.yaml` (e.g. `/` or `/services`). */
390
+ route: string;
391
+ /** The emitted HTML for that route, exactly as written to `dist/`. */
392
+ html: string;
393
+ }
394
+ /** A `FAQPage` question with no counterpart in the rendered body. */
395
+ interface FaqBodyViolation {
396
+ route: string;
397
+ /** The `Question.name` verbatim, as authored in the JSON-LD. */
398
+ question: string;
399
+ /** How many questions that document's FAQPage node(s) declared in total. */
400
+ questionCount: number;
401
+ }
402
+ /** Configuration for the rail. */
403
+ interface FaqBodyHonestyOptions {
404
+ /**
405
+ * Route paths exempted from the comparison. An exemption is still the caller's
406
+ * to LOG — same contract as `HeadHonestyOptions.allow`.
407
+ */
408
+ allow?: string[];
409
+ }
410
+ /**
411
+ * Normalise a string for PRESENCE comparison (is this text on the page?), which
412
+ * is a weaker question than head honesty's IDENTITY comparison and so normalises
413
+ * more:
414
+ * - HTML entities decoded (`&amp;` ⇒ `&`) — the two sides are escaped
415
+ * differently: JSON-LD escapes for JSON, body copy escapes for HTML.
416
+ * - typographic look-alikes folded to ASCII (`’ ‘ “ ” — –` ⇒ `' ' " " - -`) —
417
+ * the schema string and the rendered string come from the SAME content key
418
+ * but the rendered one may pass through a typography transform. A rail that
419
+ * reds because a renderer curled an apostrophe is an accusation, not a find.
420
+ * - whitespace collapsed — rendered copy is wrapped and indented.
421
+ * - lowercased — a question rendered as a title-cased heading is still visible.
422
+ *
423
+ * Deliberately NOT normalised: words. Nothing is stemmed, dropped or fuzzy-
424
+ * matched, so a "close enough" body text does not satisfy the rail.
425
+ */
426
+ declare function normalizeForPresence(value: string): string;
427
+ /**
428
+ * Collect every `Question.name` declared by every `FAQPage` node in a document.
429
+ *
430
+ * Unparseable JSON-LD is SKIPPED, not reported — `cli/seo-coverage-audit.mjs`
431
+ * already fails a route for unparseable structured data, and this rail must not
432
+ * double-report someone else's finding as an FAQ defect.
433
+ */
434
+ declare function extractFaqQuestions(html: string): string[];
435
+ /**
436
+ * The visible text of the PRERENDERED BODY — what a non-JS crawler receives.
437
+ *
438
+ * `<script>`, `<style>`, `<noscript>` and HTML comments are removed CONTENT AND
439
+ * ALL before tags are stripped. That is not tidiness: the JSON-LD under audit
440
+ * lives in a `<script>`, so leaving script content in would make every question
441
+ * trivially "present" and the rail vacuous (see the module header).
442
+ *
443
+ * Returns `''` when there is no `<body>` — an SPA shell with no prerendered body
444
+ * has no visible copy, which is the honest answer, not a parse failure.
445
+ */
446
+ declare function extractPrerenderedBodyText(html: string): string;
447
+ /**
448
+ * Report every `FAQPage` question that the route's prerendered body does not
449
+ * render. A document with no `FAQPage` node yields nothing — the rail is about
450
+ * schema that over-claims, never about pages that decline to have an FAQ.
451
+ */
452
+ declare function findFaqBodyHonestyViolations(observations: FaqBodyObservation[], options?: FaqBodyHonestyOptions): FaqBodyViolation[];
453
+ /**
454
+ * Render the full report. Names the route, the question, and the fix — both
455
+ * halves of the fix, because deleting the schema and rendering the section are
456
+ * both valid resolutions and the operator picks.
457
+ */
458
+ declare function formatFaqBodyHonestyReport(violations: FaqBodyViolation[]): string;
459
+ /** Thrown in `error` mode so the build goes red on invisible FAQ markup. */
460
+ declare class FaqBodyHonestyError extends Error {
461
+ readonly violations: FaqBodyViolation[];
462
+ constructor(violations: FaqBodyViolation[]);
463
+ }
464
+ /**
465
+ * Enforce the rail. `error` throws, `warn` logs, `off` does nothing.
466
+ * Mirrors `assertHeadHonesty` so the three-mode contract is uniform across rails.
467
+ */
468
+ declare function assertFaqBodyHonesty(observations: FaqBodyObservation[], options?: FaqBodyHonestyOptions & {
469
+ mode?: HonestyMode;
470
+ }): FaqBodyViolation[];
471
+
347
472
  /**
348
473
  * THE HEAD-AUTHORITY CONTRACT (C-356) — a source-level audit.
349
474
  *
@@ -1045,4 +1170,4 @@ declare class CharsetBudgetError extends Error {
1045
1170
  */
1046
1171
  declare function formatCharsetHeadroomWarning(file: string, result: CharsetBudgetResult, warnBelow?: number): string | null;
1047
1172
 
1048
- export { type BodyPrerenderEnableInput, BodyPrerenderError, CANONICAL_CHARSET_META, CHARSET_BUDGET_BYTES, CHARSET_HEADROOM_WARN_BYTES, CharsetBudgetError, type CharsetBudgetResult, type ClassifiedUrl, type EmittedUrl, type EmittedUrlAudit, EmittedUrlError, EmittedUrlNotRunError, type EmittedUrlViolation, type EmittedUrlWarning, type FilteredRoute, type ForeignWriterSite, HeadContractError, type HeadContractExemptionAudit, type HeadContractExemptionCheck, type HeadContractExemptionExpectation, type HeadContractExemptionRecord, type HeadContractExemptionValue, type HeadContractExemptionVerdict, type HeadContractHostProbe, type HeadContractInput, type HeadContractMode, type HeadContractReport, type HeadContractSeoConfig, type HeadContractSourceFile, type HeadContractViolation, type HeadContractViolationCode, type HeadContractWarning, type HeadContractWarningCode, HeadLinkTag, HeadMetaTag, HeadObservation, MIN_EXEMPTION_REASON_LENGTH, PageRouteEntry, type PrerenderBodiesParams, type PrerenderBodiesResult, type PrerenderFilterReason, type RemoteCheckOptions, type RemoteCheckResult, type RenderedHead, type RenderedRoute, type RouteRenderer, type SameOriginAuditResult, type UrlExpectation, type UrlLocality, assertHeadContract, auditCheckedTotal, auditExemptionHosts, auditHeadContract, auditSameOriginAssets, blankComments, checkCharsetBudget, checkRemoteAssets, checkSameOriginAssets, classifyUrl, collectUrlsFromHtml, collectUrlsFromLlmsTxt, collectUrlsFromSitemap, dedupeUrls, findCharsetByteOffset, formatCharsetHeadroomWarning, formatEmittedUrlAudit, formatEmittedUrlReport, formatExemptionAudit, formatHeadContractReport, headInputIdentities, hoistCharsetMeta, hostsNamedIn, isBodyPrerenderEnabled, normalizeExemption, parseHostProbeHtml, prerenderBodies, routeToOutputFile, snapshotBakedManagedHeadTags, spliceBodyHtml, sweepBakedManagedHeadTags };
1173
+ export { type BodyPrerenderEnableInput, BodyPrerenderError, CANONICAL_CHARSET_META, CHARSET_BUDGET_BYTES, CHARSET_HEADROOM_WARN_BYTES, CharsetBudgetError, type CharsetBudgetResult, type ClassifiedUrl, type EmittedUrl, type EmittedUrlAudit, EmittedUrlError, EmittedUrlNotRunError, type EmittedUrlViolation, type EmittedUrlWarning, FaqBodyHonestyError, type FaqBodyHonestyOptions, type FaqBodyObservation, type FaqBodyViolation, type FilteredRoute, type ForeignWriterSite, HeadContractError, type HeadContractExemptionAudit, type HeadContractExemptionCheck, type HeadContractExemptionExpectation, type HeadContractExemptionRecord, type HeadContractExemptionValue, type HeadContractExemptionVerdict, type HeadContractHostProbe, type HeadContractInput, type HeadContractMode, type HeadContractReport, type HeadContractSeoConfig, type HeadContractSourceFile, type HeadContractViolation, type HeadContractViolationCode, type HeadContractWarning, type HeadContractWarningCode, HeadLinkTag, HeadMetaTag, HeadObservation, HonestyMode, MIN_EXEMPTION_REASON_LENGTH, PageRouteEntry, type PrerenderBodiesParams, type PrerenderBodiesResult, type PrerenderFilterReason, type RemoteCheckOptions, type RemoteCheckResult, type RenderedHead, type RenderedRoute, type RouteRenderer, type SameOriginAuditResult, type UrlExpectation, type UrlLocality, assertFaqBodyHonesty, assertHeadContract, auditCheckedTotal, auditExemptionHosts, auditHeadContract, auditSameOriginAssets, blankComments, checkCharsetBudget, checkRemoteAssets, checkSameOriginAssets, classifyUrl, collectUrlsFromHtml, collectUrlsFromLlmsTxt, collectUrlsFromSitemap, dedupeUrls, extractFaqQuestions, extractPrerenderedBodyText, findCharsetByteOffset, findFaqBodyHonestyViolations, formatCharsetHeadroomWarning, formatEmittedUrlAudit, formatEmittedUrlReport, formatExemptionAudit, formatFaqBodyHonestyReport, formatHeadContractReport, headInputIdentities, hoistCharsetMeta, hostsNamedIn, isBodyPrerenderEnabled, normalizeExemption, normalizeForPresence, parseHostProbeHtml, prerenderBodies, routeToOutputFile, snapshotBakedManagedHeadTags, spliceBodyHtml, sweepBakedManagedHeadTags };
package/dist/seo/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { HeadContractError, MIN_EXEMPTION_REASON_LENGTH, assertHeadContract, auditExemptionHosts, auditHeadContract, blankComments, formatExemptionAudit, formatHeadContractReport, hostsNamedIn, normalizeExemption, parseHostProbeHtml } from '../chunk-OGAJX4KM.js';
2
- 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, 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, isLocalBusinessType, isManagedLinkRel, isManagedMetaTag, isRouteEmitted, isRouteIndexable, loadPagesManifest, normalizeHeadText, normalizeSeoHeadPath, parsePagesManifest, prerenderBodies, renderHeadTags, resolveHonestyMode, resolvePageSeo, routeExclusionReason, routeToOutputFile, slugToTitle, snapshotBakedManagedHeadTags, spliceBodyHtml, spliceHeadHtml, stripManagedHeadTags, sweepBakedManagedHeadTags } from '../chunk-64BUBTW7.js';
1
+ export { FaqBodyHonestyError, HeadContractError, MIN_EXEMPTION_REASON_LENGTH, assertFaqBodyHonesty, assertHeadContract, auditExemptionHosts, auditHeadContract, blankComments, extractFaqQuestions, extractPrerenderedBodyText, findFaqBodyHonestyViolations, formatExemptionAudit, formatFaqBodyHonestyReport, formatHeadContractReport, hostsNamedIn, normalizeExemption, normalizeForPresence, parseHostProbeHtml } from '../chunk-GBHJVHXR.js';
2
+ export { AI_BOTS, BodyPrerenderError, CANONICAL_CHARSET_META, CHARSET_BUDGET_BYTES, CHARSET_HEADROOM_WARN_BYTES, CharsetBudgetError, EmittedUrlError, EmittedUrlNotRunError, HeadHonestyError, HeadHonestyNotRunError, REQUIRED_POSTAL_ADDRESS_FIELDS, 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, collectUrlsFromHtml, collectUrlsFromLlmsTxt, collectUrlsFromSitemap, createSeoTransformPageData, decodeHtmlEntities, dedupeUrls, defaultRelativePathToRoute, deriveSameAs, escapeJsonLd, extractBakedHead, filterRealFaq, filterRealReviews, findBusinessLicense, findCharsetByteOffset, findDuplicateNormalizedPaths, findGlobalSchemasOfType, findHeadHonestyExecutionFaults, findHeadHonestyViolations, findLocalBusinessSchema, findPostalAddressGaps, findReviewItemsForPage, formatCharsetHeadroomWarning, formatDuplicateNormalizedPaths, formatEmittedUrlAudit, formatEmittedUrlReport, formatHeadHonestyExecutionReport, formatHeadHonestyReport, formatPostalAddressGapReport, generateJsonLd, generateOpenGraphMeta, generateTwitterMeta, graphAbsorbs, graphIds, headInputIdentities, hoistCharsetMeta, installSeoHead, isBodyPrerenderEnabled, isLocalBusinessType, isManagedLinkRel, isManagedMetaTag, isRouteEmitted, isRouteIndexable, loadPagesManifest, normalizeHeadText, normalizeSeoHeadPath, parsePagesManifest, prerenderBodies, renderHeadTags, resolveHonestyMode, resolvePageSeo, routeExclusionReason, routeToOutputFile, slugToTitle, snapshotBakedManagedHeadTags, spliceBodyHtml, spliceHeadHtml, stripManagedHeadTags, sweepBakedManagedHeadTags } from '../chunk-W5Q6SOAP.js';
3
3
  //# sourceMappingURL=index.js.map
4
4
  //# sourceMappingURL=index.js.map
@@ -420,6 +420,52 @@ declare function findLocalBusinessSchema(global: GlobalSeoConfig): {
420
420
  schema: SeoSchemaConfig;
421
421
  index: number;
422
422
  } | null;
423
+ /**
424
+ * The `PostalAddress` fields that together make a NAP a real, mappable address.
425
+ * Order is the order a human writes one, which is the order the warning prints.
426
+ */
427
+ declare const REQUIRED_POSTAL_ADDRESS_FIELDS: readonly ["streetAddress", "addressLocality", "addressRegion", "postalCode", "addressCountry"];
428
+ /** A LocalBusiness node that CLAIMS an address but does not finish it. */
429
+ interface PostalAddressGap {
430
+ /** The LocalBusiness `@type` as authored (e.g. `HomeAndConstructionBusiness`). */
431
+ businessType: string;
432
+ /** Required fields absent or blank, in `REQUIRED_POSTAL_ADDRESS_FIELDS` order. */
433
+ missing: string[];
434
+ /** Required fields actually supplied — what makes the node look answered. */
435
+ present: string[];
436
+ }
437
+ /**
438
+ * Report a LocalBusiness `address` that is PRESENT BUT PARTIAL.
439
+ *
440
+ * `buildGlobalGraph` spreads the hand-authored node's properties verbatim
441
+ * (`...props`), so a `PostalAddress` carrying only a locality and a region ships
442
+ * to crawlers exactly as authored, with nothing anywhere saying it is half an
443
+ * address. Google matches a LocalBusiness to a place using the NAP; a partial
444
+ * address is the shape that silently fails that match while every audit that
445
+ * only asks "is there an address node?" stays green.
446
+ *
447
+ * ## Why an ABSENT address is not reported
448
+ *
449
+ * A service-area business (a contractor working out of a home, a mobile trade)
450
+ * is *supposed* to omit `address` — that is Google's own guidance, and several
451
+ * DCS fleet sites are exactly that. Warning there would be an accusation
452
+ * levelled at correct configuration, and a warning people learn to ignore is
453
+ * worth less than no warning. The defect is the CLAIM that is not finished, so
454
+ * the trigger is an address node that exists and is incomplete.
455
+ *
456
+ * A non-object `address` (a plain string) is also skipped: it is unstructured,
457
+ * not partial, and belongs to a different finding.
458
+ *
459
+ * @returns one gap per incomplete address, or `[]` when complete/absent.
460
+ */
461
+ declare function findPostalAddressGaps(global: GlobalSeoConfig): PostalAddressGap[];
462
+ /**
463
+ * Render the partial-address warning. Names the type, what is there, and what is
464
+ * not — an operator must be able to act without re-deriving the finding, and the
465
+ * "or drop the address entirely" branch is stated because for a service-area
466
+ * business that is the CORRECT fix, not a workaround.
467
+ */
468
+ declare function formatPostalAddressGapReport(gaps: PostalAddressGap[]): string;
423
469
  /**
424
470
  * True when `buildGlobalGraph` provides this schema canonically, so it must NOT
425
471
  * also be emitted standalone: the LocalBusiness subtype it folds in, or a global
@@ -1144,4 +1190,4 @@ declare function buildVitePressSeoHead(pageData: VitePressPageData, options: Cre
1144
1190
  */
1145
1191
  declare function createSeoTransformPageData(options: CreateSeoTransformPageDataOptions): (pageData: VitePressPageData) => void;
1146
1192
 
1147
- export { parsePagesManifest as $, AI_BOTS as A, type BuildSitemapParams as B, type CreateSeoTransformPageDataOptions as C, type DcsRobotsOptions as D, type ContentConfig as E, type FaqSource as F, type GlobalSeoConfig as G, type SeoConfiguration as H, type SeoAuthorConfig as I, type SeoSocialConfig as J, type SeoImagesConfig as K, type SeoOpenGraphConfig as L, type SeoTwitterConfig as M, type SeoSchemaConfig as N, type SeoAlternateConfig as O, type PageSeoConfig as P, type SeoVerificationConfig as Q, type ResolvedPageOverrides as R, type SeoPageContext as S, type ResolvedPageSeo as T, type UseSeoReturn as U, type VitePressPageData as V, type UseSeoConfig as W, type HeadOverrides as X, type SeoHonestyBlock as Y, type PageRouteEntry as Z, loadPagesManifest as _, buildSitemapXml as a, isRouteEmitted as a0, routeExclusionReason as a1, type RouteEmissionRules as a2, type RouteExclusionReason as a3, buildHasCredential as a4, findLocalBusinessSchema as a5, findGlobalSchemasOfType as a6, graphAbsorbs as a7, findBusinessLicense as a8, deriveSameAs as a9, graphIds as aa, isLocalBusinessType as ab, type NormalisedReview as ac, type NormalisedFaq as ad, type ReviewSchemaParts as ae, buildVitePressSeoHead as b, createSeoTransformPageData as c, defaultRelativePathToRoute as d, buildRobotsTxt as e, buildLlmsTxt as f, buildGlobalGraph as g, buildBreadcrumbList as h, isRouteIndexable as i, breadcrumbTrailFromRoute as j, buildBlogPosting as k, buildFaqPage as l, buildReviewSchemaParts as m, filterRealReviews as n, filterRealFaq as o, findReviewItemsForPage as p, absolutizeUrl as q, type SeoPageTypeRule as r, slugToTitle as s, type VitePressHeadConfig as t, type BuildRobotsParams as u, type BuildLlmsParams as v, type SchemaObject as w, type BreadcrumbCrumb as x, type BlogMeta as y, type ReviewSource as z };
1193
+ export { parsePagesManifest as $, AI_BOTS as A, type BuildSitemapParams as B, type CreateSeoTransformPageDataOptions as C, type DcsRobotsOptions as D, type ContentConfig as E, type FaqSource as F, type GlobalSeoConfig as G, type SeoConfiguration as H, type SeoAuthorConfig as I, type SeoSocialConfig as J, type SeoImagesConfig as K, type SeoOpenGraphConfig as L, type SeoTwitterConfig as M, type SeoSchemaConfig as N, type SeoAlternateConfig as O, type PageSeoConfig as P, type SeoVerificationConfig as Q, type ResolvedPageOverrides as R, type SeoPageContext as S, type ResolvedPageSeo as T, type UseSeoReturn as U, type VitePressPageData as V, type UseSeoConfig as W, type HeadOverrides as X, type SeoHonestyBlock as Y, type PageRouteEntry as Z, loadPagesManifest as _, buildSitemapXml as a, isRouteEmitted as a0, routeExclusionReason as a1, type RouteEmissionRules as a2, type RouteExclusionReason as a3, buildHasCredential as a4, findLocalBusinessSchema as a5, findGlobalSchemasOfType as a6, graphAbsorbs as a7, findBusinessLicense as a8, findPostalAddressGaps as a9, formatPostalAddressGapReport as aa, REQUIRED_POSTAL_ADDRESS_FIELDS as ab, deriveSameAs as ac, graphIds as ad, isLocalBusinessType as ae, type NormalisedReview as af, type NormalisedFaq as ag, type ReviewSchemaParts as ah, type PostalAddressGap as ai, buildVitePressSeoHead as b, createSeoTransformPageData as c, defaultRelativePathToRoute as d, buildRobotsTxt as e, buildLlmsTxt as f, buildGlobalGraph as g, buildBreadcrumbList as h, isRouteIndexable as i, breadcrumbTrailFromRoute as j, buildBlogPosting as k, buildFaqPage as l, buildReviewSchemaParts as m, filterRealReviews as n, filterRealFaq as o, findReviewItemsForPage as p, absolutizeUrl as q, type SeoPageTypeRule as r, slugToTitle as s, type VitePressHeadConfig as t, type BuildRobotsParams as u, type BuildLlmsParams as v, type SchemaObject as w, type BreadcrumbCrumb as x, type BlogMeta as y, type ReviewSource as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duffcloudservices/cms",
3
- "version": "0.13.2",
3
+ "version": "0.13.4",
4
4
  "description": "Vue 3 composables and Vite plugins for DCS CMS integration",
5
5
  "type": "module",
6
6
  "exports": {
@@ -58,7 +58,7 @@
58
58
  },
59
59
  "dependencies": {
60
60
  "js-yaml": "^4.1.0",
61
- "@duffcloudservices/cms-core": "0.8.2"
61
+ "@duffcloudservices/cms-core": "0.8.4"
62
62
  },
63
63
  "devDependencies": {
64
64
  "@types/js-yaml": "^4.0.9",