@duffcloudservices/cms 0.7.1 → 0.9.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/{chunk-FUNIALH6.js → chunk-UPAMLKOQ.js} +33 -14
- package/dist/chunk-UPAMLKOQ.js.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/plugins/index.d.ts +2 -2
- package/dist/plugins/index.js +245 -88
- package/dist/plugins/index.js.map +1 -1
- package/dist/seo/index.d.ts +2 -2
- package/dist/seo/index.js +1 -1
- package/dist/{vitepressTransform-y8Ru9elj.d.ts → vitepressTransform-DeEzgGWU.d.ts} +10 -2
- package/package.json +1 -1
- package/dist/chunk-FUNIALH6.js.map +0 -1
package/dist/plugins/index.js
CHANGED
|
@@ -1,28 +1,66 @@
|
|
|
1
|
-
import { buildSitemapXml, buildRobotsTxt, buildLlmsTxt, matchesExcludedGlob, breadcrumbTrailFromRoute, findReviewItemsForPage, buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-
|
|
2
|
-
export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
import { buildSitemapXml, isHandAuthoredRobotsAcceptable, buildRobotsTxt, buildLlmsTxt, matchesExcludedGlob, breadcrumbTrailFromRoute, findReviewItemsForPage, buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-UPAMLKOQ.js';
|
|
2
|
+
export { buildVitePressSeoHead, createSeoTransformPageData, defaultRelativePathToRoute } from '../chunk-UPAMLKOQ.js';
|
|
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/dcsContentPlugin.ts
|
|
8
37
|
function dcsContentPlugin(options = {}) {
|
|
9
38
|
const { contentPath = ".dcs/content.yaml", debug = false } = options;
|
|
10
39
|
let resolvedConfig;
|
|
40
|
+
let isDev = false;
|
|
11
41
|
return {
|
|
12
42
|
name: "dcs-content",
|
|
13
43
|
configResolved(config) {
|
|
14
44
|
resolvedConfig = config;
|
|
45
|
+
isDev = config.command === "serve";
|
|
15
46
|
},
|
|
16
|
-
config(config) {
|
|
47
|
+
config(config, env) {
|
|
48
|
+
if (env.command === "serve") {
|
|
49
|
+
return {
|
|
50
|
+
define: {
|
|
51
|
+
__DCS_CONTENT__: "globalThis.__DCS_CONTENT__"
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
17
55
|
const projectRoot = config.root || process.cwd();
|
|
18
56
|
const possiblePaths = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
57
|
+
path3.resolve(projectRoot, contentPath),
|
|
58
|
+
path3.resolve(projectRoot, "..", contentPath),
|
|
59
|
+
path3.resolve(process.cwd(), contentPath)
|
|
22
60
|
];
|
|
23
61
|
let foundPath;
|
|
24
62
|
for (const testPath of possiblePaths) {
|
|
25
|
-
if (
|
|
63
|
+
if (fs3.existsSync(testPath)) {
|
|
26
64
|
foundPath = testPath;
|
|
27
65
|
break;
|
|
28
66
|
}
|
|
@@ -40,8 +78,8 @@ function dcsContentPlugin(options = {}) {
|
|
|
40
78
|
};
|
|
41
79
|
}
|
|
42
80
|
try {
|
|
43
|
-
const fileContent =
|
|
44
|
-
const content =
|
|
81
|
+
const fileContent = fs3.readFileSync(foundPath, "utf8");
|
|
82
|
+
const content = yaml3.load(fileContent);
|
|
45
83
|
if (debug) {
|
|
46
84
|
console.log(`[dcs-content] Loaded ${foundPath}`);
|
|
47
85
|
console.log(`[dcs-content] Version: ${content.version}`);
|
|
@@ -62,42 +100,88 @@ function dcsContentPlugin(options = {}) {
|
|
|
62
100
|
};
|
|
63
101
|
}
|
|
64
102
|
},
|
|
65
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Dev-only: inject the FRESH on-disk `content.yaml` as an inline
|
|
105
|
+
* `globalThis.__DCS_CONTENT__ = {…}` classic script at the top of `<head>`.
|
|
106
|
+
*
|
|
107
|
+
* Runs on every dev HTML request, so the portal's `?_hc` hard reload (or any
|
|
108
|
+
* full page reload) re-reads the YAML with no restart. A classic `<script>`
|
|
109
|
+
* in `<head>` executes during HTML parsing, before the deferred module app
|
|
110
|
+
* entry, so the global is set before the consumer's `useTextContent` setup
|
|
111
|
+
* reads it. Gated on `isDev` so it never runs in `vite`/`vitepress build`
|
|
112
|
+
* (the build path keeps the baked `define` literal).
|
|
113
|
+
*/
|
|
114
|
+
transformIndexHtml: {
|
|
115
|
+
order: "pre",
|
|
116
|
+
handler(html) {
|
|
117
|
+
if (!isDev) return html;
|
|
118
|
+
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
119
|
+
const content = readDcsYamlFresh(
|
|
120
|
+
projectRoot,
|
|
121
|
+
contentPath,
|
|
122
|
+
"dcs-content",
|
|
123
|
+
debug
|
|
124
|
+
);
|
|
125
|
+
if (content === void 0) return html;
|
|
126
|
+
if (debug) {
|
|
127
|
+
console.log("[dcs-content] serve: injecting fresh __DCS_CONTENT__ into <head>");
|
|
128
|
+
}
|
|
129
|
+
const tags = [
|
|
130
|
+
{
|
|
131
|
+
tag: "script",
|
|
132
|
+
children: buildGlobalAssignScript("__DCS_CONTENT__", content),
|
|
133
|
+
injectTo: "head-prepend"
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
return { html, tags };
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
// Watch for changes in development.
|
|
140
|
+
//
|
|
141
|
+
// Downgraded from `server.restart()` to a debounced client `full-reload`:
|
|
142
|
+
// freshness already comes from `transformIndexHtml` re-reading the YAML on
|
|
143
|
+
// every HTML request, so all we need on change is to nudge the browser to
|
|
144
|
+
// reload. A `server.restart()` would rebuild VitePress's dev MiniSearch
|
|
145
|
+
// index and can throw "server restart failed" on a duplicate heading id —
|
|
146
|
+
// exactly the fragility this fix removes. A `full-reload` does neither.
|
|
66
147
|
configureServer(server) {
|
|
67
148
|
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
68
149
|
const watchPaths = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
];
|
|
150
|
+
path3.resolve(projectRoot, contentPath),
|
|
151
|
+
path3.resolve(projectRoot, "..", contentPath)
|
|
152
|
+
].map((p) => path3.normalize(p));
|
|
72
153
|
watchPaths.forEach((watchPath) => {
|
|
73
|
-
if (
|
|
154
|
+
if (fs3.existsSync(watchPath)) {
|
|
74
155
|
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
156
|
}
|
|
84
157
|
});
|
|
158
|
+
let reloadTimer;
|
|
159
|
+
server.watcher.on("change", (changedPath) => {
|
|
160
|
+
if (!watchPaths.includes(path3.normalize(changedPath))) return;
|
|
161
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
162
|
+
reloadTimer = setTimeout(() => {
|
|
163
|
+
if (debug) {
|
|
164
|
+
console.log("[dcs-content] content.yaml changed, sending full-reload");
|
|
165
|
+
}
|
|
166
|
+
server.ws.send({ type: "full-reload" });
|
|
167
|
+
}, 100);
|
|
168
|
+
});
|
|
85
169
|
}
|
|
86
170
|
};
|
|
87
171
|
}
|
|
88
172
|
function resolveOutDir(config) {
|
|
89
173
|
const out = config.build?.outDir || "dist";
|
|
90
|
-
return
|
|
174
|
+
return path3.isAbsolute(out) ? out : path3.resolve(config.root || process.cwd(), out);
|
|
91
175
|
}
|
|
92
176
|
function loadSeoConfig(projectRoot, seoPath, debug) {
|
|
93
177
|
const possiblePaths = [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
178
|
+
path3.resolve(projectRoot, seoPath),
|
|
179
|
+
path3.resolve(projectRoot, "..", seoPath),
|
|
180
|
+
path3.resolve(process.cwd(), seoPath)
|
|
97
181
|
];
|
|
98
182
|
let foundPath;
|
|
99
183
|
for (const testPath of possiblePaths) {
|
|
100
|
-
if (
|
|
184
|
+
if (fs3.existsSync(testPath)) {
|
|
101
185
|
foundPath = testPath;
|
|
102
186
|
break;
|
|
103
187
|
}
|
|
@@ -111,8 +195,8 @@ function loadSeoConfig(projectRoot, seoPath, debug) {
|
|
|
111
195
|
return null;
|
|
112
196
|
}
|
|
113
197
|
try {
|
|
114
|
-
const fileContent =
|
|
115
|
-
const config =
|
|
198
|
+
const fileContent = fs3.readFileSync(foundPath, "utf8");
|
|
199
|
+
const config = yaml3.load(fileContent);
|
|
116
200
|
return { config, foundPath };
|
|
117
201
|
} catch (error) {
|
|
118
202
|
console.warn("[dcs-seo] Failed to parse seo.yaml:", error);
|
|
@@ -121,17 +205,17 @@ function loadSeoConfig(projectRoot, seoPath, debug) {
|
|
|
121
205
|
}
|
|
122
206
|
function loadContentConfig(projectRoot, contentRelPath, debug) {
|
|
123
207
|
const possiblePaths = [
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
208
|
+
path3.resolve(projectRoot, contentRelPath),
|
|
209
|
+
path3.resolve(projectRoot, "..", contentRelPath),
|
|
210
|
+
path3.resolve(process.cwd(), contentRelPath)
|
|
127
211
|
];
|
|
128
|
-
const foundPath = possiblePaths.find((p) =>
|
|
212
|
+
const foundPath = possiblePaths.find((p) => fs3.existsSync(p));
|
|
129
213
|
if (!foundPath) {
|
|
130
214
|
if (debug) console.log(`[dcs-seo] No content.yaml found (reviews disabled)`);
|
|
131
215
|
return void 0;
|
|
132
216
|
}
|
|
133
217
|
try {
|
|
134
|
-
const parsed =
|
|
218
|
+
const parsed = yaml3.load(fs3.readFileSync(foundPath, "utf8"));
|
|
135
219
|
if (!parsed || typeof parsed !== "object") return void 0;
|
|
136
220
|
return parsed;
|
|
137
221
|
} catch (error) {
|
|
@@ -141,19 +225,19 @@ function loadContentConfig(projectRoot, contentRelPath, debug) {
|
|
|
141
225
|
}
|
|
142
226
|
function routeToOutputFile(outDir, routePath) {
|
|
143
227
|
const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
144
|
-
if (trimmed === "") return
|
|
145
|
-
return
|
|
228
|
+
if (trimmed === "") return path3.join(outDir, "index.html");
|
|
229
|
+
return path3.join(outDir, ...trimmed.split("/"), "index.html");
|
|
146
230
|
}
|
|
147
231
|
function loadExcludedGlobs(projectRoot, pagesPath) {
|
|
148
232
|
const possiblePaths = [
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
233
|
+
path3.resolve(projectRoot, pagesPath),
|
|
234
|
+
path3.resolve(projectRoot, "..", pagesPath),
|
|
235
|
+
path3.resolve(process.cwd(), pagesPath)
|
|
152
236
|
];
|
|
153
|
-
const found = possiblePaths.find((p) =>
|
|
237
|
+
const found = possiblePaths.find((p) => fs3.existsSync(p));
|
|
154
238
|
if (!found) return [];
|
|
155
239
|
try {
|
|
156
|
-
const raw =
|
|
240
|
+
const raw = yaml3.load(fs3.readFileSync(found, "utf8"));
|
|
157
241
|
const excluded = raw?.excluded;
|
|
158
242
|
if (!Array.isArray(excluded)) return [];
|
|
159
243
|
return excluded.filter((e) => typeof e === "string");
|
|
@@ -165,14 +249,14 @@ function deriveLastmod(seoConfig, projectRoot, pagesPath) {
|
|
|
165
249
|
const fromSeo = seoConfig?.lastUpdated;
|
|
166
250
|
if (typeof fromSeo === "string" && fromSeo.length > 0) return fromSeo;
|
|
167
251
|
const possiblePaths = [
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
252
|
+
path3.resolve(projectRoot, pagesPath),
|
|
253
|
+
path3.resolve(projectRoot, "..", pagesPath),
|
|
254
|
+
path3.resolve(process.cwd(), pagesPath)
|
|
171
255
|
];
|
|
172
|
-
const found = possiblePaths.find((p) =>
|
|
256
|
+
const found = possiblePaths.find((p) => fs3.existsSync(p));
|
|
173
257
|
if (!found) return void 0;
|
|
174
258
|
try {
|
|
175
|
-
const raw =
|
|
259
|
+
const raw = yaml3.load(fs3.readFileSync(found, "utf8"));
|
|
176
260
|
const fromPages = raw?.lastUpdated;
|
|
177
261
|
if (typeof fromPages === "string" && fromPages.length > 0) return fromPages;
|
|
178
262
|
} catch {
|
|
@@ -210,7 +294,7 @@ function emitSiteFiles(params) {
|
|
|
210
294
|
lastmod
|
|
211
295
|
});
|
|
212
296
|
if (xml) {
|
|
213
|
-
|
|
297
|
+
fs3.writeFileSync(path3.join(outDir, "sitemap.xml"), xml, "utf8");
|
|
214
298
|
wroteSitemap = true;
|
|
215
299
|
written.push("sitemap.xml");
|
|
216
300
|
if (debug) console.log("[dcs-seo] wrote sitemap.xml");
|
|
@@ -219,9 +303,12 @@ function emitSiteFiles(params) {
|
|
|
219
303
|
}
|
|
220
304
|
}
|
|
221
305
|
if (robots.enabled ?? true) {
|
|
222
|
-
const robotsPath =
|
|
223
|
-
|
|
224
|
-
|
|
306
|
+
const robotsPath = path3.join(outDir, "robots.txt");
|
|
307
|
+
const existing = fs3.existsSync(robotsPath) && !robots.force ? fs3.readFileSync(robotsPath, "utf8") : null;
|
|
308
|
+
const keepHandAuthored = existing !== null && isHandAuthoredRobotsAcceptable(existing);
|
|
309
|
+
if (keepHandAuthored) {
|
|
310
|
+
if (debug)
|
|
311
|
+
console.log("[dcs-seo] dist/robots.txt is hand-authored and meets the floor; leaving it");
|
|
225
312
|
} else {
|
|
226
313
|
const txt = buildRobotsTxt({
|
|
227
314
|
siteUrl: effectiveSiteUrl,
|
|
@@ -229,9 +316,13 @@ function emitSiteFiles(params) {
|
|
|
229
316
|
robots,
|
|
230
317
|
hasSitemap: wroteSitemap
|
|
231
318
|
});
|
|
232
|
-
|
|
319
|
+
fs3.writeFileSync(robotsPath, txt, "utf8");
|
|
233
320
|
written.push("robots.txt");
|
|
234
|
-
if (debug)
|
|
321
|
+
if (debug) {
|
|
322
|
+
console.log(
|
|
323
|
+
existing !== null ? "[dcs-seo] dist/robots.txt failed the factory floor; overwrote with emitted robots" : "[dcs-seo] wrote robots.txt"
|
|
324
|
+
);
|
|
325
|
+
}
|
|
235
326
|
}
|
|
236
327
|
}
|
|
237
328
|
if (llms && !preview) {
|
|
@@ -244,7 +335,7 @@ function emitSiteFiles(params) {
|
|
|
244
335
|
excludedGlobs
|
|
245
336
|
});
|
|
246
337
|
if (txt) {
|
|
247
|
-
|
|
338
|
+
fs3.writeFileSync(path3.join(outDir, "llms.txt"), txt, "utf8");
|
|
248
339
|
written.push("llms.txt");
|
|
249
340
|
if (debug) console.log("[dcs-seo] wrote llms.txt");
|
|
250
341
|
} else if (debug) {
|
|
@@ -309,12 +400,12 @@ function emitStaticSeoHtml(params) {
|
|
|
309
400
|
});
|
|
310
401
|
const html = spliceHeadHtml(shellHtml, tags);
|
|
311
402
|
const outFile = routeToOutputFile(outDir, route.path);
|
|
312
|
-
|
|
313
|
-
|
|
403
|
+
fs3.mkdirSync(path3.dirname(outFile), { recursive: true });
|
|
404
|
+
fs3.writeFileSync(outFile, html, "utf8");
|
|
314
405
|
written++;
|
|
315
406
|
if (debug) {
|
|
316
407
|
console.log(
|
|
317
|
-
`[dcs-seo] wrote ${
|
|
408
|
+
`[dcs-seo] wrote ${path3.relative(outDir, outFile)} (title="${tags.title}"${forceNoindex ? ", noindex" : ""})`
|
|
318
409
|
);
|
|
319
410
|
}
|
|
320
411
|
}
|
|
@@ -338,12 +429,21 @@ function dcsSeoPlugin(options = {}) {
|
|
|
338
429
|
llms = true
|
|
339
430
|
} = options;
|
|
340
431
|
let resolvedConfig;
|
|
432
|
+
let isDev = false;
|
|
341
433
|
return {
|
|
342
434
|
name: "dcs-seo",
|
|
343
435
|
configResolved(config) {
|
|
344
436
|
resolvedConfig = config;
|
|
437
|
+
isDev = config.command === "serve";
|
|
345
438
|
},
|
|
346
|
-
config(config) {
|
|
439
|
+
config(config, env) {
|
|
440
|
+
if (env.command === "serve") {
|
|
441
|
+
return {
|
|
442
|
+
define: {
|
|
443
|
+
__DCS_SEO__: "globalThis.__DCS_SEO__"
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
347
447
|
const projectRoot = config.root || process.cwd();
|
|
348
448
|
const loaded = loadSeoConfig(projectRoot, seoPath, debug);
|
|
349
449
|
if (!loaded) {
|
|
@@ -389,12 +489,12 @@ function dcsSeoPlugin(options = {}) {
|
|
|
389
489
|
try {
|
|
390
490
|
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
391
491
|
const outDir = resolveOutDir(resolvedConfig);
|
|
392
|
-
if (!
|
|
492
|
+
if (!fs3.existsSync(outDir)) {
|
|
393
493
|
console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`);
|
|
394
494
|
return;
|
|
395
495
|
}
|
|
396
|
-
const shellPath =
|
|
397
|
-
if (!
|
|
496
|
+
const shellPath = path3.join(outDir, "index.html");
|
|
497
|
+
if (!fs3.existsSync(shellPath)) {
|
|
398
498
|
console.warn(
|
|
399
499
|
`[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`
|
|
400
500
|
);
|
|
@@ -413,7 +513,7 @@ function dcsSeoPlugin(options = {}) {
|
|
|
413
513
|
`[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`
|
|
414
514
|
);
|
|
415
515
|
}
|
|
416
|
-
const shellHtml =
|
|
516
|
+
const shellHtml = fs3.readFileSync(shellPath, "utf8");
|
|
417
517
|
const excludedGlobs = loadExcludedGlobs(projectRoot, pagesPath);
|
|
418
518
|
const contentConfig = loadContentConfig(projectRoot, contentPath, debug);
|
|
419
519
|
const written = emitStaticSeoHtml({
|
|
@@ -455,26 +555,64 @@ function dcsSeoPlugin(options = {}) {
|
|
|
455
555
|
console.warn("[dcs-seo] emitStaticHtml failed; build output left unchanged:", error);
|
|
456
556
|
}
|
|
457
557
|
},
|
|
458
|
-
|
|
558
|
+
/**
|
|
559
|
+
* Dev-only: inject the FRESH on-disk `seo.yaml` as an inline
|
|
560
|
+
* `globalThis.__DCS_SEO__ = {…}` classic script at the top of `<head>`,
|
|
561
|
+
* mirroring dcsContentPlugin. Lets the runtime `useSEO` SPA path reflect
|
|
562
|
+
* on-disk SEO edits on the portal's `?_hc` hard reload with no restart.
|
|
563
|
+
* Gated on `isDev` so it never runs in `vite`/`vitepress build` (the build
|
|
564
|
+
* path keeps the baked `define` literal + the `writeBundle` emitter).
|
|
565
|
+
*/
|
|
566
|
+
transformIndexHtml: {
|
|
567
|
+
order: "pre",
|
|
568
|
+
handler(html) {
|
|
569
|
+
if (!isDev) return html;
|
|
570
|
+
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
571
|
+
const config = readDcsYamlFresh(projectRoot, seoPath, "dcs-seo", debug);
|
|
572
|
+
if (config === void 0) return html;
|
|
573
|
+
if (debug) {
|
|
574
|
+
console.log("[dcs-seo] serve: injecting fresh __DCS_SEO__ into <head>");
|
|
575
|
+
}
|
|
576
|
+
const tags = [
|
|
577
|
+
{
|
|
578
|
+
tag: "script",
|
|
579
|
+
children: buildGlobalAssignScript("__DCS_SEO__", config),
|
|
580
|
+
injectTo: "head-prepend"
|
|
581
|
+
}
|
|
582
|
+
];
|
|
583
|
+
return { html, tags };
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
// Watch for changes in development.
|
|
587
|
+
//
|
|
588
|
+
// Downgraded from `server.restart()` to a debounced client `full-reload`:
|
|
589
|
+
// freshness already comes from `transformIndexHtml` re-reading the YAML on
|
|
590
|
+
// every HTML request, so all we need on change is a browser reload. A
|
|
591
|
+
// `server.restart()` would rebuild VitePress's dev MiniSearch index and can
|
|
592
|
+
// throw "server restart failed" on a duplicate heading id — the fragility
|
|
593
|
+
// this fix removes. A `full-reload` does neither.
|
|
459
594
|
configureServer(server) {
|
|
460
595
|
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
461
596
|
const watchPaths = [
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
];
|
|
597
|
+
path3.resolve(projectRoot, seoPath),
|
|
598
|
+
path3.resolve(projectRoot, "..", seoPath)
|
|
599
|
+
].map((p) => path3.normalize(p));
|
|
465
600
|
watchPaths.forEach((watchPath) => {
|
|
466
|
-
if (
|
|
601
|
+
if (fs3.existsSync(watchPath)) {
|
|
467
602
|
server.watcher.add(watchPath);
|
|
468
|
-
server.watcher.on("change", (changedPath) => {
|
|
469
|
-
if (changedPath === watchPath) {
|
|
470
|
-
if (debug) {
|
|
471
|
-
console.log("[dcs-seo] seo.yaml changed, triggering reload");
|
|
472
|
-
}
|
|
473
|
-
server.restart();
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
603
|
}
|
|
477
604
|
});
|
|
605
|
+
let reloadTimer;
|
|
606
|
+
server.watcher.on("change", (changedPath) => {
|
|
607
|
+
if (!watchPaths.includes(path3.normalize(changedPath))) return;
|
|
608
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
609
|
+
reloadTimer = setTimeout(() => {
|
|
610
|
+
if (debug) {
|
|
611
|
+
console.log("[dcs-seo] seo.yaml changed, sending full-reload");
|
|
612
|
+
}
|
|
613
|
+
server.ws.send({ type: "full-reload" });
|
|
614
|
+
}, 100);
|
|
615
|
+
});
|
|
478
616
|
}
|
|
479
617
|
};
|
|
480
618
|
}
|
|
@@ -486,11 +624,24 @@ function dcsEditorPlugin(options = {}) {
|
|
|
486
624
|
const RIBBON_VIRTUAL_PATH = "/__dcs-preview-ribbon.js";
|
|
487
625
|
let isDev = false;
|
|
488
626
|
let basePath = "/";
|
|
627
|
+
let isPreview = false;
|
|
489
628
|
return {
|
|
490
629
|
name: "dcs-editor-bridge",
|
|
630
|
+
// Preview only: stop Vite running an unreachable HMR WebSocket server.
|
|
631
|
+
// Freshness comes from the cms content/seo plugins (fresh transformIndexHtml
|
|
632
|
+
// re-read) + the portal's ?_hc hard reload, not HMR. NOTE: this alone does
|
|
633
|
+
// NOT silence the browser — Vite still injects @vite/client and calls
|
|
634
|
+
// connect(); the client-side stub in transformIndexHtml is what removes the
|
|
635
|
+
// console errors. A normal local `vite dev` (no DCS_PREVIEW) keeps full HMR.
|
|
636
|
+
config(_config, env) {
|
|
637
|
+
if (env.command === "serve" && process.env.DCS_PREVIEW === "true") {
|
|
638
|
+
return { server: { hmr: false } };
|
|
639
|
+
}
|
|
640
|
+
},
|
|
491
641
|
configResolved(config) {
|
|
492
642
|
isDev = config.command === "serve";
|
|
493
643
|
basePath = config.base ?? "/";
|
|
644
|
+
isPreview = process.env.DCS_PREVIEW === "true";
|
|
494
645
|
},
|
|
495
646
|
resolveId(id) {
|
|
496
647
|
if (id === VIRTUAL_PATH || id === RIBBON_VIRTUAL_PATH) {
|
|
@@ -558,9 +709,15 @@ if (document.readyState === 'loading') {
|
|
|
558
709
|
}
|
|
559
710
|
const editorTag = `<script type="module" src="${VIRTUAL_PATH}"></script>`;
|
|
560
711
|
const ribbonTag = `<script type="module" src="${RIBBON_VIRTUAL_PATH}"></script>`;
|
|
561
|
-
|
|
712
|
+
const bodyHtml = html.replace("</body>", `${editorTag}
|
|
562
713
|
${ribbonTag}
|
|
563
714
|
</body>`);
|
|
715
|
+
if (!isPreview) return bodyHtml;
|
|
716
|
+
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){}})()`;
|
|
717
|
+
return {
|
|
718
|
+
html: bodyHtml,
|
|
719
|
+
tags: [{ tag: "script", children: wsStub, injectTo: "head-prepend" }]
|
|
720
|
+
};
|
|
564
721
|
}
|
|
565
722
|
}
|
|
566
723
|
};
|
|
@@ -604,14 +761,14 @@ function buildPictureElement(entry, alt, extraAttrs, sizes) {
|
|
|
604
761
|
}
|
|
605
762
|
function loadCdnImageMap(projectRoot, relativeMapPath, debug) {
|
|
606
763
|
const possiblePaths = [
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
764
|
+
path3.resolve(projectRoot, relativeMapPath),
|
|
765
|
+
path3.resolve(projectRoot, "..", relativeMapPath),
|
|
766
|
+
path3.resolve(process.cwd(), relativeMapPath)
|
|
610
767
|
];
|
|
611
768
|
for (const testPath of possiblePaths) {
|
|
612
|
-
if (
|
|
769
|
+
if (fs3.existsSync(testPath)) {
|
|
613
770
|
try {
|
|
614
|
-
const raw =
|
|
771
|
+
const raw = fs3.readFileSync(testPath, "utf8");
|
|
615
772
|
const data = JSON.parse(raw);
|
|
616
773
|
const map = /* @__PURE__ */ new Map();
|
|
617
774
|
for (const entry of data.images) {
|
|
@@ -772,13 +929,13 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
772
929
|
return;
|
|
773
930
|
}
|
|
774
931
|
const outDir = siteConfig.outDir;
|
|
775
|
-
if (!
|
|
932
|
+
if (!fs3.existsSync(outDir)) return;
|
|
776
933
|
let filesProcessed = 0;
|
|
777
934
|
let refsReplaced = 0;
|
|
778
935
|
function walkDir(dir) {
|
|
779
|
-
const entries =
|
|
936
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
780
937
|
for (const entry of entries) {
|
|
781
|
-
const fullPath =
|
|
938
|
+
const fullPath = path3.join(dir, entry.name);
|
|
782
939
|
if (entry.isDirectory()) {
|
|
783
940
|
walkDir(fullPath);
|
|
784
941
|
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -787,7 +944,7 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
787
944
|
}
|
|
788
945
|
}
|
|
789
946
|
function processFile(filePath) {
|
|
790
|
-
let content =
|
|
947
|
+
let content = fs3.readFileSync(filePath, "utf8");
|
|
791
948
|
if (!pathPrefixes.some((p) => content.includes(p))) return;
|
|
792
949
|
let changed = false;
|
|
793
950
|
if (filePath.endsWith(".html")) {
|
|
@@ -822,7 +979,7 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
822
979
|
}
|
|
823
980
|
}
|
|
824
981
|
if (changed) {
|
|
825
|
-
|
|
982
|
+
fs3.writeFileSync(filePath, content, "utf8");
|
|
826
983
|
filesProcessed++;
|
|
827
984
|
}
|
|
828
985
|
}
|