@duffcloudservices/cms 0.1.6 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/editor/editorBridge.js +25 -3
- package/dist/editor/editorBridge.js.map +1 -1
- package/dist/index.d.ts +65 -2
- package/dist/index.js +30 -3
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +156 -1
- package/dist/plugins/index.js +295 -8
- package/dist/plugins/index.js.map +1 -1
- package/package.json +82 -77
- package/src/components/PreviewRibbon.vue +205 -193
- package/src/components/ResponsiveImage.vue +55 -0
package/dist/plugins/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import fs3 from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import yaml from 'js-yaml';
|
|
4
4
|
import { defineComponent, h } from 'vue';
|
|
@@ -21,7 +21,7 @@ function dcsContentPlugin(options = {}) {
|
|
|
21
21
|
];
|
|
22
22
|
let foundPath;
|
|
23
23
|
for (const testPath of possiblePaths) {
|
|
24
|
-
if (
|
|
24
|
+
if (fs3.existsSync(testPath)) {
|
|
25
25
|
foundPath = testPath;
|
|
26
26
|
break;
|
|
27
27
|
}
|
|
@@ -39,7 +39,7 @@ function dcsContentPlugin(options = {}) {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
try {
|
|
42
|
-
const fileContent =
|
|
42
|
+
const fileContent = fs3.readFileSync(foundPath, "utf8");
|
|
43
43
|
const content = yaml.load(fileContent);
|
|
44
44
|
if (debug) {
|
|
45
45
|
console.log(`[dcs-content] Loaded ${foundPath}`);
|
|
@@ -69,7 +69,7 @@ function dcsContentPlugin(options = {}) {
|
|
|
69
69
|
path.resolve(projectRoot, "..", contentPath)
|
|
70
70
|
];
|
|
71
71
|
watchPaths.forEach((watchPath) => {
|
|
72
|
-
if (
|
|
72
|
+
if (fs3.existsSync(watchPath)) {
|
|
73
73
|
server.watcher.add(watchPath);
|
|
74
74
|
server.watcher.on("change", (changedPath) => {
|
|
75
75
|
if (changedPath === watchPath) {
|
|
@@ -101,7 +101,7 @@ function dcsSeoPlugin(options = {}) {
|
|
|
101
101
|
];
|
|
102
102
|
let foundPath;
|
|
103
103
|
for (const testPath of possiblePaths) {
|
|
104
|
-
if (
|
|
104
|
+
if (fs3.existsSync(testPath)) {
|
|
105
105
|
foundPath = testPath;
|
|
106
106
|
break;
|
|
107
107
|
}
|
|
@@ -119,7 +119,7 @@ function dcsSeoPlugin(options = {}) {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
try {
|
|
122
|
-
const fileContent =
|
|
122
|
+
const fileContent = fs3.readFileSync(foundPath, "utf8");
|
|
123
123
|
const seoConfig = yaml.load(fileContent);
|
|
124
124
|
if (debug) {
|
|
125
125
|
console.log(`[dcs-seo] Loaded ${foundPath}`);
|
|
@@ -149,7 +149,7 @@ function dcsSeoPlugin(options = {}) {
|
|
|
149
149
|
path.resolve(projectRoot, "..", seoPath)
|
|
150
150
|
];
|
|
151
151
|
watchPaths.forEach((watchPath) => {
|
|
152
|
-
if (
|
|
152
|
+
if (fs3.existsSync(watchPath)) {
|
|
153
153
|
server.watcher.add(watchPath);
|
|
154
154
|
server.watcher.on("change", (changedPath) => {
|
|
155
155
|
if (changedPath === watchPath) {
|
|
@@ -242,7 +242,294 @@ function dcsPreviewPlugin(ribbonComponent, options = {}) {
|
|
|
242
242
|
}
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
|
+
function escapeHtml(str) {
|
|
246
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
247
|
+
}
|
|
248
|
+
function buildPictureElement(entry, alt, extraAttrs, sizes) {
|
|
249
|
+
const variants = entry.variants ?? [];
|
|
250
|
+
const displayVariants = variants.filter(
|
|
251
|
+
(v) => v.suffix === "-sm" || v.suffix === "-md" || v.suffix === "-lg"
|
|
252
|
+
);
|
|
253
|
+
if (displayVariants.length === 0) {
|
|
254
|
+
return `<img src="${escapeHtml(entry.cdnUrl)}" alt="${escapeHtml(alt)}"${extraAttrs} loading="lazy" decoding="async" />`;
|
|
255
|
+
}
|
|
256
|
+
const srcset = displayVariants.map((v) => `${v.cdnUrl} ${v.width}w`).join(", ");
|
|
257
|
+
const mdVariant = variants.find((v) => v.suffix === "-md");
|
|
258
|
+
const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl;
|
|
259
|
+
return [
|
|
260
|
+
"<picture>",
|
|
261
|
+
` <source srcset="${srcset}" type="image/webp" sizes="${escapeHtml(sizes)}" />`,
|
|
262
|
+
` <img src="${escapeHtml(fallbackSrc)}" alt="${escapeHtml(alt)}"${extraAttrs} loading="lazy" decoding="async" />`,
|
|
263
|
+
"</picture>"
|
|
264
|
+
].join("\n");
|
|
265
|
+
}
|
|
266
|
+
function loadCdnImageMap(projectRoot, relativeMapPath, debug) {
|
|
267
|
+
const possiblePaths = [
|
|
268
|
+
path.resolve(projectRoot, relativeMapPath),
|
|
269
|
+
path.resolve(projectRoot, "..", relativeMapPath),
|
|
270
|
+
path.resolve(process.cwd(), relativeMapPath)
|
|
271
|
+
];
|
|
272
|
+
for (const testPath of possiblePaths) {
|
|
273
|
+
if (fs3.existsSync(testPath)) {
|
|
274
|
+
try {
|
|
275
|
+
const raw = fs3.readFileSync(testPath, "utf8");
|
|
276
|
+
const data = JSON.parse(raw);
|
|
277
|
+
const map = /* @__PURE__ */ new Map();
|
|
278
|
+
for (const entry of data.images) {
|
|
279
|
+
map.set(entry.localPath, entry);
|
|
280
|
+
if (!entry.localPath.startsWith("/")) {
|
|
281
|
+
map.set("/" + entry.localPath, entry);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (debug) {
|
|
285
|
+
console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`);
|
|
286
|
+
}
|
|
287
|
+
return map;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (debug) {
|
|
294
|
+
console.log("[dcs-cdn-image] No cdn-image-map.json found at:");
|
|
295
|
+
possiblePaths.forEach((p) => console.log(` - ${p}`));
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
function dcsCdnImagePlugin(options = {}) {
|
|
300
|
+
const {
|
|
301
|
+
mapPath = ".dcs/cdn-image-map.json",
|
|
302
|
+
pathPrefixes = ["/images/"],
|
|
303
|
+
defaultSizes = "(max-width: 1024px) 100vw, 1024px",
|
|
304
|
+
debug = false
|
|
305
|
+
} = options;
|
|
306
|
+
let imageMap = null;
|
|
307
|
+
let isProduction = false;
|
|
308
|
+
function hasImageReferences(code) {
|
|
309
|
+
return pathPrefixes.some((prefix) => code.includes(prefix));
|
|
310
|
+
}
|
|
311
|
+
function replaceImagePaths(str) {
|
|
312
|
+
if (!imageMap || imageMap.size === 0) return str;
|
|
313
|
+
let result = str;
|
|
314
|
+
for (const prefix of pathPrefixes) {
|
|
315
|
+
const regex = new RegExp(
|
|
316
|
+
`(["'\`(])${escapeRegex(prefix)}([^"'\`()]+)`,
|
|
317
|
+
"g"
|
|
318
|
+
);
|
|
319
|
+
result = result.replace(regex, (match, quote, relPath) => {
|
|
320
|
+
const localPath = prefix.replace(/^\//, "") + relPath;
|
|
321
|
+
const entry = imageMap.get(localPath) || imageMap.get("/" + localPath);
|
|
322
|
+
if (!entry) return match;
|
|
323
|
+
return `${quote}${entry.cdnUrl}`;
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
function transformCode(code) {
|
|
329
|
+
if (!imageMap || imageMap.size === 0) return code;
|
|
330
|
+
const imgTagRegex = /<img\b([^>]*?)src=["'](\/(images\/[^"']+))["']([^>]*?)\/?>/gi;
|
|
331
|
+
code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {
|
|
332
|
+
const entry = imageMap.get(localPath) || imageMap.get("/" + localPath);
|
|
333
|
+
if (!entry) return _match;
|
|
334
|
+
const altMatch = (beforeSrc + afterSrc).match(/alt=["']([^"']*)["']/);
|
|
335
|
+
const alt = altMatch ? altMatch[1] : "";
|
|
336
|
+
const otherAttrs = (beforeSrc + afterSrc).replace(/\balt=["'][^"']*["']/gi, "").replace(/\bloading=["'][^"']*["']/gi, "").replace(/\bdecoding=["'][^"']*["']/gi, "").trim();
|
|
337
|
+
const extraAttrs = otherAttrs ? " " + otherAttrs : "";
|
|
338
|
+
const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=["']([^"']*)["']/);
|
|
339
|
+
const sizes = sizesMatch ? sizesMatch[1] : defaultSizes;
|
|
340
|
+
if (!entry.variants || entry.variants.length === 0) {
|
|
341
|
+
return `<img src="${escapeHtml(entry.cdnUrl)}" alt="${escapeHtml(alt)}"${extraAttrs} loading="lazy" decoding="async" />`;
|
|
342
|
+
}
|
|
343
|
+
return buildPictureElement(entry, alt, extraAttrs, sizes);
|
|
344
|
+
});
|
|
345
|
+
code = replaceImagePaths(code);
|
|
346
|
+
return code;
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
name: "dcs-cdn-image",
|
|
350
|
+
enforce: "pre",
|
|
351
|
+
configResolved(config) {
|
|
352
|
+
isProduction = config.command === "build";
|
|
353
|
+
if (!isProduction) {
|
|
354
|
+
if (debug) {
|
|
355
|
+
console.log("[dcs-cdn-image] Dev mode \u2014 plugin disabled (local images served by Vite)");
|
|
356
|
+
}
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
imageMap = loadCdnImageMap(config.root, mapPath, debug);
|
|
360
|
+
},
|
|
361
|
+
transform(code, id) {
|
|
362
|
+
if (!isProduction || !imageMap) return;
|
|
363
|
+
if (!/\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\?.*)?$/.test(id)) return;
|
|
364
|
+
if (!hasImageReferences(code)) return;
|
|
365
|
+
const transformed = transformCode(code);
|
|
366
|
+
if (transformed !== code) {
|
|
367
|
+
return { code: transformed, map: null };
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
/**
|
|
371
|
+
* Replace image paths in final rendered JS/CSS chunks.
|
|
372
|
+
*
|
|
373
|
+
* This fires AFTER Vite's `define` substitution, catching paths that were
|
|
374
|
+
* injected via `define` values (e.g. `__DCS_CONTENT__` set by
|
|
375
|
+
* `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the
|
|
376
|
+
* `transform` hook because `define` replacement happens at bundle time.
|
|
377
|
+
*/
|
|
378
|
+
renderChunk(code, _chunk) {
|
|
379
|
+
if (!isProduction || !imageMap) return null;
|
|
380
|
+
if (!hasImageReferences(code)) return null;
|
|
381
|
+
const replaced = replaceImagePaths(code);
|
|
382
|
+
if (replaced !== code) {
|
|
383
|
+
if (debug) {
|
|
384
|
+
console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`);
|
|
385
|
+
}
|
|
386
|
+
return { code: replaced, map: null };
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
},
|
|
390
|
+
/**
|
|
391
|
+
* Transform HTML output to catch references injected outside the module
|
|
392
|
+
* pipeline — e.g. VitePress `head` config tags (favicons, OG images).
|
|
393
|
+
*
|
|
394
|
+
* Note: In VitePress, this hook fires for the `index.html` template but
|
|
395
|
+
* NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full
|
|
396
|
+
* coverage of generated HTML files.
|
|
397
|
+
*/
|
|
398
|
+
transformIndexHtml(html) {
|
|
399
|
+
if (!isProduction || !imageMap) return html;
|
|
400
|
+
if (!hasImageReferences(html)) return html;
|
|
401
|
+
for (const prefix of pathPrefixes) {
|
|
402
|
+
const attrRegex = new RegExp(
|
|
403
|
+
`((?:href|src|content)=["'])${escapeRegex(prefix)}([^"']+)(["'])`,
|
|
404
|
+
"gi"
|
|
405
|
+
);
|
|
406
|
+
html = html.replace(attrRegex, (match, pre, relPath, post) => {
|
|
407
|
+
const localPath = prefix.replace(/^\//, "") + relPath;
|
|
408
|
+
const entry = imageMap.get(localPath) || imageMap.get("/" + localPath);
|
|
409
|
+
if (!entry) return match;
|
|
410
|
+
return `${pre}${entry.cdnUrl}${post}`;
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return html;
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
function escapeRegex(str) {
|
|
418
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
419
|
+
}
|
|
420
|
+
function dcsCdnBuildEnd(options = {}) {
|
|
421
|
+
const {
|
|
422
|
+
mapPath = ".dcs/cdn-image-map.json",
|
|
423
|
+
pathPrefixes = ["/images/"],
|
|
424
|
+
debug = false,
|
|
425
|
+
extensions = [".html", ".js"]
|
|
426
|
+
} = options;
|
|
427
|
+
return async (siteConfig) => {
|
|
428
|
+
const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug);
|
|
429
|
+
if (!imageMap) {
|
|
430
|
+
if (debug) {
|
|
431
|
+
console.log("[dcs-cdn-image] buildEnd: no image map found");
|
|
432
|
+
}
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const outDir = siteConfig.outDir;
|
|
436
|
+
if (!fs3.existsSync(outDir)) return;
|
|
437
|
+
let filesProcessed = 0;
|
|
438
|
+
let refsReplaced = 0;
|
|
439
|
+
function walkDir(dir) {
|
|
440
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
441
|
+
for (const entry of entries) {
|
|
442
|
+
const fullPath = path.join(dir, entry.name);
|
|
443
|
+
if (entry.isDirectory()) {
|
|
444
|
+
walkDir(fullPath);
|
|
445
|
+
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
446
|
+
processFile(fullPath);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
function processFile(filePath) {
|
|
451
|
+
let content = fs3.readFileSync(filePath, "utf8");
|
|
452
|
+
if (!pathPrefixes.some((p) => content.includes(p))) return;
|
|
453
|
+
let changed = false;
|
|
454
|
+
if (filePath.endsWith(".html")) {
|
|
455
|
+
for (const prefix of pathPrefixes) {
|
|
456
|
+
const attrRegex = new RegExp(
|
|
457
|
+
`((?:href|src|content)=["'])${escapeRegex(prefix)}([^"']+)(["'])`,
|
|
458
|
+
"gi"
|
|
459
|
+
);
|
|
460
|
+
content = content.replace(attrRegex, (match, pre, relPath, post) => {
|
|
461
|
+
const localPath = prefix.replace(/^\//, "") + relPath;
|
|
462
|
+
const mapEntry = imageMap.get(localPath) || imageMap.get("/" + localPath);
|
|
463
|
+
if (!mapEntry) return match;
|
|
464
|
+
changed = true;
|
|
465
|
+
refsReplaced++;
|
|
466
|
+
return `${pre}${mapEntry.cdnUrl}${post}`;
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
} else {
|
|
470
|
+
for (const prefix of pathPrefixes) {
|
|
471
|
+
const regex = new RegExp(
|
|
472
|
+
`(["'\`(])${escapeRegex(prefix)}([^"'\`()]+)`,
|
|
473
|
+
"g"
|
|
474
|
+
);
|
|
475
|
+
content = content.replace(regex, (match, quote, relPath) => {
|
|
476
|
+
const localPath = prefix.replace(/^\//, "") + relPath;
|
|
477
|
+
const entry = imageMap.get(localPath) || imageMap.get("/" + localPath);
|
|
478
|
+
if (!entry) return match;
|
|
479
|
+
changed = true;
|
|
480
|
+
refsReplaced++;
|
|
481
|
+
return `${quote}${entry.cdnUrl}`;
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (changed) {
|
|
486
|
+
fs3.writeFileSync(filePath, content, "utf8");
|
|
487
|
+
filesProcessed++;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
walkDir(outDir);
|
|
491
|
+
if (debug) {
|
|
492
|
+
console.log(
|
|
493
|
+
`[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`
|
|
494
|
+
);
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/plugins/responsiveImagePlugin.ts
|
|
500
|
+
var CDN_PATTERN = /^(https?:\/\/files\.[^/]+\/[^/]+\/assets\/(?:[^/]+\/)*)([a-f0-9-]+)\.(\w+)$/;
|
|
501
|
+
function escapeHtml2(str) {
|
|
502
|
+
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
503
|
+
}
|
|
504
|
+
function responsiveImagePlugin(md) {
|
|
505
|
+
const defaultImageRenderer = md.renderer.rules.image;
|
|
506
|
+
md.renderer.rules.image = (tokens, idx, options, env, self) => {
|
|
507
|
+
const token = tokens[idx];
|
|
508
|
+
const src = token.attrGet("src") ?? "";
|
|
509
|
+
const alt = token.content ?? "";
|
|
510
|
+
const match = src.match(CDN_PATTERN);
|
|
511
|
+
if (!match) {
|
|
512
|
+
if (defaultImageRenderer) {
|
|
513
|
+
return defaultImageRenderer(tokens, idx, options, env, self);
|
|
514
|
+
}
|
|
515
|
+
return self.renderToken(tokens, idx, options);
|
|
516
|
+
}
|
|
517
|
+
const [, basePath, uuid] = match;
|
|
518
|
+
const srcset = [
|
|
519
|
+
`${basePath}${uuid}-sm.webp 640w`,
|
|
520
|
+
`${basePath}${uuid}-md.webp 1024w`,
|
|
521
|
+
`${basePath}${uuid}-lg.webp 1920w`
|
|
522
|
+
].join(", ");
|
|
523
|
+
const escapedAlt = escapeHtml2(alt);
|
|
524
|
+
return [
|
|
525
|
+
"<picture>",
|
|
526
|
+
` <source srcset="${srcset}" type="image/webp" sizes="(max-width: 1024px) 100vw, 1024px" />`,
|
|
527
|
+
` <img src="${basePath}${uuid}-md.webp" alt="${escapedAlt}" loading="lazy" decoding="async" />`,
|
|
528
|
+
"</picture>"
|
|
529
|
+
].join("\n");
|
|
530
|
+
};
|
|
531
|
+
}
|
|
245
532
|
|
|
246
|
-
export { dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin };
|
|
533
|
+
export { dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, responsiveImagePlugin };
|
|
247
534
|
//# sourceMappingURL=index.js.map
|
|
248
535
|
//# 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"],"names":["path","fs","yaml"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * DCS Content Plugin for Vite\r\n *\r\n * Reads `.dcs/content.yaml` at build time and injects content\r\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsContentPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsContentPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { DcsContentFile } from '../types/content'\r\n\r\nexport interface DcsContentPluginOptions {\r\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\r\n contentPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/content.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\r\n const { contentPath = '.dcs/content.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-content',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find content.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n path.resolve(process.cwd(), contentPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-content] No content.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-content] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const content = yaml.load(fileContent) as DcsContentFile\r\n\r\n if (debug) {\r\n console.log(`[dcs-content] Loaded ${foundPath}`)\r\n console.log(`[dcs-content] Version: ${content.version}`)\r\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\r\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_CONTENT__: JSON.stringify(content),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-content] content.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS SEO Plugin for Vite\r\n *\r\n * Reads `.dcs/seo.yaml` at build time and injects content\r\n * as `__DCS_SEO__` global variable for use by useSEO.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsSeoPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsSeoPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { SeoConfiguration } from '../types/seo'\r\n\r\nexport interface DcsSeoPluginOptions {\r\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\r\n seoPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/seo.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\r\n const { seoPath = '.dcs/seo.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-seo',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find seo.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n path.resolve(process.cwd(), seoPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] No seo.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-seo] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const seoConfig = yaml.load(fileContent) as SeoConfiguration\r\n\r\n if (debug) {\r\n console.log(`[dcs-seo] Loaded ${foundPath}`)\r\n console.log(`[dcs-seo] Version: ${seoConfig.version}`)\r\n console.log(`[dcs-seo] Site Name: ${seoConfig.global?.siteName || '(not set)'}`)\r\n console.log(`[dcs-seo] Pages: ${Object.keys(seoConfig.pages ?? {}).join(', ') || '(none)'}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_SEO__: JSON.stringify(seoConfig),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Editor Plugin for Vite\r\n *\r\n * Injects the editor bridge script into customer sites when running\r\n * in portal preview mode (inside the visual editor iframe).\r\n *\r\n * The bridge enables:\r\n * - Inline text editing via contenteditable\r\n * - Section hover highlights and AI ✨ buttons\r\n * - postMessage communication with the portal\r\n *\r\n * This plugin should only be active in development/preview mode.\r\n * It's safe to include in production builds — it does nothing unless\r\n * the page detects it's inside an iframe.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsEditorPlugin()\r\n * ]\r\n * })\r\n * ```\r\n */\r\n\r\nimport type { Plugin } from 'vite'\r\n\r\nexport interface DcsEditorPluginOptions {\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects the editor bridge script for portal preview integration.\r\n *\r\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\r\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\r\n *\r\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\r\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\r\n * resolved through Vite's module graph rather than hitting the browser's native ESM\r\n * resolver, which can't handle bare specifiers.\r\n */\r\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\r\n const { debug = false } = options\r\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\r\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\r\n\r\n return {\r\n name: 'dcs-editor-bridge',\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\r\n return id\r\n }\r\n },\r\n\r\n load(id) {\r\n if (id === VIRTUAL_PATH) {\r\n return `\r\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\r\n// The bridge only activates inside an iframe (portal preview)\r\nif (window.parent !== window) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n`\r\n }\r\n\r\n if (id === RIBBON_VIRTUAL_PATH) {\r\n return `\r\nimport { createApp } from 'vue'\r\nimport PreviewRibbon from '@duffcloudservices/cms/components'\r\n\r\nfunction mountRibbon() {\r\n // Create a detached mount point\r\n const el = document.createElement('div')\r\n el.id = 'dcs-preview-ribbon-root'\r\n document.body.appendChild(el)\r\n\r\n const app = createApp(PreviewRibbon)\r\n app.mount(el)\r\n}\r\n\r\nif (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', mountRibbon)\r\n} else {\r\n mountRibbon()\r\n}\r\n`\r\n }\r\n },\r\n\r\n transformIndexHtml(html) {\r\n if (debug) {\r\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\r\n }\r\n\r\n // Inject both the editor bridge and preview ribbon scripts\r\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\r\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\r\n\r\n // Insert before closing </body> tag\r\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Preview Plugin for Vue\r\n *\r\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\r\n * component. The ribbon handles its own visibility — it only renders on\r\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\r\n * production domains, and inside the visual page editor iframe).\r\n *\r\n * Why does the caller pass the component in? Because this file is compiled\r\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\r\n * import out of the compiled plugins bundle avoids the build error while\r\n * still letting consumer code (which *does* run through Vite) resolve the\r\n * SFC at dev/build time.\r\n *\r\n * @example VitePress theme/index.ts\r\n * ```typescript\r\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\r\n * import PreviewRibbon from '@duffcloudservices/cms/components'\r\n *\r\n * export default {\r\n * Layout,\r\n * enhanceApp({ app }) {\r\n * app.use(dcsPreviewPlugin(PreviewRibbon))\r\n * }\r\n * }\r\n * ```\r\n */\r\n\r\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\r\n\r\nexport interface DcsPreviewPluginOptions {\r\n /**\r\n * Override the version string displayed on the ribbon.\r\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\r\n */\r\n version?: string | null\r\n}\r\n\r\n/**\r\n * Creates and returns the DCS Preview plugin.\r\n *\r\n * When installed, it registers the supplied ribbon component as a global\r\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\r\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\r\n * `transformIndexHtml`.\r\n *\r\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\r\n * @param options - Optional configuration\r\n */\r\nexport function dcsPreviewPlugin(\r\n ribbonComponent: Component,\r\n options: DcsPreviewPluginOptions = {},\r\n): Plugin {\r\n const Wrapper = defineComponent({\r\n name: 'DcsPreviewRibbon',\r\n setup() {\r\n return () => h(ribbonComponent, { version: options.version ?? null })\r\n },\r\n })\r\n\r\n return {\r\n install(app: App) {\r\n app.component('DcsPreviewRibbon', Wrapper)\r\n\r\n if (options.version !== undefined) {\r\n app.provide('__dcs_preview_version__', options.version)\r\n }\r\n },\r\n }\r\n}\r\n"]}
|
|
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":["fs","path","yaml","escapeHtml"],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * DCS Content Plugin for Vite\r\n *\r\n * Reads `.dcs/content.yaml` at build time and injects content\r\n * as `__DCS_CONTENT__` global variable for use by useTextContent.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsContentPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsContentPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsContentPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { DcsContentFile } from '../types/content'\r\n\r\nexport interface DcsContentPluginOptions {\r\n /** Path to content.yaml relative to project root (default: '.dcs/content.yaml') */\r\n contentPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/content.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsContentPlugin(options: DcsContentPluginOptions = {}): Plugin {\r\n const { contentPath = '.dcs/content.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-content',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find content.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n path.resolve(process.cwd(), contentPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-content] No content.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-content] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const content = yaml.load(fileContent) as DcsContentFile\r\n\r\n if (debug) {\r\n console.log(`[dcs-content] Loaded ${foundPath}`)\r\n console.log(`[dcs-content] Version: ${content.version}`)\r\n console.log(`[dcs-content] Pages: ${Object.keys(content.pages ?? {}).join(', ') || '(none)'}`)\r\n console.log(`[dcs-content] Global keys: ${Object.keys(content.global ?? {}).length}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_CONTENT__: JSON.stringify(content),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-content] Failed to parse content.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_CONTENT__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, contentPath),\r\n path.resolve(projectRoot, '..', contentPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-content] content.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS SEO Plugin for Vite\r\n *\r\n * Reads `.dcs/seo.yaml` at build time and injects content\r\n * as `__DCS_SEO__` global variable for use by useSEO.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsSeoPlugin({ debug: true })\r\n * ]\r\n * })\r\n * ```\r\n *\r\n * For VitePress:\r\n * ```typescript\r\n * // .vitepress/config.ts\r\n * import { defineConfig } from 'vitepress'\r\n * import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsSeoPlugin()\r\n * ]\r\n * }\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport yaml from 'js-yaml'\r\nimport type { Plugin, ResolvedConfig } from 'vite'\r\nimport type { SeoConfiguration } from '../types/seo'\r\n\r\nexport interface DcsSeoPluginOptions {\r\n /** Path to seo.yaml relative to project root (default: '.dcs/seo.yaml') */\r\n seoPath?: string\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects .dcs/seo.yaml at build time.\r\n *\r\n * @param options - Plugin configuration\r\n * @returns Vite plugin\r\n */\r\nexport function dcsSeoPlugin(options: DcsSeoPluginOptions = {}): Plugin {\r\n const { seoPath = '.dcs/seo.yaml', debug = false } = options\r\n\r\n let resolvedConfig: ResolvedConfig\r\n\r\n return {\r\n name: 'dcs-seo',\r\n\r\n configResolved(config) {\r\n resolvedConfig = config\r\n },\r\n\r\n config(config) {\r\n const projectRoot = config.root || process.cwd()\r\n\r\n // Try to find seo.yaml in multiple locations\r\n // VitePress projects have the docs folder as root, so check parent too\r\n const possiblePaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n path.resolve(process.cwd(), seoPath),\r\n ]\r\n\r\n let foundPath: string | undefined\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n foundPath = testPath\r\n break\r\n }\r\n }\r\n\r\n if (!foundPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] No seo.yaml found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n console.log('[dcs-seo] Using defaults only')\r\n }\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n\r\n try {\r\n const fileContent = fs.readFileSync(foundPath, 'utf8')\r\n const seoConfig = yaml.load(fileContent) as SeoConfiguration\r\n\r\n if (debug) {\r\n console.log(`[dcs-seo] Loaded ${foundPath}`)\r\n console.log(`[dcs-seo] Version: ${seoConfig.version}`)\r\n console.log(`[dcs-seo] Site Name: ${seoConfig.global?.siteName || '(not set)'}`)\r\n console.log(`[dcs-seo] Pages: ${Object.keys(seoConfig.pages ?? {}).join(', ') || '(none)'}`)\r\n }\r\n\r\n return {\r\n define: {\r\n __DCS_SEO__: JSON.stringify(seoConfig),\r\n },\r\n }\r\n } catch (error) {\r\n console.warn('[dcs-seo] Failed to parse seo.yaml:', error)\r\n return {\r\n define: {\r\n __DCS_SEO__: 'undefined',\r\n },\r\n }\r\n }\r\n },\r\n\r\n // Watch for changes in development\r\n configureServer(server) {\r\n const projectRoot = resolvedConfig?.root || process.cwd()\r\n\r\n const watchPaths = [\r\n path.resolve(projectRoot, seoPath),\r\n path.resolve(projectRoot, '..', seoPath),\r\n ]\r\n\r\n watchPaths.forEach((watchPath) => {\r\n if (fs.existsSync(watchPath)) {\r\n server.watcher.add(watchPath)\r\n\r\n server.watcher.on('change', (changedPath) => {\r\n if (changedPath === watchPath) {\r\n if (debug) {\r\n console.log('[dcs-seo] seo.yaml changed, triggering reload')\r\n }\r\n server.restart()\r\n }\r\n })\r\n }\r\n })\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Editor Plugin for Vite\r\n *\r\n * Injects the editor bridge script into customer sites when running\r\n * in portal preview mode (inside the visual editor iframe).\r\n *\r\n * The bridge enables:\r\n * - Inline text editing via contenteditable\r\n * - Section hover highlights and AI ✨ buttons\r\n * - postMessage communication with the portal\r\n *\r\n * This plugin should only be active in development/preview mode.\r\n * It's safe to include in production builds — it does nothing unless\r\n * the page detects it's inside an iframe.\r\n *\r\n * @example\r\n * ```typescript\r\n * // vite.config.ts\r\n * import { dcsEditorPlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * plugins: [\r\n * dcsEditorPlugin()\r\n * ]\r\n * })\r\n * ```\r\n */\r\n\r\nimport type { Plugin } from 'vite'\r\n\r\nexport interface DcsEditorPluginOptions {\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n/**\r\n * Vite plugin that injects the editor bridge script for portal preview integration.\r\n *\r\n * The bridge auto-initializes only when the page detects it's running inside an iframe,\r\n * so it's safe to include in all builds — it's a no-op in standalone browsing.\r\n *\r\n * Uses a virtual module (`/__dcs-editor-bridge.js`) served through Vite's dev server\r\n * so that bare module specifiers (like `@duffcloudservices/cms/editor`) are properly\r\n * resolved through Vite's module graph rather than hitting the browser's native ESM\r\n * resolver, which can't handle bare specifiers.\r\n */\r\nexport function dcsEditorPlugin(options: DcsEditorPluginOptions = {}): Plugin {\r\n const { debug = false } = options\r\n const VIRTUAL_PATH = '/__dcs-editor-bridge.js'\r\n const RIBBON_VIRTUAL_PATH = '/__dcs-preview-ribbon.js'\r\n\r\n return {\r\n name: 'dcs-editor-bridge',\r\n\r\n resolveId(id) {\r\n if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {\r\n return id\r\n }\r\n },\r\n\r\n load(id) {\r\n if (id === VIRTUAL_PATH) {\r\n return `\r\nimport { initEditorBridge } from '@duffcloudservices/cms/editor'\r\n// The bridge only activates inside an iframe (portal preview)\r\nif (window.parent !== window) {\r\n if (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', () => setTimeout(initEditorBridge, 200))\r\n } else {\r\n setTimeout(initEditorBridge, 200)\r\n }\r\n}\r\n`\r\n }\r\n\r\n if (id === RIBBON_VIRTUAL_PATH) {\r\n return `\r\nimport { createApp } from 'vue'\r\nimport PreviewRibbon from '@duffcloudservices/cms/components'\r\n\r\nfunction mountRibbon() {\r\n // Create a detached mount point\r\n const el = document.createElement('div')\r\n el.id = 'dcs-preview-ribbon-root'\r\n document.body.appendChild(el)\r\n\r\n const app = createApp(PreviewRibbon)\r\n app.mount(el)\r\n}\r\n\r\nif (document.readyState === 'loading') {\r\n document.addEventListener('DOMContentLoaded', mountRibbon)\r\n} else {\r\n mountRibbon()\r\n}\r\n`\r\n }\r\n },\r\n\r\n transformIndexHtml(html) {\r\n if (debug) {\r\n console.log('[dcs-editor] Injecting editor bridge + preview ribbon script tags')\r\n }\r\n\r\n // Inject both the editor bridge and preview ribbon scripts\r\n const editorTag = `<script type=\"module\" src=\"${VIRTUAL_PATH}\"></script>`\r\n const ribbonTag = `<script type=\"module\" src=\"${RIBBON_VIRTUAL_PATH}\"></script>`\r\n\r\n // Insert before closing </body> tag\r\n return html.replace('</body>', `${editorTag}\\n${ribbonTag}\\n</body>`)\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS Preview Plugin for Vue\r\n *\r\n * Registers a supplied ribbon component as a global `DcsPreviewRibbon`\r\n * component. The ribbon handles its own visibility — it only renders on\r\n * `preview.duffcloudservices.com` and hides everywhere else (localhost,\r\n * production domains, and inside the visual page editor iframe).\r\n *\r\n * Why does the caller pass the component in? Because this file is compiled\r\n * by tsup (esbuild) which has no `.vue` SFC loader. Keeping the raw `.vue`\r\n * import out of the compiled plugins bundle avoids the build error while\r\n * still letting consumer code (which *does* run through Vite) resolve the\r\n * SFC at dev/build time.\r\n *\r\n * @example VitePress theme/index.ts\r\n * ```typescript\r\n * import { dcsPreviewPlugin } from '@duffcloudservices/cms/plugins'\r\n * import PreviewRibbon from '@duffcloudservices/cms/components'\r\n *\r\n * export default {\r\n * Layout,\r\n * enhanceApp({ app }) {\r\n * app.use(dcsPreviewPlugin(PreviewRibbon))\r\n * }\r\n * }\r\n * ```\r\n */\r\n\r\nimport { defineComponent, h, type App, type Component, type Plugin } from 'vue'\r\n\r\nexport interface DcsPreviewPluginOptions {\r\n /**\r\n * Override the version string displayed on the ribbon.\r\n * If omitted, the ribbon auto-detects from VITE_SITE_VERSION or the API.\r\n */\r\n version?: string | null\r\n}\r\n\r\n/**\r\n * Creates and returns the DCS Preview plugin.\r\n *\r\n * When installed, it registers the supplied ribbon component as a global\r\n * `DcsPreviewRibbon` component. Add `<DcsPreviewRibbon />` to your root\r\n * Layout, or use the `dcsEditorPlugin` Vite plugin which injects it via\r\n * `transformIndexHtml`.\r\n *\r\n * @param ribbonComponent - The PreviewRibbon SFC (imported by the consumer)\r\n * @param options - Optional configuration\r\n */\r\nexport function dcsPreviewPlugin(\r\n ribbonComponent: Component,\r\n options: DcsPreviewPluginOptions = {},\r\n): Plugin {\r\n const Wrapper = defineComponent({\r\n name: 'DcsPreviewRibbon',\r\n setup() {\r\n return () => h(ribbonComponent, { version: options.version ?? null })\r\n },\r\n })\r\n\r\n return {\r\n install(app: App) {\r\n app.component('DcsPreviewRibbon', Wrapper)\r\n\r\n if (options.version !== undefined) {\r\n app.provide('__dcs_preview_version__', options.version)\r\n }\r\n },\r\n }\r\n}\r\n","/**\r\n * DCS CDN Image Plugin for Vite\r\n *\r\n * Rewrites local static image references (e.g. `/images/staff/photo.jpg`)\r\n * to CDN URLs at build time using the `.dcs/cdn-image-map.json` mapping file\r\n * generated by the `image-migrate adopt` CLI command.\r\n *\r\n * For raster images with WebP variants, `<img>` elements are transformed into\r\n * responsive `<picture>` elements with `srcset` for optimised delivery.\r\n * SVGs receive a simple URL swap with no variant handling.\r\n *\r\n * **In development mode this plugin is a no-op** — local `/images/` paths\r\n * continue to work via Vite's static asset serving so hot-reload is unaffected.\r\n *\r\n * The plugin handles two in-pipeline replacement vectors:\r\n *\r\n * 1. **Module transform** (`transform` hook) — rewrites `<img>` tags and\r\n * string literals in Vue SFCs, TS, JS, CSS, MD, and HTML modules.\r\n * 2. **Chunk rendering** (`renderChunk` hook) — rewrites image paths in\r\n * final rendered JS/CSS chunks AFTER Vite's `define` substitution.\r\n * This catches data injected by `dcsContentPlugin` via `__DCS_CONTENT__`\r\n * (from `.dcs/content.yaml`) which bypasses the `transform` hook.\r\n *\r\n * A third vector — **post-build file processing** — is handled by the\r\n * companion `dcsCdnBuildEnd` hook, which must be registered separately in\r\n * VitePress config. VitePress generates HTML *after* both Vite builds\r\n * complete, so Vite plugin hooks (`closeBundle`, `transformIndexHtml`)\r\n * cannot catch SSR-rendered `<head>` tags or static `public/` files like\r\n * `service-worker.js`.\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { dcsCdnImagePlugin, dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * vite: {\r\n * plugins: [\r\n * dcsCdnImagePlugin()\r\n * ]\r\n * },\r\n * buildEnd: dcsCdnBuildEnd()\r\n * })\r\n * ```\r\n */\r\n\r\nimport fs from 'node:fs'\r\nimport path from 'node:path'\r\nimport type { Plugin } from 'vite'\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Types\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nexport interface CdnImageMapVariant {\r\n suffix: string\r\n blobPath: string\r\n cdnUrl: string\r\n width: number\r\n height: number\r\n size: number\r\n}\r\n\r\nexport interface CdnImageMapEntry {\r\n localPath: string\r\n blobPath: string\r\n cdnUrl: string\r\n fileName: string\r\n contentType: string\r\n originalSize: number\r\n width?: number\r\n height?: number\r\n hash: string\r\n variants?: CdnImageMapVariant[]\r\n}\r\n\r\nexport interface CdnImageMap {\r\n generated: string\r\n site: string\r\n cdnBase: string\r\n images: CdnImageMapEntry[]\r\n}\r\n\r\nexport interface DcsCdnImagePluginOptions {\r\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\r\n mapPath?: string\r\n\r\n /**\r\n * Patterns to match for replacement. Each must be a **leading-slash path prefix**\r\n * that appears in source code (e.g. `/images/`). The `localPath` field in the\r\n * mapping file is compared *without* a leading slash.\r\n *\r\n * Default: `['/images/']`\r\n */\r\n pathPrefixes?: string[]\r\n\r\n /**\r\n * Default `sizes` attribute for responsive `<picture>` elements.\r\n * Override per-context via the `data-sizes` attribute on the original `<img>`.\r\n *\r\n * Default: `'(max-width: 1024px) 100vw, 1024px'`\r\n */\r\n defaultSizes?: string\r\n\r\n /** Enable debug logging */\r\n debug?: boolean\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Helpers\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&')\r\n .replace(/\"/g, '"')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n}\r\n\r\n/**\r\n * Build a responsive `<picture>` element for a raster image that has\r\n * WebP variants.\r\n */\r\nfunction buildPictureElement(\r\n entry: CdnImageMapEntry,\r\n alt: string,\r\n extraAttrs: string,\r\n sizes: string\r\n): string {\r\n const variants = entry.variants ?? []\r\n\r\n // Collect only the display-relevant variants (not thumb, not og)\r\n const displayVariants = variants.filter(\r\n (v) => v.suffix === '-sm' || v.suffix === '-md' || v.suffix === '-lg'\r\n )\r\n\r\n if (displayVariants.length === 0) {\r\n // No display variants — fall back to original CDN URL as plain <img>\r\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\r\n }\r\n\r\n const srcset = displayVariants\r\n .map((v) => `${v.cdnUrl} ${v.width}w`)\r\n .join(', ')\r\n\r\n // Pick the -md variant as the default src, or fall through to original\r\n const mdVariant = variants.find((v) => v.suffix === '-md')\r\n const fallbackSrc = mdVariant ? mdVariant.cdnUrl : entry.cdnUrl\r\n\r\n return [\r\n '<picture>',\r\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"${escapeHtml(sizes)}\" />`,\r\n ` <img src=\"${escapeHtml(fallbackSrc)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`,\r\n '</picture>',\r\n ].join('\\n')\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Shared helpers\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Try to find and load the CDN image map from common locations.\r\n * VitePress nests docs under a subfolder, so we check parent dirs too.\r\n */\r\nfunction loadCdnImageMap(\r\n projectRoot: string,\r\n relativeMapPath: string,\r\n debug: boolean\r\n): Map<string, CdnImageMapEntry> | null {\r\n const possiblePaths = [\r\n path.resolve(projectRoot, relativeMapPath),\r\n path.resolve(projectRoot, '..', relativeMapPath),\r\n path.resolve(process.cwd(), relativeMapPath),\r\n ]\r\n\r\n for (const testPath of possiblePaths) {\r\n if (fs.existsSync(testPath)) {\r\n try {\r\n const raw = fs.readFileSync(testPath, 'utf8')\r\n const data: CdnImageMap = JSON.parse(raw)\r\n\r\n const map = new Map<string, CdnImageMapEntry>()\r\n for (const entry of data.images) {\r\n // Store both with and without leading slash for flexible matching\r\n map.set(entry.localPath, entry)\r\n if (!entry.localPath.startsWith('/')) {\r\n map.set('/' + entry.localPath, entry)\r\n }\r\n }\r\n\r\n if (debug) {\r\n console.log(`[dcs-cdn-image] Loaded ${data.images.length} entries from ${testPath}`)\r\n }\r\n return map\r\n } catch (error) {\r\n console.warn(`[dcs-cdn-image] Failed to parse ${testPath}:`, error)\r\n }\r\n }\r\n }\r\n\r\n if (debug) {\r\n console.log('[dcs-cdn-image] No cdn-image-map.json found at:')\r\n possiblePaths.forEach((p) => console.log(` - ${p}`))\r\n }\r\n return null\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// Vite Plugin\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\n/**\r\n * Vite plugin that rewrites static `/images/` paths to CDN URLs at build time.\r\n */\r\nexport function dcsCdnImagePlugin(options: DcsCdnImagePluginOptions = {}): Plugin {\r\n const {\r\n mapPath = '.dcs/cdn-image-map.json',\r\n pathPrefixes = ['/images/'],\r\n defaultSizes = '(max-width: 1024px) 100vw, 1024px',\r\n debug = false,\r\n } = options\r\n\r\n // Lookup map built from the JSON file: key = localPath (no leading slash)\r\n let imageMap: Map<string, CdnImageMapEntry> | null = null\r\n let isProduction = false\r\n\r\n /**\r\n * Check whether a source code string contains any of the path prefixes\r\n * we care about. Quick bail-out for files that don't reference images.\r\n */\r\n function hasImageReferences(code: string): boolean {\r\n return pathPrefixes.some((prefix) => code.includes(prefix))\r\n }\r\n\r\n /**\r\n * Replace image path references in any string value with CDN URLs.\r\n * Used for code transforms and renderChunk post-processing.\r\n */\r\n function replaceImagePaths(str: string): string {\r\n if (!imageMap || imageMap.size === 0) return str\r\n let result = str\r\n for (const prefix of pathPrefixes) {\r\n // Global replace: matches prefix followed by a relative path bounded\r\n // by quotes, parens, or whitespace. Allows spaces in filenames.\r\n const regex = new RegExp(\r\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\r\n 'g'\r\n )\r\n result = result.replace(regex, (match, quote, relPath) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n return `${quote}${entry.cdnUrl}`\r\n })\r\n }\r\n return result\r\n }\r\n\r\n /**\r\n * Replace standalone URL references in JS/TS string literals and Vue template\r\n * attributes. This handles patterns like:\r\n * src=\"/images/foo.jpg\"\r\n * { src: '/images/foo.jpg' }\r\n * url(/images/foo.svg)\r\n *\r\n * For `<img>` tags with raster images that have variants, the entire tag is\r\n * replaced with a `<picture>` element.\r\n */\r\n function transformCode(code: string): string {\r\n if (!imageMap || imageMap.size === 0) return code\r\n\r\n // ── Pass 1: Replace <img> tags that reference mapped raster images ──────\r\n // Matches: <img ... src=\"/images/foo.jpg\" ... /> or <img ... src=\"/images/foo.jpg\" ... >\r\n const imgTagRegex = /<img\\b([^>]*?)src=[\"'](\\/(images\\/[^\"']+))[\"']([^>]*?)\\/?>/gi\r\n code = code.replace(imgTagRegex, (_match, beforeSrc, _srcWithSlash, localPath, afterSrc) => {\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return _match\r\n\r\n // Extract alt from the existing attributes\r\n const altMatch = (beforeSrc + afterSrc).match(/alt=[\"']([^\"']*)[\"']/)\r\n const alt = altMatch ? altMatch[1] : ''\r\n\r\n // Collect other attributes (not src, alt, loading, decoding — we set those)\r\n const otherAttrs = (beforeSrc + afterSrc)\r\n .replace(/\\balt=[\"'][^\"']*[\"']/gi, '')\r\n .replace(/\\bloading=[\"'][^\"']*[\"']/gi, '')\r\n .replace(/\\bdecoding=[\"'][^\"']*[\"']/gi, '')\r\n .trim()\r\n\r\n const extraAttrs = otherAttrs ? ' ' + otherAttrs : ''\r\n\r\n // Extract custom sizes if specified\r\n const sizesMatch = (beforeSrc + afterSrc).match(/data-sizes=[\"']([^\"']*)[\"']/)\r\n const sizes = sizesMatch ? sizesMatch[1] : defaultSizes\r\n\r\n // SVGs and images without variants → simple URL swap <img>\r\n if (!entry.variants || entry.variants.length === 0) {\r\n return `<img src=\"${escapeHtml(entry.cdnUrl)}\" alt=\"${escapeHtml(alt)}\"${extraAttrs} loading=\"lazy\" decoding=\"async\" />`\r\n }\r\n\r\n // Raster with variants → <picture>\r\n return buildPictureElement(entry, alt, extraAttrs, sizes)\r\n })\r\n\r\n // ── Pass 2: Replace remaining string literal / CSS url() references ─────\r\n code = replaceImagePaths(code)\r\n\r\n return code\r\n }\r\n\r\n return {\r\n name: 'dcs-cdn-image',\r\n enforce: 'pre',\r\n\r\n configResolved(config) {\r\n isProduction = config.command === 'build'\r\n if (!isProduction) {\r\n if (debug) {\r\n console.log('[dcs-cdn-image] Dev mode — plugin disabled (local images served by Vite)')\r\n }\r\n return\r\n }\r\n imageMap = loadCdnImageMap(config.root, mapPath, debug)\r\n },\r\n\r\n transform(code, id) {\r\n // Only run in production builds\r\n if (!isProduction || !imageMap) return\r\n\r\n // Only process relevant file types\r\n if (!/\\.(vue|ts|tsx|js|jsx|css|scss|md|html)(\\?.*)?$/.test(id)) return\r\n\r\n // Quick bail — skip files with no image path references\r\n if (!hasImageReferences(code)) return\r\n\r\n const transformed = transformCode(code)\r\n if (transformed !== code) {\r\n return { code: transformed, map: null }\r\n }\r\n },\r\n\r\n /**\r\n * Replace image paths in final rendered JS/CSS chunks.\r\n *\r\n * This fires AFTER Vite's `define` substitution, catching paths that were\r\n * injected via `define` values (e.g. `__DCS_CONTENT__` set by\r\n * `dcsContentPlugin` from `.dcs/content.yaml`). Those paths bypass the\r\n * `transform` hook because `define` replacement happens at bundle time.\r\n */\r\n renderChunk(code, _chunk) {\r\n if (!isProduction || !imageMap) return null\r\n if (!hasImageReferences(code)) return null\r\n\r\n const replaced = replaceImagePaths(code)\r\n if (replaced !== code) {\r\n if (debug) {\r\n console.log(`[dcs-cdn-image] renderChunk: rewrote image paths in ${_chunk.fileName}`)\r\n }\r\n return { code: replaced, map: null }\r\n }\r\n return null\r\n },\r\n\r\n /**\r\n * Transform HTML output to catch references injected outside the module\r\n * pipeline — e.g. VitePress `head` config tags (favicons, OG images).\r\n *\r\n * Note: In VitePress, this hook fires for the `index.html` template but\r\n * NOT for SSR-rendered per-page HTML. Use `dcsCdnBuildEnd` for full\r\n * coverage of generated HTML files.\r\n */\r\n transformIndexHtml(html) {\r\n if (!isProduction || !imageMap) return html\r\n if (!hasImageReferences(html)) return html\r\n\r\n // Replace href/src/content attributes that reference mapped images\r\n for (const prefix of pathPrefixes) {\r\n const attrRegex = new RegExp(\r\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\r\n 'gi'\r\n )\r\n html = html.replace(attrRegex, (match, pre, relPath, post) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n return `${pre}${entry.cdnUrl}${post}`\r\n })\r\n }\r\n\r\n return html\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Escape a string for use in a RegExp.\r\n */\r\nfunction escapeRegex(str: string): string {\r\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\r\n}\r\n\r\n// ────────────────────────────────────────────────────────────────────────────\r\n// VitePress buildEnd hook\r\n// ────────────────────────────────────────────────────────────────────────────\r\n\r\nexport interface DcsCdnBuildEndOptions {\r\n /** Path to cdn-image-map.json relative to project root (default: '.dcs/cdn-image-map.json') */\r\n mapPath?: string\r\n\r\n /**\r\n * Patterns to match for replacement.\r\n * Default: `['/images/']`\r\n */\r\n pathPrefixes?: string[]\r\n\r\n /** Enable debug logging */\r\n debug?: boolean\r\n\r\n /**\r\n * File extensions to process in the output directory.\r\n * Default: `['.html', '.js']`\r\n */\r\n extensions?: string[]\r\n}\r\n\r\n/**\r\n * VitePress `buildEnd` hook factory that post-processes generated HTML and\r\n * static files in the output directory to replace remaining `/images/` paths\r\n * with CDN URLs.\r\n *\r\n * VitePress generates HTML **after** both Vite builds complete, so Vite\r\n * plugin hooks (`closeBundle`, `transformIndexHtml`) cannot catch\r\n * SSR-rendered `<head>` tags (favicons, OG images). This hook runs after all\r\n * HTML files are written to disk.\r\n *\r\n * Also rewrites static files (e.g. `service-worker.js`) copied from\r\n * `public/` that are not part of Vite's module pipeline.\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { dcsCdnBuildEnd } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * buildEnd: dcsCdnBuildEnd({ debug: true })\r\n * })\r\n * ```\r\n */\r\nexport function dcsCdnBuildEnd(options: DcsCdnBuildEndOptions = {}) {\r\n const {\r\n mapPath = '.dcs/cdn-image-map.json',\r\n pathPrefixes = ['/images/'],\r\n debug = false,\r\n extensions = ['.html', '.js'],\r\n } = options\r\n\r\n return async (siteConfig: { root: string; outDir: string }) => {\r\n const imageMap = loadCdnImageMap(siteConfig.root, mapPath, debug)\r\n if (!imageMap) {\r\n if (debug) {\r\n console.log('[dcs-cdn-image] buildEnd: no image map found')\r\n }\r\n return\r\n }\r\n\r\n const outDir = siteConfig.outDir\r\n if (!fs.existsSync(outDir)) return\r\n\r\n let filesProcessed = 0\r\n let refsReplaced = 0\r\n\r\n function walkDir(dir: string) {\r\n const entries = fs.readdirSync(dir, { withFileTypes: true })\r\n for (const entry of entries) {\r\n const fullPath = path.join(dir, entry.name)\r\n if (entry.isDirectory()) {\r\n walkDir(fullPath)\r\n } else if (extensions.some((ext) => entry.name.endsWith(ext))) {\r\n processFile(fullPath)\r\n }\r\n }\r\n }\r\n\r\n function processFile(filePath: string) {\r\n let content = fs.readFileSync(filePath, 'utf8')\r\n if (!pathPrefixes.some((p) => content.includes(p))) return\r\n\r\n let changed = false\r\n\r\n if (filePath.endsWith('.html')) {\r\n // HTML: replace href/src/content attribute values\r\n for (const prefix of pathPrefixes) {\r\n const attrRegex = new RegExp(\r\n `((?:href|src|content)=[\"'])${escapeRegex(prefix)}([^\"']+)([\"'])`,\r\n 'gi'\r\n )\r\n content = content.replace(attrRegex, (match, pre, relPath, post) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const mapEntry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!mapEntry) return match\r\n changed = true\r\n refsReplaced++\r\n return `${pre}${mapEntry.cdnUrl}${post}`\r\n })\r\n }\r\n } else {\r\n // JS/other: replace quoted string paths\r\n for (const prefix of pathPrefixes) {\r\n const regex = new RegExp(\r\n `([\"'\\`(])${escapeRegex(prefix)}([^\"'\\`()]+)`,\r\n 'g'\r\n )\r\n content = content.replace(regex, (match, quote, relPath) => {\r\n const localPath = prefix.replace(/^\\//, '') + relPath\r\n const entry = imageMap!.get(localPath) || imageMap!.get('/' + localPath)\r\n if (!entry) return match\r\n changed = true\r\n refsReplaced++\r\n return `${quote}${entry.cdnUrl}`\r\n })\r\n }\r\n }\r\n\r\n if (changed) {\r\n fs.writeFileSync(filePath, content, 'utf8')\r\n filesProcessed++\r\n }\r\n }\r\n\r\n walkDir(outDir)\r\n\r\n if (debug) {\r\n console.log(\r\n `[dcs-cdn-image] buildEnd: post-processed ${filesProcessed} files, replaced ${refsReplaced} references`\r\n )\r\n }\r\n }\r\n}","/**\r\n * markdown-it plugin that transforms standard `` image syntax\r\n * into responsive `<picture>` elements when the URL matches the DCS CDN\r\n * asset pattern.\r\n *\r\n * Non-CDN images are rendered with the default image renderer (plain `<img>`).\r\n *\r\n * @example\r\n * ```ts\r\n * // .vitepress/config.ts\r\n * import { responsiveImagePlugin } from '@duffcloudservices/cms/plugins'\r\n *\r\n * export default defineConfig({\r\n * markdown: {\r\n * config: (md) => {\r\n * md.use(responsiveImagePlugin)\r\n * },\r\n * },\r\n * })\r\n * ```\r\n *\r\n * Input markdown:\r\n * ```md\r\n * \r\n * ```\r\n *\r\n * Rendered HTML:\r\n * ```html\r\n * <picture>\r\n * <source srcset=\"...abc-123-sm.webp 640w, ...abc-123-md.webp 1024w, ...abc-123-lg.webp 1920w\"\r\n * type=\"image/webp\"\r\n * sizes=\"(max-width: 1024px) 100vw, 1024px\" />\r\n * <img src=\"...abc-123-md.webp\" alt=\"Physical therapy session\" loading=\"lazy\" decoding=\"async\" />\r\n * </picture>\r\n * ```\r\n */\r\n\r\nimport type MarkdownIt from 'markdown-it'\r\n\r\n/** Matches DCS CDN asset URLs: base path / UUID . extension */\r\nconst CDN_PATTERN = /^(https?:\\/\\/files\\.[^/]+\\/[^/]+\\/assets\\/(?:[^/]+\\/)*)([a-f0-9-]+)\\.(\\w+)$/\r\n\r\n/**\r\n * Escapes HTML special characters for safe attribute embedding.\r\n * Falls back to a simple replace chain when `md.utils.escapeHtml` is unavailable.\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&')\r\n .replace(/\"/g, '"')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n}\r\n\r\nexport function responsiveImagePlugin(md: MarkdownIt): void {\r\n const defaultImageRenderer = md.renderer.rules.image\r\n\r\n md.renderer.rules.image = (tokens, idx, options, env, self) => {\r\n const token = tokens[idx]\r\n const src = token.attrGet('src') ?? ''\r\n const alt = token.content ?? ''\r\n\r\n const match = src.match(CDN_PATTERN)\r\n if (!match) {\r\n // Not a CDN image — render normally\r\n if (defaultImageRenderer) {\r\n return defaultImageRenderer(tokens, idx, options, env, self)\r\n }\r\n return self.renderToken(tokens, idx, options)\r\n }\r\n\r\n const [, basePath, uuid] = match\r\n\r\n const srcset = [\r\n `${basePath}${uuid}-sm.webp 640w`,\r\n `${basePath}${uuid}-md.webp 1024w`,\r\n `${basePath}${uuid}-lg.webp 1920w`,\r\n ].join(', ')\r\n\r\n const escapedAlt = escapeHtml(alt)\r\n\r\n return [\r\n '<picture>',\r\n ` <source srcset=\"${srcset}\" type=\"image/webp\" sizes=\"(max-width: 1024px) 100vw, 1024px\" />`,\r\n ` <img src=\"${basePath}${uuid}-md.webp\" alt=\"${escapedAlt}\" loading=\"lazy\" decoding=\"async\" />`,\r\n '</picture>',\r\n ].join('\\n')\r\n }\r\n}\r\n"]}
|