@duffcloudservices/cms 0.3.16 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -4
- package/dist/chunk-RDYVYYTC.js +311 -0
- package/dist/chunk-RDYVYYTC.js.map +1 -0
- package/dist/index.d.ts +199 -263
- package/dist/index.js +14 -175
- package/dist/index.js.map +1 -1
- package/dist/plugins/index.d.ts +93 -16
- package/dist/plugins/index.js +167 -61
- package/dist/plugins/index.js.map +1 -1
- package/dist/seo-DsJjfI1p.d.ts +267 -0
- package/package.json +4 -1
- package/src/components/ManagedImage.vue +66 -0
- package/src/components/PreviewRibbon.vue +4 -5
- package/src/components/ResponsiveImage.vue +11 -2
- package/src/composables/useReleaseNotes.ts +6 -7
- package/src/composables/useSEO.ts +21 -259
- package/src/composables/useSiteVersion.ts +5 -6
- package/src/composables/useTextContent.test.ts +46 -0
- package/src/composables/useTextContent.ts +13 -5
package/dist/plugins/index.d.ts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
|
+
import { S as SeoConfiguration } from '../seo-DsJjfI1p.js';
|
|
2
3
|
import { Component, Plugin as Plugin$1 } from 'vue';
|
|
3
4
|
import MarkdownIt from 'markdown-it';
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Loader for the `.dcs/pages.yaml` route manifest.
|
|
8
|
+
*
|
|
9
|
+
* `pages.yaml` is the canonical page registry maintained by the DCS portal and
|
|
10
|
+
* by Copilot when scaffolding pages. For the build-time SEO emitter we only
|
|
11
|
+
* need each route's `slug` and `path` (e.g. `{ slug: 'home', path: '/' }`).
|
|
12
|
+
*
|
|
13
|
+
* The parser is intentionally defensive: any missing/unparseable file or
|
|
14
|
+
* malformed entry yields `null` (caller logs + no-ops) so a bad manifest can
|
|
15
|
+
* never break a production build.
|
|
16
|
+
*/
|
|
17
|
+
/** A single route extracted from `.dcs/pages.yaml`. */
|
|
18
|
+
interface PageRouteEntry {
|
|
19
|
+
/** Page slug, matching an entry in `seo.yaml` `pages.<slug>` (may be absent). */
|
|
20
|
+
slug: string;
|
|
21
|
+
/** Route path, e.g. `/`, `/services`, `/blog/my-post`. */
|
|
22
|
+
path: string;
|
|
23
|
+
/**
|
|
24
|
+
* Human title from the manifest (e.g. "Kitchen Cabinet Refresh"). Used as the
|
|
25
|
+
* per-route title fallback when `seo.yaml` has no entry for this page, so
|
|
26
|
+
* un-configured routes (e.g. blog posts) get unique titles. Optional.
|
|
27
|
+
*/
|
|
28
|
+
title?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
5
31
|
/**
|
|
6
32
|
* DCS Content Plugin for Vite
|
|
7
33
|
*
|
|
@@ -53,33 +79,45 @@ declare function dcsContentPlugin(options?: DcsContentPluginOptions): Plugin;
|
|
|
53
79
|
/**
|
|
54
80
|
* DCS SEO Plugin for Vite
|
|
55
81
|
*
|
|
56
|
-
*
|
|
57
|
-
* as `__DCS_SEO__` global variable for use by useSEO.
|
|
82
|
+
* Two responsibilities, both driven by `.dcs/seo.yaml`:
|
|
58
83
|
*
|
|
59
|
-
*
|
|
84
|
+
* 1. **Build-time define** (always on): reads `.dcs/seo.yaml` and injects it as
|
|
85
|
+
* the `__DCS_SEO__` global for the runtime `useSEO` composable.
|
|
86
|
+
*
|
|
87
|
+
* 2. **Static `<head>` emitter** (opt-in via `emitStaticHtml: true`, default
|
|
88
|
+
* OFF): after the bundle is written, reads the SPA's built `index.html`,
|
|
89
|
+
* and for every route in `.dcs/pages.yaml` writes a per-route
|
|
90
|
+
* `dist/<path>/index.html` whose `<head>` carries the resolved title, meta,
|
|
91
|
+
* canonical, Open Graph, Twitter, and JSON-LD tags. This gives a Vue SPA
|
|
92
|
+
* per-route static SEO **without** vite-ssg.
|
|
93
|
+
*
|
|
94
|
+
* VitePress sites already bake SEO via their own config, so they leave this
|
|
95
|
+
* option OFF and are completely unaffected.
|
|
96
|
+
*
|
|
97
|
+
* @example Runtime define only (default — safe for VitePress)
|
|
60
98
|
* ```typescript
|
|
61
99
|
* // vite.config.ts
|
|
62
100
|
* import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'
|
|
63
101
|
*
|
|
64
102
|
* export default defineConfig({
|
|
65
|
-
* plugins: [
|
|
66
|
-
* dcsSeoPlugin({ debug: true })
|
|
67
|
-
* ]
|
|
103
|
+
* plugins: [dcsSeoPlugin({ debug: true })]
|
|
68
104
|
* })
|
|
69
105
|
* ```
|
|
70
106
|
*
|
|
71
|
-
*
|
|
107
|
+
* @example Vue SPA with per-route static <head> emission
|
|
72
108
|
* ```typescript
|
|
73
|
-
* // .
|
|
74
|
-
* import { defineConfig } from 'vitepress'
|
|
109
|
+
* // vite.config.ts
|
|
75
110
|
* import { dcsSeoPlugin } from '@duffcloudservices/cms/plugins'
|
|
76
111
|
*
|
|
77
112
|
* export default defineConfig({
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
113
|
+
* plugins: [
|
|
114
|
+
* dcsSeoPlugin({
|
|
115
|
+
* emitStaticHtml: true, // turn the emitter ON
|
|
116
|
+
* pagesPath: '.dcs/pages.yaml', // route manifest (default)
|
|
117
|
+
* noindex: ['account', 'projects'], // robots: noindex,nofollow
|
|
118
|
+
* exclude: ['/preview'], // skip these routes entirely
|
|
119
|
+
* })
|
|
120
|
+
* ]
|
|
83
121
|
* })
|
|
84
122
|
* ```
|
|
85
123
|
*/
|
|
@@ -89,9 +127,48 @@ interface DcsSeoPluginOptions {
|
|
|
89
127
|
seoPath?: string;
|
|
90
128
|
/** Enable debug logging */
|
|
91
129
|
debug?: boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Opt-in: emit per-route static `<head>` (meta + JSON-LD) into the built
|
|
132
|
+
* `dist/` at the end of the build. Default `false` — VitePress sites and any
|
|
133
|
+
* site that bakes its own SEO are unaffected when this is off.
|
|
134
|
+
*/
|
|
135
|
+
emitStaticHtml?: boolean;
|
|
136
|
+
/**
|
|
137
|
+
* Path to the route manifest relative to project root, used only when
|
|
138
|
+
* `emitStaticHtml` is true. Default `'.dcs/pages.yaml'`.
|
|
139
|
+
*/
|
|
140
|
+
pagesPath?: string;
|
|
141
|
+
/**
|
|
142
|
+
* Routes to skip entirely (no per-route HTML written). Matched against the
|
|
143
|
+
* route `path` (e.g. `'/preview'`) OR the route `slug` (e.g. `'account'`).
|
|
144
|
+
*/
|
|
145
|
+
exclude?: string[];
|
|
146
|
+
/**
|
|
147
|
+
* Routes that should receive `robots: noindex, nofollow`. Matched against the
|
|
148
|
+
* route `path` OR `slug`. The home route (`/`) still overwrites
|
|
149
|
+
* `dist/index.html`.
|
|
150
|
+
*/
|
|
151
|
+
noindex?: string[];
|
|
92
152
|
}
|
|
93
153
|
/**
|
|
94
|
-
*
|
|
154
|
+
* Core emitter, separated from Vite so it is unit-testable.
|
|
155
|
+
*
|
|
156
|
+
* For each route: build the head tags from the shared resolver, splice them
|
|
157
|
+
* into the shell HTML, and write `dist/<path>/index.html`. Returns the number
|
|
158
|
+
* of files written. Pure aside from `fs` writes — deterministic + idempotent.
|
|
159
|
+
*/
|
|
160
|
+
declare function emitStaticSeoHtml(params: {
|
|
161
|
+
outDir: string;
|
|
162
|
+
shellHtml: string;
|
|
163
|
+
routes: PageRouteEntry[];
|
|
164
|
+
seoConfig: SeoConfiguration | undefined;
|
|
165
|
+
exclude?: string[];
|
|
166
|
+
noindex?: string[];
|
|
167
|
+
debug?: boolean;
|
|
168
|
+
}): number;
|
|
169
|
+
/**
|
|
170
|
+
* Vite plugin that injects .dcs/seo.yaml at build time and, optionally, emits
|
|
171
|
+
* per-route static `<head>` HTML for SPA SEO.
|
|
95
172
|
*
|
|
96
173
|
* @param options - Plugin configuration
|
|
97
174
|
* @returns Vite plugin
|
|
@@ -345,4 +422,4 @@ declare function dcsCdnBuildEnd(options?: DcsCdnBuildEndOptions): (siteConfig: {
|
|
|
345
422
|
|
|
346
423
|
declare function responsiveImagePlugin(md: MarkdownIt): void;
|
|
347
424
|
|
|
348
|
-
export { type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, responsiveImagePlugin };
|
|
425
|
+
export { type DcsCdnBuildEndOptions, type DcsCdnImagePluginOptions, type DcsContentPluginOptions, type DcsEditorPluginOptions, type DcsPreviewPluginOptions, type DcsSeoPluginOptions, dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, emitStaticSeoHtml, responsiveImagePlugin };
|
package/dist/plugins/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import { buildHeadTags, spliceHeadHtml, loadPagesManifest } from '../chunk-RDYVYYTC.js';
|
|
2
|
+
import fs2 from 'fs';
|
|
3
|
+
import path2 from 'path';
|
|
3
4
|
import yaml from 'js-yaml';
|
|
4
5
|
import { defineComponent, h } from 'vue';
|
|
5
6
|
|
|
6
|
-
// src/plugins/dcsContentPlugin.ts
|
|
7
7
|
function dcsContentPlugin(options = {}) {
|
|
8
8
|
const { contentPath = ".dcs/content.yaml", debug = false } = options;
|
|
9
9
|
let resolvedConfig;
|
|
@@ -15,13 +15,13 @@ function dcsContentPlugin(options = {}) {
|
|
|
15
15
|
config(config) {
|
|
16
16
|
const projectRoot = config.root || process.cwd();
|
|
17
17
|
const possiblePaths = [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
path2.resolve(projectRoot, contentPath),
|
|
19
|
+
path2.resolve(projectRoot, "..", contentPath),
|
|
20
|
+
path2.resolve(process.cwd(), contentPath)
|
|
21
21
|
];
|
|
22
22
|
let foundPath;
|
|
23
23
|
for (const testPath of possiblePaths) {
|
|
24
|
-
if (
|
|
24
|
+
if (fs2.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 = fs2.readFileSync(foundPath, "utf8");
|
|
43
43
|
const content = yaml.load(fileContent);
|
|
44
44
|
if (debug) {
|
|
45
45
|
console.log(`[dcs-content] Loaded ${foundPath}`);
|
|
@@ -65,11 +65,11 @@ function dcsContentPlugin(options = {}) {
|
|
|
65
65
|
configureServer(server) {
|
|
66
66
|
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
67
67
|
const watchPaths = [
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
path2.resolve(projectRoot, contentPath),
|
|
69
|
+
path2.resolve(projectRoot, "..", contentPath)
|
|
70
70
|
];
|
|
71
71
|
watchPaths.forEach((watchPath) => {
|
|
72
|
-
if (
|
|
72
|
+
if (fs2.existsSync(watchPath)) {
|
|
73
73
|
server.watcher.add(watchPath);
|
|
74
74
|
server.watcher.on("change", (changedPath) => {
|
|
75
75
|
if (changedPath === watchPath) {
|
|
@@ -84,8 +84,83 @@ function dcsContentPlugin(options = {}) {
|
|
|
84
84
|
}
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
function resolveOutDir(config) {
|
|
88
|
+
const out = config.build?.outDir || "dist";
|
|
89
|
+
return path2.isAbsolute(out) ? out : path2.resolve(config.root || process.cwd(), out);
|
|
90
|
+
}
|
|
91
|
+
function loadSeoConfig(projectRoot, seoPath, debug) {
|
|
92
|
+
const possiblePaths = [
|
|
93
|
+
path2.resolve(projectRoot, seoPath),
|
|
94
|
+
path2.resolve(projectRoot, "..", seoPath),
|
|
95
|
+
path2.resolve(process.cwd(), seoPath)
|
|
96
|
+
];
|
|
97
|
+
let foundPath;
|
|
98
|
+
for (const testPath of possiblePaths) {
|
|
99
|
+
if (fs2.existsSync(testPath)) {
|
|
100
|
+
foundPath = testPath;
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (!foundPath) {
|
|
105
|
+
if (debug) {
|
|
106
|
+
console.log("[dcs-seo] No seo.yaml found at:");
|
|
107
|
+
possiblePaths.forEach((p) => console.log(` - ${p}`));
|
|
108
|
+
console.log("[dcs-seo] Using defaults only");
|
|
109
|
+
}
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const fileContent = fs2.readFileSync(foundPath, "utf8");
|
|
114
|
+
const config = yaml.load(fileContent);
|
|
115
|
+
return { config, foundPath };
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.warn("[dcs-seo] Failed to parse seo.yaml:", error);
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function routeToOutputFile(outDir, routePath) {
|
|
122
|
+
const trimmed = routePath.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
123
|
+
if (trimmed === "") return path2.join(outDir, "index.html");
|
|
124
|
+
return path2.join(outDir, ...trimmed.split("/"), "index.html");
|
|
125
|
+
}
|
|
126
|
+
function emitStaticSeoHtml(params) {
|
|
127
|
+
const { outDir, shellHtml, routes, seoConfig, exclude = [], noindex = [], debug = false } = params;
|
|
128
|
+
const excludeSet = new Set(exclude);
|
|
129
|
+
const noindexSet = new Set(noindex);
|
|
130
|
+
let written = 0;
|
|
131
|
+
for (const route of routes) {
|
|
132
|
+
if (excludeSet.has(route.path) || route.slug && excludeSet.has(route.slug)) {
|
|
133
|
+
if (debug) console.log(`[dcs-seo] skip (excluded): ${route.path}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const forceNoindex = noindexSet.has(route.path) || route.slug && noindexSet.has(route.slug);
|
|
137
|
+
const tags = buildHeadTags(route.slug, route.path, seoConfig, {
|
|
138
|
+
includeKeywords: true,
|
|
139
|
+
fallbackTitle: route.title,
|
|
140
|
+
robots: forceNoindex ? "noindex, nofollow" : void 0
|
|
141
|
+
});
|
|
142
|
+
const html = spliceHeadHtml(shellHtml, tags);
|
|
143
|
+
const outFile = routeToOutputFile(outDir, route.path);
|
|
144
|
+
fs2.mkdirSync(path2.dirname(outFile), { recursive: true });
|
|
145
|
+
fs2.writeFileSync(outFile, html, "utf8");
|
|
146
|
+
written++;
|
|
147
|
+
if (debug) {
|
|
148
|
+
console.log(
|
|
149
|
+
`[dcs-seo] wrote ${path2.relative(outDir, outFile)} (title="${tags.title}"${forceNoindex ? ", noindex" : ""})`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return written;
|
|
154
|
+
}
|
|
87
155
|
function dcsSeoPlugin(options = {}) {
|
|
88
|
-
const {
|
|
156
|
+
const {
|
|
157
|
+
seoPath = ".dcs/seo.yaml",
|
|
158
|
+
debug = false,
|
|
159
|
+
emitStaticHtml = false,
|
|
160
|
+
pagesPath = ".dcs/pages.yaml",
|
|
161
|
+
exclude = [],
|
|
162
|
+
noindex = []
|
|
163
|
+
} = options;
|
|
89
164
|
let resolvedConfig;
|
|
90
165
|
return {
|
|
91
166
|
name: "dcs-seo",
|
|
@@ -94,62 +169,93 @@ function dcsSeoPlugin(options = {}) {
|
|
|
94
169
|
},
|
|
95
170
|
config(config) {
|
|
96
171
|
const projectRoot = config.root || process.cwd();
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
path.resolve(projectRoot, "..", seoPath),
|
|
100
|
-
path.resolve(process.cwd(), seoPath)
|
|
101
|
-
];
|
|
102
|
-
let foundPath;
|
|
103
|
-
for (const testPath of possiblePaths) {
|
|
104
|
-
if (fs3.existsSync(testPath)) {
|
|
105
|
-
foundPath = testPath;
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (!foundPath) {
|
|
110
|
-
if (debug) {
|
|
111
|
-
console.log("[dcs-seo] No seo.yaml found at:");
|
|
112
|
-
possiblePaths.forEach((p) => console.log(` - ${p}`));
|
|
113
|
-
console.log("[dcs-seo] Using defaults only");
|
|
114
|
-
}
|
|
172
|
+
const loaded = loadSeoConfig(projectRoot, seoPath, debug);
|
|
173
|
+
if (!loaded) {
|
|
115
174
|
return {
|
|
116
175
|
define: {
|
|
117
176
|
__DCS_SEO__: "undefined"
|
|
118
177
|
}
|
|
119
178
|
};
|
|
120
179
|
}
|
|
180
|
+
if (debug) {
|
|
181
|
+
console.log(`[dcs-seo] Loaded ${loaded.foundPath}`);
|
|
182
|
+
console.log(`[dcs-seo] Version: ${loaded.config.version}`);
|
|
183
|
+
console.log(`[dcs-seo] Site Name: ${loaded.config.global?.siteName || "(not set)"}`);
|
|
184
|
+
console.log(
|
|
185
|
+
`[dcs-seo] Pages: ${Object.keys(loaded.config.pages ?? {}).join(", ") || "(none)"}`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
define: {
|
|
190
|
+
__DCS_SEO__: JSON.stringify(loaded.config)
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
/**
|
|
195
|
+
* Build-time static `<head>` emitter (opt-in via `emitStaticHtml`).
|
|
196
|
+
*
|
|
197
|
+
* Runs after the bundle is written so the built `index.html` shell exists
|
|
198
|
+
* on disk. For an SPA, Vite emits a single `index.html`; we fan it out to
|
|
199
|
+
* per-route files with route-specific `<head>` content.
|
|
200
|
+
*
|
|
201
|
+
* Fully defensive: any missing/unparseable input logs a warning and
|
|
202
|
+
* no-ops. Never throws (so it can never break a production build).
|
|
203
|
+
*/
|
|
204
|
+
writeBundle() {
|
|
205
|
+
if (!emitStaticHtml) return;
|
|
121
206
|
try {
|
|
122
|
-
const
|
|
123
|
-
const
|
|
207
|
+
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
208
|
+
const outDir = resolveOutDir(resolvedConfig);
|
|
209
|
+
if (!fs2.existsSync(outDir)) {
|
|
210
|
+
console.warn(`[dcs-seo] emitStaticHtml: outDir not found (${outDir}); skipping`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const shellPath = path2.join(outDir, "index.html");
|
|
214
|
+
if (!fs2.existsSync(shellPath)) {
|
|
215
|
+
console.warn(
|
|
216
|
+
`[dcs-seo] emitStaticHtml: no index.html in ${outDir}; nothing to emit`
|
|
217
|
+
);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const routes = loadPagesManifest(projectRoot, pagesPath, debug);
|
|
221
|
+
if (!routes) {
|
|
222
|
+
console.warn(
|
|
223
|
+
`[dcs-seo] emitStaticHtml: ${pagesPath} missing or empty; skipping per-route emission`
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const loaded = loadSeoConfig(projectRoot, seoPath, debug);
|
|
228
|
+
if (!loaded) {
|
|
229
|
+
console.warn(
|
|
230
|
+
`[dcs-seo] emitStaticHtml: ${seoPath} missing or unparseable; emitting with defaults only`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
const shellHtml = fs2.readFileSync(shellPath, "utf8");
|
|
234
|
+
const written = emitStaticSeoHtml({
|
|
235
|
+
outDir,
|
|
236
|
+
shellHtml,
|
|
237
|
+
routes,
|
|
238
|
+
seoConfig: loaded?.config,
|
|
239
|
+
exclude,
|
|
240
|
+
noindex,
|
|
241
|
+
debug
|
|
242
|
+
});
|
|
124
243
|
if (debug) {
|
|
125
|
-
console.log(`[dcs-seo]
|
|
126
|
-
console.log(`[dcs-seo] Version: ${seoConfig.version}`);
|
|
127
|
-
console.log(`[dcs-seo] Site Name: ${seoConfig.global?.siteName || "(not set)"}`);
|
|
128
|
-
console.log(`[dcs-seo] Pages: ${Object.keys(seoConfig.pages ?? {}).join(", ") || "(none)"}`);
|
|
244
|
+
console.log(`[dcs-seo] emitStaticHtml: wrote ${written} per-route HTML file(s)`);
|
|
129
245
|
}
|
|
130
|
-
return {
|
|
131
|
-
define: {
|
|
132
|
-
__DCS_SEO__: JSON.stringify(seoConfig)
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
246
|
} catch (error) {
|
|
136
|
-
console.warn("[dcs-seo]
|
|
137
|
-
return {
|
|
138
|
-
define: {
|
|
139
|
-
__DCS_SEO__: "undefined"
|
|
140
|
-
}
|
|
141
|
-
};
|
|
247
|
+
console.warn("[dcs-seo] emitStaticHtml failed; build output left unchanged:", error);
|
|
142
248
|
}
|
|
143
249
|
},
|
|
144
250
|
// Watch for changes in development
|
|
145
251
|
configureServer(server) {
|
|
146
252
|
const projectRoot = resolvedConfig?.root || process.cwd();
|
|
147
253
|
const watchPaths = [
|
|
148
|
-
|
|
149
|
-
|
|
254
|
+
path2.resolve(projectRoot, seoPath),
|
|
255
|
+
path2.resolve(projectRoot, "..", seoPath)
|
|
150
256
|
];
|
|
151
257
|
watchPaths.forEach((watchPath) => {
|
|
152
|
-
if (
|
|
258
|
+
if (fs2.existsSync(watchPath)) {
|
|
153
259
|
server.watcher.add(watchPath);
|
|
154
260
|
server.watcher.on("change", (changedPath) => {
|
|
155
261
|
if (changedPath === watchPath) {
|
|
@@ -290,14 +396,14 @@ function buildPictureElement(entry, alt, extraAttrs, sizes) {
|
|
|
290
396
|
}
|
|
291
397
|
function loadCdnImageMap(projectRoot, relativeMapPath, debug) {
|
|
292
398
|
const possiblePaths = [
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
399
|
+
path2.resolve(projectRoot, relativeMapPath),
|
|
400
|
+
path2.resolve(projectRoot, "..", relativeMapPath),
|
|
401
|
+
path2.resolve(process.cwd(), relativeMapPath)
|
|
296
402
|
];
|
|
297
403
|
for (const testPath of possiblePaths) {
|
|
298
|
-
if (
|
|
404
|
+
if (fs2.existsSync(testPath)) {
|
|
299
405
|
try {
|
|
300
|
-
const raw =
|
|
406
|
+
const raw = fs2.readFileSync(testPath, "utf8");
|
|
301
407
|
const data = JSON.parse(raw);
|
|
302
408
|
const map = /* @__PURE__ */ new Map();
|
|
303
409
|
for (const entry of data.images) {
|
|
@@ -458,13 +564,13 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
458
564
|
return;
|
|
459
565
|
}
|
|
460
566
|
const outDir = siteConfig.outDir;
|
|
461
|
-
if (!
|
|
567
|
+
if (!fs2.existsSync(outDir)) return;
|
|
462
568
|
let filesProcessed = 0;
|
|
463
569
|
let refsReplaced = 0;
|
|
464
570
|
function walkDir(dir) {
|
|
465
|
-
const entries =
|
|
571
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
466
572
|
for (const entry of entries) {
|
|
467
|
-
const fullPath =
|
|
573
|
+
const fullPath = path2.join(dir, entry.name);
|
|
468
574
|
if (entry.isDirectory()) {
|
|
469
575
|
walkDir(fullPath);
|
|
470
576
|
} else if (extensions.some((ext) => entry.name.endsWith(ext))) {
|
|
@@ -473,7 +579,7 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
473
579
|
}
|
|
474
580
|
}
|
|
475
581
|
function processFile(filePath) {
|
|
476
|
-
let content =
|
|
582
|
+
let content = fs2.readFileSync(filePath, "utf8");
|
|
477
583
|
if (!pathPrefixes.some((p) => content.includes(p))) return;
|
|
478
584
|
let changed = false;
|
|
479
585
|
if (filePath.endsWith(".html")) {
|
|
@@ -508,7 +614,7 @@ function dcsCdnBuildEnd(options = {}) {
|
|
|
508
614
|
}
|
|
509
615
|
}
|
|
510
616
|
if (changed) {
|
|
511
|
-
|
|
617
|
+
fs2.writeFileSync(filePath, content, "utf8");
|
|
512
618
|
filesProcessed++;
|
|
513
619
|
}
|
|
514
620
|
}
|
|
@@ -555,6 +661,6 @@ function responsiveImagePlugin(md) {
|
|
|
555
661
|
};
|
|
556
662
|
}
|
|
557
663
|
|
|
558
|
-
export { dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, responsiveImagePlugin };
|
|
664
|
+
export { dcsCdnBuildEnd, dcsCdnImagePlugin, dcsContentPlugin, dcsEditorPlugin, dcsPreviewPlugin, dcsSeoPlugin, emitStaticSeoHtml, responsiveImagePlugin };
|
|
559
665
|
//# sourceMappingURL=index.js.map
|
|
560
666
|
//# sourceMappingURL=index.js.map
|