@pagesmith/core 0.3.0 → 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 +9 -4
- package/REFERENCE.md +5 -2
- package/dist/ai/index.d.mts +5 -3
- package/dist/ai/index.d.mts.map +1 -1
- package/dist/ai/index.mjs +300 -206
- package/dist/ai/index.mjs.map +1 -1
- package/dist/assets/index.d.mts +10 -1
- package/dist/assets/index.d.mts.map +1 -1
- package/dist/assets/index.mjs +2 -2
- package/dist/{assets-DXiWF_KI.mjs → assets-CAPOqQ_P.mjs} +42 -5
- package/dist/assets-CAPOqQ_P.mjs.map +1 -0
- package/dist/{content-config-Bfe4W9us.d.mts → content-config-DJXUOcNG.d.mts} +49 -17
- package/dist/{content-config-Bfe4W9us.d.mts.map → content-config-DJXUOcNG.d.mts.map} +1 -1
- package/dist/{content-layer-DPK1EmfY.mjs → content-layer-B5enqWeJ.mjs} +123 -28
- package/dist/content-layer-B5enqWeJ.mjs.map +1 -0
- package/dist/content-layer-CpHYUYNN.d.mts +121 -0
- package/dist/content-layer-CpHYUYNN.d.mts.map +1 -0
- package/dist/create/index.d.mts.map +1 -1
- package/dist/create/index.mjs +26 -28
- package/dist/create/index.mjs.map +1 -1
- package/dist/css/index.d.mts +1 -1
- package/dist/{heading-BpDXnl-7.d.mts → heading-Dhvzlay-.d.mts} +1 -1
- package/dist/{heading-BpDXnl-7.d.mts.map → heading-Dhvzlay-.d.mts.map} +1 -1
- package/dist/{index-Bg9srb5U.d.mts → index-B7NRZAxd.d.mts} +1 -1
- package/dist/{index-Bg9srb5U.d.mts.map → index-B7NRZAxd.d.mts.map} +1 -1
- package/dist/{index-BBYkDxwI.d.mts → index-C0QFHYwb.d.mts} +1 -1
- package/dist/{index-BBYkDxwI.d.mts.map → index-C0QFHYwb.d.mts.map} +1 -1
- package/dist/{index-CbOKbkjJ.d.mts → index-CJkBs8YQ.d.mts} +2 -2
- package/dist/index-CJkBs8YQ.d.mts.map +1 -0
- package/dist/{index-YXQxMV6J.d.mts → index-DCznbvaV.d.mts} +2 -2
- package/dist/{index-YXQxMV6J.d.mts.map → index-DCznbvaV.d.mts.map} +1 -1
- package/dist/index.d.mts +15 -99
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +13 -9
- package/dist/index.mjs.map +1 -1
- package/dist/loaders/index.d.mts +2 -2
- package/dist/markdown/index.d.mts +2 -2
- package/dist/markdown/index.mjs +1 -1
- package/dist/{markdown-CyrHoDhP.mjs → markdown-BmDJgYeB.mjs} +23 -1
- package/dist/{markdown-CyrHoDhP.mjs.map → markdown-BmDJgYeB.mjs.map} +1 -1
- package/dist/mcp/index.d.mts +23 -0
- package/dist/mcp/index.d.mts.map +1 -0
- package/dist/mcp/index.mjs +2 -0
- package/dist/mcp/server.d.mts +13 -0
- package/dist/mcp/server.d.mts.map +1 -0
- package/dist/mcp/server.mjs +2 -0
- package/dist/runtime/index.mjs +1 -1
- package/dist/schemas/index.d.mts +3 -3
- package/dist/server-D3DHoh5f.mjs +202 -0
- package/dist/server-D3DHoh5f.mjs.map +1 -0
- package/dist/ssg-utils/index.d.mts +61 -0
- package/dist/ssg-utils/index.d.mts.map +1 -0
- package/dist/ssg-utils/index.mjs +118 -0
- package/dist/ssg-utils/index.mjs.map +1 -0
- package/dist/{types-Cn52sdoq.d.mts → types-B-V5qemH.d.mts} +1 -1
- package/dist/{types-Cn52sdoq.d.mts.map → types-B-V5qemH.d.mts.map} +1 -1
- package/dist/vite/index.d.mts +69 -34
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +294 -226
- package/dist/vite/index.mjs.map +1 -1
- package/docs/agents/AGENTS.md.template +9 -0
- package/docs/agents/changelog-notes.md +15 -0
- package/docs/agents/errors.md +96 -0
- package/docs/agents/migration.md +25 -0
- package/docs/agents/recipes.md +26 -0
- package/docs/agents/usage.md +58 -0
- package/docs/llms-full.txt +53 -0
- package/docs/llms.txt +29 -0
- package/package.json +56 -4
- package/dist/assets-DXiWF_KI.mjs.map +0 -1
- package/dist/content-layer-DPK1EmfY.mjs.map +0 -1
- package/dist/index-CbOKbkjJ.d.mts.map +0 -1
package/dist/vite/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "../markdown-
|
|
2
|
-
import {
|
|
1
|
+
import "../markdown-BmDJgYeB.mjs";
|
|
2
|
+
import { l as toSlug, t as createContentLayer } from "../content-layer-B5enqWeJ.mjs";
|
|
3
3
|
import { r as resolveLoader } from "../loaders-Cf-BXf2L.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
4
|
+
import { i as copyPublicFiles, n as collectContentAssets, t as CONTENT_ASSET_EXTS } from "../assets-CAPOqQ_P.mjs";
|
|
6
5
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
|
|
6
|
+
import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
8
8
|
import { uneval } from "devalue";
|
|
9
9
|
//#region src/vite/ssg.ts
|
|
@@ -105,27 +105,16 @@ function sharedAssetsPlugin() {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
//#endregion
|
|
108
|
-
//#region src/vite/ssg-
|
|
108
|
+
//#region src/vite/ssg-render.ts
|
|
109
109
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* Handles both development (on-the-fly SSR via middleware) and
|
|
113
|
-
* production (post-build SSG + pagefind indexing).
|
|
114
|
-
*
|
|
115
|
-
* The SSR entry module must export:
|
|
116
|
-
* - `getRoutes(config)` — returns route paths to pre-render
|
|
117
|
-
* - `render(url, config)` — renders a route to an HTML string
|
|
110
|
+
* Route collection, pre-rendering, and content asset handling for the SSG plugin.
|
|
118
111
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* base: '/my-site/',
|
|
126
|
-
* plugins: [pagesmithSsg({ entry: './src/entry-server.tsx' })],
|
|
127
|
-
* })
|
|
128
|
-
* ```
|
|
112
|
+
* Provides:
|
|
113
|
+
* - Content companion asset discovery and copying
|
|
114
|
+
* - HTML asset reference rewriting
|
|
115
|
+
* - SSR bundle building via child process
|
|
116
|
+
* - Route pre-rendering to static HTML files
|
|
117
|
+
* - Font and public file copying
|
|
129
118
|
*/
|
|
130
119
|
const MIME = {
|
|
131
120
|
".html": "text/html; charset=utf-8",
|
|
@@ -146,16 +135,6 @@ const MIME = {
|
|
|
146
135
|
".txt": "text/plain; charset=utf-8",
|
|
147
136
|
".xml": "application/xml; charset=utf-8"
|
|
148
137
|
};
|
|
149
|
-
const CONTENT_ASSET_EXTS = new Set([
|
|
150
|
-
".svg",
|
|
151
|
-
".png",
|
|
152
|
-
".jpg",
|
|
153
|
-
".jpeg",
|
|
154
|
-
".gif",
|
|
155
|
-
".webp",
|
|
156
|
-
".avif",
|
|
157
|
-
".ico"
|
|
158
|
-
]);
|
|
159
138
|
function resolveContentDirs(projectRoot, contentDirs = []) {
|
|
160
139
|
return contentDirs.map((dir) => resolve(projectRoot, dir));
|
|
161
140
|
}
|
|
@@ -175,32 +154,250 @@ function rewriteContentAssetRefs(html, base) {
|
|
|
175
154
|
return `${attr}=${quote}${basePrefix}/assets/${pathname.split("/").pop() ?? pathname}${suffix}${quote}`;
|
|
176
155
|
});
|
|
177
156
|
}
|
|
178
|
-
function collectContentAssets(contentDirs) {
|
|
179
|
-
const assets = /* @__PURE__ */ new Map();
|
|
180
|
-
function walk(dir) {
|
|
181
|
-
if (!existsSync(dir)) return;
|
|
182
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
183
|
-
if (entry.name.startsWith(".")) continue;
|
|
184
|
-
const fullPath = join(dir, entry.name);
|
|
185
|
-
if (entry.isDirectory()) {
|
|
186
|
-
walk(fullPath);
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
const ext = extname(entry.name).toLowerCase();
|
|
190
|
-
if (!CONTENT_ASSET_EXTS.has(ext)) continue;
|
|
191
|
-
if (assets.has(entry.name) && assets.get(entry.name) !== fullPath) console.warn(`pagesmith:ssg duplicate companion asset basename "${entry.name}" detected; using ${fullPath}`);
|
|
192
|
-
assets.set(entry.name, fullPath);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
for (const dir of contentDirs) walk(dir);
|
|
196
|
-
return assets;
|
|
197
|
-
}
|
|
198
157
|
function copyContentAssetsToOutDir(outDir, assets) {
|
|
199
158
|
if (assets.size === 0) return;
|
|
200
159
|
const assetsDir = join(outDir, "assets");
|
|
201
160
|
mkdirSync(assetsDir, { recursive: true });
|
|
202
161
|
for (const [fileName, sourcePath] of assets) copyFileSync(sourcePath, join(assetsDir, fileName));
|
|
203
162
|
}
|
|
163
|
+
/**
|
|
164
|
+
* Run the full SSG build: SSR bundle, route pre-rendering, asset copying.
|
|
165
|
+
*
|
|
166
|
+
* Returns the number of pages rendered.
|
|
167
|
+
*/
|
|
168
|
+
async function renderStaticSite(context) {
|
|
169
|
+
const { config, projectRoot, base, outDir, contentDirs, entry } = context;
|
|
170
|
+
console.log("\nSSG: Starting static site generation...");
|
|
171
|
+
const contentAssets = collectContentAssets(contentDirs);
|
|
172
|
+
copyFontAssets(outDir);
|
|
173
|
+
copyPublicFiles(join(projectRoot, "public"), outDir);
|
|
174
|
+
const { cssPath, jsPath } = discoverBuiltAssets(outDir, base);
|
|
175
|
+
console.log("SSG: Building SSR bundle...");
|
|
176
|
+
await buildSsrBundle(config, projectRoot, outDir, entry);
|
|
177
|
+
const entryBaseName = basename(entry).replace(/\.(c|m)?[jt]sx?$/u, ".js");
|
|
178
|
+
const serverDir = join(outDir, ".server");
|
|
179
|
+
const ssrMod = await import(pathToFileURL(join(serverDir, entryBaseName)).href);
|
|
180
|
+
const renderConfig = {
|
|
181
|
+
base,
|
|
182
|
+
root: projectRoot,
|
|
183
|
+
cssPath,
|
|
184
|
+
jsPath,
|
|
185
|
+
searchEnabled: true,
|
|
186
|
+
isDev: false
|
|
187
|
+
};
|
|
188
|
+
const routes = await ssrMod.getRoutes(renderConfig);
|
|
189
|
+
console.log(`SSG: Rendering ${routes.length} pages...`);
|
|
190
|
+
const concurrency = Math.min(routes.length, 8);
|
|
191
|
+
let routeIndex = 0;
|
|
192
|
+
async function renderWorker() {
|
|
193
|
+
while (routeIndex < routes.length) {
|
|
194
|
+
const route = routes[routeIndex++];
|
|
195
|
+
const html = rewriteContentAssetRefs(await ssrMod.render(route, renderConfig), base);
|
|
196
|
+
const outputPath = join(outDir, route === "/" ? "" : route.replace(/^\//, ""), "index.html");
|
|
197
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
198
|
+
writeFileSync(outputPath, `<!DOCTYPE html>\n${html}`);
|
|
199
|
+
if (route === "/404") writeFileSync(join(outDir, "404.html"), `<!DOCTYPE html>\n${html}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const workers = Array.from({ length: concurrency }, () => renderWorker());
|
|
203
|
+
await Promise.all(workers);
|
|
204
|
+
copyContentAssetsToOutDir(outDir, contentAssets);
|
|
205
|
+
rmSync(serverDir, {
|
|
206
|
+
recursive: true,
|
|
207
|
+
force: true
|
|
208
|
+
});
|
|
209
|
+
return routes.length;
|
|
210
|
+
}
|
|
211
|
+
function copyFontAssets(outDir) {
|
|
212
|
+
const corePkgDir = dirname(fileURLToPath(import.meta.resolve("@pagesmith/core/package.json")));
|
|
213
|
+
const coreFontsDir = join(corePkgDir, "assets", "fonts");
|
|
214
|
+
const outFontsDir = join(outDir, "assets", "fonts");
|
|
215
|
+
mkdirSync(outFontsDir, { recursive: true });
|
|
216
|
+
for (const file of readdirSync(coreFontsDir)) if (file.endsWith(".woff2")) copyFileSync(join(coreFontsDir, file), join(outFontsDir, file));
|
|
217
|
+
copyFileSync(join(corePkgDir, "assets", "fonts.css"), join(outDir, "assets", "fonts.css"));
|
|
218
|
+
}
|
|
219
|
+
function discoverBuiltAssets(outDir, base) {
|
|
220
|
+
const builtIndex = join(outDir, "index.html");
|
|
221
|
+
let cssPath = `${base}/assets/style.css`;
|
|
222
|
+
let jsPath;
|
|
223
|
+
if (existsSync(builtIndex)) {
|
|
224
|
+
const html = readFileSync(builtIndex, "utf-8");
|
|
225
|
+
const cssMatch = html.match(/href="([^"]*\.css)"/);
|
|
226
|
+
const jsMatch = html.match(/src="([^"]*\.js)"/);
|
|
227
|
+
if (cssMatch) cssPath = cssMatch[1];
|
|
228
|
+
if (jsMatch) jsPath = jsMatch[1];
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
cssPath,
|
|
232
|
+
jsPath
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
async function buildSsrBundle(config, projectRoot, outDir, entry) {
|
|
236
|
+
const { execFileSync } = await import("child_process");
|
|
237
|
+
const serverDir = join(outDir, ".server");
|
|
238
|
+
const ssrEntry = resolve(projectRoot, entry);
|
|
239
|
+
const buildScript = `
|
|
240
|
+
import { build } from 'vite';
|
|
241
|
+
await build({
|
|
242
|
+
root: ${JSON.stringify(projectRoot)},
|
|
243
|
+
logLevel: 'warn',
|
|
244
|
+
mode: ${JSON.stringify(config.mode)},
|
|
245
|
+
build: {
|
|
246
|
+
ssr: ${JSON.stringify(ssrEntry)},
|
|
247
|
+
outDir: ${JSON.stringify(serverDir)},
|
|
248
|
+
emptyOutDir: true,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
`;
|
|
252
|
+
execFileSync(process.execPath, [
|
|
253
|
+
"--input-type=module",
|
|
254
|
+
"-e",
|
|
255
|
+
buildScript
|
|
256
|
+
], {
|
|
257
|
+
stdio: "inherit",
|
|
258
|
+
cwd: projectRoot
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
//#endregion
|
|
262
|
+
//#region src/vite/ssg-hmr.ts
|
|
263
|
+
/**
|
|
264
|
+
* Dev-server middleware for the SSG plugin.
|
|
265
|
+
*
|
|
266
|
+
* Handles on-the-fly SSR rendering during development, including:
|
|
267
|
+
* - Content companion asset serving from content directories
|
|
268
|
+
* - HTML navigation request handling via server-side rendering
|
|
269
|
+
* - Vite HMR client injection for live reload
|
|
270
|
+
*/
|
|
271
|
+
/**
|
|
272
|
+
* Configure the Vite dev server with SSR middleware for on-the-fly rendering.
|
|
273
|
+
*
|
|
274
|
+
* Sets up:
|
|
275
|
+
* - File watcher for content companion assets
|
|
276
|
+
* - Middleware for serving companion assets from `/assets/` paths
|
|
277
|
+
* - Middleware for SSR-rendering HTML navigation requests
|
|
278
|
+
*/
|
|
279
|
+
function configureSsgDevServer(server, context) {
|
|
280
|
+
const { projectRoot, base, contentDirs, entry } = context;
|
|
281
|
+
let contentAssets = /* @__PURE__ */ new Map();
|
|
282
|
+
async function refreshContentArtifacts() {
|
|
283
|
+
contentAssets = collectContentAssets(contentDirs);
|
|
284
|
+
}
|
|
285
|
+
refreshContentArtifacts().catch((error) => {
|
|
286
|
+
console.warn(`pagesmith:ssg failed to prepare companion assets: ${error instanceof Error ? error.message : String(error)}`);
|
|
287
|
+
});
|
|
288
|
+
if (contentDirs.length > 0) {
|
|
289
|
+
server.watcher.add(contentDirs);
|
|
290
|
+
const refresh = () => {
|
|
291
|
+
refreshContentArtifacts().catch((error) => {
|
|
292
|
+
console.warn(`pagesmith:ssg failed to refresh companion assets: ${error instanceof Error ? error.message : String(error)}`);
|
|
293
|
+
});
|
|
294
|
+
};
|
|
295
|
+
server.watcher.on("add", refresh);
|
|
296
|
+
server.watcher.on("change", refresh);
|
|
297
|
+
server.watcher.on("unlink", refresh);
|
|
298
|
+
}
|
|
299
|
+
server.middlewares.use(async (req, res, next) => {
|
|
300
|
+
const url = req.url ?? "/";
|
|
301
|
+
const pathname = url.split(/[?#]/u, 1)[0] ?? url;
|
|
302
|
+
if (pathname.includes("/assets/")) {
|
|
303
|
+
const assetName = pathname.split("/assets/").pop();
|
|
304
|
+
const assetPath = assetName ? contentAssets.get(assetName) : void 0;
|
|
305
|
+
if (assetPath) {
|
|
306
|
+
const ext = extname(assetPath).toLowerCase();
|
|
307
|
+
res.writeHead(200, {
|
|
308
|
+
"Content-Type": MIME[ext] ?? "application/octet-stream",
|
|
309
|
+
"Cache-Control": "no-cache"
|
|
310
|
+
});
|
|
311
|
+
res.end(readFileSync(assetPath));
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const accept = req.headers.accept ?? "";
|
|
316
|
+
const pathExt = extname(pathname);
|
|
317
|
+
if (pathExt && pathExt !== ".html") return next();
|
|
318
|
+
if (!pathExt && !accept.includes("text/html")) return next();
|
|
319
|
+
if (base && (url === "/" || url === "")) {
|
|
320
|
+
res.writeHead(302, { Location: `${base}/` });
|
|
321
|
+
res.end();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (base && !url.startsWith(base)) return next();
|
|
325
|
+
try {
|
|
326
|
+
const renderFn = (await server.ssrLoadModule(resolve(projectRoot, entry))).render;
|
|
327
|
+
if (typeof renderFn !== "function") return next();
|
|
328
|
+
let html = await renderFn(url, {
|
|
329
|
+
base,
|
|
330
|
+
root: projectRoot,
|
|
331
|
+
cssPath: `${base}/src/theme.css`,
|
|
332
|
+
jsPath: void 0,
|
|
333
|
+
searchEnabled: false,
|
|
334
|
+
isDev: true
|
|
335
|
+
});
|
|
336
|
+
html = rewriteContentAssetRefs(html, base);
|
|
337
|
+
html = html.replace("</head>", `<script type="module" src="/@vite/client"><\/script>\n<link rel="stylesheet" href="${base}/src/theme.css">\n</head>`);
|
|
338
|
+
html = await server.transformIndexHtml(url, html);
|
|
339
|
+
const status = html.includes("doc-not-found") ? 404 : 200;
|
|
340
|
+
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
341
|
+
res.end(html);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
server.ssrFixStacktrace(err);
|
|
344
|
+
console.error(`SSR error for ${url}:`, err instanceof Error ? err.message : String(err));
|
|
345
|
+
next(err);
|
|
346
|
+
}
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
//#endregion
|
|
350
|
+
//#region src/vite/ssg-pagefind.ts
|
|
351
|
+
/**
|
|
352
|
+
* Pagefind search indexing for the SSG plugin.
|
|
353
|
+
*
|
|
354
|
+
* Runs the Pagefind CLI to index the generated static site output,
|
|
355
|
+
* producing the search index used by the docs theme.
|
|
356
|
+
*/
|
|
357
|
+
/**
|
|
358
|
+
* Run Pagefind indexing on the built site output directory.
|
|
359
|
+
*
|
|
360
|
+
* Resolves the Pagefind binary from the installed package and invokes it
|
|
361
|
+
* via a child process. Logs a warning and continues if Pagefind is not
|
|
362
|
+
* installed.
|
|
363
|
+
*/
|
|
364
|
+
async function runPagefindIndexing(outDir) {
|
|
365
|
+
console.log("SSG: Indexing with Pagefind...");
|
|
366
|
+
try {
|
|
367
|
+
const pagefindBin = join(dirname(fileURLToPath(import.meta.resolve("pagefind"))), "..", "lib", "runner", "bin.cjs");
|
|
368
|
+
const { execFileSync } = await import("child_process");
|
|
369
|
+
execFileSync(process.execPath, [
|
|
370
|
+
pagefindBin,
|
|
371
|
+
"--site",
|
|
372
|
+
outDir
|
|
373
|
+
], { stdio: "inherit" });
|
|
374
|
+
} catch {
|
|
375
|
+
console.warn("SSG: Pagefind not found, skipping search indexing");
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/vite/ssg-plugin.ts
|
|
380
|
+
/**
|
|
381
|
+
* Vite plugin for static site generation with @pagesmith/core.
|
|
382
|
+
*
|
|
383
|
+
* Handles both development (on-the-fly SSR via middleware) and
|
|
384
|
+
* production (post-build SSG + pagefind indexing).
|
|
385
|
+
*
|
|
386
|
+
* The SSR entry module must export:
|
|
387
|
+
* - `getRoutes(config)` — returns route paths to pre-render
|
|
388
|
+
* - `render(url, config)` — renders a route to an HTML string
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```ts
|
|
392
|
+
* // vite.config.ts
|
|
393
|
+
* import { pagesmithSsg } from '@pagesmith/core/vite'
|
|
394
|
+
*
|
|
395
|
+
* export default defineConfig({
|
|
396
|
+
* base: '/my-site/',
|
|
397
|
+
* plugins: [pagesmithSsg({ entry: './src/entry-server.tsx' })],
|
|
398
|
+
* })
|
|
399
|
+
* ```
|
|
400
|
+
*/
|
|
204
401
|
function pagesmithSsg(options) {
|
|
205
402
|
const enablePagefind = options.pagefind !== false;
|
|
206
403
|
let config;
|
|
@@ -208,7 +405,6 @@ function pagesmithSsg(options) {
|
|
|
208
405
|
let base;
|
|
209
406
|
let outDir;
|
|
210
407
|
let contentDirs = [];
|
|
211
|
-
let contentAssets = /* @__PURE__ */ new Map();
|
|
212
408
|
return [{
|
|
213
409
|
name: "pagesmith:ssg-dev",
|
|
214
410
|
apply: "serve",
|
|
@@ -223,71 +419,11 @@ function pagesmithSsg(options) {
|
|
|
223
419
|
contentDirs = resolveContentDirs(projectRoot, options.contentDirs);
|
|
224
420
|
},
|
|
225
421
|
configureServer(server) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
});
|
|
232
|
-
if (contentDirs.length > 0) {
|
|
233
|
-
server.watcher.add(contentDirs);
|
|
234
|
-
const refresh = () => {
|
|
235
|
-
refreshContentArtifacts().catch((error) => {
|
|
236
|
-
console.warn(`pagesmith:ssg failed to refresh companion assets: ${error instanceof Error ? error.message : String(error)}`);
|
|
237
|
-
});
|
|
238
|
-
};
|
|
239
|
-
server.watcher.on("add", refresh);
|
|
240
|
-
server.watcher.on("change", refresh);
|
|
241
|
-
server.watcher.on("unlink", refresh);
|
|
242
|
-
}
|
|
243
|
-
server.middlewares.use(async (req, res, next) => {
|
|
244
|
-
const url = req.url ?? "/";
|
|
245
|
-
const pathname = url.split(/[?#]/u, 1)[0] ?? url;
|
|
246
|
-
if (pathname.includes("/assets/")) {
|
|
247
|
-
const assetName = pathname.split("/assets/").pop();
|
|
248
|
-
const assetPath = assetName ? contentAssets.get(assetName) : void 0;
|
|
249
|
-
if (assetPath) {
|
|
250
|
-
const ext = extname(assetPath).toLowerCase();
|
|
251
|
-
res.writeHead(200, {
|
|
252
|
-
"Content-Type": MIME[ext] ?? "application/octet-stream",
|
|
253
|
-
"Cache-Control": "no-cache"
|
|
254
|
-
});
|
|
255
|
-
res.end(readFileSync(assetPath));
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
const accept = req.headers.accept ?? "";
|
|
260
|
-
const pathExt = extname(pathname);
|
|
261
|
-
if (pathExt && pathExt !== ".html") return next();
|
|
262
|
-
if (!pathExt && !accept.includes("text/html")) return next();
|
|
263
|
-
if (base && (url === "/" || url === "")) {
|
|
264
|
-
res.writeHead(302, { Location: `${base}/` });
|
|
265
|
-
res.end();
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
if (base && !url.startsWith(base)) return next();
|
|
269
|
-
try {
|
|
270
|
-
const renderFn = (await server.ssrLoadModule(resolve(projectRoot, options.entry))).render;
|
|
271
|
-
if (typeof renderFn !== "function") return next();
|
|
272
|
-
let html = await renderFn(url, {
|
|
273
|
-
base,
|
|
274
|
-
root: projectRoot,
|
|
275
|
-
cssPath: `${base}/src/theme.css`,
|
|
276
|
-
jsPath: void 0,
|
|
277
|
-
searchEnabled: false,
|
|
278
|
-
isDev: true
|
|
279
|
-
});
|
|
280
|
-
html = rewriteContentAssetRefs(html, base);
|
|
281
|
-
html = html.replace("</head>", `<script type="module" src="/@vite/client"><\/script>\n<link rel="stylesheet" href="${base}/src/theme.css">\n</head>`);
|
|
282
|
-
html = await server.transformIndexHtml(url, html);
|
|
283
|
-
const status = html.includes("doc-not-found") ? 404 : 200;
|
|
284
|
-
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
285
|
-
res.end(html);
|
|
286
|
-
} catch (err) {
|
|
287
|
-
server.ssrFixStacktrace(err);
|
|
288
|
-
console.error(`SSR error for ${url}:`, err.message);
|
|
289
|
-
next(err);
|
|
290
|
-
}
|
|
422
|
+
configureSsgDevServer(server, {
|
|
423
|
+
projectRoot,
|
|
424
|
+
base,
|
|
425
|
+
contentDirs,
|
|
426
|
+
entry: options.entry
|
|
291
427
|
});
|
|
292
428
|
}
|
|
293
429
|
}, {
|
|
@@ -302,123 +438,27 @@ function pagesmithSsg(options) {
|
|
|
302
438
|
},
|
|
303
439
|
async closeBundle() {
|
|
304
440
|
if (config.build.ssr) return;
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const coreFontsDir = join(corePkgDir, "assets", "fonts");
|
|
309
|
-
const outFontsDir = join(outDir, "assets", "fonts");
|
|
310
|
-
mkdirSync(outFontsDir, { recursive: true });
|
|
311
|
-
for (const file of readdirSync(coreFontsDir)) if (file.endsWith(".woff2")) copyFileSync(join(coreFontsDir, file), join(outFontsDir, file));
|
|
312
|
-
copyFileSync(join(corePkgDir, "assets", "fonts.css"), join(outDir, "assets", "fonts.css"));
|
|
313
|
-
copyPublicFiles(join(projectRoot, "public"), outDir);
|
|
314
|
-
const builtIndex = join(outDir, "index.html");
|
|
315
|
-
let cssPath = `${base}/assets/style.css`;
|
|
316
|
-
let jsPath;
|
|
317
|
-
if (existsSync(builtIndex)) {
|
|
318
|
-
const html = readFileSync(builtIndex, "utf-8");
|
|
319
|
-
const cssMatch = html.match(/href="([^"]*\.css)"/);
|
|
320
|
-
const jsMatch = html.match(/src="([^"]*\.js)"/);
|
|
321
|
-
if (cssMatch) cssPath = cssMatch[1];
|
|
322
|
-
if (jsMatch) jsPath = jsMatch[1];
|
|
323
|
-
}
|
|
324
|
-
console.log("SSG: Building SSR bundle...");
|
|
325
|
-
const { execFileSync } = await import("child_process");
|
|
326
|
-
const serverDir = join(outDir, ".server");
|
|
327
|
-
const ssrEntry = resolve(projectRoot, options.entry);
|
|
328
|
-
const buildScript = `
|
|
329
|
-
import { build } from 'vite-plus';
|
|
330
|
-
await build({
|
|
331
|
-
root: ${JSON.stringify(projectRoot)},
|
|
332
|
-
logLevel: 'warn',
|
|
333
|
-
mode: ${JSON.stringify(config.mode)},
|
|
334
|
-
build: {
|
|
335
|
-
ssr: ${JSON.stringify(ssrEntry)},
|
|
336
|
-
outDir: ${JSON.stringify(serverDir)},
|
|
337
|
-
emptyOutDir: true,
|
|
338
|
-
},
|
|
339
|
-
});
|
|
340
|
-
`;
|
|
341
|
-
execFileSync(process.execPath, [
|
|
342
|
-
"--input-type=module",
|
|
343
|
-
"-e",
|
|
344
|
-
buildScript
|
|
345
|
-
], {
|
|
346
|
-
stdio: "inherit",
|
|
347
|
-
cwd: projectRoot
|
|
348
|
-
});
|
|
349
|
-
const ssrMod = await import(pathToFileURL(join(serverDir, basename(options.entry).replace(/\.(c|m)?[jt]sx?$/u, ".js"))).href);
|
|
350
|
-
const renderConfig = {
|
|
441
|
+
const pageCount = await renderStaticSite({
|
|
442
|
+
config,
|
|
443
|
+
projectRoot,
|
|
351
444
|
base,
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
searchEnabled: true,
|
|
356
|
-
isDev: false
|
|
357
|
-
};
|
|
358
|
-
const routes = await ssrMod.getRoutes(renderConfig);
|
|
359
|
-
console.log(`SSG: Rendering ${routes.length} pages...`);
|
|
360
|
-
for (const route of routes) {
|
|
361
|
-
const html = rewriteContentAssetRefs(await ssrMod.render(route, renderConfig), base);
|
|
362
|
-
const routePath = route === "/" ? "" : route.replace(/^\//, "");
|
|
363
|
-
const outputPath = join(outDir, routePath, "index.html");
|
|
364
|
-
mkdirSync(dirname(outputPath), { recursive: true });
|
|
365
|
-
writeFileSync(outputPath, `<!DOCTYPE html>\n${html}`);
|
|
366
|
-
if (route === "/404") writeFileSync(join(outDir, "404.html"), `<!DOCTYPE html>\n${html}`);
|
|
367
|
-
}
|
|
368
|
-
copyContentAssetsToOutDir(outDir, contentAssets);
|
|
369
|
-
rmSync(serverDir, {
|
|
370
|
-
recursive: true,
|
|
371
|
-
force: true
|
|
445
|
+
outDir,
|
|
446
|
+
contentDirs,
|
|
447
|
+
entry: options.entry
|
|
372
448
|
});
|
|
373
|
-
if (enablePagefind)
|
|
374
|
-
|
|
375
|
-
try {
|
|
376
|
-
const pagefindBin = join(dirname(fileURLToPath(import.meta.resolve("pagefind"))), "..", "lib", "runner", "bin.cjs");
|
|
377
|
-
const { execFileSync } = await import("child_process");
|
|
378
|
-
execFileSync(process.execPath, [
|
|
379
|
-
pagefindBin,
|
|
380
|
-
"--site",
|
|
381
|
-
outDir
|
|
382
|
-
], { stdio: "inherit" });
|
|
383
|
-
} catch {
|
|
384
|
-
console.warn("SSG: Pagefind not found, skipping search indexing");
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
console.log(`SSG: Done — ${routes.length} pages generated`);
|
|
449
|
+
if (enablePagefind) await runPagefindIndexing(outDir);
|
|
450
|
+
console.log(`SSG: Done — ${pageCount} pages generated`);
|
|
388
451
|
}
|
|
389
452
|
}];
|
|
390
453
|
}
|
|
391
454
|
//#endregion
|
|
392
|
-
//#region src/vite/
|
|
393
|
-
const DEFAULT_MODULE_ID = "virtual:content";
|
|
455
|
+
//#region src/vite/dts.ts
|
|
394
456
|
function stripExtension(filePath) {
|
|
395
457
|
return filePath.replace(/\.(c|m)?[jt]sx?$/u, "");
|
|
396
458
|
}
|
|
397
|
-
function normalizePath(value) {
|
|
459
|
+
function normalizePath$1(value) {
|
|
398
460
|
return value.replace(/\\/g, "/");
|
|
399
461
|
}
|
|
400
|
-
function isPathWithin(parent, candidate) {
|
|
401
|
-
const rel = normalizePath(relative(parent, candidate));
|
|
402
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
403
|
-
}
|
|
404
|
-
function commonDirectory(paths) {
|
|
405
|
-
const normalized = paths.map((path) => normalizePath(resolve(path)));
|
|
406
|
-
if (normalized.length === 0) return process.cwd();
|
|
407
|
-
if (normalized.length === 1) return normalized[0];
|
|
408
|
-
const segments = normalized.map((path) => path.split("/").filter(Boolean));
|
|
409
|
-
const shared = [];
|
|
410
|
-
const first = segments[0];
|
|
411
|
-
for (let index = 0; index < first.length; index += 1) {
|
|
412
|
-
const segment = first[index];
|
|
413
|
-
if (segments.every((parts) => parts[index] === segment)) {
|
|
414
|
-
shared.push(segment);
|
|
415
|
-
continue;
|
|
416
|
-
}
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
if (shared.length === 0) return resolve("/");
|
|
420
|
-
return resolve(`/${shared.join("/")}`);
|
|
421
|
-
}
|
|
422
462
|
function resolveDtsPath(projectRoot, dts) {
|
|
423
463
|
if (dts === false) return "";
|
|
424
464
|
if (typeof dts === "string") return resolve(projectRoot, dts);
|
|
@@ -429,7 +469,7 @@ function resolveDtsPath(projectRoot, dts) {
|
|
|
429
469
|
}
|
|
430
470
|
function createDtsSource(moduleId, collectionNames, dtsPath, configPath) {
|
|
431
471
|
return `// Generated by @pagesmith/core/vite. Do not edit manually.
|
|
432
|
-
type __PagesmithCollections = typeof import('${normalizePath(stripExtension(relative(dirname(dtsPath), configPath)).replace(/^[^.]/u, "./$&"))}').default
|
|
472
|
+
type __PagesmithCollections = typeof import('${normalizePath$1(stripExtension(relative(dirname(dtsPath), configPath)).replace(/^[^.]/u, "./$&"))}').default
|
|
433
473
|
|
|
434
474
|
declare module '${moduleId}' {
|
|
435
475
|
const content: import('@pagesmith/core/vite').ContentModuleMap<__PagesmithCollections>
|
|
@@ -444,6 +484,34 @@ ${collectionNames.map((name) => `declare module '${moduleId}/${name}' {
|
|
|
444
484
|
}`).join("\n\n")}
|
|
445
485
|
`;
|
|
446
486
|
}
|
|
487
|
+
//#endregion
|
|
488
|
+
//#region src/vite/content-plugin.ts
|
|
489
|
+
const DEFAULT_MODULE_ID = "virtual:content";
|
|
490
|
+
function normalizePath(value) {
|
|
491
|
+
return value.replace(/\\/g, "/");
|
|
492
|
+
}
|
|
493
|
+
function isPathWithin(parent, candidate) {
|
|
494
|
+
const rel = normalizePath(relative(parent, candidate));
|
|
495
|
+
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
496
|
+
}
|
|
497
|
+
function commonDirectory(paths) {
|
|
498
|
+
const normalized = paths.map((path) => normalizePath(resolve(path)));
|
|
499
|
+
if (normalized.length === 0) return process.cwd();
|
|
500
|
+
if (normalized.length === 1) return normalized[0];
|
|
501
|
+
const segments = normalized.map((path) => path.split("/").filter(Boolean));
|
|
502
|
+
const shared = [];
|
|
503
|
+
const first = segments[0];
|
|
504
|
+
for (let index = 0; index < first.length; index += 1) {
|
|
505
|
+
const segment = first[index];
|
|
506
|
+
if (segments.every((parts) => parts[index] === segment)) {
|
|
507
|
+
shared.push(segment);
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
break;
|
|
511
|
+
}
|
|
512
|
+
if (shared.length === 0) return resolve("/");
|
|
513
|
+
return resolve(`/${shared.join("/")}`);
|
|
514
|
+
}
|
|
447
515
|
async function serializeCollection(layer, collectionName, collectionDef, contentRoot) {
|
|
448
516
|
const entries = await layer.getCollection(collectionName);
|
|
449
517
|
const loader = resolveLoader(collectionDef.loader);
|