@duffcloudservices/cms 0.11.0 → 0.13.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/README.md +244 -8
- package/dist/chunk-A5F4C72F.js +500 -0
- package/dist/chunk-A5F4C72F.js.map +1 -0
- package/dist/{chunk-F3EIWEZD.js → chunk-HVSF23P7.js} +971 -73
- package/dist/chunk-HVSF23P7.js.map +1 -0
- package/dist/editor/editorBridge.d.ts +53 -1
- package/dist/editor/editorBridge.js +141 -5
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/headHonesty-OzxvLuwd.d.ts +222 -0
- package/dist/index.d.ts +369 -22
- package/dist/index.js +424 -22
- package/dist/index.js.map +1 -1
- package/dist/installSeoHead-kWQwObez.d.ts +627 -0
- package/dist/plugins/index.d.ts +173 -6
- package/dist/plugins/index.js +628 -54
- package/dist/plugins/index.js.map +1 -1
- package/dist/seo/index.d.ts +763 -4
- package/dist/seo/index.js +2 -2
- package/dist/{vitepressTransform-DfmABXmK.d.ts → vitepressTransform-JG_zlaux.d.ts} +99 -6
- package/package.json +26 -16
- package/src/components/DcsCallButton.test.ts +58 -0
- package/src/components/DcsCallButton.vue +19 -4
- package/src/components/DcsReviewShowcase.vue +5 -1
- package/src/components/LiteMediaEmbed.vue +3 -3
- package/src/components/ManagedImage.test.ts +94 -0
- package/src/components/ManagedImage.vue +58 -6
- package/src/components/PreviewRibbon.vue +4 -1
- package/src/composables/useConversionTracking.test.ts +492 -0
- package/src/composables/useConversionTracking.ts +770 -0
- package/src/composables/useReleaseNotes.ts +7 -1
- package/src/composables/useResponsiveImage.ts +6 -0
- package/src/composables/useSEO.applyHead.test.ts +150 -0
- package/src/composables/useSEO.ts +63 -17
- package/src/composables/useSiteVersion.ts +4 -1
- package/src/composables/useSiteVisitorSession.test.ts +56 -0
- package/src/composables/useSiteVisitorSession.ts +39 -3
- package/src/composables/useTextContent.ts +9 -1
- package/dist/chunk-DAYLLSEE.js +0 -3
- package/dist/chunk-DAYLLSEE.js.map +0 -1
- package/dist/chunk-F3EIWEZD.js.map +0 -1
- package/dist/spliceHeadHtml-CsBEucGy.d.ts +0 -254
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Plugin, HtmlTagDescriptor } from 'vite';
|
|
2
|
-
import { D as DcsRobotsOptions, Y as PageRouteEntry, H as SeoConfiguration, E as ContentConfig } from '../vitepressTransform-
|
|
3
|
-
export { C as CreateSeoTransformPageDataOptions, R as ResolvedPageOverrides, S as SeoPageContext, r as SeoPageTypeRule, t as VitePressHeadConfig, V as VitePressPageData, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute } from '../vitepressTransform-
|
|
2
|
+
import { D as DcsRobotsOptions, Y as SeoHonestyBlock, Z as PageRouteEntry, H as SeoConfiguration, E as ContentConfig } from '../vitepressTransform-JG_zlaux.js';
|
|
3
|
+
export { C as CreateSeoTransformPageDataOptions, R as ResolvedPageOverrides, S as SeoPageContext, r as SeoPageTypeRule, t as VitePressHeadConfig, V as VitePressPageData, b as buildVitePressSeoHead, c as createSeoTransformPageData, d as defaultRelativePathToRoute } from '../vitepressTransform-JG_zlaux.js';
|
|
4
|
+
import { H as HonestyMode } from '../headHonesty-OzxvLuwd.js';
|
|
4
5
|
import { Component, Plugin as Plugin$1 } from 'vue';
|
|
5
6
|
import MarkdownIt from 'markdown-it';
|
|
6
7
|
|
|
@@ -146,6 +147,14 @@ interface DcsSeoPluginOptions {
|
|
|
146
147
|
* Routes that should receive `robots: noindex, nofollow`. Matched against the
|
|
147
148
|
* route `path` OR `slug`. The home route (`/`) still overwrites
|
|
148
149
|
* `dist/index.html`.
|
|
150
|
+
*
|
|
151
|
+
* This is a PREDICATE OVER `pages.yaml`, not a standalone directive about a
|
|
152
|
+
* URL. An entry that matches no page is a **build error**
|
|
153
|
+
* (`NoindexOrphanError`, C-416) — with no matching page nothing is emitted for
|
|
154
|
+
* it, so the entry would silently do nothing while the URL stayed
|
|
155
|
+
* `index, follow`. Matched raw, so `'/account/'` does not match a `'/account'`
|
|
156
|
+
* route; matched against the full manifest, so an entry that is also in
|
|
157
|
+
* `exclude` is fine.
|
|
149
158
|
*/
|
|
150
159
|
noindex?: string[];
|
|
151
160
|
/**
|
|
@@ -177,6 +186,63 @@ interface DcsSeoPluginOptions {
|
|
|
177
186
|
preview?: boolean;
|
|
178
187
|
/** Emit `dist/llms.txt` (default `true`; always off in preview mode). */
|
|
179
188
|
llms?: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* P1 — assert that each route's BAKED `<title>`/description equals what the
|
|
191
|
+
* app leaves in the `<head>` after it mounts. Rides the prerender browser, so
|
|
192
|
+
* the marginal cost is one `page.evaluate()` per route.
|
|
193
|
+
*
|
|
194
|
+
* Env override: `DCS_SEO_HEAD_HONESTY=error|warn|off`.
|
|
195
|
+
* seo.yaml override: `headHonesty: warn` or `headHonesty: { mode, allow }`.
|
|
196
|
+
*/
|
|
197
|
+
headHonesty?: boolean | HonestyMode | SeoHonestyBlock;
|
|
198
|
+
/**
|
|
199
|
+
* P2 — assert that every URL the factory publishes (JSON-LD logo/image IRIs,
|
|
200
|
+
* `og:image`, icon links, sitemap `<loc>`s, llms.txt links) resolves to what it
|
|
201
|
+
* promises. Same-origin assets are proven against `dist/` with no network;
|
|
202
|
+
* cross-origin assets are probed under a cache + time budget and only a
|
|
203
|
+
* DEFINITIVE wrong answer fails the build.
|
|
204
|
+
*
|
|
205
|
+
* Env override: `DCS_SEO_URL_HONESTY=error|warn|off`,
|
|
206
|
+
* `DCS_SEO_URL_HONESTY_NETWORK=off` to skip the cross-origin probes.
|
|
207
|
+
*/
|
|
208
|
+
urlHonesty?: boolean | HonestyMode | SeoHonestyBlock;
|
|
209
|
+
/**
|
|
210
|
+
* P12 — hoist `<meta charset>` to the top of `<head>` and assert it lands
|
|
211
|
+
* inside the spec's 1024-byte encoding-sniffing window.
|
|
212
|
+
*
|
|
213
|
+
* Env override: `DCS_SEO_CHARSET_BUDGET=error|warn|off`.
|
|
214
|
+
*/
|
|
215
|
+
charsetBudget?: boolean | HonestyMode | SeoHonestyBlock;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Every key a `noindex` entry can legitimately match.
|
|
219
|
+
*
|
|
220
|
+
* The plugin's contract for `noindex` is a PREDICATE OVER THE ROUTE MANIFEST,
|
|
221
|
+
* never a directive about a bare URL. Every consumer asks the question
|
|
222
|
+
* route-first and identically —
|
|
223
|
+
* `noindexSet.has(route.path) || (route.slug && noindexSet.has(route.slug))` —
|
|
224
|
+
* in {@link emitStaticSeoHtml}, in the sitemap/llms `isRouteIndexable`
|
|
225
|
+
* predicate, and in the baked `__DCS_PAGES__` manifest the runtime re-assert
|
|
226
|
+
* reads. So the set of entries that can ever do anything is exactly the union
|
|
227
|
+
* of the manifest's paths and slugs, and this function is the exact inverse of
|
|
228
|
+
* that same relation rather than a second, drifting implementation of it.
|
|
229
|
+
*/
|
|
230
|
+
declare function collectNoindexMatchKeys(routes: PageRouteEntry[]): Set<string>;
|
|
231
|
+
/**
|
|
232
|
+
* `noindex` entries that match no route in the manifest — i.e. entries that
|
|
233
|
+
* are, today, silent no-ops.
|
|
234
|
+
*
|
|
235
|
+
* Matched with the SAME raw string equality the consumers use, deliberately:
|
|
236
|
+
* a `'/account/'` entry against a `'/account'` route does not match in
|
|
237
|
+
* `emitStaticSeoHtml`, so it must not "match" here either, or the check would
|
|
238
|
+
* bless the very entry the emitter is about to ignore.
|
|
239
|
+
*/
|
|
240
|
+
declare function findOrphanNoindexEntries(noindex: string[], routes: PageRouteEntry[]): string[];
|
|
241
|
+
/** Thrown when a `noindex` entry names nothing in `pages.yaml` (C-416). */
|
|
242
|
+
declare class NoindexOrphanError extends Error {
|
|
243
|
+
readonly orphans: string[];
|
|
244
|
+
readonly knownKeys: string[];
|
|
245
|
+
constructor(orphans: string[], knownKeys: string[]);
|
|
180
246
|
}
|
|
181
247
|
/**
|
|
182
248
|
* Core site-file emitter, separated from Vite so it is unit-testable.
|
|
@@ -483,6 +549,89 @@ declare function dcsCdnBuildEnd(options?: DcsCdnBuildEndOptions): (siteConfig: {
|
|
|
483
549
|
|
|
484
550
|
declare function responsiveImagePlugin(md: MarkdownIt): void;
|
|
485
551
|
|
|
552
|
+
/**
|
|
553
|
+
* Build-time responsive-variant lint for DCS customer sites.
|
|
554
|
+
*
|
|
555
|
+
* The 2026-07-03 flagship-excellence review (P7) found desktop pages shipping
|
|
556
|
+
* un-suffixed **full-size** CDN images (e.g. a 321.9 KB hero `47af1e8f….webp`)
|
|
557
|
+
* instead of a `-md`/`-lg` responsive step. That happens when an image reaches
|
|
558
|
+
* the output HTML through a path that does NOT run the `<img>` → `<picture>`
|
|
559
|
+
* transform — content injected via `__DCS_CONTENT__` and then swapped to the
|
|
560
|
+
* base `cdnUrl` by `dcsCdnBuildEnd`, or a hand-authored `<img src>` pointing at
|
|
561
|
+
* a base CDN asset URL.
|
|
562
|
+
*
|
|
563
|
+
* A DCS CDN asset URL has the shape
|
|
564
|
+
* `https://files.<host>/[content/]<slug>/assets/[<dir>/]<uuid>.<ext>`
|
|
565
|
+
* Responsive variants insert a `-sm`/`-md`/`-lg` suffix before the extension
|
|
566
|
+
* (`…<uuid>-md.webp`). Those suffixes contain non-hex letters, so a URL that
|
|
567
|
+
* still matches the *base* pattern below is, by definition, an un-suffixed
|
|
568
|
+
* full-size asset. This lint scans the built HTML's `<img src>` and
|
|
569
|
+
* `<source srcset>` attributes for such URLs and reports (or fails on) them.
|
|
570
|
+
*
|
|
571
|
+
* It is intentionally a **static-output** lint (VitePress generates HTML after
|
|
572
|
+
* both Vite builds finish, so a Vite plugin hook cannot see the SSG output) and
|
|
573
|
+
* is registered via VitePress `buildEnd`.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* // .vitepress/config.ts
|
|
578
|
+
* import { dcsCdnBuildEnd, dcsResponsiveImageLint, chainBuildEnd } from '@duffcloudservices/cms/plugins'
|
|
579
|
+
*
|
|
580
|
+
* export default defineConfig({
|
|
581
|
+
* // run the CDN rewrite first, then lint the result
|
|
582
|
+
* buildEnd: chainBuildEnd(dcsCdnBuildEnd(), dcsResponsiveImageLint()),
|
|
583
|
+
* })
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
interface ResponsiveImageViolation {
|
|
587
|
+
/** The un-suffixed full-size CDN URL that should have been a responsive variant. */
|
|
588
|
+
url: string;
|
|
589
|
+
/** Which element referenced it. */
|
|
590
|
+
tag: 'img' | 'source';
|
|
591
|
+
}
|
|
592
|
+
/** True when `url` is a base (un-suffixed, full-size) DCS CDN asset URL. */
|
|
593
|
+
declare function isFullSizeCdnUrl(url: string): boolean;
|
|
594
|
+
/**
|
|
595
|
+
* Scan an HTML string and return every `<img src>` / `<source srcset>` reference
|
|
596
|
+
* that resolves to a base (un-suffixed) full-size DCS CDN asset. `srcset`
|
|
597
|
+
* candidates are split on commas and only the URL portion (before the width /
|
|
598
|
+
* density descriptor) is tested. `onerror` and other attributes are ignored —
|
|
599
|
+
* only the actual `src` / `srcset` sources are inspected.
|
|
600
|
+
*/
|
|
601
|
+
declare function findFullSizeCdnImages(html: string): ResponsiveImageViolation[];
|
|
602
|
+
interface DcsResponsiveImageLintOptions {
|
|
603
|
+
/**
|
|
604
|
+
* Throw (fail the build) when full-size images are found. Default `false`
|
|
605
|
+
* (warn only) so the guard can land ahead of the per-site cleanup; flip to
|
|
606
|
+
* `true` in CI once a site's output is clean to prevent regressions.
|
|
607
|
+
*/
|
|
608
|
+
failOnViolation?: boolean;
|
|
609
|
+
/** File extensions to scan in the output directory. Default `['.html']`. */
|
|
610
|
+
extensions?: string[];
|
|
611
|
+
/** Enable per-file debug logging. */
|
|
612
|
+
debug?: boolean;
|
|
613
|
+
}
|
|
614
|
+
interface ResponsiveImageLintReport {
|
|
615
|
+
filesScanned: number;
|
|
616
|
+
violations: Array<ResponsiveImageViolation & {
|
|
617
|
+
file: string;
|
|
618
|
+
}>;
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* VitePress `buildEnd` factory that lints the generated HTML for un-suffixed
|
|
622
|
+
* full-size CDN images. Returns the report (also useful for tests) and either
|
|
623
|
+
* warns or throws depending on `failOnViolation`.
|
|
624
|
+
*/
|
|
625
|
+
declare function dcsResponsiveImageLint(options?: DcsResponsiveImageLintOptions): (siteConfig: {
|
|
626
|
+
outDir: string;
|
|
627
|
+
}) => Promise<ResponsiveImageLintReport>;
|
|
628
|
+
/**
|
|
629
|
+
* Compose multiple VitePress `buildEnd` hooks into one (VitePress accepts a
|
|
630
|
+
* single `buildEnd`). Hooks run sequentially in the order given, so a rewrite
|
|
631
|
+
* hook (`dcsCdnBuildEnd`) can precede a lint (`dcsResponsiveImageLint`).
|
|
632
|
+
*/
|
|
633
|
+
declare function chainBuildEnd<T>(...hooks: Array<((siteConfig: T) => unknown | Promise<unknown>) | undefined | null>): (siteConfig: T) => Promise<void>;
|
|
634
|
+
|
|
486
635
|
/**
|
|
487
636
|
* DCS motion tokens — the customer-site copy of the shared motion vocabulary.
|
|
488
637
|
*
|
|
@@ -500,14 +649,32 @@ declare function responsiveImagePlugin(md: MarkdownIt): void;
|
|
|
500
649
|
*/
|
|
501
650
|
declare const DCS_MOTION_TOKENS_CSS = ":root {\n --motion-duration-stagger: 40ms;\n --motion-duration-micro: 80ms;\n --motion-duration-quick: 150ms;\n --motion-duration-fast: 250ms;\n --motion-duration-medium: 350ms;\n --motion-duration-slow: 400ms;\n --motion-duration-very-slow: 500ms;\n --motion-ease-out: cubic-bezier(0.22, 1, 0.36, 1);\n --motion-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);\n --motion-ease-linear: linear;\n --motion-ease-bounce: cubic-bezier(0.34, 1.36, 0.64, 1);\n --motion-ease-bounce-strong: cubic-bezier(0.34, 3.85, 0.64, 1);\n --motion-distance-xs: 4px;\n --motion-distance-sm: 8px;\n --motion-distance-md: 12px;\n --motion-distance-lg: 24px;\n --motion-scale-lg: 0.96;\n --motion-scale-md: 0.97;\n --motion-scale-sm: 0.98;\n --motion-scale-xs: 0.99;\n --motion-blur-sm: 2px;\n --motion-blur-md: 3px;\n --motion-blur-lg: 8px;\n}";
|
|
502
651
|
/**
|
|
503
|
-
* The `<head>` tag descriptor for the motion-token block.
|
|
504
|
-
*
|
|
652
|
+
* The `<head>` tag descriptor for the motion-token block.
|
|
653
|
+
*
|
|
654
|
+
* `injectTo: 'head'`, NOT `'head-prepend'` — C-414. Vite's `head-prepend`
|
|
655
|
+
* inserts immediately after the `<head>` open tag, i.e. AHEAD of the site's own
|
|
656
|
+
* `<meta charset>`. This block is 858 bytes, so it pushed the encoding
|
|
657
|
+
* declaration to byte 910 of the HTML spec's 1024-byte encoding-sniffing window
|
|
658
|
+
* on every site in the fleet (measured on Iron Oak and Bryan's 2026-07-27, and
|
|
659
|
+
* re-measured identical here), leaving ~90 bytes of headroom before every DCS
|
|
660
|
+
* site silently mojibakes.
|
|
661
|
+
*
|
|
662
|
+
* `'head'` appends to the END of the `<head>` the site authored — which is
|
|
663
|
+
* still ABOVE Vite's own build-time `<link rel=stylesheet>` / entry `<script>`,
|
|
664
|
+
* because those are injected AFTER this `order: 'pre'` hook's tags. So the
|
|
665
|
+
* per-site `:root` override still wins the cascade (that was the original
|
|
666
|
+
* reason for `head-prepend`) AND the charset lands at byte ~46 with ~954 bytes
|
|
667
|
+
* of headroom. Measured, not assumed — see `verify-honesty-rails.mjs` P12.
|
|
668
|
+
*
|
|
669
|
+
* If you ever move this back to `head-prepend`, the P12 rail in
|
|
670
|
+
* `scripts/verify-honesty-rails.mjs` will fail: it asserts the charset comes
|
|
671
|
+
* FIRST straight out of `dcsContentPlugin`, with no `dcsSeoPlugin` hoist.
|
|
505
672
|
*/
|
|
506
673
|
declare function dcsMotionTokensStyleTag(): HtmlTagDescriptor;
|
|
507
674
|
|
|
508
675
|
/**
|
|
509
676
|
* DCS AI-assistant widget loader — the cms head-inject seam for Phase 3 of the
|
|
510
|
-
* Managed Site AI Agents plan (.docs/plans/managed-site-ai-agents/
|
|
677
|
+
* Managed Site AI Agents plan (.docs/archive/plans/managed-site-ai-agents/plan.md).
|
|
511
678
|
*
|
|
512
679
|
* This rides the same always-on `dcsContentPlugin.transformIndexHtml` seam that
|
|
513
680
|
* ships the motion tokens: every DCS site already wires `dcsContentPlugin`, so a
|
|
@@ -577,4 +744,4 @@ interface DcsAgentWidgetTagOptions {
|
|
|
577
744
|
*/
|
|
578
745
|
declare function dcsAgentWidgetLoaderTag(options: DcsAgentWidgetTagOptions): HtmlTagDescriptor | null;
|
|
579
746
|
|
|
580
|
-
export { DCS_MOTION_TOKENS_CSS, DEFAULT_AGENT_API_BASE_URL, DEFAULT_AGENT_WIDGET_SCRIPT_URL, type DcsAgentWidgetLoaderOptions, type DcsAgentWidgetTagOptions, type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, buildAgentWidgetLoaderScript, dcsAgentWidgetLoaderTag, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, readSiteSlug, responsiveImagePlugin };
|
|
747
|
+
export { DCS_MOTION_TOKENS_CSS, DEFAULT_AGENT_API_BASE_URL, DEFAULT_AGENT_WIDGET_SCRIPT_URL, type DcsAgentWidgetLoaderOptions, type DcsAgentWidgetTagOptions, type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsResponsiveImageLintOptions, type DcsSeoPluginOptions, NoindexOrphanError, type ResponsiveImageLintReport, type ResponsiveImageViolation, buildAgentWidgetLoaderScript, chainBuildEnd, collectNoindexMatchKeys, dcsAgentWidgetLoaderTag, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsResponsiveImageLint, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, findFullSizeCdnImages, findOrphanNoindexEntries, isFullSizeCdnUrl, readSiteSlug, responsiveImagePlugin };
|