@duffcloudservices/cms 0.8.0 → 0.10.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.
@@ -1,4 +1,4 @@
1
- import { Plugin } from 'vite';
1
+ import { Plugin, HtmlTagDescriptor } from 'vite';
2
2
  import { D as DcsRobotsOptions, P as PageRouteEntry, S as SeoConfiguration, C as ContentConfig } from '../vitepressTransform-DeEzgGWU.js';
3
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';
@@ -467,4 +467,26 @@ declare function dcsCdnBuildEnd(options?: DcsCdnBuildEndOptions): (siteConfig: {
467
467
 
468
468
  declare function responsiveImagePlugin(md: MarkdownIt): void;
469
469
 
470
- export { type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, responsiveImagePlugin };
470
+ /**
471
+ * DCS motion tokens — the customer-site copy of the shared motion vocabulary.
472
+ *
473
+ * Injected as a constant `<style>` into every DCS site's `<head>` by
474
+ * `dcsContentPlugin` (see its `transformIndexHtml`). This is the fleet-wide
475
+ * seam: every site (vue-spa and VitePress) already wires `dcsContentPlugin`,
476
+ * so a cms version bump ships these tokens to the whole fleet with zero
477
+ * per-site source edits.
478
+ *
479
+ * Source of truth is the platform copy at
480
+ * `packages/dcs-ui/src/styles/motion.css` and the `dcs-product-motion` skill —
481
+ * keep the values here in lockstep. Values ONLY: reduced-motion stays a
482
+ * per-component responsibility so no blanket `!important` rule leaks into a
483
+ * site's isolated `.<slug>-page` design subtree.
484
+ */
485
+ 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}";
486
+ /**
487
+ * The `<head>` tag descriptor for the motion-token block. `head-prepend` keeps
488
+ * it above the site's own bundled CSS so a per-site `:root` override still wins.
489
+ */
490
+ declare function dcsMotionTokensStyleTag(): HtmlTagDescriptor;
491
+
492
+ export { DCS_MOTION_TOKENS_CSS, type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, responsiveImagePlugin };
@@ -1,28 +1,101 @@
1
1
  import { buildSitemapXml, isHandAuthoredRobotsAcceptable, buildRobotsTxt, buildLlmsTxt, matchesExcludedGlob, breadcrumbTrailFromRoute, findReviewItemsForPage, buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-UPAMLKOQ.js';
2
2
  export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-UPAMLKOQ.js';
3
- import fs2 from 'fs';
4
- import path2 from 'path';
5
- import yaml2 from 'js-yaml';
3
+ import fs3 from 'fs';
4
+ import path3 from 'path';
5
+ import yaml3 from 'js-yaml';
6
6
  import { defineComponent, h } from 'vue';
7
7
 
8
+ function dcsSearchPaths(projectRoot, relPath) {
9
+ return [
10
+ path3.resolve(projectRoot, relPath),
11
+ path3.resolve(projectRoot, "..", relPath),
12
+ path3.resolve(process.cwd(), relPath)
13
+ ];
14
+ }
15
+ function resolveDcsFile(projectRoot, relPath) {
16
+ return dcsSearchPaths(projectRoot, relPath).find((p) => fs3.existsSync(p));
17
+ }
18
+ function readDcsYamlFresh(projectRoot, relPath, label, debug = false) {
19
+ const found = resolveDcsFile(projectRoot, relPath);
20
+ if (!found) {
21
+ if (debug) console.log(`[${label}] serve: no ${relPath} found; global left unset`);
22
+ return void 0;
23
+ }
24
+ try {
25
+ return yaml3.load(fs3.readFileSync(found, "utf8"));
26
+ } catch (error) {
27
+ console.warn(`[${label}] serve: failed to parse ${relPath}:`, error);
28
+ return void 0;
29
+ }
30
+ }
31
+ function buildGlobalAssignScript(globalName, value) {
32
+ const json = JSON.stringify(value ?? null).replace(/</g, "\\u003c");
33
+ return `globalThis.${globalName} = ${json};`;
34
+ }
35
+
36
+ // src/plugins/motionTokens.ts
37
+ var DCS_MOTION_TOKENS_CSS = `:root {
38
+ --motion-duration-stagger: 40ms;
39
+ --motion-duration-micro: 80ms;
40
+ --motion-duration-quick: 150ms;
41
+ --motion-duration-fast: 250ms;
42
+ --motion-duration-medium: 350ms;
43
+ --motion-duration-slow: 400ms;
44
+ --motion-duration-very-slow: 500ms;
45
+ --motion-ease-out: cubic-bezier(0.22, 1, 0.36, 1);
46
+ --motion-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
47
+ --motion-ease-linear: linear;
48
+ --motion-ease-bounce: cubic-bezier(0.34, 1.36, 0.64, 1);
49
+ --motion-ease-bounce-strong: cubic-bezier(0.34, 3.85, 0.64, 1);
50
+ --motion-distance-xs: 4px;
51
+ --motion-distance-sm: 8px;
52
+ --motion-distance-md: 12px;
53
+ --motion-distance-lg: 24px;
54
+ --motion-scale-lg: 0.96;
55
+ --motion-scale-md: 0.97;
56
+ --motion-scale-sm: 0.98;
57
+ --motion-scale-xs: 0.99;
58
+ --motion-blur-sm: 2px;
59
+ --motion-blur-md: 3px;
60
+ --motion-blur-lg: 8px;
61
+ }`;
62
+ function dcsMotionTokensStyleTag() {
63
+ return {
64
+ tag: "style",
65
+ attrs: { "data-dcs-motion-tokens": "" },
66
+ children: DCS_MOTION_TOKENS_CSS,
67
+ injectTo: "head-prepend"
68
+ };
69
+ }
70
+
71
+ // src/plugins/dcsContentPlugin.ts
8
72
  function dcsContentPlugin(options = {}) {
9
73
  const { contentPath = ".dcs/content.yaml", debug = false } = options;
10
74
  let resolvedConfig;
75
+ let isDev = false;
11
76
  return {
12
77
  name: "dcs-content",
13
78
  configResolved(config) {
14
79
  resolvedConfig = config;
80
+ isDev = config.command === "serve";
15
81
  },
16
- config(config) {
82
+ config(config, env) {
83
+ if (env.command === "serve") {
84
+ return {
85
+ define: {
86
+ __DCS_CONTENT__: "globalThis.__DCS_CONTENT__"
87
+ }
88
+ };
89
+ }
17
90
  const projectRoot = config.root || process.cwd();
18
91
  const possiblePaths = [
19
- path2.resolve(projectRoot, contentPath),
20
- path2.resolve(projectRoot, "..", contentPath),
21
- path2.resolve(process.cwd(), contentPath)
92
+ path3.resolve(projectRoot, contentPath),
93
+ path3.resolve(projectRoot, "..", contentPath),
94
+ path3.resolve(process.cwd(), contentPath)
22
95
  ];
23
96
  let foundPath;
24
97
  for (const testPath of possiblePaths) {
25
- if (fs2.existsSync(testPath)) {
98
+ if (fs3.existsSync(testPath)) {
26
99
  foundPath = testPath;
27
100
  break;
28
101
  }
@@ -40,8 +113,8 @@ function dcsContentPlugin(options = {}) {
40
113
  };
41
114
  }
42
115
  try {
43
- const fileContent = fs2.readFileSync(foundPath, "utf8");
44
- const content = yaml2.load(fileContent);
116
+ const fileContent = fs3.readFileSync(foundPath, "utf8");
117
+ const content = yaml3.load(fileContent);
45
118
  if (debug) {
46
119
  console.log(`[dcs-content] Loaded ${foundPath}`);
47
120
  console.log(`[dcs-content] Version: ${content.version}`);
@@ -62,42 +135,96 @@ function dcsContentPlugin(options = {}) {
62
135
  };
63
136
  }
64
137
  },
65
- // Watch for changes in development
138
+ /**
139
+ * Inject into `<head>` on every HTML request. Two concerns:
140
+ *
141
+ * 1. The constant DCS motion-token `:root` block (`DCS_MOTION_TOKENS_CSS`)
142
+ * — the fleet-wide seam that gives every DCS site the shared `--motion-*`
143
+ * vocabulary. It carries no per-site data, so (unlike the content inject)
144
+ * it is NOT gated on `isDev`: it runs in both `serve` and `build`.
145
+ * `head-prepend` keeps it ABOVE the bundled site CSS, so a site's own
146
+ * `:root` (theme `tailwind.css` / `style.css`) can still override a token.
147
+ *
148
+ * 2. Dev-only: the FRESH on-disk `content.yaml` as an inline
149
+ * `globalThis.__DCS_CONTENT__ = {…}` classic script. Runs on every dev
150
+ * HTML request, so the portal's `?_hc` hard reload re-reads the YAML with
151
+ * no restart. A classic `<script>` in `<head>` executes before the
152
+ * deferred module app entry, so the global is set before the consumer's
153
+ * `useTextContent` setup reads it. Gated on `isDev` so it never runs in
154
+ * `vite`/`vitepress build` (the build path keeps the baked `define`).
155
+ */
156
+ transformIndexHtml: {
157
+ order: "pre",
158
+ handler(html) {
159
+ const tags = [dcsMotionTokensStyleTag()];
160
+ if (isDev) {
161
+ const projectRoot = resolvedConfig?.root || process.cwd();
162
+ const content = readDcsYamlFresh(
163
+ projectRoot,
164
+ contentPath,
165
+ "dcs-content",
166
+ debug
167
+ );
168
+ if (content !== void 0) {
169
+ if (debug) {
170
+ console.log("[dcs-content] serve: injecting fresh __DCS_CONTENT__ into <head>");
171
+ }
172
+ tags.push({
173
+ tag: "script",
174
+ children: buildGlobalAssignScript("__DCS_CONTENT__", content),
175
+ injectTo: "head-prepend"
176
+ });
177
+ }
178
+ }
179
+ return { html, tags };
180
+ }
181
+ },
182
+ // Watch for changes in development.
183
+ //
184
+ // Downgraded from `server.restart()` to a debounced client `full-reload`:
185
+ // freshness already comes from `transformIndexHtml` re-reading the YAML on
186
+ // every HTML request, so all we need on change is to nudge the browser to
187
+ // reload. A `server.restart()` would rebuild VitePress's dev MiniSearch
188
+ // index and can throw "server restart failed" on a duplicate heading id —
189
+ // exactly the fragility this fix removes. A `full-reload` does neither.
66
190
  configureServer(server) {
67
191
  const projectRoot = resolvedConfig?.root || process.cwd();
68
192
  const watchPaths = [
69
- path2.resolve(projectRoot, contentPath),
70
- path2.resolve(projectRoot, "..", contentPath)
71
- ];
193
+ path3.resolve(projectRoot, contentPath),
194
+ path3.resolve(projectRoot, "..", contentPath)
195
+ ].map((p) => path3.normalize(p));
72
196
  watchPaths.forEach((watchPath) => {
73
- if (fs2.existsSync(watchPath)) {
197
+ if (fs3.existsSync(watchPath)) {
74
198
  server.watcher.add(watchPath);
75
- server.watcher.on("change", (changedPath) => {
76
- if (changedPath === watchPath) {
77
- if (debug) {
78
- console.log("[dcs-content] content.yaml changed, triggering reload");
79
- }
80
- server.restart();
81
- }
82
- });
83
199
  }
84
200
  });
201
+ let reloadTimer;
202
+ server.watcher.on("change", (changedPath) => {
203
+ if (!watchPaths.includes(path3.normalize(changedPath))) return;
204
+ if (reloadTimer) clearTimeout(reloadTimer);
205
+ reloadTimer = setTimeout(() => {
206
+ if (debug) {
207
+ console.log("[dcs-content] content.yaml changed, sending full-reload");
208
+ }
209
+ server.ws.send({ type: "full-reload" });
210
+ }, 100);
211
+ });
85
212
  }
86
213
  };
87
214
  }
88
215
  function resolveOutDir(config) {
89
216
  const out = config.build?.outDir || "dist";
90
- return path2.isAbsolute(out) ? out : path2.resolve(config.root || process.cwd(), out);
217
+ return path3.isAbsolute(out) ? out : path3.resolve(config.root || process.cwd(), out);
91
218
  }
92
219
  function loadSeoConfig(projectRoot, seoPath, debug) {
93
220
  const possiblePaths = [
94
- path2.resolve(projectRoot, seoPath),
95
- path2.resolve(projectRoot, "..", seoPath),
96
- path2.resolve(process.cwd(), seoPath)
221
+ path3.resolve(projectRoot, seoPath),
222
+ path3.resolve(projectRoot, "..", seoPath),
223
+ path3.resolve(process.cwd(), seoPath)
97
224
  ];
98
225
  let foundPath;
99
226
  for (const testPath of possiblePaths) {
100
- if (fs2.existsSync(testPath)) {
227
+ if (fs3.existsSync(testPath)) {
101
228
  foundPath = testPath;
102
229
  break;
103
230
  }
@@ -111,8 +238,8 @@ function loadSeoConfig(projectRoot, seoPath, debug) {
111
238
  return null;
112
239
  }
113
240
  try {
114
- const fileContent = fs2.readFileSync(foundPath, "utf8");
115
- const config = yaml2.load(fileContent);
241
+ const fileContent = fs3.readFileSync(foundPath, "utf8");
242
+ const config = yaml3.load(fileContent);
116
243
  return { config, foundPath };
117
244
  } catch (error) {
118
245
  console.warn("[dcs-seo] Failed to parse seo.yaml:", error);
@@ -121,17 +248,17 @@ function loadSeoConfig(projectRoot, seoPath, debug) {
121
248
  }
122
249
  function loadContentConfig(projectRoot, contentRelPath, debug) {
123
250
  const possiblePaths = [
124
- path2.resolve(projectRoot, contentRelPath),
125
- path2.resolve(projectRoot, "..", contentRelPath),
126
- path2.resolve(process.cwd(), contentRelPath)
251
+ path3.resolve(projectRoot, contentRelPath),
252
+ path3.resolve(projectRoot, "..", contentRelPath),
253
+ path3.resolve(process.cwd(), contentRelPath)
127
254
  ];
128
- const foundPath = possiblePaths.find((p) => fs2.existsSync(p));
255
+ const foundPath = possiblePaths.find((p) => fs3.existsSync(p));
129
256
  if (!foundPath) {
130
257
  if (debug) console.log(`[dcs-seo] No content.yaml found (reviews disabled)`);
131
258
  return void 0;
132
259
  }
133
260
  try {
134
- const parsed = yaml2.load(fs2.readFileSync(foundPath, "utf8"));
261
+ const parsed = yaml3.load(fs3.readFileSync(foundPath, "utf8"));
135
262
  if (!parsed || typeof parsed !== "object") return void 0;
136
263
  return parsed;
137
264
  } catch (error) {
@@ -141,19 +268,19 @@ function loadContentConfig(projectRoot, contentRelPath, debug) {
141
268
  }
142
269
  function routeToOutputFile(outDir, routePath) {
143
270
  const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
144
- if (trimmed === "") return path2.join(outDir, "index.html");
145
- return path2.join(outDir, ...trimmed.split("/"), "index.html");
271
+ if (trimmed === "") return path3.join(outDir, "index.html");
272
+ return path3.join(outDir, ...trimmed.split("/"), "index.html");
146
273
  }
147
274
  function loadExcludedGlobs(projectRoot, pagesPath) {
148
275
  const possiblePaths = [
149
- path2.resolve(projectRoot, pagesPath),
150
- path2.resolve(projectRoot, "..", pagesPath),
151
- path2.resolve(process.cwd(), pagesPath)
276
+ path3.resolve(projectRoot, pagesPath),
277
+ path3.resolve(projectRoot, "..", pagesPath),
278
+ path3.resolve(process.cwd(), pagesPath)
152
279
  ];
153
- const found = possiblePaths.find((p) => fs2.existsSync(p));
280
+ const found = possiblePaths.find((p) => fs3.existsSync(p));
154
281
  if (!found) return [];
155
282
  try {
156
- const raw = yaml2.load(fs2.readFileSync(found, "utf8"));
283
+ const raw = yaml3.load(fs3.readFileSync(found, "utf8"));
157
284
  const excluded = raw?.excluded;
158
285
  if (!Array.isArray(excluded)) return [];
159
286
  return excluded.filter((e) => typeof e === "string");
@@ -165,14 +292,14 @@ function deriveLastmod(seoConfig, projectRoot, pagesPath) {
165
292
  const fromSeo = seoConfig?.lastUpdated;
166
293
  if (typeof fromSeo === "string" && fromSeo.length > 0) return fromSeo;
167
294
  const possiblePaths = [
168
- path2.resolve(projectRoot, pagesPath),
169
- path2.resolve(projectRoot, "..", pagesPath),
170
- path2.resolve(process.cwd(), pagesPath)
295
+ path3.resolve(projectRoot, pagesPath),
296
+ path3.resolve(projectRoot, "..", pagesPath),
297
+ path3.resolve(process.cwd(), pagesPath)
171
298
  ];
172
- const found = possiblePaths.find((p) => fs2.existsSync(p));
299
+ const found = possiblePaths.find((p) => fs3.existsSync(p));
173
300
  if (!found) return void 0;
174
301
  try {
175
- const raw = yaml2.load(fs2.readFileSync(found, "utf8"));
302
+ const raw = yaml3.load(fs3.readFileSync(found, "utf8"));
176
303
  const fromPages = raw?.lastUpdated;
177
304
  if (typeof fromPages === "string" && fromPages.length > 0) return fromPages;
178
305
  } catch {
@@ -210,7 +337,7 @@ function emitSiteFiles(params) {
210
337
  lastmod
211
338
  });
212
339
  if (xml) {
213
- fs2.writeFileSync(path2.join(outDir, "sitemap.xml"), xml, "utf8");
340
+ fs3.writeFileSync(path3.join(outDir, "sitemap.xml"), xml, "utf8");
214
341
  wroteSitemap = true;
215
342
  written.push("sitemap.xml");
216
343
  if (debug) console.log("[dcs-seo] wrote sitemap.xml");
@@ -219,8 +346,8 @@ function emitSiteFiles(params) {
219
346
  }
220
347
  }
221
348
  if (robots.enabled ?? true) {
222
- const robotsPath = path2.join(outDir, "robots.txt");
223
- const existing = fs2.existsSync(robotsPath) && !robots.force ? fs2.readFileSync(robotsPath, "utf8") : null;
349
+ const robotsPath = path3.join(outDir, "robots.txt");
350
+ const existing = fs3.existsSync(robotsPath) && !robots.force ? fs3.readFileSync(robotsPath, "utf8") : null;
224
351
  const keepHandAuthored = existing !== null && isHandAuthoredRobotsAcceptable(existing);
225
352
  if (keepHandAuthored) {
226
353
  if (debug)
@@ -232,7 +359,7 @@ function emitSiteFiles(params) {
232
359
  robots,
233
360
  hasSitemap: wroteSitemap
234
361
  });
235
- fs2.writeFileSync(robotsPath, txt, "utf8");
362
+ fs3.writeFileSync(robotsPath, txt, "utf8");
236
363
  written.push("robots.txt");
237
364
  if (debug) {
238
365
  console.log(
@@ -251,7 +378,7 @@ function emitSiteFiles(params) {
251
378
  excludedGlobs
252
379
  });
253
380
  if (txt) {
254
- fs2.writeFileSync(path2.join(outDir, "llms.txt"), txt, "utf8");
381
+ fs3.writeFileSync(path3.join(outDir, "llms.txt"), txt, "utf8");
255
382
  written.push("llms.txt");
256
383
  if (debug) console.log("[dcs-seo] wrote llms.txt");
257
384
  } else if (debug) {
@@ -316,12 +443,12 @@ function emitStaticSeoHtml(params) {
316
443
  });
317
444
  const html = spliceHeadHtml(shellHtml, tags);
318
445
  const outFile = routeToOutputFile(outDir, route.path);
319
- fs2.mkdirSync(path2.dirname(outFile), { recursive: true });
320
- fs2.writeFileSync(outFile, html, "utf8");
446
+ fs3.mkdirSync(path3.dirname(outFile), { recursive: true });
447
+ fs3.writeFileSync(outFile, html, "utf8");
321
448
  written++;
322
449
  if (debug) {
323
450
  console.log(
324
- `[dcs-seo] wrote ${path2.relative(outDir, outFile)} (title="${tags.title}"${forceNoindex ? ", noindex" : ""})`
451
+ `[dcs-seo] wrote ${path3.relative(outDir, outFile)} (title="${tags.title}"${forceNoindex ? ", noindex" : ""})`
325
452
  );
326
453
  }
327
454
  }
@@ -345,12 +472,21 @@ function dcsSeoPlugin(options = {}) {
345
472
  llms = true
346
473
  } = options;
347
474
  let resolvedConfig;
475
+ let isDev = false;
348
476
  return {
349
477
  name: "dcs-seo",
350
478
  configResolved(config) {
351
479
  resolvedConfig = config;
480
+ isDev = config.command === "serve";
352
481
  },
353
- config(config) {
482
+ config(config, env) {
483
+ if (env.command === "serve") {
484
+ return {
485
+ define: {
486
+ __DCS_SEO__: "globalThis.__DCS_SEO__"
487
+ }
488
+ };
489
+ }
354
490
  const projectRoot = config.root || process.cwd();
355
491
  const loaded = loadSeoConfig(projectRoot, seoPath, debug);
356
492
  if (!loaded) {
@@ -396,12 +532,12 @@ function dcsSeoPlugin(options = {}) {
396
532
  try {
397
533
  const projectRoot = resolvedConfig?.root || process.cwd();
398
534
  const outDir = resolveOutDir(resolvedConfig);
399
- if (!fs2.existsSync(outDir)) {
535
+ if (!fs3.existsSync(outDir)) {
400
536
  console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`);
401
537
  return;
402
538
  }
403
- const shellPath = path2.join(outDir, "index.html");
404
- if (!fs2.existsSync(shellPath)) {
539
+ const shellPath = path3.join(outDir, "index.html");
540
+ if (!fs3.existsSync(shellPath)) {
405
541
  console.warn(
406
542
  `[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`
407
543
  );
@@ -420,7 +556,7 @@ function dcsSeoPlugin(options = {}) {
420
556
  `[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`
421
557
  );
422
558
  }
423
- const shellHtml = fs2.readFileSync(shellPath, "utf8");
559
+ const shellHtml = fs3.readFileSync(shellPath, "utf8");
424
560
  const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath);
425
561
  const contentConfig = loadContentConfig(projectRoot, contentPath, debug);
426
562
  const written = emitStaticSeoHtml({
@@ -462,26 +598,64 @@ function dcsSeoPlugin(options = {}) {
462
598
  console.warn("[dcs-seo] emitStaticHtml failed; build output left unchanged:", error);
463
599
  }
464
600
  },
465
- // Watch for changes in development
601
+ /**
602
+ * Dev-only: inject the FRESH on-disk `seo.yaml` as an inline
603
+ * `globalThis.__DCS_SEO__ = {…}` classic script at the top of `<head>`,
604
+ * mirroring dcsContentPlugin. Lets the runtime `useSEO` SPA path reflect
605
+ * on-disk SEO edits on the portal's `?_hc` hard reload with no restart.
606
+ * Gated on `isDev` so it never runs in `vite`/`vitepress build` (the build
607
+ * path keeps the baked `define` literal + the `writeBundle` emitter).
608
+ */
609
+ transformIndexHtml: {
610
+ order: "pre",
611
+ handler(html) {
612
+ if (!isDev) return html;
613
+ const projectRoot = resolvedConfig?.root || process.cwd();
614
+ const config = readDcsYamlFresh(projectRoot, seoPath, "dcs-seo", debug);
615
+ if (config === void 0) return html;
616
+ if (debug) {
617
+ console.log("[dcs-seo] serve: injecting fresh __DCS_SEO__ into <head>");
618
+ }
619
+ const tags = [
620
+ {
621
+ tag: "script",
622
+ children: buildGlobalAssignScript("__DCS_SEO__", config),
623
+ injectTo: "head-prepend"
624
+ }
625
+ ];
626
+ return { html, tags };
627
+ }
628
+ },
629
+ // Watch for changes in development.
630
+ //
631
+ // Downgraded from `server.restart()` to a debounced client `full-reload`:
632
+ // freshness already comes from `transformIndexHtml` re-reading the YAML on
633
+ // every HTML request, so all we need on change is a browser reload. A
634
+ // `server.restart()` would rebuild VitePress's dev MiniSearch index and can
635
+ // throw "server restart failed" on a duplicate heading id — the fragility
636
+ // this fix removes. A `full-reload` does neither.
466
637
  configureServer(server) {
467
638
  const projectRoot = resolvedConfig?.root || process.cwd();
468
639
  const watchPaths = [
469
- path2.resolve(projectRoot, seoPath),
470
- path2.resolve(projectRoot, "..", seoPath)
471
- ];
640
+ path3.resolve(projectRoot, seoPath),
641
+ path3.resolve(projectRoot, "..", seoPath)
642
+ ].map((p) => path3.normalize(p));
472
643
  watchPaths.forEach((watchPath) => {
473
- if (fs2.existsSync(watchPath)) {
644
+ if (fs3.existsSync(watchPath)) {
474
645
  server.watcher.add(watchPath);
475
- server.watcher.on("change", (changedPath) => {
476
- if (changedPath === watchPath) {
477
- if (debug) {
478
- console.log("[dcs-seo] seo.yaml changed, triggering reload");
479
- }
480
- server.restart();
481
- }
482
- });
483
646
  }
484
647
  });
648
+ let reloadTimer;
649
+ server.watcher.on("change", (changedPath) => {
650
+ if (!watchPaths.includes(path3.normalize(changedPath))) return;
651
+ if (reloadTimer) clearTimeout(reloadTimer);
652
+ reloadTimer = setTimeout(() => {
653
+ if (debug) {
654
+ console.log("[dcs-seo] seo.yaml changed, sending full-reload");
655
+ }
656
+ server.ws.send({ type: "full-reload" });
657
+ }, 100);
658
+ });
485
659
  }
486
660
  };
487
661
  }
@@ -493,11 +667,24 @@ function dcsEditorPlugin(options = {}) {
493
667
  const RIBBON_VIRTUAL_PATH = "/__dcs-preview-ribbon.js";
494
668
  let isDev = false;
495
669
  let basePath = "/";
670
+ let isPreview = false;
496
671
  return {
497
672
  name: "dcs-editor-bridge",
673
+ // Preview only: stop Vite running an unreachable HMR WebSocket server.
674
+ // Freshness comes from the cms content/seo plugins (fresh transformIndexHtml
675
+ // re-read) + the portal's ?_hc hard reload, not HMR. NOTE: this alone does
676
+ // NOT silence the browser — Vite still injects @vite/client and calls
677
+ // connect(); the client-side stub in transformIndexHtml is what removes the
678
+ // console errors. A normal local `vite dev` (no DCS_PREVIEW) keeps full HMR.
679
+ config(_config, env) {
680
+ if (env.command === "serve" && process.env.DCS_PREVIEW === "true") {
681
+ return { server: { hmr: false } };
682
+ }
683
+ },
498
684
  configResolved(config) {
499
685
  isDev = config.command === "serve";
500
686
  basePath = config.base ?? "/";
687
+ isPreview = process.env.DCS_PREVIEW === "true";
501
688
  },
502
689
  resolveId(id) {
503
690
  if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {
@@ -565,9 +752,15 @@ if (document.readyState === 'loading') {
565
752
  }
566
753
  const editorTag = `<script type="module" src="${VIRTUAL_PATH}"></script>`;
567
754
  const ribbonTag = `<script type="module" src="${RIBBON_VIRTUAL_PATH}"></script>`;
568
- return html.replace("</body>", `${editorTag}
755
+ const bodyHtml = html.replace("</body>", `${editorTag}
569
756
  ${ribbonTag}
570
757
  </body>`);
758
+ if (!isPreview) return bodyHtml;
759
+ const wsStub = `(function(){try{var N=window.WebSocket;if(!N)return;function hmr(p){return p==='vite-hmr'||(Array.isArray(p)&&p.indexOf('vite-hmr')!==-1)}function Inert(){this.readyState=0}Inert.prototype.send=function(){};Inert.prototype.close=function(){};Inert.prototype.addEventListener=function(){};Inert.prototype.removeEventListener=function(){};var W=function(u,p){if(hmr(p))return new Inert();return new N(u,p)};W.prototype=N.prototype;W.CONNECTING=N.CONNECTING;W.OPEN=N.OPEN;W.CLOSING=N.CLOSING;W.CLOSED=N.CLOSED;window.WebSocket=W}catch(e){}})()`;
760
+ return {
761
+ html: bodyHtml,
762
+ tags: [{ tag: "script", children: wsStub, injectTo: "head-prepend" }]
763
+ };
571
764
  }
572
765
  }
573
766
  };
@@ -611,14 +804,14 @@ function buildPictureElement(entry, alt, extraAttrs, sizes) {
611
804
  }
612
805
  function loadCdnImageMap(projectRoot, relativeMapPath, debug) {
613
806
  const possiblePaths = [
614
- path2.resolve(projectRoot, relativeMapPath),
615
- path2.resolve(projectRoot, "..", relativeMapPath),
616
- path2.resolve(process.cwd(), relativeMapPath)
807
+ path3.resolve(projectRoot, relativeMapPath),
808
+ path3.resolve(projectRoot, "..", relativeMapPath),
809
+ path3.resolve(process.cwd(), relativeMapPath)
617
810
  ];
618
811
  for (const testPath of possiblePaths) {
619
- if (fs2.existsSync(testPath)) {
812
+ if (fs3.existsSync(testPath)) {
620
813
  try {
621
- const raw = fs2.readFileSync(testPath, "utf8");
814
+ const raw = fs3.readFileSync(testPath, "utf8");
622
815
  const data = JSON.parse(raw);
623
816
  const map = /* @__PURE__ */ new Map();
624
817
  for (const entry of data.images) {
@@ -779,13 +972,13 @@ function dcsCdnBuildEnd(options = {}) {
779
972
  return;
780
973
  }
781
974
  const outDir = siteConfig.outDir;
782
- if (!fs2.existsSync(outDir)) return;
975
+ if (!fs3.existsSync(outDir)) return;
783
976
  let filesProcessed = 0;
784
977
  let refsReplaced = 0;
785
978
  function walkDir(dir) {
786
- const entries = fs2.readdirSync(dir, { withFileTypes: true });
979
+ const entries = fs3.readdirSync(dir, { withFileTypes: true });
787
980
  for (const entry of entries) {
788
- const fullPath = path2.join(dir, entry.name);
981
+ const fullPath = path3.join(dir, entry.name);
789
982
  if (entry.isDirectory()) {
790
983
  walkDir(fullPath);
791
984
  } else if (extensions.some((ext) => entry.name.endsWith(ext))) {
@@ -794,7 +987,7 @@ function dcsCdnBuildEnd(options = {}) {
794
987
  }
795
988
  }
796
989
  function processFile(filePath) {
797
- let content = fs2.readFileSync(filePath, "utf8");
990
+ let content = fs3.readFileSync(filePath, "utf8");
798
991
  if (!pathPrefixes.some((p) => content.includes(p))) return;
799
992
  let changed = false;
800
993
  if (filePath.endsWith(".html")) {
@@ -829,7 +1022,7 @@ function dcsCdnBuildEnd(options = {}) {
829
1022
  }
830
1023
  }
831
1024
  if (changed) {
832
- fs2.writeFileSync(filePath, content, "utf8");
1025
+ fs3.writeFileSync(filePath, content, "utf8");
833
1026
  filesProcessed++;
834
1027
  }
835
1028
  }
@@ -878,6 +1071,6 @@ function responsiveImagePlugin(md) {
878
1071
  };
879
1072
  }
880
1073
 
881
- export { dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, responsiveImagePlugin };
1074
+ export { DCS_MOTION_TOKENS_CSS, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsMotionTokensStyleTag, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, responsiveImagePlugin };
882
1075
  //# sourceMappingURL=index.js.map
883
1076
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/plugins/dcsContentPlugin.ts","../../src/plugins/dcsSeoPlugin.ts","../../src/plugins/dcsEditorPlugin.ts","../../src/plugins/dcsPreviewPlugin.ts","../../src/plugins/dcsCdnImagePlugin.ts","../../src/plugins/responsiveImagePlugin.ts"],"names":["path","fs","yaml","escapeHtml"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * DCS Content Plugin for Vite\n *\n * Reads `.dcs/content.yaml` at build time and injects content\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsContentPlugin({ debug: true })\n * ]\n * })\n * ```\n *\n * For VitePress:\n * ```typescript\n * // .vitepress/config.ts\n * import { defineConfig } from 'vitepress'\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsContentPlugin()\n * ]\n * }\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { DcsContentFile } from '../types/content'\n\nexport interface DcsContentPluginOptions {\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\n contentPath?: string\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects .dcs/content.yaml at build time.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\n const { contentPath = '.dcs/content.yaml', debug = false } = options\n\n let resolvedConfig: ResolvedConfig\n\n return {\n name: 'dcs-content',\n\n configResolved(config) {\n resolvedConfig = config\n },\n\n config(config) {\n const projectRoot = config.root || process.cwd()\n\n // Try to find content.yaml in multiple locations\n // VitePress projects have the docs folder as root, so check parent too\n const possiblePaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n path.resolve(process.cwd(), contentPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-content] No content.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-content] Using defaults only')\n }\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const content = yaml.load(fileContent) as DcsContentFile\n\n if (debug) {\n console.log(`[dcs-content] Loaded ${foundPath}`)\n console.log(`[dcs-content] Version: ${content.version}`)\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\n }\n\n return {\n define: {\n __DCS_CONTENT__: JSON.stringify(content),\n },\n }\n } catch (error) {\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n },\n\n // Watch for changes in development\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n ]\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n\n server.watcher.on('change', (changedPath) => {\n if (changedPath === watchPath) {\n if (debug) {\n console.log('[dcs-content] content.yaml changed, triggering reload')\n }\n server.restart()\n }\n })\n }\n })\n },\n }\n}\n","/**\n * DCS SEO Plugin for Vite\n *\n * Two responsibilities, both driven by `.dcs/seo.yaml`:\n *\n * 1. **Build-time define** (always on): reads `.dcs/seo.yaml` and injects it as\n * the `__DCS_SEO__` global for the runtime `useSEO` composable.\n *\n * 2. **Static `<head>` emitter** (opt-in via `emitStaticHtml: true`, default\n * OFF): after the bundle is written, reads the SPA's built `index.html`,\n * and for every route in `.dcs/pages.yaml` writes a per-route\n * `dist/<path>/index.html` whose `<head>` carries the resolved title, meta,\n * canonical, Open Graph, Twitter, and JSON-LD tags. This gives a Vue SPA\n * per-route static SEO **without** vite-ssg.\n *\n * VitePress sites already bake SEO via their own config, so they leave this\n * option OFF and are completely unaffected.\n *\n * @example Runtime define only (default — safe for VitePress)\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [dcsSeoPlugin({ debug: true })]\n * })\n * ```\n *\n * @example Vue SPA with per-route static <head> emission\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsSeoPlugin({\n * emitStaticHtml: true, // turn the emitter ON\n * pagesPath: '.dcs/pages.yaml', // route manifest (default)\n * noindex: ['account', 'projects'], // robots: noindex,nofollow\n * exclude: ['/preview'], // skip these routes entirely\n * })\n * ]\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { SeoConfiguration } from '../types/seo'\nimport { buildHeadTags } from '../seo/headTags'\nimport { spliceHeadHtml } from '../seo/spliceHeadHtml'\nimport { loadPagesManifest, type PageRouteEntry } from '../seo/pagesManifest'\nimport {\n buildSitemapXml,\n buildRobotsTxt,\n buildLlmsTxt,\n matchesExcludedGlob,\n isHandAuthoredRobotsAcceptable,\n type DcsRobotsOptions,\n} from '../seo/sitemap'\nimport {\n breadcrumbTrailFromRoute,\n findReviewItemsForPage,\n type ContentConfig,\n type ReviewSource,\n type BlogMeta,\n type FaqSource,\n} from '../seo/schemaGraph'\n\nexport interface DcsSeoPluginOptions {\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\n seoPath?: string\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * Opt-in: emit per-route static `<head>` (meta + JSON-LD) into the built\n * `dist/` at the end of the build. Default `false` — VitePress sites and any\n * site that bakes its own SEO are unaffected when this is off.\n */\n emitStaticHtml?: boolean\n\n /**\n * Path to the route manifest relative to project root, used only when\n * `emitStaticHtml` is true. Default `'.dcs/pages.yaml'`.\n */\n pagesPath?: string\n\n /**\n * Path to `content.yaml` relative to project root, used only when\n * `emitStaticHtml` is true. Threads REAL reviews into the honest\n * Review/aggregateRating on the per-route LocalBusiness node. Default\n * `'.dcs/content.yaml'`. Missing/unparseable ⇒ no reviews emitted.\n */\n contentPath?: string\n\n /**\n * Routes to skip entirely (no per-route HTML written). Matched against the\n * route `path` (e.g. `'/preview'`) OR the route `slug` (e.g. `'account'`).\n */\n exclude?: string[]\n\n /**\n * Routes that should receive `robots: noindex, nofollow`. Matched against the\n * route `path` OR `slug`. The home route (`/`) still overwrites\n * `dist/index.html`.\n */\n noindex?: string[]\n\n /**\n * Opt-in: emit the site-wide static files `dist/sitemap.xml`,\n * `dist/robots.txt`, and `dist/llms.txt` after the per-route HTML loop.\n *\n * Defaults to whatever `emitStaticHtml` is — so every site already calling\n * `dcsSeoPlugin({ emitStaticHtml: true })` gets sitemap/robots/llms for free\n * with zero per-site edits. Set explicitly to `false` to keep emitting\n * per-route HTML without the site files. The whole block is wrapped in the\n * same try/catch + existsSync guards, so it can never break a build.\n */\n emitSiteFiles?: boolean\n\n /**\n * Canonical production origin, e.g. `https://ironoakcontractors.com`. Used as\n * the sitemap/llms base and the absolute `Sitemap:` line in robots.txt. When\n * absent, falls back to `seo.yaml`'s `global.siteUrl`; if neither is known and\n * no per-page canonicals exist, the sitemap + llms.txt no-op (robots still\n * emits). Never guessed.\n */\n siteUrl?: string\n\n /** robots.txt override hooks (disallow/allow/extra/aiBots/force). */\n robots?: DcsRobotsOptions\n\n /**\n * Preview / staging gate. When `true`, robots.txt becomes `Disallow: /` (no\n * `Sitemap:` line) and neither sitemap.xml nor llms.txt is emitted (privacy).\n * Detected ONLY via this explicit option — never guessed.\n */\n preview?: boolean\n\n /** Emit `dist/llms.txt` (default `true`; always off in preview mode). */\n llms?: boolean\n}\n\n/** Resolve the SPA output directory from Vite config (absolute path). */\nfunction resolveOutDir(config: ResolvedConfig): string {\n const out = config.build?.outDir || 'dist'\n return path.isAbsolute(out) ? out : path.resolve(config.root || process.cwd(), out)\n}\n\n/**\n * Load + parse seo.yaml from the usual `.dcs` locations.\n * Returns `{ config, foundPath }` or `null` (missing/unparseable).\n */\nfunction loadSeoConfig(\n projectRoot: string,\n seoPath: string,\n debug: boolean\n): { config: SeoConfiguration; foundPath: string } | null {\n const possiblePaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n path.resolve(process.cwd(), seoPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-seo] No seo.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-seo] Using defaults only')\n }\n return null\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const config = yaml.load(fileContent) as SeoConfiguration\n return { config, foundPath }\n } catch (error) {\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\n return null\n }\n}\n\n/**\n * Load + parse `.dcs/content.yaml` from the usual `.dcs` locations. Returns the\n * parsed config or `undefined` (missing/unparseable). Used to thread REAL\n * reviews into the honest Review/aggregateRating builder. Never throws.\n */\nfunction loadContentConfig(\n projectRoot: string,\n contentRelPath: string,\n debug: boolean\n): ContentConfig | undefined {\n const possiblePaths = [\n path.resolve(projectRoot, contentRelPath),\n path.resolve(projectRoot, '..', contentRelPath),\n path.resolve(process.cwd(), contentRelPath),\n ]\n const foundPath = possiblePaths.find((p) => fs.existsSync(p))\n if (!foundPath) {\n if (debug) console.log(`[dcs-seo] No content.yaml found (reviews disabled)`)\n return undefined\n }\n try {\n const parsed = yaml.load(fs.readFileSync(foundPath, 'utf8'))\n if (!parsed || typeof parsed !== 'object') return undefined\n return parsed as ContentConfig\n } catch (error) {\n console.warn('[dcs-seo] Failed to parse content.yaml (reviews disabled):', error)\n return undefined\n }\n}\n\n/**\n * Normalise a route path to the on-disk output file:\n * '/' -> '<outDir>/index.html'\n * '/services' -> '<outDir>/services/index.html'\n * '/blog/my-post' -> '<outDir>/blog/my-post/index.html'\n * '/services/' -> '<outDir>/services/index.html'\n */\nfunction routeToOutputFile(outDir: string, routePath: string): string {\n const trimmed = routePath.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (trimmed === '') return path.join(outDir, 'index.html')\n return path.join(outDir, ...trimmed.split('/'), 'index.html')\n}\n\n/** Extract the optional top-level `excluded:` glob list from pages.yaml. */\nfunction loadExcludedGlobs(projectRoot: string, pagesPath: string): string[] {\n const possiblePaths = [\n path.resolve(projectRoot, pagesPath),\n path.resolve(projectRoot, '..', pagesPath),\n path.resolve(process.cwd(), pagesPath),\n ]\n const found = possiblePaths.find((p) => fs.existsSync(p))\n if (!found) return []\n try {\n const raw = yaml.load(fs.readFileSync(found, 'utf8')) as { excluded?: unknown } | null\n const excluded = raw?.excluded\n if (!Array.isArray(excluded)) return []\n return excluded.filter((e): e is string => typeof e === 'string')\n } catch {\n return []\n }\n}\n\n/**\n * Derive a single site-wide lastmod from seo.yaml `lastUpdated` (preferred) or\n * pages.yaml `lastUpdated`. No fabricated per-page dates.\n */\nfunction deriveLastmod(\n seoConfig: SeoConfiguration | undefined,\n projectRoot: string,\n pagesPath: string\n): string | undefined {\n const fromSeo = seoConfig?.lastUpdated\n if (typeof fromSeo === 'string' && fromSeo.length > 0) return fromSeo\n\n const possiblePaths = [\n path.resolve(projectRoot, pagesPath),\n path.resolve(projectRoot, '..', pagesPath),\n path.resolve(process.cwd(), pagesPath),\n ]\n const found = possiblePaths.find((p) => fs.existsSync(p))\n if (!found) return undefined\n try {\n const raw = yaml.load(fs.readFileSync(found, 'utf8')) as { lastUpdated?: unknown } | null\n const fromPages = raw?.lastUpdated\n if (typeof fromPages === 'string' && fromPages.length > 0) return fromPages\n } catch {\n /* ignore */\n }\n return undefined\n}\n\n/**\n * Core site-file emitter, separated from Vite so it is unit-testable.\n *\n * Writes the site-wide static files into `outDir`, reusing the SAME routes +\n * seoConfig already loaded by the per-route HTML loop (no second manifest read):\n * - `sitemap.xml` (skipped in preview, or when no absolute base is derivable)\n * - `robots.txt` (always; preview = `Disallow: /`; don't-clobber unless force)\n * - `llms.txt` (skipped in preview, or when nothing indexable)\n *\n * Returns the list of relative filenames written. Pure aside from `fs` writes —\n * deterministic + idempotent. Never throws (the caller also wraps in try/catch).\n */\nexport function emitSiteFiles(params: {\n outDir: string\n projectRoot: string\n pagesPath: string\n routes: PageRouteEntry[]\n seoConfig: SeoConfiguration | undefined\n siteUrl?: string\n exclude?: string[]\n noindex?: string[]\n preview?: boolean\n robots?: DcsRobotsOptions\n llms?: boolean\n debug?: boolean\n}): string[] {\n const {\n outDir,\n projectRoot,\n pagesPath,\n routes,\n seoConfig,\n siteUrl,\n exclude = [],\n noindex = [],\n preview = false,\n robots = {},\n llms = true,\n debug = false,\n } = params\n\n const written: string[] = []\n\n const effectiveSiteUrl = siteUrl || seoConfig?.global?.siteUrl\n const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath)\n const lastmod = deriveLastmod(seoConfig, projectRoot, pagesPath)\n\n // ── sitemap.xml (not in preview) ──────────────────────────────────────────\n let wroteSitemap = false\n if (!preview) {\n const xml = buildSitemapXml({\n routes,\n siteUrl: effectiveSiteUrl,\n seoConfig,\n exclude,\n noindex,\n excludedGlobs,\n lastmod,\n })\n if (xml) {\n fs.writeFileSync(path.join(outDir, 'sitemap.xml'), xml, 'utf8')\n wroteSitemap = true\n written.push('sitemap.xml')\n if (debug) console.log('[dcs-seo] wrote sitemap.xml')\n } else if (debug) {\n console.log('[dcs-seo] no absolute <loc> derivable; sitemap.xml not written')\n }\n }\n\n // ── robots.txt (always, unless robots.enabled is false) ───────────────────\n if (robots.enabled ?? true) {\n const robotsPath = path.join(outDir, 'robots.txt')\n // Factory-as-single-source (validated override): a hand-authored\n // public/robots.txt Vite copied into dist is KEPT only when it meets the\n // factory floor (a real `User-agent: *` group + every AI-bot tier). A stub\n // (`Sitemap:`-only, like just-posh) or an SPA-shell HTML page fails the\n // floor and is overwritten so AI discovery is guaranteed on every site.\n // `force` always overwrites; an absent file is always written.\n const existing =\n fs.existsSync(robotsPath) && !robots.force ? fs.readFileSync(robotsPath, 'utf8') : null\n const keepHandAuthored = existing !== null && isHandAuthoredRobotsAcceptable(existing)\n if (keepHandAuthored) {\n if (debug)\n console.log('[dcs-seo] dist/robots.txt is hand-authored and meets the floor; leaving it')\n } else {\n const txt = buildRobotsTxt({\n siteUrl: effectiveSiteUrl,\n preview,\n robots,\n hasSitemap: wroteSitemap,\n })\n fs.writeFileSync(robotsPath, txt, 'utf8')\n written.push('robots.txt')\n if (debug) {\n console.log(\n existing !== null\n ? '[dcs-seo] dist/robots.txt failed the factory floor; overwrote with emitted robots'\n : '[dcs-seo] wrote robots.txt'\n )\n }\n }\n }\n\n // ── llms.txt (not in preview) ─────────────────────────────────────────────\n if (llms && !preview) {\n const txt = buildLlmsTxt({\n routes,\n siteUrl: effectiveSiteUrl,\n seoConfig,\n exclude,\n noindex,\n excludedGlobs,\n })\n if (txt) {\n fs.writeFileSync(path.join(outDir, 'llms.txt'), txt, 'utf8')\n written.push('llms.txt')\n if (debug) console.log('[dcs-seo] wrote llms.txt')\n } else if (debug) {\n console.log('[dcs-seo] nothing indexable; llms.txt not written')\n }\n }\n\n return written\n}\n\n/**\n * Pick a structured FAQ array (`[{ q, a }]`) for a page from content.yaml, if\n * one exists under a `faq` key (page-scoped, then global). Returns `undefined`\n * when there is no structured Q&A — a `<FaqPage />` component is NOT a source,\n * so its pages correctly yield nothing (honesty).\n */\nfunction pickPageFaq(\n content: ContentConfig | undefined,\n pageSlug: string\n): FaqSource[] | undefined {\n if (!content) return undefined\n const fromBlock = (block: Record<string, unknown> | undefined): FaqSource[] | undefined => {\n if (!block) return undefined\n const v = block['faq']\n return Array.isArray(v) ? (v as FaqSource[]) : undefined\n }\n return fromBlock(content.pages?.[pageSlug]) ?? fromBlock(content.global)\n}\n\n/**\n * Core emitter, separated from Vite so it is unit-testable.\n *\n * For each route: build the head tags from the shared resolver, splice them\n * into the shell HTML, and write `dist/<path>/index.html`. Returns the number\n * of files written. Pure aside from `fs` writes — deterministic + idempotent.\n */\nexport function emitStaticSeoHtml(params: {\n outDir: string\n shellHtml: string\n routes: PageRouteEntry[]\n seoConfig: SeoConfiguration | undefined\n /**\n * Parsed `.dcs/content.yaml` — threads REAL review items (honesty-gated) into\n * the per-route LocalBusiness node. Optional; omit ⇒ no Review/aggregateRating.\n */\n contentConfig?: ContentConfig\n exclude?: string[]\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs — routes matching are NOT emitted. */\n excludedGlobs?: string[]\n debug?: boolean\n}): number {\n const {\n outDir,\n shellHtml,\n routes,\n seoConfig,\n contentConfig,\n exclude = [],\n noindex = [],\n excludedGlobs = [],\n debug = false,\n } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n // Route → human title map (for breadcrumb hop labels), from the manifest.\n const siteUrl = seoConfig?.global?.siteUrl ?? ''\n const routeTitles: Record<string, string> = { '/': 'Home' }\n for (const r of routes) {\n if (r.title && r.path) routeTitles[r.path.replace(/\\/+$/, '') || '/'] = r.title\n }\n\n let written = 0\n for (const route of routes) {\n if (excludeSet.has(route.path) || (route.slug && excludeSet.has(route.slug))) {\n if (debug) console.log(`[dcs-seo] skip (excluded): ${route.path}`)\n continue\n }\n\n // Honor pages.yaml top-level `excluded:` globs (e.g. `/dev-*`, `/_*`): an\n // excluded route must NOT get a per-route index.html, or it becomes a\n // crawlable/indexable page despite being intentionally hidden — a privacy\n // and crawl-budget leak. Same glob semantics as the sitemap path.\n if (matchesExcludedGlob(route.path, excludedGlobs)) {\n if (debug) console.log(`[dcs-seo] skip (excluded glob): ${route.path}`)\n continue\n }\n\n const forceNoindex =\n noindexSet.has(route.path) || (route.slug && noindexSet.has(route.slug))\n\n // Breadcrumb trail from route depth (home ⇒ none); titles from the manifest.\n const breadcrumbTrail = siteUrl\n ? breadcrumbTrailFromRoute(route.path, siteUrl, routeTitles)\n : undefined\n\n // REAL reviews for this page from content.yaml (honesty-gated downstream).\n const reviews: ReviewSource[] | undefined = contentConfig\n ? findReviewItemsForPage(contentConfig, route.slug)\n : undefined\n\n // BlogPosting for a blog post route — only when seo.yaml has NOT already\n // hand-authored one (buildHeadTags skips ours if it has). A blog post is a\n // route nested under `/blog/` (depth ≥ 2) with a manifest title.\n const isBlogPost = /^\\/blog\\/.+/.test(route.path) && !!route.title\n const blogMeta: BlogMeta | undefined = isBlogPost\n ? { headline: route.title, url: breadcrumbTrail?.[breadcrumbTrail.length - 1]?.item }\n : undefined\n\n // FAQ from a structured `.dcs/faq.yaml`-style block on the content page, if\n // present (honesty-gated). Component-only FAQ pages carry no such data.\n const faq: FaqSource[] | undefined = pickPageFaq(contentConfig, route.slug)\n\n const tags = buildHeadTags(route.slug, route.path, seoConfig, {\n includeKeywords: true,\n fallbackTitle: route.title,\n robots: forceNoindex ? 'noindex, nofollow' : undefined,\n emitGraph: true,\n breadcrumbTrail,\n reviews,\n blogMeta,\n faq,\n })\n\n const html = spliceHeadHtml(shellHtml, tags)\n const outFile = routeToOutputFile(outDir, route.path)\n\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, html, 'utf8')\n written++\n\n if (debug) {\n console.log(\n `[dcs-seo] wrote ${path.relative(outDir, outFile)} (title=\"${tags.title}\"${\n forceNoindex ? ', noindex' : ''\n })`\n )\n }\n }\n\n return written\n}\n\n/**\n * Vite plugin that injects .dcs/seo.yaml at build time and, optionally, emits\n * per-route static `<head>` HTML for SPA SEO.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\n const {\n seoPath = '.dcs/seo.yaml',\n debug = false,\n emitStaticHtml = false,\n pagesPath = '.dcs/pages.yaml',\n contentPath = '.dcs/content.yaml',\n exclude = [],\n noindex = [],\n // Site files default ON whenever per-route HTML emission is on, so the\n // sites already using emitStaticHtml gain sitemap/robots/llms with no edit.\n emitSiteFiles: emitSiteFilesOpt = emitStaticHtml,\n siteUrl,\n robots = {},\n preview = false,\n llms = true,\n } = options\n\n let resolvedConfig: ResolvedConfig\n\n return {\n name: 'dcs-seo',\n\n configResolved(config) {\n resolvedConfig = config\n },\n\n config(config) {\n const projectRoot = config.root || process.cwd()\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n return {\n define: {\n __DCS_SEO__: 'undefined',\n },\n }\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${loaded.foundPath}`)\n console.log(`[dcs-seo] Version: ${loaded.config.version}`)\n console.log(`[dcs-seo] Site Name: ${loaded.config.global?.siteName || '(not set)'}`)\n console.log(\n `[dcs-seo] Pages: ${Object.keys(loaded.config.pages ?? {}).join(', ') || '(none)'}`\n )\n }\n\n return {\n define: {\n __DCS_SEO__: JSON.stringify(loaded.config),\n },\n }\n },\n\n /**\n * Build-time static `<head>` emitter (opt-in via `emitStaticHtml`).\n *\n * Runs after the bundle is written so the built `index.html` shell exists\n * on disk. For an SPA, Vite emits a single `index.html`; we fan it out to\n * per-route files with route-specific `<head>` content.\n *\n * Fully defensive: any missing/unparseable input logs a warning and\n * no-ops. Never throws (so it can never break a production build).\n */\n writeBundle() {\n if (!emitStaticHtml) {\n // The whole emit path is gated on emitStaticHtml. If the operator\n // explicitly asked for site files (`emitSiteFiles: true`) while\n // emitStaticHtml is off, that request is silently inert — warn so it's\n // not mistaken for \"robots/sitemap were emitted\".\n if (options.emitSiteFiles === true) {\n console.warn(\n '[dcs-seo] emitSiteFiles:true is ignored because emitStaticHtml is false; ' +\n 'set emitStaticHtml:true to emit sitemap.xml/robots.txt/llms.txt'\n )\n }\n return\n }\n\n try {\n const projectRoot = resolvedConfig?.root || process.cwd()\n const outDir = resolveOutDir(resolvedConfig)\n\n if (!fs.existsSync(outDir)) {\n console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`)\n return\n }\n\n const shellPath = path.join(outDir, 'index.html')\n if (!fs.existsSync(shellPath)) {\n console.warn(\n `[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`\n )\n return\n }\n\n const routes = loadPagesManifest(projectRoot, pagesPath, debug)\n if (!routes) {\n console.warn(\n `[dcs-seo] emitStaticHtml: ${pagesPath} missing or empty; skipping per-route emission`\n )\n return\n }\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n // seo.yaml is optional for emission — routes still get baked with\n // global/runtime defaults (which here means just the shell title and\n // a derived canonical-less head). Warn so the operator knows.\n console.warn(\n `[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`\n )\n }\n\n const shellHtml = fs.readFileSync(shellPath, 'utf8')\n\n // pages.yaml top-level `excluded:` globs gate BOTH the per-route HTML\n // emission below and the sitemap/llms set inside emitSiteFiles, so an\n // excluded route is never written as a crawlable index.html.\n const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath)\n // Optional: load content.yaml so REAL reviews can feed the honest\n // Review/aggregateRating on the per-route LocalBusiness node. Absent or\n // unparseable ⇒ undefined ⇒ no reviews emitted (no synthesis).\n const contentConfig = loadContentConfig(projectRoot, contentPath, debug)\n\n const written = emitStaticSeoHtml({\n outDir,\n shellHtml,\n routes,\n seoConfig: loaded?.config,\n contentConfig,\n exclude,\n noindex,\n excludedGlobs,\n debug,\n })\n\n if (debug) {\n console.log(`[dcs-seo] emitStaticHtml: wrote ${written} per-route HTML file(s)`)\n }\n\n // ── site-wide static files (sitemap.xml, robots.txt, llms.txt) ──────\n // Emitted AFTER the per-route HTML loop so canonicals/routes are\n // resolved and the shell exists. Reuses the already-loaded routes +\n // seoConfig (no second manifest read). Same outDir/existsSync guard +\n // the outer try/catch — so it can never break a production build.\n if (emitSiteFilesOpt) {\n const siteFiles = emitSiteFiles({\n outDir,\n projectRoot,\n pagesPath,\n routes,\n seoConfig: loaded?.config,\n siteUrl,\n exclude,\n noindex,\n preview,\n robots,\n llms,\n debug,\n })\n if (debug) {\n console.log(\n `[dcs-seo] emitSiteFiles: wrote ${siteFiles.length} site file(s): ${\n siteFiles.join(', ') || '(none)'\n }`\n )\n }\n }\n } catch (error) {\n // Never break the build on SEO emission failure.\n console.warn('[dcs-seo] emitStaticHtml failed; build output left unchanged:', error)\n }\n },\n\n // Watch for changes in development\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n ]\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n\n server.watcher.on('change', (changedPath) => {\n if (changedPath === watchPath) {\n if (debug) {\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\n }\n server.restart()\n }\n })\n }\n })\n },\n }\n}\n","/**\n * DCS Editor Plugin for Vite\n *\n * Injects the editor bridge script into customer sites when running\n * in portal preview mode (inside the visual editor iframe).\n *\n * The bridge enables:\n * - Inline text editing via contenteditable\n * - Section hover highlights and AI ✨ buttons\n * - postMessage communication with the portal\n *\n * This plugin should only be active in development/preview mode.\n * It's safe to include in production builds — it does nothing unless\n * the page detects it's inside an iframe.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsEditorPlugin()\n * ]\n * })\n * ```\n */\n\nimport type { Plugin } from 'vite'\n\nexport interface DcsEditorPluginOptions {\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects the editor bridge script for portal preview integration.\n *\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\n *\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\n * resolved through Vite's module graph rather than hitting the browser's native ESM\n * resolver, which can't handle bare specifiers.\n */\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\n const { debug = false } = options\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\n\n // Captured during configResolved — used to gate HTML injection.\n let isDev = false\n let basePath = '/'\n\n return {\n name: 'dcs-editor-bridge',\n\n configResolved(config) {\n isDev = config.command === 'serve'\n basePath = config.base ?? '/'\n },\n\n resolveId(id) {\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\n return id\n }\n },\n\n load(id) {\n if (id === VIRTUAL_PATH) {\n return `\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\n// The bridge only activates inside an iframe (portal preview)\nif (window.parent !== window) {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\n } else {\n setTimeout(initEditorBridge, 200)\n }\n}\n`\n }\n\n if (id === RIBBON_VIRTUAL_PATH) {\n return `\nimport { createApp } from 'vue'\nimport PreviewRibbon from '@duffcloudservices/cms/components'\n\nfunction mountRibbon() {\n // Create a detached mount point\n const el = document.createElement('div')\n el.id = 'dcs-preview-ribbon-root'\n document.body.appendChild(el)\n\n const app = createApp(PreviewRibbon)\n app.mount(el)\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', mountRibbon)\n} else {\n mountRibbon()\n}\n`\n }\n },\n\n /**\n * Only inject the editor bridge and ribbon scripts when:\n * 1. Running in dev mode (Vite dev server), AND\n * 2. The app is served at the root path (base === '/')\n *\n * These are virtual modules resolved via absolute paths (e.g.\n * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a\n * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT\n * routed to the Vite dev server by the reverse proxy — they hit the main\n * DCS server instead and get redirected to portal.duffcloudservices.com\n * (no CORS headers), causing browser console errors.\n *\n * In production builds, App.vue imports and mounts PreviewRibbon\n * directly, so virtual script injection is always redundant anyway.\n */\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n if (!isDev || basePath !== '/') return html\n\n if (debug) {\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\n }\n\n // Inject both the editor bridge and preview ribbon scripts\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\n\n // Insert before closing </body> tag\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\n },\n },\n }\n}\n","/**\n * DCS Preview Plugin for Vue\n *\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\n * component. The ribbon handles its own visibility — it only renders on\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\n * production domains, and inside the visual page editor iframe).\n *\n * Why does the caller pass the component in? Because this file is compiled\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\n * import out of the compiled plugins bundle avoids the build error while\n * still letting consumer code (which *does* run through Vite) resolve the\n * SFC at dev/build time.\n *\n * @example VitePress theme/index.ts\n * ```typescript\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\n * import PreviewRibbon from '@duffcloudservices/cms/components'\n *\n * export default {\n * Layout,\n * enhanceApp({ app }) {\n * app.use(dcsPreviewPlugin(PreviewRibbon))\n * }\n * }\n * ```\n */\n\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\n\nexport interface DcsPreviewPluginOptions {\n /**\n * Override the version string displayed on the ribbon.\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\n */\n version?: string | null\n}\n\n/**\n * Creates and returns the DCS Preview plugin.\n *\n * When installed, it registers the supplied ribbon component as a global\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\n * `transformIndexHtml`.\n *\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\n * @param options - Optional configuration\n */\nexport function dcsPreviewPlugin(\n ribbonComponent: Component,\n options: DcsPreviewPluginOptions = {},\n): Plugin {\n const Wrapper = defineComponent({\n name: 'DcsPreviewRibbon',\n setup() {\n return () => h(ribbonComponent, { version: options.version ?? null })\n },\n })\n\n return {\n install(app: App) {\n app.component('DcsPreviewRibbon', Wrapper)\n\n if (options.version !== undefined) {\n app.provide('__dcs_preview_version__', options.version)\n }\n },\n }\n}\n","/**\n * DCS CDN Image Plugin for Vite\n *\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\n * generated by the `image-migrate adopt` CLI command.\n *\n * For raster images with WebP variants, `<img>` elements are transformed into\n * responsive `<picture>` elements with `srcset` for optimised delivery.\n * SVGs receive a simple URL swap with no variant handling.\n *\n * **In development mode this plugin is a no-op** — local `/images/` paths\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\n *\n * The plugin handles two in-pipeline replacement vectors:\n *\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\n *\n * A third vector — **post-build file processing** — is handled by the\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\n * VitePress config. VitePress generates HTML *after* both Vite builds\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\n * `service-worker.js`.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsCdnImagePlugin()\n * ]\n * },\n * buildEnd: dcsCdnBuildEnd()\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { Plugin } from 'vite'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface CdnImageMapVariant {\n suffix: string\n blobPath: string\n cdnUrl: string\n width: number\n height: number\n size: number\n}\n\nexport interface CdnImageMapEntry {\n localPath: string\n blobPath: string\n cdnUrl: string\n fileName: string\n contentType: string\n originalSize: number\n width?: number\n height?: number\n hash: string\n variants?: CdnImageMapVariant[]\n}\n\nexport interface CdnImageMap {\n generated: string\n site: string\n cdnBase: string\n images: CdnImageMapEntry[]\n}\n\nexport interface DcsCdnImagePluginOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\n * mapping file is compared *without* a leading slash.\n *\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /**\n * Default `sizes` attribute for responsive `<picture>` elements.\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\n *\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\n */\n defaultSizes?: string\n\n /** Enable debug logging */\n debug?: boolean\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\n/**\n * Build a responsive `<picture>` element for a raster image that has\n * WebP variants.\n */\nfunction buildPictureElement(\n entry: CdnImageMapEntry,\n alt: string,\n extraAttrs: string,\n sizes: string\n): string {\n const variants = entry.variants ?? []\n\n // Collect only the display-relevant variants (not thumb, not og)\n const displayVariants = variants.filter(\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\n )\n\n if (displayVariants.length === 0) {\n // No display variants — fall back to original CDN URL as plain <img>\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n const srcset = displayVariants\n .map((v) => `${v.cdnUrl} ${v.width}w`)\n .join(', ')\n\n // Pick the -md variant as the default src, or fall through to original\n const mdVariant = variants.find((v) => v.suffix === '-md')\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\n '</picture>',\n ].join('\\n')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Shared helpers\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Try to find and load the CDN image map from common locations.\n * VitePress nests docs under a subfolder, so we check parent dirs too.\n */\nfunction loadCdnImageMap(\n projectRoot: string,\n relativeMapPath: string,\n debug: boolean\n): Map<string, CdnImageMapEntry> | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativeMapPath),\n path.resolve(projectRoot, '..', relativeMapPath),\n path.resolve(process.cwd(), relativeMapPath),\n ]\n\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n try {\n const raw = fs.readFileSync(testPath, 'utf8')\n const data: CdnImageMap = JSON.parse(raw)\n\n const map = new Map<string, CdnImageMapEntry>()\n for (const entry of data.images) {\n // Store both with and without leading slash for flexible matching\n map.set(entry.localPath, entry)\n if (!entry.localPath.startsWith('/')) {\n map.set('/' + entry.localPath, entry)\n }\n }\n\n if (debug) {\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\n }\n return map\n } catch (error) {\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\n }\n }\n }\n\n if (debug) {\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n }\n return null\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Vite Plugin\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\n */\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\n debug = false,\n } = options\n\n // Lookup map built from the JSON file: key = localPath (no leading slash)\n let imageMap: Map<string, CdnImageMapEntry> | null = null\n let isProduction = false\n\n /**\n * Check whether a source code string contains any of the path prefixes\n * we care about. Quick bail-out for files that don't reference images.\n */\n function hasImageReferences(code: string): boolean {\n return pathPrefixes.some((prefix) => code.includes(prefix))\n }\n\n /**\n * Replace image path references in any string value with CDN URLs.\n * Used for code transforms and renderChunk post-processing.\n */\n function replaceImagePaths(str: string): string {\n if (!imageMap || imageMap.size === 0) return str\n let result = str\n for (const prefix of pathPrefixes) {\n // Global replace: matches prefix followed by a relative path bounded\n // by quotes, parens, or whitespace. Allows spaces in filenames.\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n result = result.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${quote}${entry.cdnUrl}`\n })\n }\n return result\n }\n\n /**\n * Replace standalone URL references in JS/TS string literals and Vue template\n * attributes. This handles patterns like:\n * src=\"/images/foo.jpg\"\n * { src: '/images/foo.jpg' }\n * url(/images/foo.svg)\n *\n * For `<img>` tags with raster images that have variants, the entire tag is\n * replaced with a `<picture>` element.\n */\n function transformCode(code: string): string {\n if (!imageMap || imageMap.size === 0) return code\n\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return _match\n\n // Extract alt from the existing attributes\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\n const alt = altMatch ? altMatch[1] : ''\n\n // Collect other attributes (not src, alt, loading, decoding — we set those)\n const otherAttrs = (beforeSrc + afterSrc)\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\n .trim()\n\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\n\n // Extract custom sizes if specified\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\n\n // SVGs and images without variants → simple URL swap <img>\n if (!entry.variants || entry.variants.length === 0) {\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n // Raster with variants → <picture>\n return buildPictureElement(entry, alt, extraAttrs, sizes)\n })\n\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\n code = replaceImagePaths(code)\n\n return code\n }\n\n return {\n name: 'dcs-cdn-image',\n enforce: 'pre',\n\n configResolved(config) {\n isProduction = config.command === 'build'\n if (!isProduction) {\n if (debug) {\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\n }\n return\n }\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\n },\n\n transform(code, id) {\n // Only run in production builds\n if (!isProduction || !imageMap) return\n\n // Only process relevant file types\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\n\n // Quick bail — skip files with no image path references\n if (!hasImageReferences(code)) return\n\n const transformed = transformCode(code)\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n\n /**\n * Replace image paths in final rendered JS/CSS chunks.\n *\n * This fires AFTER Vite's `define` substitution, catching paths that were\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\n * `transform` hook because `define` replacement happens at bundle time.\n */\n renderChunk(code, _chunk) {\n if (!isProduction || !imageMap) return null\n if (!hasImageReferences(code)) return null\n\n const replaced = replaceImagePaths(code)\n if (replaced !== code) {\n if (debug) {\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\n }\n return { code: replaced, map: null }\n }\n return null\n },\n\n /**\n * Transform HTML output to catch references injected outside the module\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\n *\n * Note: In VitePress, this hook fires for the `index.html` template but\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\n * coverage of generated HTML files.\n */\n transformIndexHtml(html) {\n if (!isProduction || !imageMap) return html\n if (!hasImageReferences(html)) return html\n\n // Replace href/src/content attributes that reference mapped images\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${pre}${entry.cdnUrl}${post}`\n })\n }\n\n return html\n },\n }\n}\n\n/**\n * Escape a string for use in a RegExp.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// VitePress buildEnd hook\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface DcsCdnBuildEndOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement.\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * File extensions to process in the output directory.\n * Default: `['.html', '.js']`\n */\n extensions?: string[]\n}\n\n/**\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\n * static files in the output directory to replace remaining `/images/` paths\n * with CDN URLs.\n *\n * VitePress generates HTML **after** both Vite builds complete, so Vite\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\n * HTML files are written to disk.\n *\n * Also rewrites static files (e.g. `service-worker.js`) copied from\n * `public/` that are not part of Vite's module pipeline.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * buildEnd: dcsCdnBuildEnd({ debug: true })\n * })\n * ```\n */\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n debug = false,\n extensions = ['.html', '.js'],\n } = options\n\n return async (siteConfig: { root: string; outDir: string }) => {\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\n if (!imageMap) {\n if (debug) {\n console.log('[dcs-cdn-image] buildEnd: no image map found')\n }\n return\n }\n\n const outDir = siteConfig.outDir\n if (!fs.existsSync(outDir)) return\n\n let filesProcessed = 0\n let refsReplaced = 0\n\n function walkDir(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n walkDir(fullPath)\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\n processFile(fullPath)\n }\n }\n }\n\n function processFile(filePath: string) {\n let content = fs.readFileSync(filePath, 'utf8')\n if (!pathPrefixes.some((p) => content.includes(p))) return\n\n let changed = false\n\n if (filePath.endsWith('.html')) {\n // HTML: replace href/src/content attribute values\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!mapEntry) return match\n changed = true\n refsReplaced++\n return `${pre}${mapEntry.cdnUrl}${post}`\n })\n }\n } else {\n // JS/other: replace quoted string paths\n for (const prefix of pathPrefixes) {\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n content = content.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n changed = true\n refsReplaced++\n return `${quote}${entry.cdnUrl}`\n })\n }\n }\n\n if (changed) {\n fs.writeFileSync(filePath, content, 'utf8')\n filesProcessed++\n }\n }\n\n walkDir(outDir)\n\n if (debug) {\n console.log(\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\n )\n }\n }\n}","/**\n * markdown-it plugin that transforms standard `![alt](url)` image syntax\n * into responsive `<picture>` elements when the URL matches the DCS CDN\n * asset pattern.\n *\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * markdown: {\n * config: (md) => {\n * md.use(responsiveImagePlugin)\n * },\n * },\n * })\n * ```\n *\n * Input markdown:\n * ```md\n * ![Physical therapy session](https://files.duffcloudservices.com/kept/assets/blog/abc-123.jpg)\n * ```\n *\n * Rendered HTML:\n * ```html\n * <picture>\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\n * type=\"image/webp\"\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\n * </picture>\n * ```\n */\n\nimport type MarkdownIt from 'markdown-it'\n\n/**\n * Matches DCS CDN asset URLs: base path / UUID . extension.\n *\n * Aligned with `@duffcloudservices/cms-core` `responsive-image.ts`: the\n * `(?:content\\/)?` group covers the content-container shape\n * `https://files.duffcloudservices.com/content/{slug}/assets/...` in addition\n * to the branding-container shape `.../{slug}/assets/...`. The group is\n * non-capturing, so the captured base path / UUID / extension are unchanged.\n */\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/(?:content\\/)?[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\n\n/**\n * Escapes HTML special characters for safe attribute embedding.\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\nexport function responsiveImagePlugin(md: MarkdownIt): void {\n const defaultImageRenderer = md.renderer.rules.image\n\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\n const token = tokens[idx]\n const src = token.attrGet('src') ?? ''\n const alt = token.content ?? ''\n\n const match = src.match(CDN_PATTERN)\n if (!match) {\n // Not a CDN image — render normally\n if (defaultImageRenderer) {\n return defaultImageRenderer(tokens, idx, options, env, self)\n }\n return self.renderToken(tokens, idx, options)\n }\n\n const [, basePath, uuid] = match\n\n const srcset = [\n `${basePath}${uuid}-sm.webp 640w`,\n `${basePath}${uuid}-md.webp 1024w`,\n `${basePath}${uuid}-lg.webp 1920w`,\n ].join(', ')\n\n const escapedAlt = escapeHtml(alt)\n\n // Self-heal: this plugin emits a STATIC HTML string at build time (no Vue\n // runtime), so the fallback must be an inline onerror. A fit-variant\n // (`-md`/`-lg`) may legitimately be absent (internal/imaging variants.go\n // skips variants by DIMENSION). On the first error, swap the `<img>` to the\n // ORIGINAL url AND strip the sibling `<source>` elements — a `<picture>`\n // does NOT re-resolve after a `<source>` 404s, so the variant set must be\n // removed for the original to display. (`this.onerror=null` prevents loops.)\n const escapedOriginal = escapeHtml(src)\n const onError =\n `this.onerror=null;this.src='${escapedOriginal}';` +\n `this.closest('picture')?.querySelectorAll('source').forEach(s=>s.remove())`\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" onerror=\"${onError}\" />`,\n '</picture>',\n ].join('\\n')\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/plugins/serveDefineInject.ts","../../src/plugins/motionTokens.ts","../../src/plugins/dcsContentPlugin.ts","../../src/plugins/dcsSeoPlugin.ts","../../src/plugins/dcsEditorPlugin.ts","../../src/plugins/dcsPreviewPlugin.ts","../../src/plugins/dcsCdnImagePlugin.ts","../../src/plugins/responsiveImagePlugin.ts"],"names":["path","fs","yaml","escapeHtml"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Shared serve-mode helpers for the DCS content + SEO Vite plugins.\n *\n * In a production build the plugins bake the `.dcs/*.yaml` payload into a Vite\n * `define` literal (`define: { __DCS_X__: JSON.stringify(data) }`) so the SSG /\n * SPA bundle inlines it exactly as before.\n *\n * In dev (`vite` / `vitepress dev`, i.e. the portal preview) baking a literal\n * is wrong: the value is frozen at server-start and only a `server.restart()`\n * would refresh it — and that restart is exactly what triggers VitePress's\n * MiniSearch rebuild and the duplicate-heading \"server restart failed\" crash.\n *\n * Instead, in serve mode the plugins:\n * 1. redirect the `__DCS_X__` define to a browser global\n * (`define: { __DCS_X__: 'globalThis.__DCS_X__' }`), and\n * 2. inject an inline classic `<script>` at the top of `<head>` on every HTML\n * request that assigns the FRESH on-disk YAML to that global.\n *\n * Because `transformIndexHtml` re-runs on every dev HTML request, a plain\n * iframe reload (the portal's `?_hc` hard reload) reflects the latest on-disk\n * content with NO `server.restart()` and NO module-graph surgery. The consumer\n * is untouched: it still reads the bare `__DCS_X__` identifier, which the define\n * rewrites to `globalThis.__DCS_X__`.\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\n\n/**\n * The three `.dcs` search locations the plugins have always used. VitePress\n * projects have the `docs/` folder as root, so the parent is checked too.\n */\nexport function dcsSearchPaths(projectRoot: string, relPath: string): string[] {\n return [\n path.resolve(projectRoot, relPath),\n path.resolve(projectRoot, '..', relPath),\n path.resolve(process.cwd(), relPath),\n ]\n}\n\n/** First existing `.dcs` file from the standard search, or `undefined`. */\nexport function resolveDcsFile(projectRoot: string, relPath: string): string | undefined {\n return dcsSearchPaths(projectRoot, relPath).find((p) => fs.existsSync(p))\n}\n\n/**\n * Read + parse a `.dcs` YAML file FRESH from disk on every call (no caching).\n * Returns the parsed value, or `undefined` when the file is missing or\n * unparseable. Never throws — a dev HTML request must never 500 on bad YAML.\n */\nexport function readDcsYamlFresh<T = unknown>(\n projectRoot: string,\n relPath: string,\n label: string,\n debug = false\n): T | undefined {\n const found = resolveDcsFile(projectRoot, relPath)\n if (!found) {\n if (debug) console.log(`[${label}] serve: no ${relPath} found; global left unset`)\n return undefined\n }\n try {\n return yaml.load(fs.readFileSync(found, 'utf8')) as T\n } catch (error) {\n console.warn(`[${label}] serve: failed to parse ${relPath}:`, error)\n return undefined\n }\n}\n\n/**\n * Build a safe inline `<script>` body that assigns a JSON-serialisable value to\n * a browser global. Every `<` is escaped to its `<` unicode form so an\n * embedded `</script>` (or `<!--`) inside the content data can never break out\n * of the inline script.\n */\nexport function buildGlobalAssignScript(globalName: string, value: unknown): string {\n const json = JSON.stringify(value ?? null).replace(/</g, '\\\\u003c')\n return `globalThis.${globalName} = ${json};`\n}\n","import type { HtmlTagDescriptor } from 'vite'\n\n/**\n * DCS motion tokens — the customer-site copy of the shared motion vocabulary.\n *\n * Injected as a constant `<style>` into every DCS site's `<head>` by\n * `dcsContentPlugin` (see its `transformIndexHtml`). This is the fleet-wide\n * seam: every site (vue-spa and VitePress) already wires `dcsContentPlugin`,\n * so a cms version bump ships these tokens to the whole fleet with zero\n * per-site source edits.\n *\n * Source of truth is the platform copy at\n * `packages/dcs-ui/src/styles/motion.css` and the `dcs-product-motion` skill —\n * keep the values here in lockstep. Values ONLY: reduced-motion stays a\n * per-component responsibility so no blanket `!important` rule leaks into a\n * site's isolated `.<slug>-page` design subtree.\n */\nexport 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}`\n\n/**\n * The `<head>` tag descriptor for the motion-token block. `head-prepend` keeps\n * it above the site's own bundled CSS so a per-site `:root` override still wins.\n */\nexport function dcsMotionTokensStyleTag(): HtmlTagDescriptor {\n return {\n tag: 'style',\n attrs: { 'data-dcs-motion-tokens': '' },\n children: DCS_MOTION_TOKENS_CSS,\n injectTo: 'head-prepend',\n }\n}\n","/**\n * DCS Content Plugin for Vite\n *\n * Reads `.dcs/content.yaml` at build time and injects content\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsContentPlugin({ debug: true })\n * ]\n * })\n * ```\n *\n * For VitePress:\n * ```typescript\n * // .vitepress/config.ts\n * import { defineConfig } from 'vitepress'\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsContentPlugin()\n * ]\n * }\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig, HtmlTagDescriptor } from 'vite'\nimport type { DcsContentFile } from '../types/content'\nimport { readDcsYamlFresh, buildGlobalAssignScript } from './serveDefineInject'\nimport { dcsMotionTokensStyleTag } from './motionTokens'\n\nexport interface DcsContentPluginOptions {\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\n contentPath?: string\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects .dcs/content.yaml at build time.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\n const { contentPath = '.dcs/content.yaml', debug = false } = options\n\n let resolvedConfig: ResolvedConfig\n // Captured during configResolved — gates the serve-only inline HTML inject.\n let isDev = false\n\n return {\n name: 'dcs-content',\n\n configResolved(config) {\n resolvedConfig = config\n isDev = config.command === 'serve'\n },\n\n config(config, env) {\n // ── Dev / preview (serve) ──────────────────────────────────────────────\n // Don't bake content into the bundle. Redirect the `__DCS_CONTENT__`\n // define to a browser global that `transformIndexHtml` (below) refreshes\n // from disk on every HTML request — so a plain iframe reload reflects the\n // latest on-disk content with NO server.restart() and NO module-graph\n // surgery. The consumer is untouched: it still reads the bare\n // `__DCS_CONTENT__` identifier, which this define rewrites to\n // `globalThis.__DCS_CONTENT__`.\n if (env.command === 'serve') {\n return {\n define: {\n __DCS_CONTENT__: 'globalThis.__DCS_CONTENT__',\n },\n }\n }\n\n // ── Production build ───────────────────────────────────────────────────\n // Unchanged: read content.yaml now and bake it into the bundle as a\n // JSON literal so the SSG / SPA output inlines content exactly as before.\n const projectRoot = config.root || process.cwd()\n\n // Try to find content.yaml in multiple locations\n // VitePress projects have the docs folder as root, so check parent too\n const possiblePaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n path.resolve(process.cwd(), contentPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-content] No content.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-content] Using defaults only')\n }\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const content = yaml.load(fileContent) as DcsContentFile\n\n if (debug) {\n console.log(`[dcs-content] Loaded ${foundPath}`)\n console.log(`[dcs-content] Version: ${content.version}`)\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\n }\n\n return {\n define: {\n __DCS_CONTENT__: JSON.stringify(content),\n },\n }\n } catch (error) {\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\n return {\n define: {\n __DCS_CONTENT__: 'undefined',\n },\n }\n }\n },\n\n /**\n * Inject into `<head>` on every HTML request. Two concerns:\n *\n * 1. The constant DCS motion-token `:root` block (`DCS_MOTION_TOKENS_CSS`)\n * — the fleet-wide seam that gives every DCS site the shared `--motion-*`\n * vocabulary. It carries no per-site data, so (unlike the content inject)\n * it is NOT gated on `isDev`: it runs in both `serve` and `build`.\n * `head-prepend` keeps it ABOVE the bundled site CSS, so a site's own\n * `:root` (theme `tailwind.css` / `style.css`) can still override a token.\n *\n * 2. Dev-only: the FRESH on-disk `content.yaml` as an inline\n * `globalThis.__DCS_CONTENT__ = {…}` classic script. Runs on every dev\n * HTML request, so the portal's `?_hc` hard reload re-reads the YAML with\n * no restart. A classic `<script>` in `<head>` executes before the\n * deferred module app entry, so the global is set before the consumer's\n * `useTextContent` setup reads it. Gated on `isDev` so it never runs in\n * `vite`/`vitepress build` (the build path keeps the baked `define`).\n */\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n // (1) Always-on, per the note above: the constant motion-token block.\n const tags: HtmlTagDescriptor[] = [dcsMotionTokensStyleTag()]\n\n // (2) Dev-only fresh content inject.\n if (isDev) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n const content = readDcsYamlFresh<DcsContentFile>(\n projectRoot,\n contentPath,\n 'dcs-content',\n debug\n )\n // No file (or unparseable) ⇒ leave the global unset; the consumer's\n // `typeof globalThis.__DCS_CONTENT__` check falls back to defaults.\n if (content !== undefined) {\n if (debug) {\n console.log('[dcs-content] serve: injecting fresh __DCS_CONTENT__ into <head>')\n }\n tags.push({\n tag: 'script',\n children: buildGlobalAssignScript('__DCS_CONTENT__', content),\n injectTo: 'head-prepend',\n })\n }\n }\n\n return { html, tags }\n },\n },\n\n // Watch for changes in development.\n //\n // Downgraded from `server.restart()` to a debounced client `full-reload`:\n // freshness already comes from `transformIndexHtml` re-reading the YAML on\n // every HTML request, so all we need on change is to nudge the browser to\n // reload. A `server.restart()` would rebuild VitePress's dev MiniSearch\n // index and can throw \"server restart failed\" on a duplicate heading id —\n // exactly the fragility this fix removes. A `full-reload` does neither.\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, contentPath),\n path.resolve(projectRoot, '..', contentPath),\n ].map((p) => path.normalize(p))\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n }\n })\n\n let reloadTimer: ReturnType<typeof setTimeout> | undefined\n server.watcher.on('change', (changedPath) => {\n if (!watchPaths.includes(path.normalize(changedPath))) return\n if (reloadTimer) clearTimeout(reloadTimer)\n reloadTimer = setTimeout(() => {\n if (debug) {\n console.log('[dcs-content] content.yaml changed, sending full-reload')\n }\n server.ws.send({ type: 'full-reload' })\n }, 100)\n })\n },\n }\n}\n","/**\n * DCS SEO Plugin for Vite\n *\n * Two responsibilities, both driven by `.dcs/seo.yaml`:\n *\n * 1. **Build-time define** (always on): reads `.dcs/seo.yaml` and injects it as\n * the `__DCS_SEO__` global for the runtime `useSEO` composable.\n *\n * 2. **Static `<head>` emitter** (opt-in via `emitStaticHtml: true`, default\n * OFF): after the bundle is written, reads the SPA's built `index.html`,\n * and for every route in `.dcs/pages.yaml` writes a per-route\n * `dist/<path>/index.html` whose `<head>` carries the resolved title, meta,\n * canonical, Open Graph, Twitter, and JSON-LD tags. This gives a Vue SPA\n * per-route static SEO **without** vite-ssg.\n *\n * VitePress sites already bake SEO via their own config, so they leave this\n * option OFF and are completely unaffected.\n *\n * @example Runtime define only (default — safe for VitePress)\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [dcsSeoPlugin({ debug: true })]\n * })\n * ```\n *\n * @example Vue SPA with per-route static <head> emission\n * ```typescript\n * // vite.config.ts\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsSeoPlugin({\n * emitStaticHtml: true, // turn the emitter ON\n * pagesPath: '.dcs/pages.yaml', // route manifest (default)\n * noindex: ['account', 'projects'], // robots: noindex,nofollow\n * exclude: ['/preview'], // skip these routes entirely\n * })\n * ]\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport yaml from 'js-yaml'\nimport type { Plugin, ResolvedConfig, HtmlTagDescriptor } from 'vite'\nimport type { SeoConfiguration } from '../types/seo'\nimport { readDcsYamlFresh, buildGlobalAssignScript } from './serveDefineInject'\nimport { buildHeadTags } from '../seo/headTags'\nimport { spliceHeadHtml } from '../seo/spliceHeadHtml'\nimport { loadPagesManifest, type PageRouteEntry } from '../seo/pagesManifest'\nimport {\n buildSitemapXml,\n buildRobotsTxt,\n buildLlmsTxt,\n matchesExcludedGlob,\n isHandAuthoredRobotsAcceptable,\n type DcsRobotsOptions,\n} from '../seo/sitemap'\nimport {\n breadcrumbTrailFromRoute,\n findReviewItemsForPage,\n type ContentConfig,\n type ReviewSource,\n type BlogMeta,\n type FaqSource,\n} from '../seo/schemaGraph'\n\nexport interface DcsSeoPluginOptions {\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\n seoPath?: string\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * Opt-in: emit per-route static `<head>` (meta + JSON-LD) into the built\n * `dist/` at the end of the build. Default `false` — VitePress sites and any\n * site that bakes its own SEO are unaffected when this is off.\n */\n emitStaticHtml?: boolean\n\n /**\n * Path to the route manifest relative to project root, used only when\n * `emitStaticHtml` is true. Default `'.dcs/pages.yaml'`.\n */\n pagesPath?: string\n\n /**\n * Path to `content.yaml` relative to project root, used only when\n * `emitStaticHtml` is true. Threads REAL reviews into the honest\n * Review/aggregateRating on the per-route LocalBusiness node. Default\n * `'.dcs/content.yaml'`. Missing/unparseable ⇒ no reviews emitted.\n */\n contentPath?: string\n\n /**\n * Routes to skip entirely (no per-route HTML written). Matched against the\n * route `path` (e.g. `'/preview'`) OR the route `slug` (e.g. `'account'`).\n */\n exclude?: string[]\n\n /**\n * Routes that should receive `robots: noindex, nofollow`. Matched against the\n * route `path` OR `slug`. The home route (`/`) still overwrites\n * `dist/index.html`.\n */\n noindex?: string[]\n\n /**\n * Opt-in: emit the site-wide static files `dist/sitemap.xml`,\n * `dist/robots.txt`, and `dist/llms.txt` after the per-route HTML loop.\n *\n * Defaults to whatever `emitStaticHtml` is — so every site already calling\n * `dcsSeoPlugin({ emitStaticHtml: true })` gets sitemap/robots/llms for free\n * with zero per-site edits. Set explicitly to `false` to keep emitting\n * per-route HTML without the site files. The whole block is wrapped in the\n * same try/catch + existsSync guards, so it can never break a build.\n */\n emitSiteFiles?: boolean\n\n /**\n * Canonical production origin, e.g. `https://ironoakcontractors.com`. Used as\n * the sitemap/llms base and the absolute `Sitemap:` line in robots.txt. When\n * absent, falls back to `seo.yaml`'s `global.siteUrl`; if neither is known and\n * no per-page canonicals exist, the sitemap + llms.txt no-op (robots still\n * emits). Never guessed.\n */\n siteUrl?: string\n\n /** robots.txt override hooks (disallow/allow/extra/aiBots/force). */\n robots?: DcsRobotsOptions\n\n /**\n * Preview / staging gate. When `true`, robots.txt becomes `Disallow: /` (no\n * `Sitemap:` line) and neither sitemap.xml nor llms.txt is emitted (privacy).\n * Detected ONLY via this explicit option — never guessed.\n */\n preview?: boolean\n\n /** Emit `dist/llms.txt` (default `true`; always off in preview mode). */\n llms?: boolean\n}\n\n/** Resolve the SPA output directory from Vite config (absolute path). */\nfunction resolveOutDir(config: ResolvedConfig): string {\n const out = config.build?.outDir || 'dist'\n return path.isAbsolute(out) ? out : path.resolve(config.root || process.cwd(), out)\n}\n\n/**\n * Load + parse seo.yaml from the usual `.dcs` locations.\n * Returns `{ config, foundPath }` or `null` (missing/unparseable).\n */\nfunction loadSeoConfig(\n projectRoot: string,\n seoPath: string,\n debug: boolean\n): { config: SeoConfiguration; foundPath: string } | null {\n const possiblePaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n path.resolve(process.cwd(), seoPath),\n ]\n\n let foundPath: string | undefined\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n foundPath = testPath\n break\n }\n }\n\n if (!foundPath) {\n if (debug) {\n console.log('[dcs-seo] No seo.yaml found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n console.log('[dcs-seo] Using defaults only')\n }\n return null\n }\n\n try {\n const fileContent = fs.readFileSync(foundPath, 'utf8')\n const config = yaml.load(fileContent) as SeoConfiguration\n return { config, foundPath }\n } catch (error) {\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\n return null\n }\n}\n\n/**\n * Load + parse `.dcs/content.yaml` from the usual `.dcs` locations. Returns the\n * parsed config or `undefined` (missing/unparseable). Used to thread REAL\n * reviews into the honest Review/aggregateRating builder. Never throws.\n */\nfunction loadContentConfig(\n projectRoot: string,\n contentRelPath: string,\n debug: boolean\n): ContentConfig | undefined {\n const possiblePaths = [\n path.resolve(projectRoot, contentRelPath),\n path.resolve(projectRoot, '..', contentRelPath),\n path.resolve(process.cwd(), contentRelPath),\n ]\n const foundPath = possiblePaths.find((p) => fs.existsSync(p))\n if (!foundPath) {\n if (debug) console.log(`[dcs-seo] No content.yaml found (reviews disabled)`)\n return undefined\n }\n try {\n const parsed = yaml.load(fs.readFileSync(foundPath, 'utf8'))\n if (!parsed || typeof parsed !== 'object') return undefined\n return parsed as ContentConfig\n } catch (error) {\n console.warn('[dcs-seo] Failed to parse content.yaml (reviews disabled):', error)\n return undefined\n }\n}\n\n/**\n * Normalise a route path to the on-disk output file:\n * '/' -> '<outDir>/index.html'\n * '/services' -> '<outDir>/services/index.html'\n * '/blog/my-post' -> '<outDir>/blog/my-post/index.html'\n * '/services/' -> '<outDir>/services/index.html'\n */\nfunction routeToOutputFile(outDir: string, routePath: string): string {\n const trimmed = routePath.replace(/^\\/+/, '').replace(/\\/+$/, '')\n if (trimmed === '') return path.join(outDir, 'index.html')\n return path.join(outDir, ...trimmed.split('/'), 'index.html')\n}\n\n/** Extract the optional top-level `excluded:` glob list from pages.yaml. */\nfunction loadExcludedGlobs(projectRoot: string, pagesPath: string): string[] {\n const possiblePaths = [\n path.resolve(projectRoot, pagesPath),\n path.resolve(projectRoot, '..', pagesPath),\n path.resolve(process.cwd(), pagesPath),\n ]\n const found = possiblePaths.find((p) => fs.existsSync(p))\n if (!found) return []\n try {\n const raw = yaml.load(fs.readFileSync(found, 'utf8')) as { excluded?: unknown } | null\n const excluded = raw?.excluded\n if (!Array.isArray(excluded)) return []\n return excluded.filter((e): e is string => typeof e === 'string')\n } catch {\n return []\n }\n}\n\n/**\n * Derive a single site-wide lastmod from seo.yaml `lastUpdated` (preferred) or\n * pages.yaml `lastUpdated`. No fabricated per-page dates.\n */\nfunction deriveLastmod(\n seoConfig: SeoConfiguration | undefined,\n projectRoot: string,\n pagesPath: string\n): string | undefined {\n const fromSeo = seoConfig?.lastUpdated\n if (typeof fromSeo === 'string' && fromSeo.length > 0) return fromSeo\n\n const possiblePaths = [\n path.resolve(projectRoot, pagesPath),\n path.resolve(projectRoot, '..', pagesPath),\n path.resolve(process.cwd(), pagesPath),\n ]\n const found = possiblePaths.find((p) => fs.existsSync(p))\n if (!found) return undefined\n try {\n const raw = yaml.load(fs.readFileSync(found, 'utf8')) as { lastUpdated?: unknown } | null\n const fromPages = raw?.lastUpdated\n if (typeof fromPages === 'string' && fromPages.length > 0) return fromPages\n } catch {\n /* ignore */\n }\n return undefined\n}\n\n/**\n * Core site-file emitter, separated from Vite so it is unit-testable.\n *\n * Writes the site-wide static files into `outDir`, reusing the SAME routes +\n * seoConfig already loaded by the per-route HTML loop (no second manifest read):\n * - `sitemap.xml` (skipped in preview, or when no absolute base is derivable)\n * - `robots.txt` (always; preview = `Disallow: /`; don't-clobber unless force)\n * - `llms.txt` (skipped in preview, or when nothing indexable)\n *\n * Returns the list of relative filenames written. Pure aside from `fs` writes —\n * deterministic + idempotent. Never throws (the caller also wraps in try/catch).\n */\nexport function emitSiteFiles(params: {\n outDir: string\n projectRoot: string\n pagesPath: string\n routes: PageRouteEntry[]\n seoConfig: SeoConfiguration | undefined\n siteUrl?: string\n exclude?: string[]\n noindex?: string[]\n preview?: boolean\n robots?: DcsRobotsOptions\n llms?: boolean\n debug?: boolean\n}): string[] {\n const {\n outDir,\n projectRoot,\n pagesPath,\n routes,\n seoConfig,\n siteUrl,\n exclude = [],\n noindex = [],\n preview = false,\n robots = {},\n llms = true,\n debug = false,\n } = params\n\n const written: string[] = []\n\n const effectiveSiteUrl = siteUrl || seoConfig?.global?.siteUrl\n const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath)\n const lastmod = deriveLastmod(seoConfig, projectRoot, pagesPath)\n\n // ── sitemap.xml (not in preview) ──────────────────────────────────────────\n let wroteSitemap = false\n if (!preview) {\n const xml = buildSitemapXml({\n routes,\n siteUrl: effectiveSiteUrl,\n seoConfig,\n exclude,\n noindex,\n excludedGlobs,\n lastmod,\n })\n if (xml) {\n fs.writeFileSync(path.join(outDir, 'sitemap.xml'), xml, 'utf8')\n wroteSitemap = true\n written.push('sitemap.xml')\n if (debug) console.log('[dcs-seo] wrote sitemap.xml')\n } else if (debug) {\n console.log('[dcs-seo] no absolute <loc> derivable; sitemap.xml not written')\n }\n }\n\n // ── robots.txt (always, unless robots.enabled is false) ───────────────────\n if (robots.enabled ?? true) {\n const robotsPath = path.join(outDir, 'robots.txt')\n // Factory-as-single-source (validated override): a hand-authored\n // public/robots.txt Vite copied into dist is KEPT only when it meets the\n // factory floor (a real `User-agent: *` group + every AI-bot tier). A stub\n // (`Sitemap:`-only, like just-posh) or an SPA-shell HTML page fails the\n // floor and is overwritten so AI discovery is guaranteed on every site.\n // `force` always overwrites; an absent file is always written.\n const existing =\n fs.existsSync(robotsPath) && !robots.force ? fs.readFileSync(robotsPath, 'utf8') : null\n const keepHandAuthored = existing !== null && isHandAuthoredRobotsAcceptable(existing)\n if (keepHandAuthored) {\n if (debug)\n console.log('[dcs-seo] dist/robots.txt is hand-authored and meets the floor; leaving it')\n } else {\n const txt = buildRobotsTxt({\n siteUrl: effectiveSiteUrl,\n preview,\n robots,\n hasSitemap: wroteSitemap,\n })\n fs.writeFileSync(robotsPath, txt, 'utf8')\n written.push('robots.txt')\n if (debug) {\n console.log(\n existing !== null\n ? '[dcs-seo] dist/robots.txt failed the factory floor; overwrote with emitted robots'\n : '[dcs-seo] wrote robots.txt'\n )\n }\n }\n }\n\n // ── llms.txt (not in preview) ─────────────────────────────────────────────\n if (llms && !preview) {\n const txt = buildLlmsTxt({\n routes,\n siteUrl: effectiveSiteUrl,\n seoConfig,\n exclude,\n noindex,\n excludedGlobs,\n })\n if (txt) {\n fs.writeFileSync(path.join(outDir, 'llms.txt'), txt, 'utf8')\n written.push('llms.txt')\n if (debug) console.log('[dcs-seo] wrote llms.txt')\n } else if (debug) {\n console.log('[dcs-seo] nothing indexable; llms.txt not written')\n }\n }\n\n return written\n}\n\n/**\n * Pick a structured FAQ array (`[{ q, a }]`) for a page from content.yaml, if\n * one exists under a `faq` key (page-scoped, then global). Returns `undefined`\n * when there is no structured Q&A — a `<FaqPage />` component is NOT a source,\n * so its pages correctly yield nothing (honesty).\n */\nfunction pickPageFaq(\n content: ContentConfig | undefined,\n pageSlug: string\n): FaqSource[] | undefined {\n if (!content) return undefined\n const fromBlock = (block: Record<string, unknown> | undefined): FaqSource[] | undefined => {\n if (!block) return undefined\n const v = block['faq']\n return Array.isArray(v) ? (v as FaqSource[]) : undefined\n }\n return fromBlock(content.pages?.[pageSlug]) ?? fromBlock(content.global)\n}\n\n/**\n * Core emitter, separated from Vite so it is unit-testable.\n *\n * For each route: build the head tags from the shared resolver, splice them\n * into the shell HTML, and write `dist/<path>/index.html`. Returns the number\n * of files written. Pure aside from `fs` writes — deterministic + idempotent.\n */\nexport function emitStaticSeoHtml(params: {\n outDir: string\n shellHtml: string\n routes: PageRouteEntry[]\n seoConfig: SeoConfiguration | undefined\n /**\n * Parsed `.dcs/content.yaml` — threads REAL review items (honesty-gated) into\n * the per-route LocalBusiness node. Optional; omit ⇒ no Review/aggregateRating.\n */\n contentConfig?: ContentConfig\n exclude?: string[]\n noindex?: string[]\n /** `pages.yaml` top-level `excluded:` globs — routes matching are NOT emitted. */\n excludedGlobs?: string[]\n debug?: boolean\n}): number {\n const {\n outDir,\n shellHtml,\n routes,\n seoConfig,\n contentConfig,\n exclude = [],\n noindex = [],\n excludedGlobs = [],\n debug = false,\n } = params\n\n const excludeSet = new Set(exclude)\n const noindexSet = new Set(noindex)\n\n // Route → human title map (for breadcrumb hop labels), from the manifest.\n const siteUrl = seoConfig?.global?.siteUrl ?? ''\n const routeTitles: Record<string, string> = { '/': 'Home' }\n for (const r of routes) {\n if (r.title && r.path) routeTitles[r.path.replace(/\\/+$/, '') || '/'] = r.title\n }\n\n let written = 0\n for (const route of routes) {\n if (excludeSet.has(route.path) || (route.slug && excludeSet.has(route.slug))) {\n if (debug) console.log(`[dcs-seo] skip (excluded): ${route.path}`)\n continue\n }\n\n // Honor pages.yaml top-level `excluded:` globs (e.g. `/dev-*`, `/_*`): an\n // excluded route must NOT get a per-route index.html, or it becomes a\n // crawlable/indexable page despite being intentionally hidden — a privacy\n // and crawl-budget leak. Same glob semantics as the sitemap path.\n if (matchesExcludedGlob(route.path, excludedGlobs)) {\n if (debug) console.log(`[dcs-seo] skip (excluded glob): ${route.path}`)\n continue\n }\n\n const forceNoindex =\n noindexSet.has(route.path) || (route.slug && noindexSet.has(route.slug))\n\n // Breadcrumb trail from route depth (home ⇒ none); titles from the manifest.\n const breadcrumbTrail = siteUrl\n ? breadcrumbTrailFromRoute(route.path, siteUrl, routeTitles)\n : undefined\n\n // REAL reviews for this page from content.yaml (honesty-gated downstream).\n const reviews: ReviewSource[] | undefined = contentConfig\n ? findReviewItemsForPage(contentConfig, route.slug)\n : undefined\n\n // BlogPosting for a blog post route — only when seo.yaml has NOT already\n // hand-authored one (buildHeadTags skips ours if it has). A blog post is a\n // route nested under `/blog/` (depth ≥ 2) with a manifest title.\n const isBlogPost = /^\\/blog\\/.+/.test(route.path) && !!route.title\n const blogMeta: BlogMeta | undefined = isBlogPost\n ? { headline: route.title, url: breadcrumbTrail?.[breadcrumbTrail.length - 1]?.item }\n : undefined\n\n // FAQ from a structured `.dcs/faq.yaml`-style block on the content page, if\n // present (honesty-gated). Component-only FAQ pages carry no such data.\n const faq: FaqSource[] | undefined = pickPageFaq(contentConfig, route.slug)\n\n const tags = buildHeadTags(route.slug, route.path, seoConfig, {\n includeKeywords: true,\n fallbackTitle: route.title,\n robots: forceNoindex ? 'noindex, nofollow' : undefined,\n emitGraph: true,\n breadcrumbTrail,\n reviews,\n blogMeta,\n faq,\n })\n\n const html = spliceHeadHtml(shellHtml, tags)\n const outFile = routeToOutputFile(outDir, route.path)\n\n fs.mkdirSync(path.dirname(outFile), { recursive: true })\n fs.writeFileSync(outFile, html, 'utf8')\n written++\n\n if (debug) {\n console.log(\n `[dcs-seo] wrote ${path.relative(outDir, outFile)} (title=\"${tags.title}\"${\n forceNoindex ? ', noindex' : ''\n })`\n )\n }\n }\n\n return written\n}\n\n/**\n * Vite plugin that injects .dcs/seo.yaml at build time and, optionally, emits\n * per-route static `<head>` HTML for SPA SEO.\n *\n * @param options - Plugin configuration\n * @returns Vite plugin\n */\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\n const {\n seoPath = '.dcs/seo.yaml',\n debug = false,\n emitStaticHtml = false,\n pagesPath = '.dcs/pages.yaml',\n contentPath = '.dcs/content.yaml',\n exclude = [],\n noindex = [],\n // Site files default ON whenever per-route HTML emission is on, so the\n // sites already using emitStaticHtml gain sitemap/robots/llms with no edit.\n emitSiteFiles: emitSiteFilesOpt = emitStaticHtml,\n siteUrl,\n robots = {},\n preview = false,\n llms = true,\n } = options\n\n let resolvedConfig: ResolvedConfig\n // Captured during configResolved — gates the serve-only inline HTML inject.\n let isDev = false\n\n return {\n name: 'dcs-seo',\n\n configResolved(config) {\n resolvedConfig = config\n isDev = config.command === 'serve'\n },\n\n config(config, env) {\n // ── Dev / preview (serve) ──────────────────────────────────────────────\n // Don't bake seo.yaml into the bundle. Redirect the `__DCS_SEO__` define\n // to a browser global that `transformIndexHtml` (below) refreshes from\n // disk on every HTML request — live preview with NO server.restart().\n // The consumer is untouched: it still reads the bare `__DCS_SEO__`\n // identifier, which this define rewrites to `globalThis.__DCS_SEO__`.\n // (VitePress treats `useSEO` as a no-op and bakes SEO via\n // transformPageData, so this only affects the runtime `useSEO` SPA path.)\n if (env.command === 'serve') {\n return {\n define: {\n __DCS_SEO__: 'globalThis.__DCS_SEO__',\n },\n }\n }\n\n // ── Production build ───────────────────────────────────────────────────\n // Unchanged: read seo.yaml now and bake it into the bundle as a literal.\n const projectRoot = config.root || process.cwd()\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n return {\n define: {\n __DCS_SEO__: 'undefined',\n },\n }\n }\n\n if (debug) {\n console.log(`[dcs-seo] Loaded ${loaded.foundPath}`)\n console.log(`[dcs-seo] Version: ${loaded.config.version}`)\n console.log(`[dcs-seo] Site Name: ${loaded.config.global?.siteName || '(not set)'}`)\n console.log(\n `[dcs-seo] Pages: ${Object.keys(loaded.config.pages ?? {}).join(', ') || '(none)'}`\n )\n }\n\n return {\n define: {\n __DCS_SEO__: JSON.stringify(loaded.config),\n },\n }\n },\n\n /**\n * Build-time static `<head>` emitter (opt-in via `emitStaticHtml`).\n *\n * Runs after the bundle is written so the built `index.html` shell exists\n * on disk. For an SPA, Vite emits a single `index.html`; we fan it out to\n * per-route files with route-specific `<head>` content.\n *\n * Fully defensive: any missing/unparseable input logs a warning and\n * no-ops. Never throws (so it can never break a production build).\n */\n writeBundle() {\n if (!emitStaticHtml) {\n // The whole emit path is gated on emitStaticHtml. If the operator\n // explicitly asked for site files (`emitSiteFiles: true`) while\n // emitStaticHtml is off, that request is silently inert — warn so it's\n // not mistaken for \"robots/sitemap were emitted\".\n if (options.emitSiteFiles === true) {\n console.warn(\n '[dcs-seo] emitSiteFiles:true is ignored because emitStaticHtml is false; ' +\n 'set emitStaticHtml:true to emit sitemap.xml/robots.txt/llms.txt'\n )\n }\n return\n }\n\n try {\n const projectRoot = resolvedConfig?.root || process.cwd()\n const outDir = resolveOutDir(resolvedConfig)\n\n if (!fs.existsSync(outDir)) {\n console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`)\n return\n }\n\n const shellPath = path.join(outDir, 'index.html')\n if (!fs.existsSync(shellPath)) {\n console.warn(\n `[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`\n )\n return\n }\n\n const routes = loadPagesManifest(projectRoot, pagesPath, debug)\n if (!routes) {\n console.warn(\n `[dcs-seo] emitStaticHtml: ${pagesPath} missing or empty; skipping per-route emission`\n )\n return\n }\n\n const loaded = loadSeoConfig(projectRoot, seoPath, debug)\n if (!loaded) {\n // seo.yaml is optional for emission — routes still get baked with\n // global/runtime defaults (which here means just the shell title and\n // a derived canonical-less head). Warn so the operator knows.\n console.warn(\n `[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`\n )\n }\n\n const shellHtml = fs.readFileSync(shellPath, 'utf8')\n\n // pages.yaml top-level `excluded:` globs gate BOTH the per-route HTML\n // emission below and the sitemap/llms set inside emitSiteFiles, so an\n // excluded route is never written as a crawlable index.html.\n const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath)\n // Optional: load content.yaml so REAL reviews can feed the honest\n // Review/aggregateRating on the per-route LocalBusiness node. Absent or\n // unparseable ⇒ undefined ⇒ no reviews emitted (no synthesis).\n const contentConfig = loadContentConfig(projectRoot, contentPath, debug)\n\n const written = emitStaticSeoHtml({\n outDir,\n shellHtml,\n routes,\n seoConfig: loaded?.config,\n contentConfig,\n exclude,\n noindex,\n excludedGlobs,\n debug,\n })\n\n if (debug) {\n console.log(`[dcs-seo] emitStaticHtml: wrote ${written} per-route HTML file(s)`)\n }\n\n // ── site-wide static files (sitemap.xml, robots.txt, llms.txt) ──────\n // Emitted AFTER the per-route HTML loop so canonicals/routes are\n // resolved and the shell exists. Reuses the already-loaded routes +\n // seoConfig (no second manifest read). Same outDir/existsSync guard +\n // the outer try/catch — so it can never break a production build.\n if (emitSiteFilesOpt) {\n const siteFiles = emitSiteFiles({\n outDir,\n projectRoot,\n pagesPath,\n routes,\n seoConfig: loaded?.config,\n siteUrl,\n exclude,\n noindex,\n preview,\n robots,\n llms,\n debug,\n })\n if (debug) {\n console.log(\n `[dcs-seo] emitSiteFiles: wrote ${siteFiles.length} site file(s): ${\n siteFiles.join(', ') || '(none)'\n }`\n )\n }\n }\n } catch (error) {\n // Never break the build on SEO emission failure.\n console.warn('[dcs-seo] emitStaticHtml failed; build output left unchanged:', error)\n }\n },\n\n /**\n * Dev-only: inject the FRESH on-disk `seo.yaml` as an inline\n * `globalThis.__DCS_SEO__ = {…}` classic script at the top of `<head>`,\n * mirroring dcsContentPlugin. Lets the runtime `useSEO` SPA path reflect\n * on-disk SEO edits on the portal's `?_hc` hard reload with no restart.\n * Gated on `isDev` so it never runs in `vite`/`vitepress build` (the build\n * path keeps the baked `define` literal + the `writeBundle` emitter).\n */\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n if (!isDev) return html\n\n const projectRoot = resolvedConfig?.root || process.cwd()\n const config = readDcsYamlFresh<SeoConfiguration>(projectRoot, seoPath, 'dcs-seo', debug)\n // No file (or unparseable) ⇒ leave the global unset; the consumer's\n // `typeof globalThis.__DCS_SEO__` check falls back to defaults.\n if (config === undefined) return html\n\n if (debug) {\n console.log('[dcs-seo] serve: injecting fresh __DCS_SEO__ into <head>')\n }\n\n const tags: HtmlTagDescriptor[] = [\n {\n tag: 'script',\n children: buildGlobalAssignScript('__DCS_SEO__', config),\n injectTo: 'head-prepend',\n },\n ]\n return { html, tags }\n },\n },\n\n // Watch for changes in development.\n //\n // Downgraded from `server.restart()` to a debounced client `full-reload`:\n // freshness already comes from `transformIndexHtml` re-reading the YAML on\n // every HTML request, so all we need on change is a browser reload. A\n // `server.restart()` would rebuild VitePress's dev MiniSearch index and can\n // throw \"server restart failed\" on a duplicate heading id — the fragility\n // this fix removes. A `full-reload` does neither.\n configureServer(server) {\n const projectRoot = resolvedConfig?.root || process.cwd()\n\n const watchPaths = [\n path.resolve(projectRoot, seoPath),\n path.resolve(projectRoot, '..', seoPath),\n ].map((p) => path.normalize(p))\n\n watchPaths.forEach((watchPath) => {\n if (fs.existsSync(watchPath)) {\n server.watcher.add(watchPath)\n }\n })\n\n let reloadTimer: ReturnType<typeof setTimeout> | undefined\n server.watcher.on('change', (changedPath) => {\n if (!watchPaths.includes(path.normalize(changedPath))) return\n if (reloadTimer) clearTimeout(reloadTimer)\n reloadTimer = setTimeout(() => {\n if (debug) {\n console.log('[dcs-seo] seo.yaml changed, sending full-reload')\n }\n server.ws.send({ type: 'full-reload' })\n }, 100)\n })\n },\n }\n}\n","/**\n * DCS Editor Plugin for Vite\n *\n * Injects the editor bridge script into customer sites when running\n * in portal preview mode (inside the visual editor iframe).\n *\n * The bridge enables:\n * - Inline text editing via contenteditable\n * - Section hover highlights and AI ✨ buttons\n * - postMessage communication with the portal\n *\n * This plugin should only be active in development/preview mode.\n * It's safe to include in production builds — it does nothing unless\n * the page detects it's inside an iframe.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * plugins: [\n * dcsEditorPlugin()\n * ]\n * })\n * ```\n */\n\nimport type { Plugin } from 'vite'\n\nexport interface DcsEditorPluginOptions {\n /** Enable debug logging */\n debug?: boolean\n}\n\n/**\n * Vite plugin that injects the editor bridge script for portal preview integration.\n *\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\n *\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\n * resolved through Vite's module graph rather than hitting the browser's native ESM\n * resolver, which can't handle bare specifiers.\n */\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\n const { debug = false } = options\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\n\n // Captured during configResolved — used to gate HTML injection.\n let isDev = false\n let basePath = '/'\n let isPreview = false\n\n return {\n name: 'dcs-editor-bridge',\n\n // Preview only: stop Vite running an unreachable HMR WebSocket server.\n // Freshness comes from the cms content/seo plugins (fresh transformIndexHtml\n // re-read) + the portal's ?_hc hard reload, not HMR. NOTE: this alone does\n // NOT silence the browser — Vite still injects @vite/client and calls\n // connect(); the client-side stub in transformIndexHtml is what removes the\n // console errors. A normal local `vite dev` (no DCS_PREVIEW) keeps full HMR.\n config(_config, env) {\n if (env.command === 'serve' && process.env.DCS_PREVIEW === 'true') {\n return { server: { hmr: false } }\n }\n },\n\n configResolved(config) {\n isDev = config.command === 'serve'\n basePath = config.base ?? '/'\n isPreview = process.env.DCS_PREVIEW === 'true'\n },\n\n resolveId(id) {\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\n return id\n }\n },\n\n load(id) {\n if (id === VIRTUAL_PATH) {\n return `\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\n// The bridge only activates inside an iframe (portal preview)\nif (window.parent !== window) {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\n } else {\n setTimeout(initEditorBridge, 200)\n }\n}\n`\n }\n\n if (id === RIBBON_VIRTUAL_PATH) {\n return `\nimport { createApp } from 'vue'\nimport PreviewRibbon from '@duffcloudservices/cms/components'\n\nfunction mountRibbon() {\n // Create a detached mount point\n const el = document.createElement('div')\n el.id = 'dcs-preview-ribbon-root'\n document.body.appendChild(el)\n\n const app = createApp(PreviewRibbon)\n app.mount(el)\n}\n\nif (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', mountRibbon)\n} else {\n mountRibbon()\n}\n`\n }\n },\n\n /**\n * Only inject the editor bridge and ribbon scripts when:\n * 1. Running in dev mode (Vite dev server), AND\n * 2. The app is served at the root path (base === '/')\n *\n * These are virtual modules resolved via absolute paths (e.g.\n * `/__dcs-editor-bridge.js`). When base !== '/', the app is served at a\n * sub-path (e.g. /handyman-bryan/) and the absolute virtual paths are NOT\n * routed to the Vite dev server by the reverse proxy — they hit the main\n * DCS server instead and get redirected to portal.duffcloudservices.com\n * (no CORS headers), causing browser console errors.\n *\n * In production builds, App.vue imports and mounts PreviewRibbon\n * directly, so virtual script injection is always redundant anyway.\n */\n transformIndexHtml: {\n order: 'pre',\n handler(html) {\n if (!isDev || basePath !== '/') return html\n\n if (debug) {\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\n }\n\n // Inject both the editor bridge and preview ribbon scripts\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\n const bodyHtml = html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\n\n if (!isPreview) return bodyHtml\n\n // Preview only: neutralise Vite's HMR WebSocket. Vite always injects\n // @vite/client and unconditionally calls transport.connect(); behind the\n // preview proxy chain there is no reachable HMR WS, so it logs\n // WebSocket connection to 'wss://<portal>/?token=…' failed\n // WebSocket connection to 'wss://localhost:<port>/?token=…' failed\n // [vite] failed to connect to websocket\n // server.hmr:false does NOT stop this (the client is still injected). A\n // classic <script> in <head> runs during parse, BEFORE the deferred\n // @vite/client module, so returning an inert socket for the 'vite-hmr'\n // subprotocol silences the attempt with zero effect on app/runtime\n // WebSockets. No-op outside preview / in production builds.\n const wsStub =\n `(function(){try{var N=window.WebSocket;if(!N)return;` +\n `function hmr(p){return p==='vite-hmr'||(Array.isArray(p)&&p.indexOf('vite-hmr')!==-1)}` +\n `function Inert(){this.readyState=0}` +\n `Inert.prototype.send=function(){};Inert.prototype.close=function(){};` +\n `Inert.prototype.addEventListener=function(){};Inert.prototype.removeEventListener=function(){};` +\n `var W=function(u,p){if(hmr(p))return new Inert();return new N(u,p)};` +\n `W.prototype=N.prototype;W.CONNECTING=N.CONNECTING;W.OPEN=N.OPEN;W.CLOSING=N.CLOSING;W.CLOSED=N.CLOSED;` +\n `window.WebSocket=W}catch(e){}})()`\n\n return {\n html: bodyHtml,\n tags: [{ tag: 'script', children: wsStub, injectTo: 'head-prepend' }],\n }\n },\n },\n }\n}\n","/**\n * DCS Preview Plugin for Vue\n *\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\n * component. The ribbon handles its own visibility — it only renders on\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\n * production domains, and inside the visual page editor iframe).\n *\n * Why does the caller pass the component in? Because this file is compiled\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\n * import out of the compiled plugins bundle avoids the build error while\n * still letting consumer code (which *does* run through Vite) resolve the\n * SFC at dev/build time.\n *\n * @example VitePress theme/index.ts\n * ```typescript\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\n * import PreviewRibbon from '@duffcloudservices/cms/components'\n *\n * export default {\n * Layout,\n * enhanceApp({ app }) {\n * app.use(dcsPreviewPlugin(PreviewRibbon))\n * }\n * }\n * ```\n */\n\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\n\nexport interface DcsPreviewPluginOptions {\n /**\n * Override the version string displayed on the ribbon.\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\n */\n version?: string | null\n}\n\n/**\n * Creates and returns the DCS Preview plugin.\n *\n * When installed, it registers the supplied ribbon component as a global\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\n * `transformIndexHtml`.\n *\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\n * @param options - Optional configuration\n */\nexport function dcsPreviewPlugin(\n ribbonComponent: Component,\n options: DcsPreviewPluginOptions = {},\n): Plugin {\n const Wrapper = defineComponent({\n name: 'DcsPreviewRibbon',\n setup() {\n return () => h(ribbonComponent, { version: options.version ?? null })\n },\n })\n\n return {\n install(app: App) {\n app.component('DcsPreviewRibbon', Wrapper)\n\n if (options.version !== undefined) {\n app.provide('__dcs_preview_version__', options.version)\n }\n },\n }\n}\n","/**\n * DCS CDN Image Plugin for Vite\n *\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\n * generated by the `image-migrate adopt` CLI command.\n *\n * For raster images with WebP variants, `<img>` elements are transformed into\n * responsive `<picture>` elements with `srcset` for optimised delivery.\n * SVGs receive a simple URL swap with no variant handling.\n *\n * **In development mode this plugin is a no-op** — local `/images/` paths\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\n *\n * The plugin handles two in-pipeline replacement vectors:\n *\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\n *\n * A third vector — **post-build file processing** — is handled by the\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\n * VitePress config. VitePress generates HTML *after* both Vite builds\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\n * `service-worker.js`.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * vite: {\n * plugins: [\n * dcsCdnImagePlugin()\n * ]\n * },\n * buildEnd: dcsCdnBuildEnd()\n * })\n * ```\n */\n\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { Plugin } from 'vite'\n\n// ────────────────────────────────────────────────────────────────────────────\n// Types\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface CdnImageMapVariant {\n suffix: string\n blobPath: string\n cdnUrl: string\n width: number\n height: number\n size: number\n}\n\nexport interface CdnImageMapEntry {\n localPath: string\n blobPath: string\n cdnUrl: string\n fileName: string\n contentType: string\n originalSize: number\n width?: number\n height?: number\n hash: string\n variants?: CdnImageMapVariant[]\n}\n\nexport interface CdnImageMap {\n generated: string\n site: string\n cdnBase: string\n images: CdnImageMapEntry[]\n}\n\nexport interface DcsCdnImagePluginOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\n * mapping file is compared *without* a leading slash.\n *\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /**\n * Default `sizes` attribute for responsive `<picture>` elements.\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\n *\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\n */\n defaultSizes?: string\n\n /** Enable debug logging */\n debug?: boolean\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ────────────────────────────────────────────────────────────────────────────\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\n/**\n * Build a responsive `<picture>` element for a raster image that has\n * WebP variants.\n */\nfunction buildPictureElement(\n entry: CdnImageMapEntry,\n alt: string,\n extraAttrs: string,\n sizes: string\n): string {\n const variants = entry.variants ?? []\n\n // Collect only the display-relevant variants (not thumb, not og)\n const displayVariants = variants.filter(\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\n )\n\n if (displayVariants.length === 0) {\n // No display variants — fall back to original CDN URL as plain <img>\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n const srcset = displayVariants\n .map((v) => `${v.cdnUrl} ${v.width}w`)\n .join(', ')\n\n // Pick the -md variant as the default src, or fall through to original\n const mdVariant = variants.find((v) => v.suffix === '-md')\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\n '</picture>',\n ].join('\\n')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Shared helpers\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Try to find and load the CDN image map from common locations.\n * VitePress nests docs under a subfolder, so we check parent dirs too.\n */\nfunction loadCdnImageMap(\n projectRoot: string,\n relativeMapPath: string,\n debug: boolean\n): Map<string, CdnImageMapEntry> | null {\n const possiblePaths = [\n path.resolve(projectRoot, relativeMapPath),\n path.resolve(projectRoot, '..', relativeMapPath),\n path.resolve(process.cwd(), relativeMapPath),\n ]\n\n for (const testPath of possiblePaths) {\n if (fs.existsSync(testPath)) {\n try {\n const raw = fs.readFileSync(testPath, 'utf8')\n const data: CdnImageMap = JSON.parse(raw)\n\n const map = new Map<string, CdnImageMapEntry>()\n for (const entry of data.images) {\n // Store both with and without leading slash for flexible matching\n map.set(entry.localPath, entry)\n if (!entry.localPath.startsWith('/')) {\n map.set('/' + entry.localPath, entry)\n }\n }\n\n if (debug) {\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\n }\n return map\n } catch (error) {\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\n }\n }\n }\n\n if (debug) {\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\n possiblePaths.forEach((p) => console.log(` - ${p}`))\n }\n return null\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// Vite Plugin\n// ────────────────────────────────────────────────────────────────────────────\n\n/**\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\n */\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\n debug = false,\n } = options\n\n // Lookup map built from the JSON file: key = localPath (no leading slash)\n let imageMap: Map<string, CdnImageMapEntry> | null = null\n let isProduction = false\n\n /**\n * Check whether a source code string contains any of the path prefixes\n * we care about. Quick bail-out for files that don't reference images.\n */\n function hasImageReferences(code: string): boolean {\n return pathPrefixes.some((prefix) => code.includes(prefix))\n }\n\n /**\n * Replace image path references in any string value with CDN URLs.\n * Used for code transforms and renderChunk post-processing.\n */\n function replaceImagePaths(str: string): string {\n if (!imageMap || imageMap.size === 0) return str\n let result = str\n for (const prefix of pathPrefixes) {\n // Global replace: matches prefix followed by a relative path bounded\n // by quotes, parens, or whitespace. Allows spaces in filenames.\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n result = result.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${quote}${entry.cdnUrl}`\n })\n }\n return result\n }\n\n /**\n * Replace standalone URL references in JS/TS string literals and Vue template\n * attributes. This handles patterns like:\n * src=\"/images/foo.jpg\"\n * { src: '/images/foo.jpg' }\n * url(/images/foo.svg)\n *\n * For `<img>` tags with raster images that have variants, the entire tag is\n * replaced with a `<picture>` element.\n */\n function transformCode(code: string): string {\n if (!imageMap || imageMap.size === 0) return code\n\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return _match\n\n // Extract alt from the existing attributes\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\n const alt = altMatch ? altMatch[1] : ''\n\n // Collect other attributes (not src, alt, loading, decoding — we set those)\n const otherAttrs = (beforeSrc + afterSrc)\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\n .trim()\n\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\n\n // Extract custom sizes if specified\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\n\n // SVGs and images without variants → simple URL swap <img>\n if (!entry.variants || entry.variants.length === 0) {\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\n }\n\n // Raster with variants → <picture>\n return buildPictureElement(entry, alt, extraAttrs, sizes)\n })\n\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\n code = replaceImagePaths(code)\n\n return code\n }\n\n return {\n name: 'dcs-cdn-image',\n enforce: 'pre',\n\n configResolved(config) {\n isProduction = config.command === 'build'\n if (!isProduction) {\n if (debug) {\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\n }\n return\n }\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\n },\n\n transform(code, id) {\n // Only run in production builds\n if (!isProduction || !imageMap) return\n\n // Only process relevant file types\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\n\n // Quick bail — skip files with no image path references\n if (!hasImageReferences(code)) return\n\n const transformed = transformCode(code)\n if (transformed !== code) {\n return { code: transformed, map: null }\n }\n },\n\n /**\n * Replace image paths in final rendered JS/CSS chunks.\n *\n * This fires AFTER Vite's `define` substitution, catching paths that were\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\n * `transform` hook because `define` replacement happens at bundle time.\n */\n renderChunk(code, _chunk) {\n if (!isProduction || !imageMap) return null\n if (!hasImageReferences(code)) return null\n\n const replaced = replaceImagePaths(code)\n if (replaced !== code) {\n if (debug) {\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\n }\n return { code: replaced, map: null }\n }\n return null\n },\n\n /**\n * Transform HTML output to catch references injected outside the module\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\n *\n * Note: In VitePress, this hook fires for the `index.html` template but\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\n * coverage of generated HTML files.\n */\n transformIndexHtml(html) {\n if (!isProduction || !imageMap) return html\n if (!hasImageReferences(html)) return html\n\n // Replace href/src/content attributes that reference mapped images\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n return `${pre}${entry.cdnUrl}${post}`\n })\n }\n\n return html\n },\n }\n}\n\n/**\n * Escape a string for use in a RegExp.\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n// ────────────────────────────────────────────────────────────────────────────\n// VitePress buildEnd hook\n// ────────────────────────────────────────────────────────────────────────────\n\nexport interface DcsCdnBuildEndOptions {\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\n mapPath?: string\n\n /**\n * Patterns to match for replacement.\n * Default: `['/images/']`\n */\n pathPrefixes?: string[]\n\n /** Enable debug logging */\n debug?: boolean\n\n /**\n * File extensions to process in the output directory.\n * Default: `['.html', '.js']`\n */\n extensions?: string[]\n}\n\n/**\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\n * static files in the output directory to replace remaining `/images/` paths\n * with CDN URLs.\n *\n * VitePress generates HTML **after** both Vite builds complete, so Vite\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\n * HTML files are written to disk.\n *\n * Also rewrites static files (e.g. `service-worker.js`) copied from\n * `public/` that are not part of Vite's module pipeline.\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * buildEnd: dcsCdnBuildEnd({ debug: true })\n * })\n * ```\n */\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\n const {\n mapPath = '.dcs/cdn-image-map.json',\n pathPrefixes = ['/images/'],\n debug = false,\n extensions = ['.html', '.js'],\n } = options\n\n return async (siteConfig: { root: string; outDir: string }) => {\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\n if (!imageMap) {\n if (debug) {\n console.log('[dcs-cdn-image] buildEnd: no image map found')\n }\n return\n }\n\n const outDir = siteConfig.outDir\n if (!fs.existsSync(outDir)) return\n\n let filesProcessed = 0\n let refsReplaced = 0\n\n function walkDir(dir: string) {\n const entries = fs.readdirSync(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name)\n if (entry.isDirectory()) {\n walkDir(fullPath)\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\n processFile(fullPath)\n }\n }\n }\n\n function processFile(filePath: string) {\n let content = fs.readFileSync(filePath, 'utf8')\n if (!pathPrefixes.some((p) => content.includes(p))) return\n\n let changed = false\n\n if (filePath.endsWith('.html')) {\n // HTML: replace href/src/content attribute values\n for (const prefix of pathPrefixes) {\n const attrRegex = new RegExp(\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\n 'gi'\n )\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!mapEntry) return match\n changed = true\n refsReplaced++\n return `${pre}${mapEntry.cdnUrl}${post}`\n })\n }\n } else {\n // JS/other: replace quoted string paths\n for (const prefix of pathPrefixes) {\n const regex = new RegExp(\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\n 'g'\n )\n content = content.replace(regex, (match, quote, relPath) => {\n const localPath = prefix.replace(/^\\//, '') + relPath\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\n if (!entry) return match\n changed = true\n refsReplaced++\n return `${quote}${entry.cdnUrl}`\n })\n }\n }\n\n if (changed) {\n fs.writeFileSync(filePath, content, 'utf8')\n filesProcessed++\n }\n }\n\n walkDir(outDir)\n\n if (debug) {\n console.log(\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\n )\n }\n }\n}","/**\n * markdown-it plugin that transforms standard `![alt](url)` image syntax\n * into responsive `<picture>` elements when the URL matches the DCS CDN\n * asset pattern.\n *\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\n *\n * @example\n * ```ts\n * // .vitepress/config.ts\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\n *\n * export default defineConfig({\n * markdown: {\n * config: (md) => {\n * md.use(responsiveImagePlugin)\n * },\n * },\n * })\n * ```\n *\n * Input markdown:\n * ```md\n * ![Physical therapy session](https://files.duffcloudservices.com/kept/assets/blog/abc-123.jpg)\n * ```\n *\n * Rendered HTML:\n * ```html\n * <picture>\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\n * type=\"image/webp\"\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\n * </picture>\n * ```\n */\n\nimport type MarkdownIt from 'markdown-it'\n\n/**\n * Matches DCS CDN asset URLs: base path / UUID . extension.\n *\n * Aligned with `@duffcloudservices/cms-core` `responsive-image.ts`: the\n * `(?:content\\/)?` group covers the content-container shape\n * `https://files.duffcloudservices.com/content/{slug}/assets/...` in addition\n * to the branding-container shape `.../{slug}/assets/...`. The group is\n * non-capturing, so the captured base path / UUID / extension are unchanged.\n */\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/(?:content\\/)?[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\n\n/**\n * Escapes HTML special characters for safe attribute embedding.\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\n */\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n}\n\nexport function responsiveImagePlugin(md: MarkdownIt): void {\n const defaultImageRenderer = md.renderer.rules.image\n\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\n const token = tokens[idx]\n const src = token.attrGet('src') ?? ''\n const alt = token.content ?? ''\n\n const match = src.match(CDN_PATTERN)\n if (!match) {\n // Not a CDN image — render normally\n if (defaultImageRenderer) {\n return defaultImageRenderer(tokens, idx, options, env, self)\n }\n return self.renderToken(tokens, idx, options)\n }\n\n const [, basePath, uuid] = match\n\n const srcset = [\n `${basePath}${uuid}-sm.webp 640w`,\n `${basePath}${uuid}-md.webp 1024w`,\n `${basePath}${uuid}-lg.webp 1920w`,\n ].join(', ')\n\n const escapedAlt = escapeHtml(alt)\n\n // Self-heal: this plugin emits a STATIC HTML string at build time (no Vue\n // runtime), so the fallback must be an inline onerror. A fit-variant\n // (`-md`/`-lg`) may legitimately be absent (internal/imaging variants.go\n // skips variants by DIMENSION). On the first error, swap the `<img>` to the\n // ORIGINAL url AND strip the sibling `<source>` elements — a `<picture>`\n // does NOT re-resolve after a `<source>` 404s, so the variant set must be\n // removed for the original to display. (`this.onerror=null` prevents loops.)\n const escapedOriginal = escapeHtml(src)\n const onError =\n `this.onerror=null;this.src='${escapedOriginal}';` +\n `this.closest('picture')?.querySelectorAll('source').forEach(s=>s.remove())`\n\n return [\n '<picture>',\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" onerror=\"${onError}\" />`,\n '</picture>',\n ].join('\\n')\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duffcloudservices/cms",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "Vue 3 composables and Vite plugins for DCS CMS integration",
5
5
  "type": "module",
6
6
  "exports": {