@duffcloudservices/cms 0.5.1 → 0.7.1
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/chunk-FUNIALH6.js +1003 -0
- package/dist/chunk-FUNIALH6.js.map +1 -0
- package/dist/chunk-KCWMS7P4.js +3 -0
- package/dist/chunk-KCWMS7P4.js.map +1 -0
- package/dist/index.d.ts +4 -195
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +72 -28
- package/dist/plugins/index.js +221 -12
- package/dist/plugins/index.js.map +1 -1
- package/dist/seo/index.d.ts +248 -0
- package/dist/seo/index.js +4 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/vitepressTransform-y8Ru9elj.d.ts +971 -0
- package/package.json +8 -3
- package/src/components/ManagedImage.test.ts +75 -0
- package/src/components/ManagedImage.vue +19 -3
- package/src/components/ResponsiveImage.test.ts +47 -0
- package/src/components/ResponsiveImage.vue +15 -3
- package/dist/chunk-JJK7OGC2.js +0 -459
- package/dist/chunk-JJK7OGC2.js.map +0 -1
- package/dist/vitepressTransform-D9P9D7So.d.ts +0 -479
package/dist/plugins/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-
|
|
2
|
-
export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-
|
|
1
|
+
import { buildSitemapXml, buildRobotsTxt, buildLlmsTxt, matchesExcludedGlob, breadcrumbTrailFromRoute, findReviewItemsForPage, buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-FUNIALH6.js';
|
|
2
|
+
export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-FUNIALH6.js';
|
|
3
3
|
import fs2 from 'fs';
|
|
4
4
|
import path2 from 'path';
|
|
5
|
-
import
|
|
5
|
+
import yaml2 from 'js-yaml';
|
|
6
6
|
import { defineComponent, h } from 'vue';
|
|
7
7
|
|
|
8
8
|
function dcsContentPlugin(options = {}) {
|
|
@@ -41,7 +41,7 @@ function dcsContentPlugin(options = {}) {
|
|
|
41
41
|
}
|
|
42
42
|
try {
|
|
43
43
|
const fileContent = fs2.readFileSync(foundPath, "utf8");
|
|
44
|
-
const content =
|
|
44
|
+
const content = yaml2.load(fileContent);
|
|
45
45
|
if (debug) {
|
|
46
46
|
console.log(`[dcs-content] Loaded ${foundPath}`);
|
|
47
47
|
console.log(`[dcs-content] Version: ${content.version}`);
|
|
@@ -112,33 +112,200 @@ function loadSeoConfig(projectRoot, seoPath, debug) {
|
|
|
112
112
|
}
|
|
113
113
|
try {
|
|
114
114
|
const fileContent = fs2.readFileSync(foundPath, "utf8");
|
|
115
|
-
const config =
|
|
115
|
+
const config = yaml2.load(fileContent);
|
|
116
116
|
return { config, foundPath };
|
|
117
117
|
} catch (error) {
|
|
118
118
|
console.warn("[dcs-seo] Failed to parse seo.yaml:", error);
|
|
119
119
|
return null;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
function loadContentConfig(projectRoot, contentRelPath, debug) {
|
|
123
|
+
const possiblePaths = [
|
|
124
|
+
path2.resolve(projectRoot, contentRelPath),
|
|
125
|
+
path2.resolve(projectRoot, "..", contentRelPath),
|
|
126
|
+
path2.resolve(process.cwd(), contentRelPath)
|
|
127
|
+
];
|
|
128
|
+
const foundPath = possiblePaths.find((p) => fs2.existsSync(p));
|
|
129
|
+
if (!foundPath) {
|
|
130
|
+
if (debug) console.log(`[dcs-seo] No content.yaml found (reviews disabled)`);
|
|
131
|
+
return void 0;
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
const parsed = yaml2.load(fs2.readFileSync(foundPath, "utf8"));
|
|
135
|
+
if (!parsed || typeof parsed !== "object") return void 0;
|
|
136
|
+
return parsed;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn("[dcs-seo] Failed to parse content.yaml (reviews disabled):", error);
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
122
142
|
function routeToOutputFile(outDir, routePath) {
|
|
123
143
|
const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
124
144
|
if (trimmed === "") return path2.join(outDir, "index.html");
|
|
125
145
|
return path2.join(outDir, ...trimmed.split("/"), "index.html");
|
|
126
146
|
}
|
|
147
|
+
function loadExcludedGlobs(projectRoot, pagesPath) {
|
|
148
|
+
const possiblePaths = [
|
|
149
|
+
path2.resolve(projectRoot, pagesPath),
|
|
150
|
+
path2.resolve(projectRoot, "..", pagesPath),
|
|
151
|
+
path2.resolve(process.cwd(), pagesPath)
|
|
152
|
+
];
|
|
153
|
+
const found = possiblePaths.find((p) => fs2.existsSync(p));
|
|
154
|
+
if (!found) return [];
|
|
155
|
+
try {
|
|
156
|
+
const raw = yaml2.load(fs2.readFileSync(found, "utf8"));
|
|
157
|
+
const excluded = raw?.excluded;
|
|
158
|
+
if (!Array.isArray(excluded)) return [];
|
|
159
|
+
return excluded.filter((e) => typeof e === "string");
|
|
160
|
+
} catch {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function deriveLastmod(seoConfig, projectRoot, pagesPath) {
|
|
165
|
+
const fromSeo = seoConfig?.lastUpdated;
|
|
166
|
+
if (typeof fromSeo === "string" && fromSeo.length > 0) return fromSeo;
|
|
167
|
+
const possiblePaths = [
|
|
168
|
+
path2.resolve(projectRoot, pagesPath),
|
|
169
|
+
path2.resolve(projectRoot, "..", pagesPath),
|
|
170
|
+
path2.resolve(process.cwd(), pagesPath)
|
|
171
|
+
];
|
|
172
|
+
const found = possiblePaths.find((p) => fs2.existsSync(p));
|
|
173
|
+
if (!found) return void 0;
|
|
174
|
+
try {
|
|
175
|
+
const raw = yaml2.load(fs2.readFileSync(found, "utf8"));
|
|
176
|
+
const fromPages = raw?.lastUpdated;
|
|
177
|
+
if (typeof fromPages === "string" && fromPages.length > 0) return fromPages;
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
return void 0;
|
|
181
|
+
}
|
|
182
|
+
function emitSiteFiles(params) {
|
|
183
|
+
const {
|
|
184
|
+
outDir,
|
|
185
|
+
projectRoot,
|
|
186
|
+
pagesPath,
|
|
187
|
+
routes,
|
|
188
|
+
seoConfig,
|
|
189
|
+
siteUrl,
|
|
190
|
+
exclude = [],
|
|
191
|
+
noindex = [],
|
|
192
|
+
preview = false,
|
|
193
|
+
robots = {},
|
|
194
|
+
llms = true,
|
|
195
|
+
debug = false
|
|
196
|
+
} = params;
|
|
197
|
+
const written = [];
|
|
198
|
+
const effectiveSiteUrl = siteUrl || seoConfig?.global?.siteUrl;
|
|
199
|
+
const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath);
|
|
200
|
+
const lastmod = deriveLastmod(seoConfig, projectRoot, pagesPath);
|
|
201
|
+
let wroteSitemap = false;
|
|
202
|
+
if (!preview) {
|
|
203
|
+
const xml = buildSitemapXml({
|
|
204
|
+
routes,
|
|
205
|
+
siteUrl: effectiveSiteUrl,
|
|
206
|
+
seoConfig,
|
|
207
|
+
exclude,
|
|
208
|
+
noindex,
|
|
209
|
+
excludedGlobs,
|
|
210
|
+
lastmod
|
|
211
|
+
});
|
|
212
|
+
if (xml) {
|
|
213
|
+
fs2.writeFileSync(path2.join(outDir, "sitemap.xml"), xml, "utf8");
|
|
214
|
+
wroteSitemap = true;
|
|
215
|
+
written.push("sitemap.xml");
|
|
216
|
+
if (debug) console.log("[dcs-seo] wrote sitemap.xml");
|
|
217
|
+
} else if (debug) {
|
|
218
|
+
console.log("[dcs-seo] no absolute <loc> derivable; sitemap.xml not written");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
if (robots.enabled ?? true) {
|
|
222
|
+
const robotsPath = path2.join(outDir, "robots.txt");
|
|
223
|
+
if (fs2.existsSync(robotsPath) && !robots.force) {
|
|
224
|
+
if (debug) console.log("[dcs-seo] dist/robots.txt exists and force not set; leaving it");
|
|
225
|
+
} else {
|
|
226
|
+
const txt = buildRobotsTxt({
|
|
227
|
+
siteUrl: effectiveSiteUrl,
|
|
228
|
+
preview,
|
|
229
|
+
robots,
|
|
230
|
+
hasSitemap: wroteSitemap
|
|
231
|
+
});
|
|
232
|
+
fs2.writeFileSync(robotsPath, txt, "utf8");
|
|
233
|
+
written.push("robots.txt");
|
|
234
|
+
if (debug) console.log("[dcs-seo] wrote robots.txt");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (llms && !preview) {
|
|
238
|
+
const txt = buildLlmsTxt({
|
|
239
|
+
routes,
|
|
240
|
+
siteUrl: effectiveSiteUrl,
|
|
241
|
+
seoConfig,
|
|
242
|
+
exclude,
|
|
243
|
+
noindex,
|
|
244
|
+
excludedGlobs
|
|
245
|
+
});
|
|
246
|
+
if (txt) {
|
|
247
|
+
fs2.writeFileSync(path2.join(outDir, "llms.txt"), txt, "utf8");
|
|
248
|
+
written.push("llms.txt");
|
|
249
|
+
if (debug) console.log("[dcs-seo] wrote llms.txt");
|
|
250
|
+
} else if (debug) {
|
|
251
|
+
console.log("[dcs-seo] nothing indexable; llms.txt not written");
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return written;
|
|
255
|
+
}
|
|
256
|
+
function pickPageFaq(content, pageSlug) {
|
|
257
|
+
if (!content) return void 0;
|
|
258
|
+
const fromBlock = (block) => {
|
|
259
|
+
if (!block) return void 0;
|
|
260
|
+
const v = block["faq"];
|
|
261
|
+
return Array.isArray(v) ? v : void 0;
|
|
262
|
+
};
|
|
263
|
+
return fromBlock(content.pages?.[pageSlug]) ?? fromBlock(content.global);
|
|
264
|
+
}
|
|
127
265
|
function emitStaticSeoHtml(params) {
|
|
128
|
-
const {
|
|
266
|
+
const {
|
|
267
|
+
outDir,
|
|
268
|
+
shellHtml,
|
|
269
|
+
routes,
|
|
270
|
+
seoConfig,
|
|
271
|
+
contentConfig,
|
|
272
|
+
exclude = [],
|
|
273
|
+
noindex = [],
|
|
274
|
+
excludedGlobs = [],
|
|
275
|
+
debug = false
|
|
276
|
+
} = params;
|
|
129
277
|
const excludeSet = new Set(exclude);
|
|
130
278
|
const noindexSet = new Set(noindex);
|
|
279
|
+
const siteUrl = seoConfig?.global?.siteUrl ?? "";
|
|
280
|
+
const routeTitles = { "/": "Home" };
|
|
281
|
+
for (const r of routes) {
|
|
282
|
+
if (r.title && r.path) routeTitles[r.path.replace(/\/+$/, "") || "/"] = r.title;
|
|
283
|
+
}
|
|
131
284
|
let written = 0;
|
|
132
285
|
for (const route of routes) {
|
|
133
286
|
if (excludeSet.has(route.path) || route.slug && excludeSet.has(route.slug)) {
|
|
134
287
|
if (debug) console.log(`[dcs-seo] skip (excluded): ${route.path}`);
|
|
135
288
|
continue;
|
|
136
289
|
}
|
|
290
|
+
if (matchesExcludedGlob(route.path, excludedGlobs)) {
|
|
291
|
+
if (debug) console.log(`[dcs-seo] skip (excluded glob): ${route.path}`);
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
137
294
|
const forceNoindex = noindexSet.has(route.path) || route.slug && noindexSet.has(route.slug);
|
|
295
|
+
const breadcrumbTrail = siteUrl ? breadcrumbTrailFromRoute(route.path, siteUrl, routeTitles) : void 0;
|
|
296
|
+
const reviews = contentConfig ? findReviewItemsForPage(contentConfig, route.slug) : void 0;
|
|
297
|
+
const isBlogPost = /^\/blog\/.+/.test(route.path) && !!route.title;
|
|
298
|
+
const blogMeta = isBlogPost ? { headline: route.title, url: breadcrumbTrail?.[breadcrumbTrail.length - 1]?.item } : void 0;
|
|
299
|
+
const faq = pickPageFaq(contentConfig, route.slug);
|
|
138
300
|
const tags = buildHeadTags(route.slug, route.path, seoConfig, {
|
|
139
301
|
includeKeywords: true,
|
|
140
302
|
fallbackTitle: route.title,
|
|
141
|
-
robots: forceNoindex ? "noindex, nofollow" : void 0
|
|
303
|
+
robots: forceNoindex ? "noindex, nofollow" : void 0,
|
|
304
|
+
emitGraph: true,
|
|
305
|
+
breadcrumbTrail,
|
|
306
|
+
reviews,
|
|
307
|
+
blogMeta,
|
|
308
|
+
faq
|
|
142
309
|
});
|
|
143
310
|
const html = spliceHeadHtml(shellHtml, tags);
|
|
144
311
|
const outFile = routeToOutputFile(outDir, route.path);
|
|
@@ -159,8 +326,16 @@ function dcsSeoPlugin(options = {}) {
|
|
|
159
326
|
debug = false,
|
|
160
327
|
emitStaticHtml = false,
|
|
161
328
|
pagesPath = ".dcs/pages.yaml",
|
|
329
|
+
contentPath = ".dcs/content.yaml",
|
|
162
330
|
exclude = [],
|
|
163
|
-
noindex = []
|
|
331
|
+
noindex = [],
|
|
332
|
+
// Site files default ON whenever per-route HTML emission is on, so the
|
|
333
|
+
// sites already using emitStaticHtml gain sitemap/robots/llms with no edit.
|
|
334
|
+
emitSiteFiles: emitSiteFilesOpt = emitStaticHtml,
|
|
335
|
+
siteUrl,
|
|
336
|
+
robots = {},
|
|
337
|
+
preview = false,
|
|
338
|
+
llms = true
|
|
164
339
|
} = options;
|
|
165
340
|
let resolvedConfig;
|
|
166
341
|
return {
|
|
@@ -203,7 +378,14 @@ function dcsSeoPlugin(options = {}) {
|
|
|
203
378
|
* no-ops. Never throws (so it can never break a production build).
|
|
204
379
|
*/
|
|
205
380
|
writeBundle() {
|
|
206
|
-
if (!emitStaticHtml)
|
|
381
|
+
if (!emitStaticHtml) {
|
|
382
|
+
if (options.emitSiteFiles === true) {
|
|
383
|
+
console.warn(
|
|
384
|
+
"[dcs-seo] emitSiteFiles:true is ignored because emitStaticHtml is false; set emitStaticHtml:true to emit sitemap.xml/robots.txt/llms.txt"
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
207
389
|
try {
|
|
208
390
|
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
209
391
|
const outDir = resolveOutDir(resolvedConfig);
|
|
@@ -232,18 +414,43 @@ function dcsSeoPlugin(options = {}) {
|
|
|
232
414
|
);
|
|
233
415
|
}
|
|
234
416
|
const shellHtml = fs2.readFileSync(shellPath, "utf8");
|
|
417
|
+
const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath);
|
|
418
|
+
const contentConfig = loadContentConfig(projectRoot, contentPath, debug);
|
|
235
419
|
const written = emitStaticSeoHtml({
|
|
236
420
|
outDir,
|
|
237
421
|
shellHtml,
|
|
238
422
|
routes,
|
|
239
423
|
seoConfig: loaded?.config,
|
|
424
|
+
contentConfig,
|
|
240
425
|
exclude,
|
|
241
426
|
noindex,
|
|
427
|
+
excludedGlobs,
|
|
242
428
|
debug
|
|
243
429
|
});
|
|
244
430
|
if (debug) {
|
|
245
431
|
console.log(`[dcs-seo] emitStaticHtml: wrote ${written} per-route HTML file(s)`);
|
|
246
432
|
}
|
|
433
|
+
if (emitSiteFilesOpt) {
|
|
434
|
+
const siteFiles = emitSiteFiles({
|
|
435
|
+
outDir,
|
|
436
|
+
projectRoot,
|
|
437
|
+
pagesPath,
|
|
438
|
+
routes,
|
|
439
|
+
seoConfig: loaded?.config,
|
|
440
|
+
siteUrl,
|
|
441
|
+
exclude,
|
|
442
|
+
noindex,
|
|
443
|
+
preview,
|
|
444
|
+
robots,
|
|
445
|
+
llms,
|
|
446
|
+
debug
|
|
447
|
+
});
|
|
448
|
+
if (debug) {
|
|
449
|
+
console.log(
|
|
450
|
+
`[dcs-seo] emitSiteFiles: wrote ${siteFiles.length} site file(s): ${siteFiles.join(", ") || "(none)"}`
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
247
454
|
} catch (error) {
|
|
248
455
|
console.warn("[dcs-seo] emitStaticHtml failed; build output left unchanged:", error);
|
|
249
456
|
}
|
|
@@ -629,7 +836,7 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
629
836
|
}
|
|
630
837
|
|
|
631
838
|
// src/plugins/responsiveImagePlugin.ts
|
|
632
|
-
var CDN_PATTERN = /^(https?:\/\/files\.[^/]+\/[^/]+\/assets\/(?:[^/]+\/)*)([a-f0-9-]+)\.(\w+)$/;
|
|
839
|
+
var CDN_PATTERN = /^(https?:\/\/files\.[^/]+\/(?:content\/)?[^/]+\/assets\/(?:[^/]+\/)*)([a-f0-9-]+)\.(\w+)$/;
|
|
633
840
|
function escapeHtml2(str) {
|
|
634
841
|
return str.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
635
842
|
}
|
|
@@ -653,15 +860,17 @@ function responsiveImagePlugin(md) {
|
|
|
653
860
|
`${basePath}${uuid}-lg.webp 1920w`
|
|
654
861
|
].join(", ");
|
|
655
862
|
const escapedAlt = escapeHtml2(alt);
|
|
863
|
+
const escapedOriginal = escapeHtml2(src);
|
|
864
|
+
const onError = `this.onerror=null;this.src='${escapedOriginal}';this.closest('picture')?.querySelectorAll('source').forEach(s=>s.remove())`;
|
|
656
865
|
return [
|
|
657
866
|
"<picture>",
|
|
658
867
|
` <source srcset="${srcset}" type="image/webp" sizes="(max-width: 1024px) 100vw, 1024px" />`,
|
|
659
|
-
` <img src="${basePath}${uuid}-md.webp" alt="${escapedAlt}" loading="lazy" decoding="async" />`,
|
|
868
|
+
` <img src="${basePath}${uuid}-md.webp" alt="${escapedAlt}" loading="lazy" decoding="async" onerror="${onError}" />`,
|
|
660
869
|
"</picture>"
|
|
661
870
|
].join("\n");
|
|
662
871
|
};
|
|
663
872
|
}
|
|
664
873
|
|
|
665
|
-
export { dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, emitStaticSeoHtml, responsiveImagePlugin };
|
|
874
|
+
export { dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, emitSiteFiles, emitStaticSeoHtml, responsiveImagePlugin };
|
|
666
875
|
//# sourceMappingURL=index.js.map
|
|
667
876
|
//# sourceMappingURL=index.js.map
|