@pagesmith/core 0.2.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 +284 -0
- package/dist/ai/index.d.mts +7 -4
- package/dist/ai/index.d.mts.map +1 -1
- package/dist/ai/index.mjs +605 -136
- 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-bX08zEJm.mjs → assets-CAPOqQ_P.mjs} +42 -5
- package/dist/assets-CAPOqQ_P.mjs.map +1 -0
- package/dist/{content-config-wW-3r5gG.d.mts → content-config-DJXUOcNG.d.mts} +47 -15
- package/dist/{content-config-wW-3r5gG.d.mts.map → content-config-DJXUOcNG.d.mts.map} +1 -1
- package/dist/{content-layer-DWdgdBeI.mjs → content-layer-B5enqWeJ.mjs} +195 -63
- 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/css/index.mjs +1 -1
- package/dist/{css-ekIt2Fdb.mjs → css-BneO430t.mjs} +5 -4
- package/dist/css-BneO430t.mjs.map +1 -0
- package/dist/index-B7NRZAxd.d.mts +13 -0
- package/dist/index-B7NRZAxd.d.mts.map +1 -0
- package/dist/{index-D79hUFbK.d.mts → index-C0QFHYwb.d.mts} +1 -1
- package/dist/{index-D79hUFbK.d.mts.map → index-C0QFHYwb.d.mts.map} +1 -1
- package/dist/{index-DpRBzO8Q.d.mts → index-CJkBs8YQ.d.mts} +2 -2
- package/dist/index-CJkBs8YQ.d.mts.map +1 -0
- package/dist/{index-Dbsw1QON.d.mts → index-DCznbvaV.d.mts} +4 -2
- package/dist/{index-Dbsw1QON.d.mts.map → index-DCznbvaV.d.mts.map} +1 -1
- package/dist/index.d.mts +13 -91
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +16 -13
- package/dist/index.mjs.map +1 -1
- package/dist/loaders/index.d.mts +2 -2
- package/dist/loaders/index.mjs +2 -2
- package/dist/{loaders-Bla48ZN9.mjs → loaders-Cf-BXf2L.mjs} +10 -2
- package/dist/{loaders-Bla48ZN9.mjs.map → loaders-Cf-BXf2L.mjs.map} +1 -1
- package/dist/markdown/index.d.mts +1 -1
- package/dist/markdown/index.mjs +1 -1
- package/dist/{markdown-Cj5X26FL.mjs → markdown-BmDJgYeB.mjs} +59 -9
- package/dist/markdown-BmDJgYeB.mjs.map +1 -0
- 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 +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/{schemas-BZEPTGWs.mjs → schemas-UL4ynWsA.mjs} +1 -1
- package/dist/{schemas-BZEPTGWs.mjs.map → schemas-UL4ynWsA.mjs.map} +1 -1
- 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/vite/index.d.mts +68 -33
- package/dist/vite/index.d.mts.map +1 -1
- package/dist/vite/index.mjs +302 -227
- 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 +58 -6
- package/dist/assets-bX08zEJm.mjs.map +0 -1
- package/dist/content-layer-DWdgdBeI.mjs.map +0 -1
- package/dist/convert-XdGgNqH0.mjs +0 -27
- package/dist/convert-XdGgNqH0.mjs.map +0 -1
- package/dist/css-ekIt2Fdb.mjs.map +0 -1
- package/dist/index-CeNDTM-y.d.mts +0 -7
- package/dist/index-CeNDTM-y.d.mts.map +0 -1
- package/dist/index-DpRBzO8Q.d.mts.map +0 -1
- package/dist/markdown-Cj5X26FL.mjs.map +0 -1
package/dist/vite/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import "../markdown-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
1
|
+
import "../markdown-BmDJgYeB.mjs";
|
|
2
|
+
import { l as toSlug, t as createContentLayer } from "../content-layer-B5enqWeJ.mjs";
|
|
3
|
+
import { r as resolveLoader } from "../loaders-Cf-BXf2L.mjs";
|
|
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
|
}
|
|
@@ -166,39 +145,259 @@ function isAssetReference(ref) {
|
|
|
166
145
|
}
|
|
167
146
|
function rewriteContentAssetRefs(html, base) {
|
|
168
147
|
const basePrefix = base.replace(/\/+$/u, "");
|
|
169
|
-
return html.replace(/(src|href|srcset)="([^"]+)"/g, (match, attr,
|
|
148
|
+
return html.replace(/(src|href|srcset)=(?:"([^"]+)"|'([^']+)')/g, (match, attr, doubleRef, singleRef) => {
|
|
149
|
+
const ref = doubleRef ?? singleRef ?? "";
|
|
170
150
|
if (!isAssetReference(ref)) return match;
|
|
171
151
|
const pathname = ref.split(/[?#]/u, 1)[0] ?? ref;
|
|
172
152
|
const suffix = ref.slice(pathname.length);
|
|
173
|
-
|
|
153
|
+
const quote = doubleRef !== void 0 ? "\"" : "'";
|
|
154
|
+
return `${attr}=${quote}${basePrefix}/assets/${pathname.split("/").pop() ?? pathname}${suffix}${quote}`;
|
|
174
155
|
});
|
|
175
156
|
}
|
|
176
|
-
function collectContentAssets(contentDirs) {
|
|
177
|
-
const assets = /* @__PURE__ */ new Map();
|
|
178
|
-
function walk(dir) {
|
|
179
|
-
if (!existsSync(dir)) return;
|
|
180
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
181
|
-
if (entry.name.startsWith(".")) continue;
|
|
182
|
-
const fullPath = join(dir, entry.name);
|
|
183
|
-
if (entry.isDirectory()) {
|
|
184
|
-
walk(fullPath);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const ext = extname(entry.name).toLowerCase();
|
|
188
|
-
if (!CONTENT_ASSET_EXTS.has(ext)) continue;
|
|
189
|
-
if (assets.has(entry.name) && assets.get(entry.name) !== fullPath) console.warn(`pagesmith:ssg duplicate companion asset basename "${entry.name}" detected; using ${fullPath}`);
|
|
190
|
-
assets.set(entry.name, fullPath);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
for (const dir of contentDirs) walk(dir);
|
|
194
|
-
return assets;
|
|
195
|
-
}
|
|
196
157
|
function copyContentAssetsToOutDir(outDir, assets) {
|
|
197
158
|
if (assets.size === 0) return;
|
|
198
159
|
const assetsDir = join(outDir, "assets");
|
|
199
160
|
mkdirSync(assetsDir, { recursive: true });
|
|
200
161
|
for (const [fileName, sourcePath] of assets) copyFileSync(sourcePath, join(assetsDir, fileName));
|
|
201
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
|
+
*/
|
|
202
401
|
function pagesmithSsg(options) {
|
|
203
402
|
const enablePagefind = options.pagefind !== false;
|
|
204
403
|
let config;
|
|
@@ -206,7 +405,6 @@ function pagesmithSsg(options) {
|
|
|
206
405
|
let base;
|
|
207
406
|
let outDir;
|
|
208
407
|
let contentDirs = [];
|
|
209
|
-
let contentAssets = /* @__PURE__ */ new Map();
|
|
210
408
|
return [{
|
|
211
409
|
name: "pagesmith:ssg-dev",
|
|
212
410
|
apply: "serve",
|
|
@@ -221,68 +419,11 @@ function pagesmithSsg(options) {
|
|
|
221
419
|
contentDirs = resolveContentDirs(projectRoot, options.contentDirs);
|
|
222
420
|
},
|
|
223
421
|
configureServer(server) {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
});
|
|
230
|
-
if (contentDirs.length > 0) {
|
|
231
|
-
server.watcher.add(contentDirs);
|
|
232
|
-
const refresh = () => {
|
|
233
|
-
refreshContentArtifacts().catch((error) => {
|
|
234
|
-
console.warn(`pagesmith:ssg failed to refresh companion assets: ${error instanceof Error ? error.message : String(error)}`);
|
|
235
|
-
});
|
|
236
|
-
};
|
|
237
|
-
server.watcher.on("add", refresh);
|
|
238
|
-
server.watcher.on("change", refresh);
|
|
239
|
-
server.watcher.on("unlink", refresh);
|
|
240
|
-
}
|
|
241
|
-
server.middlewares.use(async (req, res, next) => {
|
|
242
|
-
const url = req.url ?? "/";
|
|
243
|
-
const pathname = url.split(/[?#]/u, 1)[0] ?? url;
|
|
244
|
-
if (pathname.includes("/assets/")) {
|
|
245
|
-
const assetName = pathname.split("/assets/").pop();
|
|
246
|
-
const assetPath = assetName ? contentAssets.get(assetName) : void 0;
|
|
247
|
-
if (assetPath) {
|
|
248
|
-
const ext = extname(assetPath).toLowerCase();
|
|
249
|
-
res.writeHead(200, {
|
|
250
|
-
"Content-Type": MIME[ext] ?? "application/octet-stream",
|
|
251
|
-
"Cache-Control": "no-cache"
|
|
252
|
-
});
|
|
253
|
-
res.end(readFileSync(assetPath));
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
if (!(req.headers.accept ?? "").includes("text/html")) return next();
|
|
258
|
-
if (base && (url === "/" || url === "")) {
|
|
259
|
-
res.writeHead(302, { Location: `${base}/` });
|
|
260
|
-
res.end();
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
if (base && !url.startsWith(base)) return next();
|
|
264
|
-
try {
|
|
265
|
-
const renderFn = (await server.ssrLoadModule(resolve(projectRoot, options.entry))).render;
|
|
266
|
-
if (typeof renderFn !== "function") return next();
|
|
267
|
-
let html = await renderFn(url, {
|
|
268
|
-
base,
|
|
269
|
-
root: projectRoot,
|
|
270
|
-
cssPath: `${base}/src/theme.css`,
|
|
271
|
-
jsPath: void 0,
|
|
272
|
-
searchEnabled: false,
|
|
273
|
-
isDev: true
|
|
274
|
-
});
|
|
275
|
-
html = rewriteContentAssetRefs(html, base);
|
|
276
|
-
html = html.replace("</head>", `<script type="module" src="/@vite/client"><\/script>\n<link rel="stylesheet" href="${base}/src/theme.css">\n</head>`);
|
|
277
|
-
html = await server.transformIndexHtml(url, html);
|
|
278
|
-
const status = html.includes("doc-not-found") ? 404 : 200;
|
|
279
|
-
res.writeHead(status, { "Content-Type": "text/html; charset=utf-8" });
|
|
280
|
-
res.end(html);
|
|
281
|
-
} catch (err) {
|
|
282
|
-
server.ssrFixStacktrace(err);
|
|
283
|
-
console.error(`SSR error for ${url}:`, err.message);
|
|
284
|
-
next(err);
|
|
285
|
-
}
|
|
422
|
+
configureSsgDevServer(server, {
|
|
423
|
+
projectRoot,
|
|
424
|
+
base,
|
|
425
|
+
contentDirs,
|
|
426
|
+
entry: options.entry
|
|
286
427
|
});
|
|
287
428
|
}
|
|
288
429
|
}, {
|
|
@@ -297,123 +438,27 @@ function pagesmithSsg(options) {
|
|
|
297
438
|
},
|
|
298
439
|
async closeBundle() {
|
|
299
440
|
if (config.build.ssr) return;
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const coreFontsDir = join(corePkgDir, "assets", "fonts");
|
|
304
|
-
const outFontsDir = join(outDir, "assets", "fonts");
|
|
305
|
-
mkdirSync(outFontsDir, { recursive: true });
|
|
306
|
-
for (const file of readdirSync(coreFontsDir)) if (file.endsWith(".woff2")) copyFileSync(join(coreFontsDir, file), join(outFontsDir, file));
|
|
307
|
-
copyFileSync(join(corePkgDir, "assets", "fonts.css"), join(outDir, "assets", "fonts.css"));
|
|
308
|
-
copyPublicFiles(join(projectRoot, "public"), outDir);
|
|
309
|
-
const builtIndex = join(outDir, "index.html");
|
|
310
|
-
let cssPath = `${base}/assets/style.css`;
|
|
311
|
-
let jsPath;
|
|
312
|
-
if (existsSync(builtIndex)) {
|
|
313
|
-
const html = readFileSync(builtIndex, "utf-8");
|
|
314
|
-
const cssMatch = html.match(/href="([^"]*\.css)"/);
|
|
315
|
-
const jsMatch = html.match(/src="([^"]*\.js)"/);
|
|
316
|
-
if (cssMatch) cssPath = cssMatch[1];
|
|
317
|
-
if (jsMatch) jsPath = jsMatch[1];
|
|
318
|
-
}
|
|
319
|
-
console.log("SSG: Building SSR bundle...");
|
|
320
|
-
const { execFileSync } = await import("child_process");
|
|
321
|
-
const serverDir = join(outDir, ".server");
|
|
322
|
-
const ssrEntry = resolve(projectRoot, options.entry);
|
|
323
|
-
const buildScript = `
|
|
324
|
-
import { build } from 'vite-plus';
|
|
325
|
-
await build({
|
|
326
|
-
root: ${JSON.stringify(projectRoot)},
|
|
327
|
-
logLevel: 'warn',
|
|
328
|
-
mode: ${JSON.stringify(config.mode)},
|
|
329
|
-
build: {
|
|
330
|
-
ssr: ${JSON.stringify(ssrEntry)},
|
|
331
|
-
outDir: ${JSON.stringify(serverDir)},
|
|
332
|
-
emptyOutDir: true,
|
|
333
|
-
},
|
|
334
|
-
});
|
|
335
|
-
`;
|
|
336
|
-
execFileSync(process.execPath, [
|
|
337
|
-
"--input-type=module",
|
|
338
|
-
"-e",
|
|
339
|
-
buildScript
|
|
340
|
-
], {
|
|
341
|
-
stdio: "inherit",
|
|
342
|
-
cwd: projectRoot
|
|
343
|
-
});
|
|
344
|
-
const ssrMod = await import(pathToFileURL(join(serverDir, basename(options.entry).replace(/\.(c|m)?[jt]sx?$/u, ".js"))).href);
|
|
345
|
-
const renderConfig = {
|
|
441
|
+
const pageCount = await renderStaticSite({
|
|
442
|
+
config,
|
|
443
|
+
projectRoot,
|
|
346
444
|
base,
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
searchEnabled: true,
|
|
351
|
-
isDev: false
|
|
352
|
-
};
|
|
353
|
-
const routes = await ssrMod.getRoutes(renderConfig);
|
|
354
|
-
console.log(`SSG: Rendering ${routes.length} pages...`);
|
|
355
|
-
for (const route of routes) {
|
|
356
|
-
const html = rewriteContentAssetRefs(await ssrMod.render(route, renderConfig), base);
|
|
357
|
-
const routePath = route === "/" ? "" : route.replace(/^\//, "");
|
|
358
|
-
const outputPath = join(outDir, routePath, "index.html");
|
|
359
|
-
mkdirSync(dirname(outputPath), { recursive: true });
|
|
360
|
-
writeFileSync(outputPath, `<!DOCTYPE html>\n${html}`);
|
|
361
|
-
if (route === "/404") writeFileSync(join(outDir, "404.html"), `<!DOCTYPE html>\n${html}`);
|
|
362
|
-
}
|
|
363
|
-
copyContentAssetsToOutDir(outDir, contentAssets);
|
|
364
|
-
rmSync(serverDir, {
|
|
365
|
-
recursive: true,
|
|
366
|
-
force: true
|
|
445
|
+
outDir,
|
|
446
|
+
contentDirs,
|
|
447
|
+
entry: options.entry
|
|
367
448
|
});
|
|
368
|
-
if (enablePagefind)
|
|
369
|
-
|
|
370
|
-
try {
|
|
371
|
-
const pagefindBin = join(dirname(fileURLToPath(import.meta.resolve("pagefind"))), "..", "lib", "runner", "bin.cjs");
|
|
372
|
-
const { execFileSync } = await import("child_process");
|
|
373
|
-
execFileSync(process.execPath, [
|
|
374
|
-
pagefindBin,
|
|
375
|
-
"--site",
|
|
376
|
-
outDir
|
|
377
|
-
], { stdio: "inherit" });
|
|
378
|
-
} catch {
|
|
379
|
-
console.warn("SSG: Pagefind not found, skipping search indexing");
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
console.log(`SSG: Done — ${routes.length} pages generated`);
|
|
449
|
+
if (enablePagefind) await runPagefindIndexing(outDir);
|
|
450
|
+
console.log(`SSG: Done — ${pageCount} pages generated`);
|
|
383
451
|
}
|
|
384
452
|
}];
|
|
385
453
|
}
|
|
386
454
|
//#endregion
|
|
387
|
-
//#region src/vite/
|
|
388
|
-
const DEFAULT_MODULE_ID = "virtual:content";
|
|
455
|
+
//#region src/vite/dts.ts
|
|
389
456
|
function stripExtension(filePath) {
|
|
390
457
|
return filePath.replace(/\.(c|m)?[jt]sx?$/u, "");
|
|
391
458
|
}
|
|
392
|
-
function normalizePath(value) {
|
|
459
|
+
function normalizePath$1(value) {
|
|
393
460
|
return value.replace(/\\/g, "/");
|
|
394
461
|
}
|
|
395
|
-
function isPathWithin(parent, candidate) {
|
|
396
|
-
const rel = normalizePath(relative(parent, candidate));
|
|
397
|
-
return rel === "" || !rel.startsWith("..") && !rel.startsWith("/");
|
|
398
|
-
}
|
|
399
|
-
function commonDirectory(paths) {
|
|
400
|
-
const normalized = paths.map((path) => normalizePath(resolve(path)));
|
|
401
|
-
if (normalized.length === 0) return process.cwd();
|
|
402
|
-
if (normalized.length === 1) return normalized[0];
|
|
403
|
-
const segments = normalized.map((path) => path.split("/").filter(Boolean));
|
|
404
|
-
const shared = [];
|
|
405
|
-
const first = segments[0];
|
|
406
|
-
for (let index = 0; index < first.length; index += 1) {
|
|
407
|
-
const segment = first[index];
|
|
408
|
-
if (segments.every((parts) => parts[index] === segment)) {
|
|
409
|
-
shared.push(segment);
|
|
410
|
-
continue;
|
|
411
|
-
}
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
if (shared.length === 0) return resolve("/");
|
|
415
|
-
return resolve(`/${shared.join("/")}`);
|
|
416
|
-
}
|
|
417
462
|
function resolveDtsPath(projectRoot, dts) {
|
|
418
463
|
if (dts === false) return "";
|
|
419
464
|
if (typeof dts === "string") return resolve(projectRoot, dts);
|
|
@@ -424,7 +469,7 @@ function resolveDtsPath(projectRoot, dts) {
|
|
|
424
469
|
}
|
|
425
470
|
function createDtsSource(moduleId, collectionNames, dtsPath, configPath) {
|
|
426
471
|
return `// Generated by @pagesmith/core/vite. Do not edit manually.
|
|
427
|
-
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
|
|
428
473
|
|
|
429
474
|
declare module '${moduleId}' {
|
|
430
475
|
const content: import('@pagesmith/core/vite').ContentModuleMap<__PagesmithCollections>
|
|
@@ -439,6 +484,34 @@ ${collectionNames.map((name) => `declare module '${moduleId}/${name}' {
|
|
|
439
484
|
}`).join("\n\n")}
|
|
440
485
|
`;
|
|
441
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
|
+
}
|
|
442
515
|
async function serializeCollection(layer, collectionName, collectionDef, contentRoot) {
|
|
443
516
|
const entries = await layer.getCollection(collectionName);
|
|
444
517
|
const loader = resolveLoader(collectionDef.loader);
|
|
@@ -539,7 +612,9 @@ function pagesmithContent(collectionsOrOptions, maybeOptions = {}) {
|
|
|
539
612
|
if (touchesContent) for (const name of affectedCollections) {
|
|
540
613
|
const moduleNode = server.moduleGraph.getModuleById(`${resolvedPrefix}${name}`);
|
|
541
614
|
if (moduleNode) server.moduleGraph.invalidateModule(moduleNode);
|
|
542
|
-
getLayer().invalidateCollection(name)
|
|
615
|
+
getLayer().invalidateCollection(name).catch((err) => {
|
|
616
|
+
console.warn(`[pagesmith] Failed to invalidate collection "${name}":`, err);
|
|
617
|
+
});
|
|
543
618
|
}
|
|
544
619
|
else {
|
|
545
620
|
for (const name of collectionNames) {
|