@pagesmith/core 0.1.0 → 0.3.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.
Files changed (61) hide show
  1. package/REFERENCE.md +281 -0
  2. package/assets/favicon.svg +4 -0
  3. package/dist/ai/index.d.mts +3 -2
  4. package/dist/ai/index.d.mts.map +1 -1
  5. package/dist/ai/index.mjs +454 -79
  6. package/dist/ai/index.mjs.map +1 -1
  7. package/dist/assets/index.mjs +1 -1
  8. package/dist/{assets-bX08zEJm.mjs → assets-DXiWF_KI.mjs} +1 -1
  9. package/dist/{assets-bX08zEJm.mjs.map → assets-DXiWF_KI.mjs.map} +1 -1
  10. package/dist/{content-config-fHPaFZ7i.d.mts → content-config-Bfe4W9us.d.mts} +4 -6
  11. package/dist/{content-config-fHPaFZ7i.d.mts.map → content-config-Bfe4W9us.d.mts.map} +1 -1
  12. package/dist/{content-layer-B7fQ3im4.mjs → content-layer-DPK1EmfY.mjs} +100 -52
  13. package/dist/content-layer-DPK1EmfY.mjs.map +1 -0
  14. package/dist/css/index.d.mts +1 -1
  15. package/dist/css/index.mjs +1 -1
  16. package/dist/{css-ekIt2Fdb.mjs → css-BneO430t.mjs} +5 -4
  17. package/dist/css-BneO430t.mjs.map +1 -0
  18. package/dist/{heading-Dhvzlay-.d.mts → heading-BpDXnl-7.d.mts} +1 -1
  19. package/dist/{heading-Dhvzlay-.d.mts.map → heading-BpDXnl-7.d.mts.map} +1 -1
  20. package/dist/{index-BQ6B1-qG.d.mts → index-BBYkDxwI.d.mts} +7 -7
  21. package/dist/{index-BQ6B1-qG.d.mts.map → index-BBYkDxwI.d.mts.map} +1 -1
  22. package/dist/index-Bg9srb5U.d.mts +13 -0
  23. package/dist/index-Bg9srb5U.d.mts.map +1 -0
  24. package/dist/{index-DpRBzO8Q.d.mts → index-CbOKbkjJ.d.mts} +3 -3
  25. package/dist/index-CbOKbkjJ.d.mts.map +1 -0
  26. package/dist/{index-sFCx17CD.d.mts → index-YXQxMV6J.d.mts} +10 -8
  27. package/dist/index-YXQxMV6J.d.mts.map +1 -0
  28. package/dist/index.d.mts +19 -13
  29. package/dist/index.d.mts.map +1 -1
  30. package/dist/index.mjs +30 -7
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/loaders/index.d.mts +3 -3
  33. package/dist/loaders/index.mjs +2 -2
  34. package/dist/{loaders-DyABmDrE.mjs → loaders-Cf-BXf2L.mjs} +32 -18
  35. package/dist/loaders-Cf-BXf2L.mjs.map +1 -0
  36. package/dist/markdown/index.d.mts +2 -2
  37. package/dist/markdown/index.mjs +1 -1
  38. package/dist/{markdown-Cj5X26FL.mjs → markdown-CyrHoDhP.mjs} +37 -9
  39. package/dist/markdown-CyrHoDhP.mjs.map +1 -0
  40. package/dist/schemas/index.d.mts +3 -3
  41. package/dist/schemas/index.mjs +1 -1
  42. package/dist/{schemas-DJS7wOzd.mjs → schemas-UL4ynWsA.mjs} +3 -3
  43. package/dist/schemas-UL4ynWsA.mjs.map +1 -0
  44. package/dist/{types-DUsjRE7Y.d.mts → types-Cn52sdoq.d.mts} +2 -2
  45. package/dist/{types-DUsjRE7Y.d.mts.map → types-Cn52sdoq.d.mts.map} +1 -1
  46. package/dist/vite/index.d.mts +2 -2
  47. package/dist/vite/index.d.mts.map +1 -1
  48. package/dist/vite/index.mjs +33 -17
  49. package/dist/vite/index.mjs.map +1 -1
  50. package/package.json +3 -7
  51. package/dist/content-layer-B7fQ3im4.mjs.map +0 -1
  52. package/dist/convert-DnuB6SVV.mjs +0 -52
  53. package/dist/convert-DnuB6SVV.mjs.map +0 -1
  54. package/dist/css-ekIt2Fdb.mjs.map +0 -1
  55. package/dist/index-CeNDTM-y.d.mts +0 -7
  56. package/dist/index-CeNDTM-y.d.mts.map +0 -1
  57. package/dist/index-DpRBzO8Q.d.mts.map +0 -1
  58. package/dist/index-sFCx17CD.d.mts.map +0 -1
  59. package/dist/loaders-DyABmDrE.mjs.map +0 -1
  60. package/dist/markdown-Cj5X26FL.mjs.map +0 -1
  61. package/dist/schemas-DJS7wOzd.mjs.map +0 -1
@@ -1,8 +1,8 @@
1
- import "../markdown-Cj5X26FL.mjs";
2
- import { s as toSlug, t as createContentLayer } from "../content-layer-B7fQ3im4.mjs";
3
- import { n as resolveLoader } from "../loaders-DyABmDrE.mjs";
4
- import { n as copyPublicFiles } from "../assets-bX08zEJm.mjs";
5
- import { dirname, extname, join, relative, resolve } from "path";
1
+ import "../markdown-CyrHoDhP.mjs";
2
+ import { c as toSlug, t as createContentLayer } from "../content-layer-DPK1EmfY.mjs";
3
+ import { r as resolveLoader } from "../loaders-Cf-BXf2L.mjs";
4
+ import { n as copyPublicFiles } from "../assets-DXiWF_KI.mjs";
5
+ import { basename, dirname, extname, join, relative, resolve } from "path";
6
6
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "fs";
7
7
  import { fileURLToPath, pathToFileURL } from "url";
8
8
  import { uneval } from "devalue";
@@ -166,11 +166,13 @@ function isAssetReference(ref) {
166
166
  }
167
167
  function rewriteContentAssetRefs(html, base) {
168
168
  const basePrefix = base.replace(/\/+$/u, "");
169
- return html.replace(/(src|href|srcset)="([^"]+)"/g, (match, attr, ref) => {
169
+ return html.replace(/(src|href|srcset)=(?:"([^"]+)"|'([^']+)')/g, (match, attr, doubleRef, singleRef) => {
170
+ const ref = doubleRef ?? singleRef ?? "";
170
171
  if (!isAssetReference(ref)) return match;
171
172
  const pathname = ref.split(/[?#]/u, 1)[0] ?? ref;
172
173
  const suffix = ref.slice(pathname.length);
173
- return `${attr}="${basePrefix}/assets/${pathname.split("/").pop() ?? pathname}${suffix}"`;
174
+ const quote = doubleRef !== void 0 ? "\"" : "'";
175
+ return `${attr}=${quote}${basePrefix}/assets/${pathname.split("/").pop() ?? pathname}${suffix}${quote}`;
174
176
  });
175
177
  }
176
178
  function collectContentAssets(contentDirs) {
@@ -254,7 +256,10 @@ function pagesmithSsg(options) {
254
256
  return;
255
257
  }
256
258
  }
257
- if (!(req.headers.accept ?? "").includes("text/html")) return next();
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();
258
263
  if (base && (url === "/" || url === "")) {
259
264
  res.writeHead(302, { Location: `${base}/` });
260
265
  res.end();
@@ -341,7 +346,7 @@ function pagesmithSsg(options) {
341
346
  stdio: "inherit",
342
347
  cwd: projectRoot
343
348
  });
344
- const ssrMod = await import(pathToFileURL(join(serverDir, "entry-server.js")).href);
349
+ const ssrMod = await import(pathToFileURL(join(serverDir, basename(options.entry).replace(/\.(c|m)?[jt]sx?$/u, ".js"))).href);
345
350
  const renderConfig = {
346
351
  base,
347
352
  root: projectRoot,
@@ -485,10 +490,11 @@ function pagesmithContent(collectionsOrOptions, maybeOptions = {}) {
485
490
  let contentRoot = projectRoot;
486
491
  let configPath = resolve(projectRoot, options.configPath ?? "content.config.ts");
487
492
  let dtsPath = resolveDtsPath(projectRoot, options.dts);
488
- let layer = createContentLayer({
489
- ...options,
490
- root: layerRoot
491
- });
493
+ let layer = null;
494
+ function getLayer() {
495
+ if (!layer) throw new Error("pagesmith-content: ContentLayer not initialized. configResolved has not run yet.");
496
+ return layer;
497
+ }
492
498
  const ensureDeclarations = () => {
493
499
  if (options.dts === false) return;
494
500
  const source = createDtsSource(moduleId, collectionNames, dtsPath, configPath);
@@ -524,21 +530,31 @@ function pagesmithContent(collectionsOrOptions, maybeOptions = {}) {
524
530
  const collectionName = id.slice(resolvedPrefix.length);
525
531
  const collectionDef = options.collections[collectionName];
526
532
  if (!collectionDef) return;
527
- return serializeCollection(layer, collectionName, collectionDef, contentRoot);
533
+ return serializeCollection(getLayer(), collectionName, collectionDef, contentRoot);
528
534
  },
529
535
  handleHotUpdate({ file, server }) {
530
536
  const resolvedFile = resolve(file);
531
537
  const touchesConfig = resolvedFile === configPath;
532
- const touchesContent = collectionNames.some((name) => isPathWithin(resolve(layerRoot, options.collections[name].directory), resolvedFile));
538
+ const affectedCollections = collectionNames.filter((name) => isPathWithin(resolve(layerRoot, options.collections[name].directory), resolvedFile));
539
+ const touchesContent = affectedCollections.length > 0;
533
540
  if (!touchesConfig && !touchesContent) return;
534
541
  if (touchesConfig) ensureDeclarations();
535
542
  const rootModule = server.moduleGraph.getModuleById(resolvedRootId);
536
543
  if (rootModule) server.moduleGraph.invalidateModule(rootModule);
537
- for (const name of collectionNames) {
544
+ if (touchesContent) for (const name of affectedCollections) {
538
545
  const moduleNode = server.moduleGraph.getModuleById(`${resolvedPrefix}${name}`);
539
546
  if (moduleNode) server.moduleGraph.invalidateModule(moduleNode);
547
+ getLayer().invalidateCollection(name).catch((err) => {
548
+ console.warn(`[pagesmith] Failed to invalidate collection "${name}":`, err);
549
+ });
550
+ }
551
+ else {
552
+ for (const name of collectionNames) {
553
+ const moduleNode = server.moduleGraph.getModuleById(`${resolvedPrefix}${name}`);
554
+ if (moduleNode) server.moduleGraph.invalidateModule(moduleNode);
555
+ }
556
+ getLayer().invalidateAll();
540
557
  }
541
- layer.invalidateAll();
542
558
  server.ws.send({ type: "full-reload" });
543
559
  }
544
560
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/ssg.ts","../../src/vite/shared-assets.ts","../../src/vite/ssg-plugin.ts","../../src/vite/index.ts"],"sourcesContent":["/**\n * Pre-rendering utility for Vite-based SSG sites.\n *\n * Call after running both the client and SSR Vite builds.\n * Loads the SSR module, renders each route, injects into the\n * client HTML template, and writes static files.\n */\n\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'\nimport { dirname, resolve } from 'path'\nimport { pathToFileURL } from 'url'\n\nexport type PrerenderOptions = {\n /** Absolute path to the client build output directory (e.g., `dist/`) */\n outDir: string\n /** Absolute path to the built SSR entry module (e.g., `dist/.server/entry-server.js`) */\n serverEntry: string\n /** Routes to pre-render (e.g., `['/', '/about', '/posts/hello-world']`) */\n routes: string[]\n /** HTML placeholder to replace with rendered content (default: `'<!--ssr-outlet-->'`) */\n placeholder?: string\n /** Remove the server build directory after pre-rendering (default: true) */\n cleanup?: boolean\n}\n\n/**\n * Pre-render routes to static HTML files.\n *\n * Expects the SSR entry to export a `render(url: string): string` function.\n *\n * @example\n * ```ts\n * import { build } from 'vite'\n * import { prerenderRoutes } from '@pagesmith/core/vite'\n *\n * // 1. Client build\n * await build({ build: { outDir: 'dist' } })\n *\n * // 2. SSR build\n * await build({ build: { ssr: 'src/entry-server.tsx', outDir: 'dist/.server' } })\n *\n * // 3. Pre-render\n * await prerenderRoutes({\n * outDir: resolve('dist'),\n * serverEntry: resolve('dist/.server/entry-server.js'),\n * routes: ['/', '/about', '/posts/hello-world'],\n * })\n * ```\n */\nexport async function prerenderRoutes(options: PrerenderOptions): Promise<{ pages: number }> {\n const placeholder = options.placeholder ?? '<!--ssr-outlet-->'\n const cleanup = options.cleanup ?? true\n\n // Load SSR module\n if (!existsSync(options.serverEntry)) {\n throw new Error(`SSR entry not found: ${options.serverEntry}`)\n }\n\n const mod = await import(pathToFileURL(options.serverEntry).href)\n const render: (url: string) => string | Promise<string> = mod.render ?? mod.default?.render\n\n if (typeof render !== 'function') {\n throw new Error(\n `SSR entry must export a 'render(url: string)' function. ` +\n `Found exports: ${Object.keys(mod).join(', ')}`,\n )\n }\n\n // Read client HTML template\n const templatePath = resolve(options.outDir, 'index.html')\n const template = readFileSync(templatePath, 'utf-8')\n\n if (!template.includes(placeholder)) {\n throw new Error(\n `HTML template does not contain placeholder \"${placeholder}\". ` +\n `Add it to your index.html where SSR content should be injected.`,\n )\n }\n\n // Pre-render each route\n for (const route of options.routes) {\n const rendered = await render(route)\n const html = template.replace(placeholder, rendered)\n\n const routePath = route === '/' ? '' : route.replace(/^\\//, '')\n const outPath = resolve(options.outDir, routePath, 'index.html')\n mkdirSync(dirname(outPath), { recursive: true })\n writeFileSync(outPath, html)\n }\n\n // Clean up server build\n if (cleanup) {\n const serverDir = dirname(options.serverEntry)\n rmSync(serverDir, { recursive: true, force: true })\n }\n\n return { pages: options.routes.length }\n}\n","import { existsSync, readFileSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\nimport type { Plugin } from 'vite'\n\n/**\n * Vite plugin that serves shared font assets during development.\n * In production, fonts are copied to the output directory by the build script.\n */\nexport function sharedAssetsPlugin(): Plugin {\n const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..')\n const assetsDir = join(pkgDir, 'assets')\n\n return {\n name: 'pagesmith:shared-assets',\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? ''\n\n // Serve fonts.css\n if (url === '/assets/fonts.css' || url.endsWith('/assets/fonts.css')) {\n const filePath = join(assetsDir, 'fonts.css')\n if (existsSync(filePath)) {\n res.writeHead(200, { 'Content-Type': 'text/css', 'Cache-Control': 'no-cache' })\n res.end(readFileSync(filePath, 'utf-8'))\n return\n }\n }\n\n // Serve font files\n if (url.includes('/assets/fonts/') && url.endsWith('.woff2')) {\n const fileName = url.split('/assets/fonts/').pop()\n if (fileName) {\n const filePath = join(assetsDir, 'fonts', fileName)\n if (existsSync(filePath)) {\n res.writeHead(200, {\n 'Content-Type': 'font/woff2',\n 'Cache-Control': 'public, max-age=31536000',\n })\n res.end(readFileSync(filePath))\n return\n }\n }\n }\n\n next()\n })\n },\n }\n}\n","/**\n * Vite plugin for static site generation with @pagesmith/core.\n *\n * Handles both development (on-the-fly SSR via middleware) and\n * production (post-build SSG + pagefind indexing).\n *\n * The SSR entry module must export:\n * - `getRoutes(config)` — returns route paths to pre-render\n * - `render(url, config)` — renders a route to an HTML string\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { pagesmithSsg } from '@pagesmith/core/vite'\n *\n * export default defineConfig({\n * base: '/my-site/',\n * plugins: [pagesmithSsg({ entry: './src/entry-server.tsx' })],\n * })\n * ```\n */\n\nimport {\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'fs'\nimport { dirname, extname, join, resolve } from 'path'\nimport { fileURLToPath, pathToFileURL } from 'url'\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'\nimport { copyPublicFiles } from '../assets'\n\nexport type SsgPluginOptions = {\n /** Path to the SSR entry module (e.g., './src/entry-server.tsx') */\n entry: string\n /** Run pagefind after build (default: true) */\n pagefind?: boolean\n /** Content roots used for copying companion assets. */\n contentDirs?: string[]\n}\n\nexport type SsgRenderConfig = {\n /** Base path without trailing slash (e.g., '/my-site') */\n base: string\n /** Absolute path to the project root */\n root: string\n /** Path to the built CSS asset */\n cssPath: string\n /** Path to the built JS asset (undefined in dev for inline-script examples) */\n jsPath?: string\n /** Whether search is enabled (false in dev) */\n searchEnabled: boolean\n /** Whether running in dev mode */\n isDev: boolean\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n '.woff2': 'font/woff2',\n '.woff': 'font/woff',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain; charset=utf-8',\n '.xml': 'application/xml; charset=utf-8',\n}\n\nconst WS_RELOAD_SCRIPT = `<script type=\"module\">\nimport 'vite/modulepreload-polyfill'\nif (import.meta.hot) {\n import.meta.hot.on('full-reload', () => location.reload())\n}\n</script>`\n\nconst CONTENT_ASSET_EXTS = new Set([\n '.svg',\n '.png',\n '.jpg',\n '.jpeg',\n '.gif',\n '.webp',\n '.avif',\n '.ico',\n])\n\nfunction resolveContentDirs(projectRoot: string, contentDirs: string[] = []): string[] {\n return contentDirs.map((dir) => resolve(projectRoot, dir))\n}\n\nfunction isAssetReference(ref: string): boolean {\n if (!ref.startsWith('./')) return false\n const path = ref.split(/[?#]/u, 1)[0] ?? ref\n return CONTENT_ASSET_EXTS.has(extname(path).toLowerCase())\n}\n\nfunction rewriteContentAssetRefs(html: string, base: string): string {\n const basePrefix = base.replace(/\\/+$/u, '')\n\n return html.replace(/(src|href|srcset)=\"([^\"]+)\"/g, (match, attr: string, ref: string) => {\n if (!isAssetReference(ref)) return match\n const pathname = ref.split(/[?#]/u, 1)[0] ?? ref\n const suffix = ref.slice(pathname.length)\n return `${attr}=\"${basePrefix}/assets/${pathname.split('/').pop() ?? pathname}${suffix}\"`\n })\n}\n\nfunction collectContentAssets(contentDirs: string[]): Map<string, string> {\n const assets = new Map<string, string>()\n\n function walk(dir: string): void {\n if (!existsSync(dir)) return\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.')) continue\n\n const fullPath = join(dir, entry.name)\n\n if (entry.isDirectory()) {\n walk(fullPath)\n continue\n }\n\n const ext = extname(entry.name).toLowerCase()\n if (!CONTENT_ASSET_EXTS.has(ext)) continue\n\n if (assets.has(entry.name) && assets.get(entry.name) !== fullPath) {\n console.warn(\n `pagesmith:ssg duplicate companion asset basename \"${entry.name}\" detected; using ${fullPath}`,\n )\n }\n\n assets.set(entry.name, fullPath)\n }\n }\n\n for (const dir of contentDirs) {\n walk(dir)\n }\n\n return assets\n}\n\nfunction copyContentAssetsToOutDir(outDir: string, assets: Map<string, string>): void {\n if (assets.size === 0) return\n\n const assetsDir = join(outDir, 'assets')\n mkdirSync(assetsDir, { recursive: true })\n\n for (const [fileName, sourcePath] of assets) {\n copyFileSync(sourcePath, join(assetsDir, fileName))\n }\n}\n\nexport function pagesmithSsg(options: SsgPluginOptions): Plugin[] {\n const enablePagefind = options.pagefind !== false\n let config: ResolvedConfig\n let projectRoot: string\n let base: string // e.g., '/my-site'\n let outDir: string\n let contentDirs: string[] = []\n let contentAssets = new Map<string, string>()\n\n // ── Dev plugin: SSR middleware ──\n const devPlugin: Plugin = {\n name: 'pagesmith:ssg-dev',\n apply: 'serve',\n\n config() {\n // Disable Vite's built-in SPA HTML serving so the SSG middleware\n // can handle all HTML requests via server-side rendering.\n return { appType: 'custom' }\n },\n\n configResolved(resolved) {\n config = resolved\n projectRoot = resolved.root\n base = resolved.base.replace(/\\/+$/, '')\n outDir = resolve(projectRoot, resolved.build.outDir)\n contentDirs = resolveContentDirs(projectRoot, options.contentDirs)\n },\n\n configureServer(server: ViteDevServer) {\n async function refreshContentArtifacts(): Promise<void> {\n contentAssets = collectContentAssets(contentDirs)\n }\n\n void refreshContentArtifacts().catch((error) => {\n console.warn(\n `pagesmith:ssg failed to prepare companion assets: ${error instanceof Error ? error.message : String(error)}`,\n )\n })\n\n if (contentDirs.length > 0) {\n server.watcher.add(contentDirs)\n\n const refresh = () => {\n void refreshContentArtifacts().catch((error) => {\n console.warn(\n `pagesmith:ssg failed to refresh companion assets: ${error instanceof Error ? error.message : String(error)}`,\n )\n })\n }\n\n server.watcher.on('add', refresh)\n server.watcher.on('change', refresh)\n server.watcher.on('unlink', refresh)\n }\n\n // Register middleware directly — appType: 'custom' disables Vite's\n // built-in HTML serving, so we handle all HTML requests via SSR.\n server.middlewares.use(async (req, res, next) => {\n const url = req.url ?? '/'\n const pathname = url.split(/[?#]/u, 1)[0] ?? url\n\n if (pathname.includes('/assets/')) {\n const assetName = pathname.split('/assets/').pop()\n const assetPath = assetName ? contentAssets.get(assetName) : undefined\n\n if (assetPath) {\n const ext = extname(assetPath).toLowerCase()\n res.writeHead(200, {\n 'Content-Type': MIME[ext] ?? 'application/octet-stream',\n 'Cache-Control': 'no-cache',\n })\n res.end(readFileSync(assetPath))\n return\n }\n }\n\n // Only handle HTML navigation requests (not assets)\n const accept = req.headers.accept ?? ''\n if (!accept.includes('text/html')) return next()\n\n // Redirect root to base\n if (base && (url === '/' || url === '')) {\n res.writeHead(302, { Location: `${base}/` })\n res.end()\n return\n }\n\n // Must start with base path\n if (base && !url.startsWith(base)) return next()\n\n try {\n // Load SSR module on-the-fly (Vite transforms TSX etc.)\n const ssrMod = await server.ssrLoadModule(resolve(projectRoot, options.entry))\n const renderFn: (url: string, cfg: SsgRenderConfig) => Promise<string> | string =\n ssrMod.render\n\n if (typeof renderFn !== 'function') {\n return next()\n }\n\n const renderConfig: SsgRenderConfig = {\n base,\n root: projectRoot,\n cssPath: `${base}/src/theme.css`, // Vite transforms this in dev\n jsPath: undefined,\n searchEnabled: false,\n isDev: true,\n }\n\n let html = await renderFn(url, renderConfig)\n html = rewriteContentAssetRefs(html, base)\n\n // Inject Vite's client + HMR script for live reload\n html = html.replace(\n '</head>',\n `<script type=\"module\" src=\"/@vite/client\"></script>\\n` +\n `<link rel=\"stylesheet\" href=\"${base}/src/theme.css\">\\n` +\n `</head>`,\n )\n\n // Let Vite transform the HTML (resolves module URLs, etc.)\n html = await server.transformIndexHtml(url, html)\n\n const status = html.includes('doc-not-found') ? 404 : 200\n res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(html)\n } catch (err: any) {\n server.ssrFixStacktrace(err)\n console.error(`SSR error for ${url}:`, err.message)\n next(err)\n }\n })\n },\n }\n\n // ── Build plugin: SSG post-build ──\n const buildPlugin: Plugin = {\n name: 'pagesmith:ssg-build',\n apply: 'build',\n\n configResolved(resolved) {\n config = resolved\n projectRoot = resolved.root\n base = resolved.base.replace(/\\/+$/, '')\n outDir = resolve(projectRoot, resolved.build.outDir)\n contentDirs = resolveContentDirs(projectRoot, options.contentDirs)\n },\n\n async closeBundle() {\n // Skip SSG during the SSR build itself (detected by ssr option)\n if (config.build.ssr) return\n\n console.log('\\nSSG: Starting static site generation...')\n\n contentAssets = collectContentAssets(contentDirs)\n\n // Copy font assets from @pagesmith/core\n const corePkgDir = dirname(fileURLToPath(import.meta.resolve('@pagesmith/core/package.json')))\n const coreFontsDir = join(corePkgDir, 'assets', 'fonts')\n const outFontsDir = join(outDir, 'assets', 'fonts')\n mkdirSync(outFontsDir, { recursive: true })\n for (const file of readdirSync(coreFontsDir)) {\n if (file.endsWith('.woff2')) {\n copyFileSync(join(coreFontsDir, file), join(outFontsDir, file))\n }\n }\n copyFileSync(join(corePkgDir, 'assets', 'fonts.css'), join(outDir, 'assets', 'fonts.css'))\n\n // Copy public/ files (favicon etc.)\n const publicDir = join(projectRoot, 'public')\n copyPublicFiles(publicDir, outDir)\n\n // Discover built asset paths from the client build output\n const builtIndex = join(outDir, 'index.html')\n let cssPath = `${base}/assets/style.css`\n let jsPath: string | undefined\n if (existsSync(builtIndex)) {\n const html = readFileSync(builtIndex, 'utf-8')\n const cssMatch = html.match(/href=\"([^\"]*\\.css)\"/)\n const jsMatch = html.match(/src=\"([^\"]*\\.js)\"/)\n if (cssMatch) cssPath = cssMatch[1]\n if (jsMatch) jsPath = jsMatch[1]\n }\n\n // SSR build — use child process to avoid nested Vite resolution issues\n console.log('SSG: Building SSR bundle...')\n const { execFileSync } = await import('child_process')\n const serverDir = join(outDir, '.server')\n const ssrEntry = resolve(projectRoot, options.entry)\n // Write a temp build script that externalizes node_modules and skips the SSG plugin\n const buildScript = `\n import { build } from 'vite-plus';\n await build({\n root: ${JSON.stringify(projectRoot)},\n logLevel: 'warn',\n mode: ${JSON.stringify(config.mode)},\n build: {\n ssr: ${JSON.stringify(ssrEntry)},\n outDir: ${JSON.stringify(serverDir)},\n emptyOutDir: true,\n },\n });\n `\n execFileSync(process.execPath, ['--input-type=module', '-e', buildScript], {\n stdio: 'inherit',\n cwd: projectRoot,\n })\n\n // Load SSR module\n const serverEntry = join(serverDir, 'entry-server.js')\n const ssrMod = await import(pathToFileURL(serverEntry).href)\n\n const renderConfig: SsgRenderConfig = {\n base,\n root: projectRoot,\n cssPath,\n jsPath,\n searchEnabled: true,\n isDev: false,\n }\n\n // Get routes and render\n const routes: string[] = await ssrMod.getRoutes(renderConfig)\n console.log(`SSG: Rendering ${routes.length} pages...`)\n\n for (const route of routes) {\n const html = rewriteContentAssetRefs(await ssrMod.render(route, renderConfig), base)\n const routePath = route === '/' ? '' : route.replace(/^\\//, '')\n const outputPath = join(outDir, routePath, 'index.html')\n mkdirSync(dirname(outputPath), { recursive: true })\n writeFileSync(outputPath, `<!DOCTYPE html>\\n${html}`)\n\n if (route === '/404') {\n writeFileSync(join(outDir, '404.html'), `<!DOCTYPE html>\\n${html}`)\n }\n }\n\n copyContentAssetsToOutDir(outDir, contentAssets)\n\n // Cleanup SSR build\n rmSync(serverDir, { recursive: true, force: true })\n\n // Run pagefind\n if (enablePagefind) {\n console.log('SSG: Indexing with Pagefind...')\n try {\n const pagefindMain = fileURLToPath(import.meta.resolve('pagefind'))\n const pagefindBin = join(dirname(pagefindMain), '..', 'lib', 'runner', 'bin.cjs')\n const { execFileSync } = await import('child_process')\n execFileSync(process.execPath, [pagefindBin, '--site', outDir], { stdio: 'inherit' })\n } catch {\n console.warn('SSG: Pagefind not found, skipping search indexing')\n }\n }\n\n console.log(`SSG: Done — ${routes.length} pages generated`)\n },\n }\n\n return [devPlugin, buildPlugin]\n}\n","export { prerenderRoutes } from './ssg'\nexport type { PrerenderOptions } from './ssg'\nexport { sharedAssetsPlugin } from './shared-assets.js'\nexport { pagesmithSsg } from './ssg-plugin.js'\nexport type { SsgPluginOptions, SsgRenderConfig } from './ssg-plugin.js'\n\nimport { existsSync, mkdirSync, writeFileSync } from 'fs'\nimport { dirname, relative, resolve } from 'path'\nimport { uneval } from 'devalue'\nimport { createContentLayer } from '../content-layer'\nimport { resolveLoader } from '../loaders'\nimport type { Heading } from '../schemas/heading'\nimport type { CollectionDef, CollectionMap, InferCollectionData } from '../schemas/collection'\nimport type { ContentLayerConfig } from '../schemas/content-config'\nimport { toSlug } from '../utils/slug'\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {}\n\ntype PagesmithResolvedConfig = {\n root: string\n}\n\ntype PagesmithModuleGraph = {\n getModuleById(id: string): unknown\n invalidateModule(module: unknown): void\n}\n\ntype PagesmithDevServer = {\n moduleGraph: PagesmithModuleGraph\n ws: {\n send(payload: { type: string }): void\n }\n}\n\nexport type PagesmithVitePlugin = {\n name: string\n enforce?: 'pre' | 'post'\n configResolved?: (config: PagesmithResolvedConfig) => void\n buildStart?: () => void\n resolveId?: (id: string) => string | void\n load?: (id: string) => Promise<string | void> | string | void\n handleHotUpdate?: (context: { file: string; server: PagesmithDevServer }) => void\n}\n\nexport type BaseContentModuleEntry = {\n id: string\n contentSlug: string\n}\n\nexport type MarkdownContentModuleEntry<TCollection extends CollectionDef<any, any, any>> = Simplify<\n BaseContentModuleEntry & {\n html: string\n headings: Heading[]\n frontmatter: InferCollectionData<TCollection>\n }\n>\n\nexport type DataContentModuleEntry<TCollection extends CollectionDef<any, any, any>> = Simplify<\n BaseContentModuleEntry & {\n data: InferCollectionData<TCollection>\n }\n>\n\ntype LoaderKindFromCollection<TCollection extends CollectionDef<any, any, any>> =\n TCollection['loader'] extends 'markdown'\n ? 'markdown'\n : TCollection['loader'] extends { kind: infer TKind }\n ? TKind\n : 'data'\n\nexport type ContentCollectionModule<TCollection extends CollectionDef<any, any, any>> =\n LoaderKindFromCollection<TCollection> extends 'markdown'\n ? MarkdownContentModuleEntry<TCollection>[]\n : DataContentModuleEntry<TCollection>[]\n\nexport type ContentModuleMap<TCollections extends CollectionMap> = {\n [TName in keyof TCollections]: ContentCollectionModule<TCollections[TName]>\n}\n\nexport type PagesmithContentPluginOptions<TCollections extends CollectionMap> = Omit<\n ContentLayerConfig,\n 'collections'\n> & {\n collections: TCollections\n /**\n * Shared content root used to compute `id` and `contentSlug`.\n * Defaults to the deepest common parent directory across all collection directories.\n */\n contentRoot?: string\n /**\n * Root virtual module id.\n * Per-collection modules are exposed as `${moduleId}/<collection-name>`.\n */\n moduleId?: string\n /**\n * Path to the content config module used for generated typings.\n * Defaults to `./content.config.ts`.\n */\n configPath?: string\n /**\n * Generate module declarations for the virtual modules.\n * Defaults to `src/pagesmith-content.d.ts` when `src/` exists, otherwise `pagesmith-content.d.ts`.\n */\n dts?: boolean | string | { path?: string }\n}\n\nconst DEFAULT_MODULE_ID = 'virtual:content'\n\nfunction stripExtension(filePath: string): string {\n return filePath.replace(/\\.(c|m)?[jt]sx?$/u, '')\n}\n\nfunction normalizePath(value: string): string {\n return value.replace(/\\\\/g, '/')\n}\n\nfunction isPathWithin(parent: string, candidate: string): boolean {\n const rel = normalizePath(relative(parent, candidate))\n return rel === '' || (!rel.startsWith('..') && !rel.startsWith('/'))\n}\n\nfunction commonDirectory(paths: string[]): string {\n const normalized = paths.map((path) => normalizePath(resolve(path)))\n if (normalized.length === 0) return process.cwd()\n if (normalized.length === 1) return normalized[0]\n\n const segments = normalized.map((path) => path.split('/').filter(Boolean))\n const shared: string[] = []\n const first = segments[0]!\n\n for (let index = 0; index < first.length; index += 1) {\n const segment = first[index]\n if (segments.every((parts) => parts[index] === segment)) {\n shared.push(segment)\n continue\n }\n break\n }\n\n if (shared.length === 0) {\n return resolve('/')\n }\n\n return resolve(`/${shared.join('/')}`)\n}\n\nfunction resolveDtsPath(\n projectRoot: string,\n dts: PagesmithContentPluginOptions<any>['dts'],\n): string {\n if (dts === false) {\n return ''\n }\n\n if (typeof dts === 'string') {\n return resolve(projectRoot, dts)\n }\n\n if (typeof dts === 'object' && dts?.path) {\n return resolve(projectRoot, dts.path)\n }\n\n const srcPath = resolve(projectRoot, 'src')\n if (existsSync(srcPath)) {\n return resolve(srcPath, 'pagesmith-content.d.ts')\n }\n\n return resolve(projectRoot, 'pagesmith-content.d.ts')\n}\n\nfunction createDtsSource(\n moduleId: string,\n collectionNames: string[],\n dtsPath: string,\n configPath: string,\n): string {\n const configImportPath = normalizePath(\n stripExtension(relative(dirname(dtsPath), configPath)).replace(/^[^.]/u, './$&'),\n )\n\n const moduleLines = collectionNames\n .map(\n (name) => `declare module '${moduleId}/${name}' {\n const collection: import('@pagesmith/core/vite').ContentCollectionModule<\n __PagesmithCollections['${name.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\")}']\n >\n export default collection\n}`,\n )\n .join('\\n\\n')\n\n return `// Generated by @pagesmith/core/vite. Do not edit manually.\ntype __PagesmithCollections = typeof import('${configImportPath}').default\n\ndeclare module '${moduleId}' {\n const content: import('@pagesmith/core/vite').ContentModuleMap<__PagesmithCollections>\n export default content\n}\n\n${moduleLines}\n`\n}\n\nasync function serializeCollection(\n layer: ReturnType<typeof createContentLayer>,\n collectionName: string,\n collectionDef: CollectionDef<any, any, any>,\n contentRoot: string,\n): Promise<string> {\n const entries = await layer.getCollection(collectionName)\n const loader = resolveLoader(collectionDef.loader)\n const sortedEntries = [...entries].sort((left, right) =>\n left.filePath.localeCompare(right.filePath),\n )\n\n const payload = await Promise.all(\n sortedEntries.map(async (entry) => {\n const contentSlug = toSlug(entry.filePath, contentRoot)\n const base = {\n id: contentSlug,\n contentSlug,\n }\n\n if (loader.kind === 'markdown') {\n const rendered = await entry.render()\n return {\n ...base,\n html: rendered.html,\n headings: rendered.headings,\n frontmatter: entry.data,\n }\n }\n\n return {\n ...base,\n data: entry.data,\n }\n }),\n )\n\n return `const collection = ${uneval(payload)};\\nexport default collection;\\n`\n}\n\nfunction createRootModuleSource(moduleId: string, collectionNames: string[]): string {\n const imports = collectionNames\n .map((name, index) => `import collection${index} from '${moduleId}/${name}'`)\n .join('\\n')\n const contentMap = collectionNames\n .map((name, index) => `${JSON.stringify(name)}: collection${index}`)\n .join(', ')\n\n return `${imports}\\n\\nexport default { ${contentMap} };\\n`\n}\n\nfunction resolvePluginOptions<TCollections extends CollectionMap>(\n collectionsOrOptions: TCollections | PagesmithContentPluginOptions<TCollections>,\n maybeOptions: Omit<PagesmithContentPluginOptions<TCollections>, 'collections'> = {},\n): PagesmithContentPluginOptions<TCollections> {\n if ('collections' in collectionsOrOptions) {\n return collectionsOrOptions as PagesmithContentPluginOptions<TCollections>\n }\n\n return {\n ...maybeOptions,\n collections: collectionsOrOptions,\n }\n}\n\nexport function pagesmithContent<TCollections extends CollectionMap>(\n collections: TCollections,\n options?: Omit<PagesmithContentPluginOptions<TCollections>, 'collections'>,\n): PagesmithVitePlugin\nexport function pagesmithContent<TCollections extends CollectionMap>(\n options: PagesmithContentPluginOptions<TCollections>,\n): PagesmithVitePlugin\nexport function pagesmithContent<TCollections extends CollectionMap>(\n collectionsOrOptions: TCollections | PagesmithContentPluginOptions<TCollections>,\n maybeOptions: Omit<PagesmithContentPluginOptions<TCollections>, 'collections'> = {},\n): PagesmithVitePlugin {\n const options = resolvePluginOptions(collectionsOrOptions, maybeOptions)\n const collectionNames = Object.keys(options.collections)\n const moduleId = options.moduleId ?? DEFAULT_MODULE_ID\n const resolvedPrefix = `\\0${moduleId}/`\n const resolvedRootId = `\\0${moduleId}`\n\n let projectRoot = process.cwd()\n let layerRoot = projectRoot\n let contentRoot = projectRoot\n let configPath = resolve(projectRoot, options.configPath ?? 'content.config.ts')\n let dtsPath = resolveDtsPath(projectRoot, options.dts)\n let layer = createContentLayer({\n ...options,\n root: layerRoot,\n })\n\n const ensureDeclarations = (): void => {\n if (options.dts === false) return\n\n const source = createDtsSource(moduleId, collectionNames, dtsPath, configPath)\n mkdirSync(dirname(dtsPath), { recursive: true })\n writeFileSync(dtsPath, source)\n }\n\n return {\n name: 'pagesmith-content',\n enforce: 'pre',\n\n configResolved(config) {\n projectRoot = resolve(config.root)\n layerRoot = resolve(projectRoot, options.root ?? '.')\n configPath = resolve(projectRoot, options.configPath ?? 'content.config.ts')\n dtsPath = resolveDtsPath(projectRoot, options.dts)\n\n const collectionDirectories = collectionNames.map((name) =>\n resolve(layerRoot, options.collections[name]!.directory),\n )\n contentRoot = options.contentRoot\n ? resolve(layerRoot, options.contentRoot)\n : commonDirectory(collectionDirectories)\n\n layer = createContentLayer({\n ...options,\n root: layerRoot,\n })\n\n ensureDeclarations()\n },\n\n buildStart() {\n ensureDeclarations()\n },\n\n resolveId(id) {\n if (id === moduleId) {\n return resolvedRootId\n }\n\n for (const name of collectionNames) {\n if (id === `${moduleId}/${name}`) {\n return `${resolvedPrefix}${name}`\n }\n }\n },\n\n async load(id) {\n if (id === resolvedRootId) {\n return createRootModuleSource(moduleId, collectionNames)\n }\n\n if (!id.startsWith(resolvedPrefix)) return\n\n const collectionName = id.slice(resolvedPrefix.length)\n const collectionDef = options.collections[collectionName]\n if (!collectionDef) return\n\n return serializeCollection(layer, collectionName, collectionDef, contentRoot)\n },\n\n handleHotUpdate({ file, server }) {\n const resolvedFile = resolve(file)\n const touchesConfig = resolvedFile === configPath\n const touchesContent = collectionNames.some((name) =>\n isPathWithin(resolve(layerRoot, options.collections[name]!.directory), resolvedFile),\n )\n\n if (!touchesConfig && !touchesContent) return\n\n if (touchesConfig) {\n ensureDeclarations()\n }\n\n const rootModule = server.moduleGraph.getModuleById(resolvedRootId)\n if (rootModule) {\n server.moduleGraph.invalidateModule(rootModule)\n }\n\n for (const name of collectionNames) {\n const moduleNode = server.moduleGraph.getModuleById(`${resolvedPrefix}${name}`)\n if (moduleNode) {\n server.moduleGraph.invalidateModule(moduleNode)\n }\n }\n\n layer.invalidateAll()\n server.ws.send({ type: 'full-reload' })\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/vite/ssg.ts","../../src/vite/shared-assets.ts","../../src/vite/ssg-plugin.ts","../../src/vite/index.ts"],"sourcesContent":["/**\n * Pre-rendering utility for Vite-based SSG sites.\n *\n * Call after running both the client and SSR Vite builds.\n * Loads the SSR module, renders each route, injects into the\n * client HTML template, and writes static files.\n */\n\nimport { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs'\nimport { dirname, resolve } from 'path'\nimport { pathToFileURL } from 'url'\n\nexport type PrerenderOptions = {\n /** Absolute path to the client build output directory (e.g., `dist/`) */\n outDir: string\n /** Absolute path to the built SSR entry module (e.g., `dist/.server/entry-server.js`) */\n serverEntry: string\n /** Routes to pre-render (e.g., `['/', '/about', '/posts/hello-world']`) */\n routes: string[]\n /** HTML placeholder to replace with rendered content (default: `'<!--ssr-outlet-->'`) */\n placeholder?: string\n /** Remove the server build directory after pre-rendering (default: true) */\n cleanup?: boolean\n}\n\n/**\n * Pre-render routes to static HTML files.\n *\n * Expects the SSR entry to export a `render(url: string): string` function.\n *\n * @example\n * ```ts\n * import { build } from 'vite'\n * import { prerenderRoutes } from '@pagesmith/core/vite'\n *\n * // 1. Client build\n * await build({ build: { outDir: 'dist' } })\n *\n * // 2. SSR build\n * await build({ build: { ssr: 'src/entry-server.tsx', outDir: 'dist/.server' } })\n *\n * // 3. Pre-render\n * await prerenderRoutes({\n * outDir: resolve('dist'),\n * serverEntry: resolve('dist/.server/entry-server.js'),\n * routes: ['/', '/about', '/posts/hello-world'],\n * })\n * ```\n */\nexport async function prerenderRoutes(options: PrerenderOptions): Promise<{ pages: number }> {\n const placeholder = options.placeholder ?? '<!--ssr-outlet-->'\n const cleanup = options.cleanup ?? true\n\n // Load SSR module\n if (!existsSync(options.serverEntry)) {\n throw new Error(`SSR entry not found: ${options.serverEntry}`)\n }\n\n const mod = await import(pathToFileURL(options.serverEntry).href)\n const render: (url: string) => string | Promise<string> = mod.render ?? mod.default?.render\n\n if (typeof render !== 'function') {\n throw new Error(\n `SSR entry must export a 'render(url: string)' function. ` +\n `Found exports: ${Object.keys(mod).join(', ')}`,\n )\n }\n\n // Read client HTML template\n const templatePath = resolve(options.outDir, 'index.html')\n const template = readFileSync(templatePath, 'utf-8')\n\n if (!template.includes(placeholder)) {\n throw new Error(\n `HTML template does not contain placeholder \"${placeholder}\". ` +\n `Add it to your index.html where SSR content should be injected.`,\n )\n }\n\n // Pre-render each route\n for (const route of options.routes) {\n const rendered = await render(route)\n const html = template.replace(placeholder, rendered)\n\n const routePath = route === '/' ? '' : route.replace(/^\\//, '')\n const outPath = resolve(options.outDir, routePath, 'index.html')\n mkdirSync(dirname(outPath), { recursive: true })\n writeFileSync(outPath, html)\n }\n\n // Clean up server build\n if (cleanup) {\n const serverDir = dirname(options.serverEntry)\n rmSync(serverDir, { recursive: true, force: true })\n }\n\n return { pages: options.routes.length }\n}\n","import { existsSync, readFileSync } from 'fs'\nimport { dirname, join } from 'path'\nimport { fileURLToPath } from 'url'\nimport type { Plugin } from 'vite'\n\n/**\n * Vite plugin that serves shared font assets during development.\n * In production, fonts are copied to the output directory by the build script.\n */\nexport function sharedAssetsPlugin(): Plugin {\n const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..')\n const assetsDir = join(pkgDir, 'assets')\n\n return {\n name: 'pagesmith:shared-assets',\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? ''\n\n // Serve fonts.css\n if (url === '/assets/fonts.css' || url.endsWith('/assets/fonts.css')) {\n const filePath = join(assetsDir, 'fonts.css')\n if (existsSync(filePath)) {\n res.writeHead(200, { 'Content-Type': 'text/css', 'Cache-Control': 'no-cache' })\n res.end(readFileSync(filePath, 'utf-8'))\n return\n }\n }\n\n // Serve font files\n if (url.includes('/assets/fonts/') && url.endsWith('.woff2')) {\n const fileName = url.split('/assets/fonts/').pop()\n if (fileName) {\n const filePath = join(assetsDir, 'fonts', fileName)\n if (existsSync(filePath)) {\n res.writeHead(200, {\n 'Content-Type': 'font/woff2',\n 'Cache-Control': 'public, max-age=31536000',\n })\n res.end(readFileSync(filePath))\n return\n }\n }\n }\n\n next()\n })\n },\n }\n}\n","/**\n * Vite plugin for static site generation with @pagesmith/core.\n *\n * Handles both development (on-the-fly SSR via middleware) and\n * production (post-build SSG + pagefind indexing).\n *\n * The SSR entry module must export:\n * - `getRoutes(config)` — returns route paths to pre-render\n * - `render(url, config)` — renders a route to an HTML string\n *\n * @example\n * ```ts\n * // vite.config.ts\n * import { pagesmithSsg } from '@pagesmith/core/vite'\n *\n * export default defineConfig({\n * base: '/my-site/',\n * plugins: [pagesmithSsg({ entry: './src/entry-server.tsx' })],\n * })\n * ```\n */\n\nimport {\n copyFileSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmSync,\n writeFileSync,\n} from 'fs'\nimport { basename, dirname, extname, join, resolve } from 'path'\nimport { fileURLToPath, pathToFileURL } from 'url'\nimport type { Plugin, ResolvedConfig, ViteDevServer } from 'vite'\nimport { copyPublicFiles } from '../assets'\n\nexport type SsgPluginOptions = {\n /** Path to the SSR entry module (e.g., './src/entry-server.tsx') */\n entry: string\n /** Run pagefind after build (default: true) */\n pagefind?: boolean\n /** Content roots used for copying companion assets. */\n contentDirs?: string[]\n}\n\nexport type SsgRenderConfig = {\n /** Base path without trailing slash (e.g., '/my-site') */\n base: string\n /** Absolute path to the project root */\n root: string\n /** Path to the built CSS asset */\n cssPath: string\n /** Path to the built JS asset (undefined in dev for inline-script examples) */\n jsPath?: string\n /** Whether search is enabled (false in dev) */\n searchEnabled: boolean\n /** Whether running in dev mode */\n isDev: boolean\n}\n\nconst MIME: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.js': 'application/javascript; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.avif': 'image/avif',\n '.ico': 'image/x-icon',\n '.woff2': 'font/woff2',\n '.woff': 'font/woff',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain; charset=utf-8',\n '.xml': 'application/xml; charset=utf-8',\n}\n\nconst WS_RELOAD_SCRIPT = `<script type=\"module\">\nimport 'vite/modulepreload-polyfill'\nif (import.meta.hot) {\n import.meta.hot.on('full-reload', () => location.reload())\n}\n</script>`\n\nconst CONTENT_ASSET_EXTS = new Set([\n '.svg',\n '.png',\n '.jpg',\n '.jpeg',\n '.gif',\n '.webp',\n '.avif',\n '.ico',\n])\n\nfunction resolveContentDirs(projectRoot: string, contentDirs: string[] = []): string[] {\n return contentDirs.map((dir) => resolve(projectRoot, dir))\n}\n\nfunction isAssetReference(ref: string): boolean {\n if (!ref.startsWith('./')) return false\n const path = ref.split(/[?#]/u, 1)[0] ?? ref\n return CONTENT_ASSET_EXTS.has(extname(path).toLowerCase())\n}\n\nfunction rewriteContentAssetRefs(html: string, base: string): string {\n const basePrefix = base.replace(/\\/+$/u, '')\n\n return html.replace(\n /(src|href|srcset)=(?:\"([^\"]+)\"|'([^']+)')/g,\n (match, attr: string, doubleRef: string | undefined, singleRef: string | undefined) => {\n const ref = doubleRef ?? singleRef ?? ''\n if (!isAssetReference(ref)) return match\n const pathname = ref.split(/[?#]/u, 1)[0] ?? ref\n const suffix = ref.slice(pathname.length)\n const quote = doubleRef !== undefined ? '\"' : \"'\"\n return `${attr}=${quote}${basePrefix}/assets/${pathname.split('/').pop() ?? pathname}${suffix}${quote}`\n },\n )\n}\n\nfunction collectContentAssets(contentDirs: string[]): Map<string, string> {\n const assets = new Map<string, string>()\n\n function walk(dir: string): void {\n if (!existsSync(dir)) return\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.name.startsWith('.')) continue\n\n const fullPath = join(dir, entry.name)\n\n if (entry.isDirectory()) {\n walk(fullPath)\n continue\n }\n\n const ext = extname(entry.name).toLowerCase()\n if (!CONTENT_ASSET_EXTS.has(ext)) continue\n\n if (assets.has(entry.name) && assets.get(entry.name) !== fullPath) {\n console.warn(\n `pagesmith:ssg duplicate companion asset basename \"${entry.name}\" detected; using ${fullPath}`,\n )\n }\n\n assets.set(entry.name, fullPath)\n }\n }\n\n for (const dir of contentDirs) {\n walk(dir)\n }\n\n return assets\n}\n\nfunction copyContentAssetsToOutDir(outDir: string, assets: Map<string, string>): void {\n if (assets.size === 0) return\n\n const assetsDir = join(outDir, 'assets')\n mkdirSync(assetsDir, { recursive: true })\n\n for (const [fileName, sourcePath] of assets) {\n copyFileSync(sourcePath, join(assetsDir, fileName))\n }\n}\n\nexport function pagesmithSsg(options: SsgPluginOptions): Plugin[] {\n const enablePagefind = options.pagefind !== false\n let config: ResolvedConfig\n let projectRoot: string\n let base: string // e.g., '/my-site'\n let outDir: string\n let contentDirs: string[] = []\n let contentAssets = new Map<string, string>()\n\n // ── Dev plugin: SSR middleware ──\n const devPlugin: Plugin = {\n name: 'pagesmith:ssg-dev',\n apply: 'serve',\n\n config() {\n // Disable Vite's built-in SPA HTML serving so the SSG middleware\n // can handle all HTML requests via server-side rendering.\n return { appType: 'custom' }\n },\n\n configResolved(resolved) {\n config = resolved\n projectRoot = resolved.root\n base = resolved.base.replace(/\\/+$/, '')\n outDir = resolve(projectRoot, resolved.build.outDir)\n contentDirs = resolveContentDirs(projectRoot, options.contentDirs)\n },\n\n configureServer(server: ViteDevServer) {\n async function refreshContentArtifacts(): Promise<void> {\n contentAssets = collectContentAssets(contentDirs)\n }\n\n void refreshContentArtifacts().catch((error) => {\n console.warn(\n `pagesmith:ssg failed to prepare companion assets: ${error instanceof Error ? error.message : String(error)}`,\n )\n })\n\n if (contentDirs.length > 0) {\n server.watcher.add(contentDirs)\n\n const refresh = () => {\n void refreshContentArtifacts().catch((error) => {\n console.warn(\n `pagesmith:ssg failed to refresh companion assets: ${error instanceof Error ? error.message : String(error)}`,\n )\n })\n }\n\n server.watcher.on('add', refresh)\n server.watcher.on('change', refresh)\n server.watcher.on('unlink', refresh)\n }\n\n // Register middleware directly — appType: 'custom' disables Vite's\n // built-in HTML serving, so we handle all HTML requests via SSR.\n server.middlewares.use(async (req, res, next) => {\n const url = req.url ?? '/'\n const pathname = url.split(/[?#]/u, 1)[0] ?? url\n\n if (pathname.includes('/assets/')) {\n const assetName = pathname.split('/assets/').pop()\n const assetPath = assetName ? contentAssets.get(assetName) : undefined\n\n if (assetPath) {\n const ext = extname(assetPath).toLowerCase()\n res.writeHead(200, {\n 'Content-Type': MIME[ext] ?? 'application/octet-stream',\n 'Cache-Control': 'no-cache',\n })\n res.end(readFileSync(assetPath))\n return\n }\n }\n\n // Only handle HTML navigation requests (not assets, not files with extensions)\n const accept = req.headers.accept ?? ''\n const pathExt = extname(pathname)\n if (pathExt && pathExt !== '.html') return next()\n if (!pathExt && !accept.includes('text/html')) return next()\n\n // Redirect root to base\n if (base && (url === '/' || url === '')) {\n res.writeHead(302, { Location: `${base}/` })\n res.end()\n return\n }\n\n // Must start with base path\n if (base && !url.startsWith(base)) return next()\n\n try {\n // Load SSR module on-the-fly (Vite transforms TSX etc.)\n const ssrMod = await server.ssrLoadModule(resolve(projectRoot, options.entry))\n const renderFn: (url: string, cfg: SsgRenderConfig) => Promise<string> | string =\n ssrMod.render\n\n if (typeof renderFn !== 'function') {\n return next()\n }\n\n const renderConfig: SsgRenderConfig = {\n base,\n root: projectRoot,\n cssPath: `${base}/src/theme.css`, // Vite transforms this in dev\n jsPath: undefined,\n searchEnabled: false,\n isDev: true,\n }\n\n let html = await renderFn(url, renderConfig)\n html = rewriteContentAssetRefs(html, base)\n\n // Inject Vite's client + HMR script for live reload\n html = html.replace(\n '</head>',\n `<script type=\"module\" src=\"/@vite/client\"></script>\\n` +\n `<link rel=\"stylesheet\" href=\"${base}/src/theme.css\">\\n` +\n `</head>`,\n )\n\n // Let Vite transform the HTML (resolves module URLs, etc.)\n html = await server.transformIndexHtml(url, html)\n\n const status = html.includes('doc-not-found') ? 404 : 200\n res.writeHead(status, { 'Content-Type': 'text/html; charset=utf-8' })\n res.end(html)\n } catch (err: any) {\n server.ssrFixStacktrace(err)\n console.error(`SSR error for ${url}:`, err.message)\n next(err)\n }\n })\n },\n }\n\n // ── Build plugin: SSG post-build ──\n const buildPlugin: Plugin = {\n name: 'pagesmith:ssg-build',\n apply: 'build',\n\n configResolved(resolved) {\n config = resolved\n projectRoot = resolved.root\n base = resolved.base.replace(/\\/+$/, '')\n outDir = resolve(projectRoot, resolved.build.outDir)\n contentDirs = resolveContentDirs(projectRoot, options.contentDirs)\n },\n\n async closeBundle() {\n // Skip SSG during the SSR build itself (detected by ssr option)\n if (config.build.ssr) return\n\n console.log('\\nSSG: Starting static site generation...')\n\n contentAssets = collectContentAssets(contentDirs)\n\n // Copy font assets from @pagesmith/core\n const corePkgDir = dirname(fileURLToPath(import.meta.resolve('@pagesmith/core/package.json')))\n const coreFontsDir = join(corePkgDir, 'assets', 'fonts')\n const outFontsDir = join(outDir, 'assets', 'fonts')\n mkdirSync(outFontsDir, { recursive: true })\n for (const file of readdirSync(coreFontsDir)) {\n if (file.endsWith('.woff2')) {\n copyFileSync(join(coreFontsDir, file), join(outFontsDir, file))\n }\n }\n copyFileSync(join(corePkgDir, 'assets', 'fonts.css'), join(outDir, 'assets', 'fonts.css'))\n\n // Copy public/ files (favicon etc.)\n const publicDir = join(projectRoot, 'public')\n copyPublicFiles(publicDir, outDir)\n\n // Discover built asset paths from the client build output\n const builtIndex = join(outDir, 'index.html')\n let cssPath = `${base}/assets/style.css`\n let jsPath: string | undefined\n if (existsSync(builtIndex)) {\n const html = readFileSync(builtIndex, 'utf-8')\n const cssMatch = html.match(/href=\"([^\"]*\\.css)\"/)\n const jsMatch = html.match(/src=\"([^\"]*\\.js)\"/)\n if (cssMatch) cssPath = cssMatch[1]\n if (jsMatch) jsPath = jsMatch[1]\n }\n\n // SSR build — use child process to avoid nested Vite resolution issues\n console.log('SSG: Building SSR bundle...')\n const { execFileSync } = await import('child_process')\n const serverDir = join(outDir, '.server')\n const ssrEntry = resolve(projectRoot, options.entry)\n // Write a temp build script that externalizes node_modules and skips the SSG plugin\n const buildScript = `\n import { build } from 'vite-plus';\n await build({\n root: ${JSON.stringify(projectRoot)},\n logLevel: 'warn',\n mode: ${JSON.stringify(config.mode)},\n build: {\n ssr: ${JSON.stringify(ssrEntry)},\n outDir: ${JSON.stringify(serverDir)},\n emptyOutDir: true,\n },\n });\n `\n execFileSync(process.execPath, ['--input-type=module', '-e', buildScript], {\n stdio: 'inherit',\n cwd: projectRoot,\n })\n\n // Load SSR module — derive output filename from the configured entry path\n const entryBaseName = basename(options.entry).replace(/\\.(c|m)?[jt]sx?$/u, '.js')\n const serverEntry = join(serverDir, entryBaseName)\n const ssrMod = await import(pathToFileURL(serverEntry).href)\n\n const renderConfig: SsgRenderConfig = {\n base,\n root: projectRoot,\n cssPath,\n jsPath,\n searchEnabled: true,\n isDev: false,\n }\n\n // Get routes and render\n const routes: string[] = await ssrMod.getRoutes(renderConfig)\n console.log(`SSG: Rendering ${routes.length} pages...`)\n\n for (const route of routes) {\n const html = rewriteContentAssetRefs(await ssrMod.render(route, renderConfig), base)\n const routePath = route === '/' ? '' : route.replace(/^\\//, '')\n const outputPath = join(outDir, routePath, 'index.html')\n mkdirSync(dirname(outputPath), { recursive: true })\n writeFileSync(outputPath, `<!DOCTYPE html>\\n${html}`)\n\n if (route === '/404') {\n writeFileSync(join(outDir, '404.html'), `<!DOCTYPE html>\\n${html}`)\n }\n }\n\n copyContentAssetsToOutDir(outDir, contentAssets)\n\n // Cleanup SSR build\n rmSync(serverDir, { recursive: true, force: true })\n\n // Run pagefind\n if (enablePagefind) {\n console.log('SSG: Indexing with Pagefind...')\n try {\n const pagefindMain = fileURLToPath(import.meta.resolve('pagefind'))\n const pagefindBin = join(dirname(pagefindMain), '..', 'lib', 'runner', 'bin.cjs')\n const { execFileSync } = await import('child_process')\n execFileSync(process.execPath, [pagefindBin, '--site', outDir], { stdio: 'inherit' })\n } catch {\n console.warn('SSG: Pagefind not found, skipping search indexing')\n }\n }\n\n console.log(`SSG: Done — ${routes.length} pages generated`)\n },\n }\n\n return [devPlugin, buildPlugin]\n}\n","export { prerenderRoutes } from './ssg'\nexport type { PrerenderOptions } from './ssg'\nexport { sharedAssetsPlugin } from './shared-assets.js'\nexport { pagesmithSsg } from './ssg-plugin.js'\nexport type { SsgPluginOptions, SsgRenderConfig } from './ssg-plugin.js'\n\nimport { existsSync, mkdirSync, writeFileSync } from 'fs'\nimport { dirname, relative, resolve } from 'path'\nimport { uneval } from 'devalue'\nimport { createContentLayer } from '../content-layer'\nimport { resolveLoader } from '../loaders'\nimport type { Heading } from '../schemas/heading'\nimport type { CollectionDef, CollectionMap, InferCollectionData } from '../schemas/collection'\nimport type { ContentLayerConfig } from '../schemas/content-config'\nimport { toSlug } from '../utils/slug'\n\ntype Simplify<T> = { [K in keyof T]: T[K] } & {}\n\ntype PagesmithResolvedConfig = {\n root: string\n}\n\ntype PagesmithModuleGraph = {\n getModuleById(id: string): unknown\n invalidateModule(module: unknown): void\n}\n\ntype PagesmithDevServer = {\n moduleGraph: PagesmithModuleGraph\n ws: {\n send(payload: { type: string }): void\n }\n}\n\nexport type PagesmithVitePlugin = {\n name: string\n enforce?: 'pre' | 'post'\n configResolved?: (config: PagesmithResolvedConfig) => void\n buildStart?: () => void\n resolveId?: (id: string) => string | void\n load?: (id: string) => Promise<string | void> | string | void\n handleHotUpdate?: (context: { file: string; server: PagesmithDevServer }) => void\n}\n\nexport type BaseContentModuleEntry = {\n id: string\n contentSlug: string\n}\n\nexport type MarkdownContentModuleEntry<TCollection extends CollectionDef<any, any, any>> = Simplify<\n BaseContentModuleEntry & {\n html: string\n headings: Heading[]\n frontmatter: InferCollectionData<TCollection>\n }\n>\n\nexport type DataContentModuleEntry<TCollection extends CollectionDef<any, any, any>> = Simplify<\n BaseContentModuleEntry & {\n data: InferCollectionData<TCollection>\n }\n>\n\ntype LoaderKindFromCollection<TCollection extends CollectionDef<any, any, any>> =\n TCollection['loader'] extends 'markdown'\n ? 'markdown'\n : TCollection['loader'] extends { kind: infer TKind }\n ? TKind\n : 'data'\n\nexport type ContentCollectionModule<TCollection extends CollectionDef<any, any, any>> =\n LoaderKindFromCollection<TCollection> extends 'markdown'\n ? MarkdownContentModuleEntry<TCollection>[]\n : DataContentModuleEntry<TCollection>[]\n\nexport type ContentModuleMap<TCollections extends CollectionMap> = {\n [TName in keyof TCollections]: ContentCollectionModule<TCollections[TName]>\n}\n\nexport type PagesmithContentPluginOptions<TCollections extends CollectionMap> = Omit<\n ContentLayerConfig,\n 'collections'\n> & {\n collections: TCollections\n /**\n * Shared content root used to compute `id` and `contentSlug`.\n * Defaults to the deepest common parent directory across all collection directories.\n */\n contentRoot?: string\n /**\n * Root virtual module id.\n * Per-collection modules are exposed as `${moduleId}/<collection-name>`.\n */\n moduleId?: string\n /**\n * Path to the content config module used for generated typings.\n * Defaults to `./content.config.ts`.\n */\n configPath?: string\n /**\n * Generate module declarations for the virtual modules.\n * Defaults to `src/pagesmith-content.d.ts` when `src/` exists, otherwise `pagesmith-content.d.ts`.\n */\n dts?: boolean | string | { path?: string }\n}\n\nconst DEFAULT_MODULE_ID = 'virtual:content'\n\nfunction stripExtension(filePath: string): string {\n return filePath.replace(/\\.(c|m)?[jt]sx?$/u, '')\n}\n\nfunction normalizePath(value: string): string {\n return value.replace(/\\\\/g, '/')\n}\n\nfunction isPathWithin(parent: string, candidate: string): boolean {\n const rel = normalizePath(relative(parent, candidate))\n return rel === '' || (!rel.startsWith('..') && !rel.startsWith('/'))\n}\n\nfunction commonDirectory(paths: string[]): string {\n const normalized = paths.map((path) => normalizePath(resolve(path)))\n if (normalized.length === 0) return process.cwd()\n if (normalized.length === 1) return normalized[0]\n\n const segments = normalized.map((path) => path.split('/').filter(Boolean))\n const shared: string[] = []\n const first = segments[0]!\n\n for (let index = 0; index < first.length; index += 1) {\n const segment = first[index]\n if (segments.every((parts) => parts[index] === segment)) {\n shared.push(segment)\n continue\n }\n break\n }\n\n if (shared.length === 0) {\n return resolve('/')\n }\n\n return resolve(`/${shared.join('/')}`)\n}\n\nfunction resolveDtsPath(\n projectRoot: string,\n dts: PagesmithContentPluginOptions<any>['dts'],\n): string {\n if (dts === false) {\n return ''\n }\n\n if (typeof dts === 'string') {\n return resolve(projectRoot, dts)\n }\n\n if (typeof dts === 'object' && dts?.path) {\n return resolve(projectRoot, dts.path)\n }\n\n const srcPath = resolve(projectRoot, 'src')\n if (existsSync(srcPath)) {\n return resolve(srcPath, 'pagesmith-content.d.ts')\n }\n\n return resolve(projectRoot, 'pagesmith-content.d.ts')\n}\n\nfunction createDtsSource(\n moduleId: string,\n collectionNames: string[],\n dtsPath: string,\n configPath: string,\n): string {\n const configImportPath = normalizePath(\n stripExtension(relative(dirname(dtsPath), configPath)).replace(/^[^.]/u, './$&'),\n )\n\n const moduleLines = collectionNames\n .map(\n (name) => `declare module '${moduleId}/${name}' {\n const collection: import('@pagesmith/core/vite').ContentCollectionModule<\n __PagesmithCollections['${name.replaceAll('\\\\', '\\\\\\\\').replaceAll(\"'\", \"\\\\'\")}']\n >\n export default collection\n}`,\n )\n .join('\\n\\n')\n\n return `// Generated by @pagesmith/core/vite. Do not edit manually.\ntype __PagesmithCollections = typeof import('${configImportPath}').default\n\ndeclare module '${moduleId}' {\n const content: import('@pagesmith/core/vite').ContentModuleMap<__PagesmithCollections>\n export default content\n}\n\n${moduleLines}\n`\n}\n\nasync function serializeCollection(\n layer: ReturnType<typeof createContentLayer>,\n collectionName: string,\n collectionDef: CollectionDef<any, any, any>,\n contentRoot: string,\n): Promise<string> {\n const entries = await layer.getCollection(collectionName)\n const loader = resolveLoader(collectionDef.loader)\n const sortedEntries = [...entries].sort((left, right) =>\n left.filePath.localeCompare(right.filePath),\n )\n\n const payload = await Promise.all(\n sortedEntries.map(async (entry) => {\n const contentSlug = toSlug(entry.filePath, contentRoot)\n const base = {\n id: contentSlug,\n contentSlug,\n }\n\n if (loader.kind === 'markdown') {\n const rendered = await entry.render()\n return {\n ...base,\n html: rendered.html,\n headings: rendered.headings,\n frontmatter: entry.data,\n }\n }\n\n return {\n ...base,\n data: entry.data,\n }\n }),\n )\n\n return `const collection = ${uneval(payload)};\\nexport default collection;\\n`\n}\n\nfunction createRootModuleSource(moduleId: string, collectionNames: string[]): string {\n const imports = collectionNames\n .map((name, index) => `import collection${index} from '${moduleId}/${name}'`)\n .join('\\n')\n const contentMap = collectionNames\n .map((name, index) => `${JSON.stringify(name)}: collection${index}`)\n .join(', ')\n\n return `${imports}\\n\\nexport default { ${contentMap} };\\n`\n}\n\nfunction resolvePluginOptions<TCollections extends CollectionMap>(\n collectionsOrOptions: TCollections | PagesmithContentPluginOptions<TCollections>,\n maybeOptions: Omit<PagesmithContentPluginOptions<TCollections>, 'collections'> = {},\n): PagesmithContentPluginOptions<TCollections> {\n if ('collections' in collectionsOrOptions) {\n return collectionsOrOptions as PagesmithContentPluginOptions<TCollections>\n }\n\n return {\n ...maybeOptions,\n collections: collectionsOrOptions,\n }\n}\n\nexport function pagesmithContent<TCollections extends CollectionMap>(\n collections: TCollections,\n options?: Omit<PagesmithContentPluginOptions<TCollections>, 'collections'>,\n): PagesmithVitePlugin\nexport function pagesmithContent<TCollections extends CollectionMap>(\n options: PagesmithContentPluginOptions<TCollections>,\n): PagesmithVitePlugin\nexport function pagesmithContent<TCollections extends CollectionMap>(\n collectionsOrOptions: TCollections | PagesmithContentPluginOptions<TCollections>,\n maybeOptions: Omit<PagesmithContentPluginOptions<TCollections>, 'collections'> = {},\n): PagesmithVitePlugin {\n const options = resolvePluginOptions(collectionsOrOptions, maybeOptions)\n const collectionNames = Object.keys(options.collections)\n const moduleId = options.moduleId ?? DEFAULT_MODULE_ID\n const resolvedPrefix = `\\0${moduleId}/`\n const resolvedRootId = `\\0${moduleId}`\n\n let projectRoot = process.cwd()\n let layerRoot = projectRoot\n let contentRoot = projectRoot\n let configPath = resolve(projectRoot, options.configPath ?? 'content.config.ts')\n let dtsPath = resolveDtsPath(projectRoot, options.dts)\n let layer: ReturnType<typeof createContentLayer> | null = null\n\n function getLayer(): ReturnType<typeof createContentLayer> {\n if (!layer) {\n throw new Error(\n 'pagesmith-content: ContentLayer not initialized. configResolved has not run yet.',\n )\n }\n return layer\n }\n\n const ensureDeclarations = (): void => {\n if (options.dts === false) return\n\n const source = createDtsSource(moduleId, collectionNames, dtsPath, configPath)\n mkdirSync(dirname(dtsPath), { recursive: true })\n writeFileSync(dtsPath, source)\n }\n\n return {\n name: 'pagesmith-content',\n enforce: 'pre',\n\n configResolved(config) {\n projectRoot = resolve(config.root)\n layerRoot = resolve(projectRoot, options.root ?? '.')\n configPath = resolve(projectRoot, options.configPath ?? 'content.config.ts')\n dtsPath = resolveDtsPath(projectRoot, options.dts)\n\n const collectionDirectories = collectionNames.map((name) =>\n resolve(layerRoot, options.collections[name]!.directory),\n )\n contentRoot = options.contentRoot\n ? resolve(layerRoot, options.contentRoot)\n : commonDirectory(collectionDirectories)\n\n layer = createContentLayer({\n ...options,\n root: layerRoot,\n })\n\n ensureDeclarations()\n },\n\n buildStart() {\n ensureDeclarations()\n },\n\n resolveId(id) {\n if (id === moduleId) {\n return resolvedRootId\n }\n\n for (const name of collectionNames) {\n if (id === `${moduleId}/${name}`) {\n return `${resolvedPrefix}${name}`\n }\n }\n },\n\n async load(id) {\n if (id === resolvedRootId) {\n return createRootModuleSource(moduleId, collectionNames)\n }\n\n if (!id.startsWith(resolvedPrefix)) return\n\n const collectionName = id.slice(resolvedPrefix.length)\n const collectionDef = options.collections[collectionName]\n if (!collectionDef) return\n\n return serializeCollection(getLayer(), collectionName, collectionDef, contentRoot)\n },\n\n handleHotUpdate({ file, server }) {\n const resolvedFile = resolve(file)\n const touchesConfig = resolvedFile === configPath\n\n // Determine which collection(s) the changed file belongs to\n const affectedCollections = collectionNames.filter((name) =>\n isPathWithin(resolve(layerRoot, options.collections[name]!.directory), resolvedFile),\n )\n\n const touchesContent = affectedCollections.length > 0\n\n if (!touchesConfig && !touchesContent) return\n\n if (touchesConfig) {\n ensureDeclarations()\n }\n\n const rootModule = server.moduleGraph.getModuleById(resolvedRootId)\n if (rootModule) {\n server.moduleGraph.invalidateModule(rootModule)\n }\n\n if (touchesContent) {\n // Only invalidate affected collections instead of all\n for (const name of affectedCollections) {\n const moduleNode = server.moduleGraph.getModuleById(`${resolvedPrefix}${name}`)\n if (moduleNode) {\n server.moduleGraph.invalidateModule(moduleNode)\n }\n getLayer()\n .invalidateCollection(name)\n .catch((err) => {\n console.warn(`[pagesmith] Failed to invalidate collection \"${name}\":`, err)\n })\n }\n } else {\n // Config change: invalidate all collection modules\n for (const name of collectionNames) {\n const moduleNode = server.moduleGraph.getModuleById(`${resolvedPrefix}${name}`)\n if (moduleNode) {\n server.moduleGraph.invalidateModule(moduleNode)\n }\n }\n getLayer().invalidateAll()\n }\n\n server.ws.send({ type: 'full-reload' })\n },\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pagesmith/core",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "File-based CMS — schema-validated collections, lazy markdown rendering, and runtime CSS/JS exports",
5
5
  "keywords": [
6
6
  "cms",
@@ -24,7 +24,8 @@
24
24
  "dist/",
25
25
  "src/styles/",
26
26
  "assets/",
27
- "templates/"
27
+ "templates/",
28
+ "REFERENCE.md"
28
29
  ],
29
30
  "type": "module",
30
31
  "exports": {
@@ -99,7 +100,6 @@
99
100
  "test": "vp test run"
100
101
  },
101
102
  "dependencies": {
102
- "chokidar": "^4.0.3",
103
103
  "devalue": "^5.1.1",
104
104
  "fast-glob": "^3.3.0",
105
105
  "gray-matter": "^4.0.3",
@@ -119,17 +119,13 @@
119
119
  "remark-parse": "^11.0.0",
120
120
  "remark-rehype": "^11.1.1",
121
121
  "remark-smartypants": "^3.0.2",
122
- "rolldown": "^1.0.0-rc.10",
123
122
  "smol-toml": "^1.3.0",
124
123
  "unified": "^11.0.5",
125
- "unist-util-visit": "^5.0.0",
126
- "ws": "^8.19.0",
127
124
  "yaml": "^2.7.0",
128
125
  "zod": "^4.3.6"
129
126
  },
130
127
  "devDependencies": {
131
128
  "@types/node": "^25.5.0",
132
- "@types/ws": "^8.18.1",
133
129
  "typescript": "^5.9.0",
134
130
  "vite-plus": "^0.1.13"
135
131
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"content-layer-B7fQ3im4.mjs","names":[],"sources":["../src/utils/read-time.ts","../src/entry.ts","../src/plugins/index.ts","../src/utils/glob.ts","../src/utils/slug.ts","../src/validation/schema-validator.ts","../src/validation/code-block-validator.ts","../src/validation/heading-validator.ts","../src/validation/link-validator.ts","../src/validation/runner.ts","../src/store.ts","../src/content-layer.ts"],"sourcesContent":["/**\n * Read time estimation.\n *\n * Computes estimated reading time from markdown source (~200 words per minute).\n */\n\n/** Compute read time in minutes from raw markdown. */\nexport function computeReadTime(rawMarkdown: string): number {\n const plainText = rawMarkdown\n .replace(/```[\\s\\S]*?```/g, ' ')\n .replace(/^( {4}|\\t).+$/gm, ' ')\n .replace(/^---[\\s\\S]*?---/m, ' ')\n .replace(/<[^>]+>/g, ' ')\n .replace(/!?\\[([^\\]]*)\\]\\([^)]*\\)/g, '$1')\n .replace(/[#*_~`>]/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n const wordCount = plainText.split(' ').filter(Boolean).length\n return Math.max(1, Math.ceil(wordCount / 200))\n}\n","/**\n * ContentEntry — represents a single content entry in a collection.\n *\n * Supports lazy rendering: data is available immediately after loading,\n * but HTML rendering is deferred until render() is called.\n */\n\nimport type { Heading } from './schemas/heading'\nimport type { MarkdownConfig } from './schemas/markdown-config'\nimport { processMarkdown } from './markdown'\nimport { computeReadTime } from './utils/read-time'\n\nexport type RenderedContent = {\n /** Processed HTML */\n html: string\n /** Extracted headings for TOC */\n headings: Heading[]\n /** Estimated read time in minutes */\n readTime: number\n}\n\nexport class ContentEntry<T = Record<string, any>> {\n /** URL-friendly identifier */\n readonly slug: string\n /** Collection this entry belongs to */\n readonly collection: string\n /** Absolute path to source file */\n readonly filePath: string\n /** Validated data (frontmatter or parsed data) */\n readonly data: T\n /** Raw body content (markdown only) */\n readonly rawContent?: string\n\n /** Cached render result */\n private _rendered?: RenderedContent\n /** Markdown config for rendering */\n private _markdownConfig: MarkdownConfig\n\n constructor(\n slug: string,\n collection: string,\n filePath: string,\n data: T,\n rawContent: string | undefined,\n markdownConfig: MarkdownConfig,\n ) {\n this.slug = slug\n this.collection = collection\n this.filePath = filePath\n this.data = data\n this.rawContent = rawContent\n this._markdownConfig = markdownConfig\n }\n\n /** Render the entry content to HTML. Cached after first call. */\n async render(options?: { force?: boolean }): Promise<RenderedContent> {\n if (this._rendered && !options?.force) {\n return this._rendered\n }\n\n if (!this.rawContent) {\n // Non-markdown entries have no renderable content\n this._rendered = { html: '', headings: [], readTime: 0 }\n return this._rendered\n }\n\n const result = await processMarkdown(this.rawContent, this._markdownConfig, {\n content: this.rawContent,\n frontmatter: this.data as Record<string, unknown>,\n })\n const readTime = computeReadTime(this.rawContent)\n\n this._rendered = {\n html: result.html,\n headings: result.headings,\n readTime,\n }\n\n return this._rendered\n }\n\n /** Clear cached render result. */\n clearRenderCache(): void {\n this._rendered = undefined\n }\n}\n","/**\n * Plugin registration and execution.\n */\n\nimport type { ContentPlugin } from '../schemas/content-config'\n\n/** Collect all remark plugins from content plugins. */\nexport function collectRemarkPlugins(plugins: ContentPlugin[]): any[] {\n return plugins.filter((p) => p.remarkPlugin).map((p) => p.remarkPlugin!)\n}\n\n/** Collect all rehype plugins from content plugins. */\nexport function collectRehypePlugins(plugins: ContentPlugin[]): any[] {\n return plugins.filter((p) => p.rehypePlugin).map((p) => p.rehypePlugin!)\n}\n\n/** Run all plugin validators against an entry. */\nexport function runPluginValidators(\n plugins: ContentPlugin[],\n entry: { data: Record<string, any>; content?: string },\n): string[] {\n const issues: string[] = []\n for (const plugin of plugins) {\n if (plugin.validate) {\n issues.push(...plugin.validate(entry))\n }\n }\n return issues\n}\n\nexport type { ContentPlugin } from '../schemas/content-config'\n","/**\n * File discovery via glob patterns.\n */\n\nimport fg from 'fast-glob'\nimport { resolve } from 'path'\n\nexport interface DiscoverOptions {\n /** Directory to search in (absolute path) */\n directory: string\n /** Glob patterns to include */\n include: string[]\n /** Glob patterns to exclude */\n exclude?: string[]\n}\n\n/** Discover files matching glob patterns in a directory. */\nexport function discoverFiles(options: DiscoverOptions): string[] {\n const { directory, include, exclude = [] } = options\n\n return fg\n .sync(include, {\n cwd: directory,\n absolute: true,\n ignore: ['**/node_modules/**', '**/dist/**', '**/dev/**', ...exclude],\n })\n .map((p) => resolve(p))\n}\n","/**\n * Path-to-slug conversion.\n *\n * Generalized from packages/core/src/content/collector.ts.\n */\n\nimport { extname, relative } from 'path'\n\n/**\n * Convert a content file path to a URL-friendly slug.\n *\n * Examples:\n * content/posts/hello-world/README.md -> 'hello-world'\n * content/posts/hello-world/index.md -> 'hello-world'\n * content/posts/hello-world.md -> 'hello-world'\n * content/authors/john.json -> 'john'\n * content/posts/nested/deep/post.md -> 'nested/deep/post'\n */\nexport function toSlug(filePath: string, directory: string): string {\n const ext = extname(filePath)\n let slug = relative(directory, filePath).replace(/\\\\/g, '/')\n\n // Remove file extension\n if (ext) {\n slug = slug.slice(0, -ext.length)\n }\n\n // Strip README / index suffixes\n if (slug === 'README' || slug === 'index') return '/'\n if (slug.endsWith('/README')) slug = slug.slice(0, -7)\n if (slug.endsWith('/index')) slug = slug.slice(0, -6)\n\n return slug\n}\n","/**\n * Schema validation with rich error reporting.\n *\n * Wraps Zod's safeParse to produce human-readable validation issues.\n */\n\nimport type { ZodError, ZodType } from 'zod'\n\nexport type ValidationIssue = {\n /** Field path (e.g. 'tags[0]') */\n field?: string\n /** Human-readable error message */\n message: string\n /** Error severity */\n severity: 'error' | 'warn'\n}\n\nexport type ValidationEntryResult = {\n slug: string\n filePath: string\n issues: ValidationIssue[]\n}\n\nexport type ValidationResult = {\n collection: string\n entries: ValidationEntryResult[]\n errors: number\n warnings: number\n}\n\n/** Format a Zod error path into a human-readable field path. */\nexport function formatPath(path: PropertyKey[]): string {\n return path\n .map((segment, i) => {\n if (typeof segment === 'number') return `[${segment}]`\n if (typeof segment === 'symbol') return `[${String(segment)}]`\n if (i === 0) return segment\n return `.${segment}`\n })\n .join('')\n}\n\n/** Validate data against a Zod schema and return structured issues. */\nexport function validateSchema(\n data: Record<string, any>,\n schema: ZodType,\n): {\n issues: ValidationIssue[]\n validatedData: any\n} {\n const result = schema.safeParse(data)\n if (result.success) {\n return {\n issues: [],\n validatedData: result.data,\n }\n }\n\n return {\n issues: (result.error as ZodError).issues.map((issue) => ({\n field: issue.path.length > 0 ? formatPath(issue.path) : undefined,\n message: issue.message,\n severity: 'error' as const,\n })),\n validatedData: data,\n }\n}\n","/**\n * Code block validator — checks fenced code block meta syntax.\n *\n * Walks the shared MDAST for `code` nodes. Validates known meta properties\n * and language identifiers. Meta syntax follows Expressive Code conventions.\n */\n\nimport type { ValidationIssue } from './schema-validator'\nimport type { ContentValidator, ValidatorContext } from './types'\n\ntype MdastNode = {\n type: string\n lang?: string | null\n meta?: string | null\n value?: string\n children?: MdastNode[]\n position?: { start: { line: number } }\n}\n\n/** Known meta properties accepted by Expressive Code and its plugins. */\nconst KNOWN_META_PROPS = new Set([\n 'title',\n 'showLineNumbers',\n 'startLineNumber',\n 'wrap',\n 'frame',\n 'collapse',\n 'mark',\n 'ins',\n 'del',\n])\n\n/** Extract the property name portion of a meta token (before `=` or `{`). */\nfunction extractMetaPropNames(meta: string): string[] {\n const props: string[] = []\n\n // Match key=value, key={...}, or bare flags\n const tokenRegex = /(\\w+)(?:=(?:\\{[^}]*\\}|\"[^\"]*\"|'[^']*'|\\S+))?/g\n let match: RegExpExecArray | null\n while ((match = tokenRegex.exec(meta)) !== null) {\n props.push(match[1]!)\n }\n\n return props\n}\n\n/** Collect all `code` nodes from MDAST. */\nfunction collectCodeBlocks(node: MdastNode): MdastNode[] {\n const blocks: MdastNode[] = []\n\n if (node.type === 'code') {\n blocks.push(node)\n }\n\n if (node.children) {\n for (const child of node.children) {\n blocks.push(...collectCodeBlocks(child))\n }\n }\n\n return blocks\n}\n\nexport const codeBlockValidator: ContentValidator = {\n name: 'code-blocks',\n\n validate(ctx: ValidatorContext): ValidationIssue[] {\n if (!ctx.rawContent || !ctx.mdast) return []\n\n const issues: ValidationIssue[] = []\n const tree = ctx.mdast as MdastNode\n\n const codeBlocks = collectCodeBlocks(tree)\n\n for (const block of codeBlocks) {\n const line = block.position?.start.line\n const lineInfo = line ? ` (line ${line})` : ''\n const meta = block.meta ?? ''\n const hasMeta = meta.trim().length > 0\n\n // Language required when using syntax features\n if (hasMeta && !block.lang) {\n issues.push({\n field: `code-block${lineInfo}`,\n message: 'Code block has meta properties but no language identifier',\n severity: 'warn',\n })\n }\n\n if (!hasMeta) continue\n\n // Check for unknown meta properties\n const propNames = extractMetaPropNames(meta)\n for (const prop of propNames) {\n if (!KNOWN_META_PROPS.has(prop)) {\n issues.push({\n field: `code-block${lineInfo}`,\n message: `Unknown code block meta property: \"${prop}\"`,\n severity: 'warn',\n })\n }\n }\n }\n\n return issues\n },\n}\n","/**\n * Heading validator — checks heading structure in markdown.\n *\n * Walks the shared MDAST for heading nodes. Validates level ordering and h1 uniqueness.\n */\n\nimport type { ValidationIssue } from './schema-validator'\nimport type { ContentValidator, ValidatorContext } from './types'\n\ntype MdastNode = {\n type: string\n depth?: number\n children?: MdastNode[]\n position?: { start: { line: number } }\n value?: string\n}\n\n/** Extract plain text from a heading node's children. */\nfunction getTextContent(node: MdastNode): string {\n if (node.type === 'text') return node.value ?? ''\n if (node.children) return node.children.map(getTextContent).join('')\n return ''\n}\n\n/** Collect all heading nodes from MDAST. */\nfunction collectHeadings(node: MdastNode): Array<{ depth: number; text: string; line?: number }> {\n const headings: Array<{ depth: number; text: string; line?: number }> = []\n\n if (node.type === 'heading' && node.depth) {\n headings.push({\n depth: node.depth,\n text: getTextContent(node),\n line: node.position?.start.line,\n })\n }\n\n if (node.children) {\n for (const child of node.children) {\n headings.push(...collectHeadings(child))\n }\n }\n\n return headings\n}\n\nexport const headingValidator: ContentValidator = {\n name: 'headings',\n\n validate(ctx: ValidatorContext): ValidationIssue[] {\n if (!ctx.rawContent || !ctx.mdast) return []\n\n const issues: ValidationIssue[] = []\n const tree = ctx.mdast as MdastNode\n\n const headings = collectHeadings(tree)\n\n // No headings in a document with content is worth noting\n if (headings.length === 0) {\n const hasContent = ctx.rawContent.trim().length > 0\n if (hasContent) {\n issues.push({\n message: 'Document has content but no headings',\n severity: 'warn',\n })\n }\n return issues\n }\n\n // At most one h1\n const h1s = headings.filter((h) => h.depth === 1)\n if (h1s.length > 1) {\n for (const h of h1s.slice(1)) {\n const lineInfo = h.line ? ` (line ${h.line})` : ''\n issues.push({\n field: `headings${lineInfo}`,\n message: `Multiple h1 headings found: \"${h.text}\"`,\n severity: 'warn',\n })\n }\n }\n\n // No skipped levels (only flag when going deeper)\n for (let i = 1; i < headings.length; i++) {\n const prev = headings[i - 1]!\n const curr = headings[i]!\n if (curr.depth > prev.depth + 1) {\n const lineInfo = curr.line ? ` (line ${curr.line})` : ''\n issues.push({\n field: `headings${lineInfo}`,\n message: `Heading level skip: h${prev.depth} -> h${curr.depth} (\"${curr.text}\")`,\n severity: 'warn',\n })\n }\n }\n\n return issues\n },\n}\n","/**\n * Link validator — checks links in markdown content.\n *\n * Walks the shared MDAST for link/image nodes. Internal links are checked for file existence;\n * external links are checked for well-formed URL format.\n */\n\nimport { existsSync } from 'fs'\nimport { dirname, resolve } from 'path'\nimport type { ValidationIssue } from './schema-validator'\nimport type { ContentValidator, ValidatorContext } from './types'\n\ntype MdastNode = {\n type: string\n url?: string\n children?: MdastNode[]\n position?: { start: { line: number } }\n}\n\n/** Walk MDAST tree, collecting link and image nodes. */\nfunction collectLinks(node: MdastNode): Array<{ url: string; line?: number }> {\n const links: Array<{ url: string; line?: number }> = []\n\n if ((node.type === 'link' || node.type === 'image') && node.url) {\n links.push({\n url: node.url,\n line: node.position?.start.line,\n })\n }\n\n if (node.children) {\n for (const child of node.children) {\n links.push(...collectLinks(child))\n }\n }\n\n return links\n}\n\nfunction isInternalLink(url: string): boolean {\n if (url.startsWith('#')) return false\n if (url.startsWith('http://') || url.startsWith('https://')) return false\n if (url.startsWith('//')) return false\n if (url.startsWith('mailto:')) return false\n if (url.startsWith('tel:')) return false\n return true\n}\n\nfunction isWellFormedUrl(url: string): boolean {\n try {\n new URL(url)\n return true\n } catch {\n return false\n }\n}\n\nexport const linkValidator: ContentValidator = {\n name: 'links',\n\n validate(ctx: ValidatorContext): ValidationIssue[] {\n if (!ctx.rawContent || !ctx.mdast) return []\n\n const issues: ValidationIssue[] = []\n const tree = ctx.mdast as MdastNode\n\n const links = collectLinks(tree)\n const fileDir = dirname(ctx.filePath)\n\n for (const link of links) {\n const lineInfo = link.line ? ` (line ${link.line})` : ''\n\n // External links — check URL format\n if (link.url.startsWith('http://') || link.url.startsWith('https://')) {\n if (!isWellFormedUrl(link.url)) {\n issues.push({\n field: `links${lineInfo}`,\n message: `Malformed external URL: ${link.url}`,\n severity: 'warn',\n })\n }\n continue\n }\n\n // Internal links — check file exists\n if (isInternalLink(link.url)) {\n // Strip fragment and query\n const urlPath = link.url.split('#')[0]!.split('?')[0]!\n if (!urlPath) continue // pure fragment link\n\n const resolved = resolve(fileDir, urlPath)\n if (!existsSync(resolved)) {\n issues.push({\n field: `links${lineInfo}`,\n message: `Broken internal link: ${link.url}`,\n severity: 'error',\n })\n }\n }\n }\n\n return issues\n },\n}\n","/**\n * Validation runner — executes a list of content validators on an entry.\n *\n * Validators that throw are caught and converted to error-severity issues\n * so one failing validator does not block the rest.\n */\n\nimport remarkParse from 'remark-parse'\nimport { unified } from 'unified'\nimport type { ValidationIssue } from './schema-validator'\nimport type { ContentValidator, ValidatorContext } from './types'\n\nimport { codeBlockValidator } from './code-block-validator'\nimport { headingValidator } from './heading-validator'\nimport { linkValidator } from './link-validator'\n\n/** The built-in validators for markdown content. */\nexport const builtinMarkdownValidators: ContentValidator[] = [\n linkValidator,\n codeBlockValidator,\n headingValidator,\n]\n\n/** Run all validators on a single content entry. */\nexport async function runValidators(\n ctx: ValidatorContext,\n validators: ContentValidator[],\n): Promise<ValidationIssue[]> {\n if (ctx.rawContent && !ctx.mdast) {\n ctx.mdast = unified().use(remarkParse).parse(ctx.rawContent)\n }\n\n const issues: ValidationIssue[] = []\n\n for (const validator of validators) {\n try {\n const result = await validator.validate(ctx)\n issues.push(...result)\n } catch (err) {\n // Convert thrown errors into issues so one bad validator doesn't abort all\n const message = err instanceof Error ? err.message : String(err)\n issues.push({\n message: `Validator \"${validator.name}\" threw: ${message}`,\n severity: 'error',\n })\n }\n }\n\n return issues\n}\n","/**\n * ContentStore — in-memory cache for loaded collections.\n *\n * Handles file discovery, loading, validation, and caching.\n * Not exported directly — used internally by ContentLayer.\n */\n\nimport { resolve } from 'path'\nimport type { MarkdownConfig } from './schemas/markdown-config'\nimport type { ZodType } from 'zod'\nimport { ContentEntry } from './entry'\nimport { defaultIncludePatterns, resolveLoader } from './loaders'\nimport type { Loader } from './loaders/types'\nimport { collectRehypePlugins, collectRemarkPlugins, runPluginValidators } from './plugins'\nimport type { CollectionDef, RawEntry } from './schemas/collection'\nimport type { ContentLayerConfig } from './schemas/content-config'\nimport { discoverFiles } from './utils/glob'\nimport { toSlug } from './utils/slug'\nimport { validateSchema, type ValidationIssue } from './validation'\nimport { builtinMarkdownValidators, runValidators } from './validation/runner'\nimport type { ContentValidator } from './validation/types'\n\ntype CacheEntry = {\n entry: ContentEntry<any>\n issues: ValidationIssue[]\n}\n\nexport class ContentStore {\n private cache = new Map<string, Map<string, CacheEntry>>()\n private loaded = new Set<string>()\n private config: ContentLayerConfig\n private rootDir: string\n private markdownConfig: MarkdownConfig\n\n constructor(config: ContentLayerConfig) {\n this.config = config\n this.rootDir = config.root ? resolve(config.root) : process.cwd()\n this.markdownConfig = this.createMarkdownConfig()\n }\n\n /** Load a collection (if not already loaded) and return entries. */\n async loadCollection<S extends ZodType>(\n name: string,\n def: CollectionDef<S>,\n ): Promise<ContentEntry<any>[]> {\n if (this.loaded.has(name)) {\n const cached = this.cache.get(name)\n return cached ? Array.from(cached.values()).map((c) => c.entry) : []\n }\n\n const loader = resolveLoader(def.loader)\n const directory = resolve(this.rootDir, def.directory)\n const include = def.include ?? defaultIncludePatterns(loader)\n\n const files = discoverFiles({\n directory,\n include,\n exclude: def.exclude,\n })\n\n const entries = new Map<string, CacheEntry>()\n const results = await Promise.all(\n files.map(async (filePath) => {\n try {\n return await this.loadEntry(name, filePath, directory, loader, def)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const slug = def.slugify ? def.slugify(filePath, directory) : toSlug(filePath, directory)\n return {\n entry: new ContentEntry(slug, name, filePath, {}, undefined, this.markdownConfig),\n issues: [{ message: `Failed to load: ${message}`, severity: 'error' as const }],\n }\n }\n }),\n )\n\n for (const result of results) {\n if (result) {\n entries.set(result.entry.slug, result)\n }\n }\n\n this.cache.set(name, entries)\n this.loaded.add(name)\n\n return Array.from(entries.values()).map((c) => c.entry)\n }\n\n /** Load a single entry from a file. */\n private async loadEntry(\n collectionName: string,\n filePath: string,\n directory: string,\n loader: Loader,\n def: CollectionDef<any>,\n ): Promise<CacheEntry | null> {\n const loaded = await loader.load(filePath)\n const slug = def.slugify ? def.slugify(filePath, directory) : toSlug(filePath, directory)\n\n let raw: RawEntry = {\n data: loaded.data,\n content: loaded.content,\n filePath,\n slug,\n }\n\n // Apply transform\n if (def.transform) {\n raw = await def.transform(raw)\n }\n\n // Apply computed fields\n if (def.computed) {\n for (const [key, fn] of Object.entries(def.computed) as Array<\n [string, (entry: RawEntry) => any]\n >) {\n raw.data[key] = fn(raw)\n }\n }\n\n // Apply filter\n if (def.filter && !def.filter(raw)) {\n return null\n }\n\n // Validate schema once to collect issues and transformed data.\n const { issues, validatedData } = validateSchema(raw.data, def.schema)\n\n // Custom validation\n if (def.validate) {\n const customError = def.validate(raw)\n if (customError) {\n issues.push({ message: customError, severity: 'error' })\n }\n }\n\n // Run content validators on markdown entries\n const isMarkdownEntry = raw.content !== undefined\n if (isMarkdownEntry) {\n const validators = this.resolveValidators(def)\n if (validators.length > 0) {\n const contentIssues = await runValidators(\n {\n filePath,\n slug,\n collection: collectionName,\n rawContent: raw.content,\n data: raw.data,\n },\n validators,\n )\n issues.push(...contentIssues)\n }\n }\n\n if (this.config.plugins?.length) {\n const pluginIssues = runPluginValidators(this.config.plugins, {\n data: raw.data,\n content: raw.content,\n })\n for (const message of pluginIssues) {\n issues.push({ message, severity: 'error' })\n }\n }\n\n const entry = new ContentEntry(\n slug,\n collectionName,\n filePath,\n validatedData,\n raw.content,\n this.markdownConfig,\n )\n\n return { entry, issues }\n }\n\n /** Get a single entry by slug. */\n getEntry(collection: string, slug: string): ContentEntry<any> | undefined {\n return this.cache.get(collection)?.get(slug)?.entry\n }\n\n /** Get validation issues for a collection. */\n getIssues(collection: string): Map<string, ValidationIssue[]> {\n const result = new Map<string, ValidationIssue[]>()\n const entries = this.cache.get(collection)\n if (!entries) return result\n for (const [slug, cached] of entries) {\n if (cached.issues.length > 0) {\n result.set(slug, cached.issues)\n }\n }\n return result\n }\n\n /** Invalidate a single entry (forces full collection reload on next access). */\n invalidate(collection: string, slug: string): void {\n this.cache.get(collection)?.delete(slug)\n this.loaded.delete(collection)\n }\n\n /** Invalidate an entire collection. */\n invalidateCollection(collection: string): void {\n this.cache.delete(collection)\n this.loaded.delete(collection)\n }\n\n /** Invalidate all collections. */\n invalidateAll(): void {\n this.cache.clear()\n this.loaded.clear()\n }\n\n /** Resolve the list of content validators for a collection. */\n private resolveValidators(def: CollectionDef<any>): ContentValidator[] {\n const builtin = def.disableBuiltinValidators ? [] : builtinMarkdownValidators\n const custom = def.validators ?? []\n return [...builtin, ...custom]\n }\n\n private createMarkdownConfig(): MarkdownConfig {\n const base = this.config.markdown ?? {}\n const remarkPlugins = [...(base.remarkPlugins ?? [])]\n const rehypePlugins = [...(base.rehypePlugins ?? [])]\n const plugins = this.config.plugins ?? []\n\n if (plugins.length > 0) {\n remarkPlugins.push(...collectRemarkPlugins(plugins))\n rehypePlugins.push(...collectRehypePlugins(plugins))\n }\n\n return {\n ...base,\n ...(remarkPlugins.length > 0 ? { remarkPlugins } : {}),\n ...(rehypePlugins.length > 0 ? { rehypePlugins } : {}),\n }\n }\n}\n","/**\n * ContentLayer — the main API for working with content collections.\n *\n * Created via createContentLayer(config). Provides methods to:\n * - Load and query collections (getCollection, getEntry)\n * - Convert markdown directly (convert)\n * - Invalidate cache (invalidate, invalidateCollection, invalidateAll)\n * - Validate all entries (validate)\n */\n\nimport type { ConvertResult } from './convert'\nimport type { MarkdownConfig } from './schemas/markdown-config'\nimport type { ContentEntry } from './entry'\nimport type { CollectionDef } from './schemas/collection'\nimport type { ContentLayerConfig } from './schemas/content-config'\nimport { ContentStore } from './store'\nimport type { ValidationResult } from './validation'\n\nexport interface ContentLayer {\n /** Get all entries in a collection. */\n getCollection(name: string): Promise<ContentEntry<any>[]>\n\n /** Get a single entry by collection name and slug. */\n getEntry(collection: string, slug: string): Promise<ContentEntry<any> | undefined>\n\n /** Convert raw markdown to HTML (no collection, no validation). */\n convert(markdown: string, options?: LayerConvertOptions): Promise<ConvertResult>\n\n /** Invalidate a single entry's cache. */\n invalidate(collection: string, slug: string): void\n\n /** Invalidate an entire collection's cache. */\n invalidateCollection(collection: string): void\n\n /** Invalidate all cached data. */\n invalidateAll(): void\n\n /** Validate all entries in a collection (or all collections). */\n validate(collection?: string): Promise<ValidationResult[]>\n\n /** Get the names of all configured collections. */\n getCollectionNames(): string[]\n\n /** Get the definition of a collection. */\n getCollectionDef(name: string): CollectionDef | undefined\n}\n\nexport type LayerConvertOptions = {\n markdown?: MarkdownConfig\n}\n\nclass ContentLayerImpl implements ContentLayer {\n private store: ContentStore\n private config: ContentLayerConfig\n\n constructor(config: ContentLayerConfig) {\n this.config = config\n this.store = new ContentStore(config)\n }\n\n async getCollection(name: string): Promise<ContentEntry<any>[]> {\n const def = this.config.collections[name]\n if (!def) {\n throw new Error(\n `Collection \"${name}\" not found. Available: ${Object.keys(this.config.collections).join(\n ', ',\n )}`,\n )\n }\n return this.store.loadCollection(name, def)\n }\n\n async getEntry(collection: string, slug: string): Promise<ContentEntry<any> | undefined> {\n // The first getEntry call loads the full collection and then serves from cache.\n // Single-entry loading would skip collection-level transforms and validation context.\n await this.getCollection(collection)\n return this.store.getEntry(collection, slug)\n }\n\n async convert(markdown: string, options?: LayerConvertOptions): Promise<ConvertResult> {\n const { convert: coreConvert } = await import('./convert.js')\n return coreConvert(markdown, {\n markdown: options?.markdown ?? this.config.markdown,\n })\n }\n\n invalidate(collection: string, slug: string): void {\n this.store.invalidate(collection, slug)\n }\n\n invalidateCollection(collection: string): void {\n this.store.invalidateCollection(collection)\n }\n\n invalidateAll(): void {\n this.store.invalidateAll()\n }\n\n async validate(collection?: string): Promise<ValidationResult[]> {\n const names = collection ? [collection] : Object.keys(this.config.collections)\n const results: ValidationResult[] = []\n\n for (const name of names) {\n // Ensure loaded\n await this.getCollection(name)\n\n const issues = this.store.getIssues(name)\n const entries = Array.from(issues.entries()).map(([slug, entryIssues]) => {\n const entry = this.store.getEntry(name, slug)\n return {\n slug,\n filePath: entry?.filePath ?? '',\n issues: entryIssues,\n }\n })\n\n let errors = 0\n let warnings = 0\n for (const entry of entries) {\n for (const issue of entry.issues) {\n if (issue.severity === 'error') errors++\n else warnings++\n }\n }\n\n results.push({ collection: name, entries, errors, warnings })\n }\n\n return results\n }\n\n getCollectionNames(): string[] {\n return Object.keys(this.config.collections)\n }\n\n getCollectionDef(name: string): CollectionDef | undefined {\n return this.config.collections[name]\n }\n}\n\n/** Create a new content layer from a configuration. */\nexport function createContentLayer(config: ContentLayerConfig): ContentLayer {\n return new ContentLayerImpl(config)\n}\n"],"mappings":"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"}
@@ -1,52 +0,0 @@
1
- import { t as processMarkdown } from "./markdown-Cj5X26FL.mjs";
2
- //#region \0rolldown/runtime.js
3
- var __defProp = Object.defineProperty;
4
- var __exportAll = (all, no_symbols) => {
5
- let target = {};
6
- for (var name in all) __defProp(target, name, {
7
- get: all[name],
8
- enumerable: true
9
- });
10
- if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
11
- return target;
12
- };
13
- //#endregion
14
- //#region src/toc.ts
15
- /**
16
- * Extract table of contents headings from an HTML string.
17
- *
18
- * Regex-based: finds <h[1-6] id="...">text</h[1-6]>, strips inner HTML tags.
19
- * No dependency on unified — works on any HTML string.
20
- */
21
- function extractToc(html) {
22
- const headings = [];
23
- const re = /<h([1-6])\s+id="([^"]*)"[^>]*>([\s\S]*?)<\/h\1>/gi;
24
- let match;
25
- while ((match = re.exec(html)) !== null) {
26
- const depth = parseInt(match[1], 10);
27
- const slug = match[2];
28
- const text = match[3].replace(/<[^>]+>/g, "").trim();
29
- headings.push({
30
- depth,
31
- text,
32
- slug
33
- });
34
- }
35
- return headings;
36
- }
37
- //#endregion
38
- //#region src/convert.ts
39
- var convert_exports = /* @__PURE__ */ __exportAll({ convert: () => convert });
40
- async function convert(input, options = {}) {
41
- const result = await processMarkdown(input, options.markdown || {});
42
- const toc = extractToc(result.html);
43
- return {
44
- html: result.html,
45
- toc,
46
- frontmatter: result.frontmatter
47
- };
48
- }
49
- //#endregion
50
- export { convert_exports as n, extractToc as r, convert as t };
51
-
52
- //# sourceMappingURL=convert-DnuB6SVV.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"convert-DnuB6SVV.mjs","names":[],"sources":["../src/toc.ts","../src/convert.ts"],"sourcesContent":["import type { Heading } from './schemas/heading'\n\n/**\n * Extract table of contents headings from an HTML string.\n *\n * Regex-based: finds <h[1-6] id=\"...\">text</h[1-6]>, strips inner HTML tags.\n * No dependency on unified — works on any HTML string.\n */\nexport function extractToc(html: string): Heading[] {\n const headings: Heading[] = []\n const re = /<h([1-6])\\s+id=\"([^\"]*)\"[^>]*>([\\s\\S]*?)<\\/h\\1>/gi\n let match: RegExpExecArray | null\n\n while ((match = re.exec(html)) !== null) {\n const depth = parseInt(match[1], 10)\n const slug = match[2]\n // Strip inner HTML tags to get plain text\n const text = match[3].replace(/<[^>]+>/g, '').trim()\n headings.push({ depth, text, slug })\n }\n\n return headings\n}\n","import { processMarkdown } from './markdown'\nimport type { Heading } from './schemas/heading'\nimport type { MarkdownConfig } from './schemas/markdown-config'\nimport { extractToc } from './toc'\n\nexport type ConvertOptions = {\n markdown?: MarkdownConfig\n}\n\nexport type ConvertResult = {\n html: string\n toc: Heading[]\n frontmatter: Record<string, any>\n}\n\nexport async function convert(input: string, options: ConvertOptions = {}): Promise<ConvertResult> {\n const result = await processMarkdown(input, options.markdown || {})\n const toc = extractToc(result.html)\n return { html: result.html, toc, frontmatter: result.frontmatter }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAQA,SAAgB,WAAW,MAAyB;CAClD,MAAM,WAAsB,EAAE;CAC9B,MAAM,KAAK;CACX,IAAI;AAEJ,SAAQ,QAAQ,GAAG,KAAK,KAAK,MAAM,MAAM;EACvC,MAAM,QAAQ,SAAS,MAAM,IAAI,GAAG;EACpC,MAAM,OAAO,MAAM;EAEnB,MAAM,OAAO,MAAM,GAAG,QAAQ,YAAY,GAAG,CAAC,MAAM;AACpD,WAAS,KAAK;GAAE;GAAO;GAAM;GAAM,CAAC;;AAGtC,QAAO;;;;;ACNT,eAAsB,QAAQ,OAAe,UAA0B,EAAE,EAA0B;CACjG,MAAM,SAAS,MAAM,gBAAgB,OAAO,QAAQ,YAAY,EAAE,CAAC;CACnE,MAAM,MAAM,WAAW,OAAO,KAAK;AACnC,QAAO;EAAE,MAAM,OAAO;EAAM;EAAK,aAAa,OAAO;EAAa"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"css-ekIt2Fdb.mjs","names":[],"sources":["../src/css/builder.ts"],"sourcesContent":["import { bundle } from 'lightningcss'\nimport { resolve } from 'path'\n\nexport function buildCss(entryPath: string, config?: { minify?: boolean }): string {\n const { code } = bundle({\n filename: resolve(entryPath),\n minify: config?.minify ?? true,\n targets: {\n chrome: 123 << 16,\n firefox: 120 << 16,\n safari: 18 << 16,\n },\n })\n return new TextDecoder().decode(code)\n}\n"],"mappings":";;;AAGA,SAAgB,SAAS,WAAmB,QAAuC;CACjF,MAAM,EAAE,SAAS,OAAO;EACtB,UAAU,QAAQ,UAAU;EAC5B,QAAQ,QAAQ,UAAU;EAC1B,SAAS;GACP,QAAQ;GACR,SAAS;GACT,QAAQ,MAAM;GACf;EACF,CAAC;AACF,QAAO,IAAI,aAAa,CAAC,OAAO,KAAK"}
@@ -1,7 +0,0 @@
1
- //#region src/css/builder.d.ts
2
- declare function buildCss(entryPath: string, config?: {
3
- minify?: boolean;
4
- }): string;
5
- //#endregion
6
- export { buildCss as t };
7
- //# sourceMappingURL=index-CeNDTM-y.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-CeNDTM-y.d.mts","names":[],"sources":["../src/css/builder.ts"],"mappings":";iBAGgB,QAAA,CAAS,SAAA,UAAmB,MAAA;EAAW,MAAA;AAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-DpRBzO8Q.d.mts","names":[],"sources":["../src/markdown/pipeline.ts"],"mappings":";;;KAuBY,cAAA;EACV,IAAA;EACA,QAAA,EAAU,OAAA;EACV,WAAA,EAAa,MAAA;AAAA;AAAA,iBAgGO,eAAA,CACpB,GAAA,UACA,MAAA,GAAS,cAAA,EACT,YAAA;EAAiB,OAAA;EAAiB,WAAA,EAAa,MAAA;AAAA,IAC9C,OAAA,CAAQ,cAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-sFCx17CD.d.mts","names":[],"sources":["../src/loaders/json.ts","../src/loaders/jsonc.ts","../src/loaders/errors.ts","../src/loaders/markdown.ts","../src/loaders/toml.ts","../src/loaders/yaml.ts","../src/loaders/index.ts"],"mappings":";;;cAWa,UAAA,YAAsB,MAAA;EACjC,IAAA;EACA,IAAA;EACA,UAAA;EAEA,IAAA,CAAK,QAAA,WAAmB,YAAA;AAAA;;;cCoCb,WAAA,YAAuB,MAAA;EAClC,IAAA;EACA,IAAA;EACA,UAAA;EAEA,IAAA,CAAK,QAAA,WAAmB,YAAA;AAAA;;;cCzDb,WAAA,SAAoB,KAAA;EAAA,SACtB,QAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;cAEG,OAAA,UAAiB,QAAA,UAAkB,MAAA,UAAgB,IAAA,WAAe,MAAA;AAAA;;;cCOnE,cAAA,YAA0B,MAAA;EACrC,IAAA;EACA,IAAA;EACA,UAAA;EAEA,IAAA,CAAK,QAAA,WAAmB,YAAA;AAAA;;;cCTb,UAAA,YAAsB,MAAA;EACjC,IAAA;EACA,IAAA;EACA,UAAA;EAEA,IAAA,CAAK,QAAA,WAAmB,YAAA;AAAA;;;cCLb,UAAA,YAAsB,MAAA;EACjC,IAAA;EACA,IAAA;EACA,UAAA;EAEA,IAAA,CAAK,QAAA,WAAmB,YAAA;AAAA;;;;iBCMV,aAAA,CAAc,YAAA,EAAc,UAAA,GAAa,MAAA,GAAS,MAAA;;iBAqBlD,sBAAA,CAAuB,MAAA,EAAQ,MAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"loaders-DyABmDrE.mjs","names":["parseYaml","parse"],"sources":["../src/loaders/errors.ts","../src/loaders/json.ts","../src/loaders/jsonc.ts","../src/loaders/markdown.ts","../src/loaders/toml.ts","../src/loaders/yaml.ts","../src/loaders/index.ts"],"sourcesContent":["export class LoaderError extends Error {\n readonly filePath: string\n readonly format: string\n readonly line?: number\n readonly column?: number\n\n constructor(message: string, filePath: string, format: string, line?: number, column?: number) {\n super(`${format} parse error in ${filePath}${line ? `:${line}` : ''}: ${message}`)\n this.name = 'LoaderError'\n this.filePath = filePath\n this.format = format\n this.line = line\n this.column = column\n }\n}\n","/**\n * JSON / JSON5 / JSONC loader.\n *\n * Detects format from file extension and parses accordingly.\n */\n\nimport { readFileSync } from 'fs'\nimport JSON5 from 'json5'\nimport { LoaderError } from './errors'\nimport type { Loader, LoaderResult } from './types'\n\nexport class JsonLoader implements Loader {\n name = 'json'\n kind = 'data' as const\n extensions = ['.json', '.json5']\n\n load(filePath: string): LoaderResult {\n const raw = readFileSync(filePath, 'utf-8')\n const isJson = filePath.endsWith('.json')\n\n try {\n const data = isJson ? JSON.parse(raw) : JSON5.parse(raw)\n return { data }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new LoaderError(message, filePath, isJson ? 'JSON' : 'JSON5')\n }\n }\n}\n","/**\n * JSONC loader — JSON with Comments.\n *\n * Strips single-line (//) and multi-line comments before parsing.\n * Delegates to JSON.parse after stripping (no JSON5 superset features).\n */\n\nimport { readFileSync } from 'fs'\nimport { LoaderError } from './errors'\nimport type { Loader, LoaderResult } from './types'\n\n/** Strip // and block comments, preserving strings. */\nfunction stripComments(raw: string): string {\n let result = ''\n let i = 0\n const len = raw.length\n\n while (i < len) {\n // String literal — copy verbatim to preserve contents\n if (raw[i] === '\"') {\n let end = i + 1\n while (end < len && raw[end] !== '\"') {\n if (raw[end] === '\\\\') end++ // skip escaped char\n end++\n }\n result += raw.slice(i, end + 1)\n i = end + 1\n continue\n }\n\n // Single-line comment\n if (raw[i] === '/' && raw[i + 1] === '/') {\n i += 2\n while (i < len && raw[i] !== '\\n') i++\n continue\n }\n\n // Block comment\n if (raw[i] === '/' && raw[i + 1] === '*') {\n i += 2\n while (i < len && !(raw[i] === '*' && raw[i + 1] === '/')) i++\n i += 2 // skip closing */\n continue\n }\n\n result += raw[i]\n i++\n }\n\n return result\n}\n\nexport class JsoncLoader implements Loader {\n name = 'jsonc'\n kind = 'data' as const\n extensions = ['.jsonc']\n\n load(filePath: string): LoaderResult {\n const raw = readFileSync(filePath, 'utf-8')\n try {\n const stripped = stripComments(raw)\n const data = JSON.parse(stripped)\n return { data }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new LoaderError(message, filePath, 'JSONC')\n }\n }\n}\n","/**\n * Markdown loader.\n *\n * Uses gray-matter to extract YAML frontmatter and body content.\n * Rendering is NOT done here — it's deferred to ContentEntry.render().\n */\n\nimport { readFileSync } from 'fs'\nimport matter from 'gray-matter'\nimport { parse as parseYaml } from 'yaml'\nimport { LoaderError } from './errors'\nimport type { Loader, LoaderResult } from './types'\n\nexport class MarkdownLoader implements Loader {\n name = 'markdown'\n kind = 'markdown' as const\n extensions = ['.md']\n\n load(filePath: string): LoaderResult {\n const raw = readFileSync(filePath, 'utf-8')\n try {\n const { data, content } = matter(raw, { engines: { yaml: parseYaml } })\n return { data, content }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new LoaderError(message, filePath, 'Markdown')\n }\n }\n}\n","/**\n * TOML loader.\n */\n\nimport { readFileSync } from 'fs'\nimport { parse } from 'smol-toml'\nimport { LoaderError } from './errors'\nimport type { Loader, LoaderResult } from './types'\n\nexport class TomlLoader implements Loader {\n name = 'toml'\n kind = 'data' as const\n extensions = ['.toml']\n\n load(filePath: string): LoaderResult {\n const raw = readFileSync(filePath, 'utf-8')\n try {\n const data = parse(raw)\n return { data }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new LoaderError(message, filePath, 'TOML')\n }\n }\n}\n","/**\n * YAML loader.\n */\n\nimport { readFileSync } from 'fs'\nimport { parse } from 'yaml'\nimport { LoaderError } from './errors'\nimport type { Loader, LoaderResult } from './types'\n\nexport class YamlLoader implements Loader {\n name = 'yaml'\n kind = 'data' as const\n extensions = ['.yaml', '.yml']\n\n load(filePath: string): LoaderResult {\n const raw = readFileSync(filePath, 'utf-8')\n try {\n const data = parse(raw) ?? {}\n return { data }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n const line =\n err != null && typeof err === 'object' && 'linePos' in err\n ? (err as any).linePos?.[0]?.line\n : undefined\n throw new LoaderError(message, filePath, 'YAML', line)\n }\n }\n}\n","/**\n * Loader registry — resolves loader type strings to Loader instances.\n */\n\nexport { JsonLoader } from './json'\nexport { JsoncLoader } from './jsonc'\nexport { LoaderError } from './errors'\nexport { MarkdownLoader } from './markdown'\nexport { TomlLoader } from './toml'\nexport type { Loader, LoaderResult, LoaderType } from './types'\nexport { YamlLoader } from './yaml'\n\nimport { JsonLoader } from './json'\nimport { JsoncLoader } from './jsonc'\nimport { MarkdownLoader } from './markdown'\nimport { TomlLoader } from './toml'\nimport type { Loader, LoaderType } from './types'\nimport { YamlLoader } from './yaml'\n\n/** Resolve a loader type string or custom Loader instance. */\nexport function resolveLoader(loaderOrType: LoaderType | Loader): Loader {\n if (typeof loaderOrType === 'object') return loaderOrType\n\n switch (loaderOrType) {\n case 'markdown':\n return new MarkdownLoader()\n case 'json':\n case 'json5':\n return new JsonLoader()\n case 'jsonc':\n return new JsoncLoader()\n case 'yaml':\n return new YamlLoader()\n case 'toml':\n return new TomlLoader()\n default:\n throw new Error(`Unknown loader type: ${loaderOrType as string}`)\n }\n}\n\n/** Get default include glob patterns for a loader. */\nexport function defaultIncludePatterns(loader: Loader): string[] {\n return loader.extensions.map((ext) => `**/*${ext}`)\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"markdown-Cj5X26FL.mjs","names":["parseYaml"],"sources":["../src/markdown/pipeline.ts"],"sourcesContent":["import matter from 'gray-matter'\nimport { parse as parseYaml } from 'yaml'\nimport { rehypeAccessibleEmojis } from 'rehype-accessible-emojis'\nimport rehypeAutolinkHeadings from 'rehype-autolink-headings'\nimport rehypeExpressiveCode, {\n type BundledShikiTheme,\n type RehypeExpressiveCodeOptions,\n} from 'rehype-expressive-code'\nimport rehypeExternalLinks from 'rehype-external-links'\nimport rehypeMathjax from 'rehype-mathjax/svg'\nimport rehypeSlug from 'rehype-slug'\nimport rehypeStringify from 'rehype-stringify'\nimport remarkFrontmatter from 'remark-frontmatter'\nimport remarkGfm from 'remark-gfm'\nimport remarkGithubAlerts from 'remark-github-alerts'\nimport remarkMath from 'remark-math'\nimport remarkParse from 'remark-parse'\nimport remarkRehype from 'remark-rehype'\nimport remarkSmartypants from 'remark-smartypants'\nimport { unified } from 'unified'\nimport type { Heading } from '../schemas/heading'\nimport type { MarkdownConfig } from '../schemas/markdown-config'\n\nexport type MarkdownResult = {\n html: string\n headings: Heading[]\n frontmatter: Record<string, any>\n}\n\nexport type { MarkdownConfig }\n\nconst DEFAULT_MARKDOWN_CONFIG: MarkdownConfig = {}\n\nfunction getTextContent(node: any): string {\n if (node.type === 'text') return node.value || ''\n if (node.children) return node.children.map(getTextContent).join('')\n return ''\n}\n\nfunction extractHeadings(tree: any, headings: Heading[]): void {\n if (tree.type === 'element' && /^h[1-6]$/.test(tree.tagName)) {\n headings.push({\n depth: parseInt(tree.tagName[1]),\n text: getTextContent(tree),\n slug: tree.properties?.id || '',\n })\n }\n if (tree.children) {\n for (const child of tree.children) {\n extractHeadings(child, headings)\n }\n }\n}\n\nfunction createProcessor(config: MarkdownConfig) {\n const processor = unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkMath)\n .use(remarkFrontmatter, ['yaml'])\n // GitHub-flavored alerts: > [!NOTE], > [!TIP], > [!IMPORTANT], > [!WARNING], > [!CAUTION]\n .use(remarkGithubAlerts)\n // Smart typography: \"smart quotes\", em—dashes, el…lipses\n .use(remarkSmartypants)\n\n if (config.remarkPlugins) {\n for (const plugin of config.remarkPlugins) {\n if (Array.isArray(plugin)) processor.use(plugin[0], plugin[1])\n else processor.use(plugin)\n }\n }\n\n processor.use(remarkRehype, { allowDangerousHtml: true })\n\n // Expressive Code — syntax highlighting, code frames, tabs, copy button\n const lightTheme = (config.shiki?.themes?.light || 'github-light') as BundledShikiTheme\n const darkTheme = (config.shiki?.themes?.dark || 'github-dark') as BundledShikiTheme\n\n processor.use(rehypeExpressiveCode, {\n themes: [darkTheme, lightTheme],\n useDarkModeMediaQuery: true,\n styleOverrides: {\n uiFontFamily: 'var(--ps-font-sans, var(--font-family, system-ui, sans-serif))',\n codeFontFamily: 'var(--ps-font-mono, var(--font-mono, ui-monospace, monospace))',\n codeFontSize: 'var(--ps-font-size-sm, 0.875rem)',\n codeLineHeight: '1.7',\n borderRadius: 'var(--ps-radius-lg, 0.5rem)',\n borderColor: 'var(--ps-color-border-subtle, var(--color-border-subtle, #e5e7eb))',\n },\n } satisfies RehypeExpressiveCodeOptions)\n\n processor\n .use(rehypeMathjax)\n .use(rehypeSlug)\n .use(rehypeAutolinkHeadings, { behavior: 'wrap' })\n // External links: add target=\"_blank\" rel=\"noopener noreferrer\" to absolute URLs\n .use(rehypeExternalLinks, {\n target: '_blank',\n rel: ['noopener', 'noreferrer'],\n })\n // Accessible emojis: wrap emoji characters in <span role=\"img\" aria-label=\"...\">\n .use(rehypeAccessibleEmojis)\n\n processor.use(() => (tree: any, file: any) => {\n const headings: Heading[] = []\n extractHeadings(tree, headings)\n file.data.headings = headings\n })\n\n if (config.rehypePlugins) {\n for (const plugin of config.rehypePlugins) {\n if (Array.isArray(plugin)) processor.use(plugin[0], plugin[1])\n else processor.use(plugin)\n }\n }\n\n processor.use(rehypeStringify, { allowDangerousHtml: true })\n return processor\n}\n\nconst processorCache = new WeakMap<MarkdownConfig, ReturnType<typeof createProcessor>>()\n\nexport async function processMarkdown(\n raw: string,\n config?: MarkdownConfig,\n preExtracted?: { content: string; frontmatter: Record<string, unknown> },\n): Promise<MarkdownResult> {\n let frontmatter: Record<string, unknown>\n let content: string\n if (preExtracted) {\n frontmatter = preExtracted.frontmatter\n content = preExtracted.content\n } else {\n const parsed = matter(raw, { engines: { yaml: parseYaml } })\n frontmatter = parsed.data\n content = parsed.content\n }\n const resolvedConfig = config && Object.keys(config).length > 0 ? config : DEFAULT_MARKDOWN_CONFIG\n let processor = processorCache.get(resolvedConfig)\n if (!processor) {\n processor = createProcessor(resolvedConfig)\n processorCache.set(resolvedConfig, processor)\n }\n const result = await processor.process(content)\n const headings = Array.isArray(result.data.headings) ? (result.data.headings as Heading[]) : []\n\n return { html: String(result), headings, frontmatter }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA+BA,MAAM,0BAA0C,EAAE;AAElD,SAAS,eAAe,MAAmB;AACzC,KAAI,KAAK,SAAS,OAAQ,QAAO,KAAK,SAAS;AAC/C,KAAI,KAAK,SAAU,QAAO,KAAK,SAAS,IAAI,eAAe,CAAC,KAAK,GAAG;AACpE,QAAO;;AAGT,SAAS,gBAAgB,MAAW,UAA2B;AAC7D,KAAI,KAAK,SAAS,aAAa,WAAW,KAAK,KAAK,QAAQ,CAC1D,UAAS,KAAK;EACZ,OAAO,SAAS,KAAK,QAAQ,GAAG;EAChC,MAAM,eAAe,KAAK;EAC1B,MAAM,KAAK,YAAY,MAAM;EAC9B,CAAC;AAEJ,KAAI,KAAK,SACP,MAAK,MAAM,SAAS,KAAK,SACvB,iBAAgB,OAAO,SAAS;;AAKtC,SAAS,gBAAgB,QAAwB;CAC/C,MAAM,YAAY,SAAS,CACxB,IAAI,YAAY,CAChB,IAAI,UAAU,CACd,IAAI,WAAW,CACf,IAAI,mBAAmB,CAAC,OAAO,CAAC,CAEhC,IAAI,mBAAmB,CAEvB,IAAI,kBAAkB;AAEzB,KAAI,OAAO,cACT,MAAK,MAAM,UAAU,OAAO,cAC1B,KAAI,MAAM,QAAQ,OAAO,CAAE,WAAU,IAAI,OAAO,IAAI,OAAO,GAAG;KACzD,WAAU,IAAI,OAAO;AAI9B,WAAU,IAAI,cAAc,EAAE,oBAAoB,MAAM,CAAC;CAGzD,MAAM,aAAc,OAAO,OAAO,QAAQ,SAAS;CACnD,MAAM,YAAa,OAAO,OAAO,QAAQ,QAAQ;AAEjD,WAAU,IAAI,sBAAsB;EAClC,QAAQ,CAAC,WAAW,WAAW;EAC/B,uBAAuB;EACvB,gBAAgB;GACd,cAAc;GACd,gBAAgB;GAChB,cAAc;GACd,gBAAgB;GAChB,cAAc;GACd,aAAa;GACd;EACF,CAAuC;AAExC,WACG,IAAI,cAAc,CAClB,IAAI,WAAW,CACf,IAAI,wBAAwB,EAAE,UAAU,QAAQ,CAAC,CAEjD,IAAI,qBAAqB;EACxB,QAAQ;EACR,KAAK,CAAC,YAAY,aAAa;EAChC,CAAC,CAED,IAAI,uBAAuB;AAE9B,WAAU,WAAW,MAAW,SAAc;EAC5C,MAAM,WAAsB,EAAE;AAC9B,kBAAgB,MAAM,SAAS;AAC/B,OAAK,KAAK,WAAW;GACrB;AAEF,KAAI,OAAO,cACT,MAAK,MAAM,UAAU,OAAO,cAC1B,KAAI,MAAM,QAAQ,OAAO,CAAE,WAAU,IAAI,OAAO,IAAI,OAAO,GAAG;KACzD,WAAU,IAAI,OAAO;AAI9B,WAAU,IAAI,iBAAiB,EAAE,oBAAoB,MAAM,CAAC;AAC5D,QAAO;;AAGT,MAAM,iCAAiB,IAAI,SAA6D;AAExF,eAAsB,gBACpB,KACA,QACA,cACyB;CACzB,IAAI;CACJ,IAAI;AACJ,KAAI,cAAc;AAChB,gBAAc,aAAa;AAC3B,YAAU,aAAa;QAClB;EACL,MAAM,SAAS,OAAO,KAAK,EAAE,SAAS,EAAE,MAAMA,OAAW,EAAE,CAAC;AAC5D,gBAAc,OAAO;AACrB,YAAU,OAAO;;CAEnB,MAAM,iBAAiB,UAAU,OAAO,KAAK,OAAO,CAAC,SAAS,IAAI,SAAS;CAC3E,IAAI,YAAY,eAAe,IAAI,eAAe;AAClD,KAAI,CAAC,WAAW;AACd,cAAY,gBAAgB,eAAe;AAC3C,iBAAe,IAAI,gBAAgB,UAAU;;CAE/C,MAAM,SAAS,MAAM,UAAU,QAAQ,QAAQ;CAC/C,MAAM,WAAW,MAAM,QAAQ,OAAO,KAAK,SAAS,GAAI,OAAO,KAAK,WAAyB,EAAE;AAE/F,QAAO;EAAE,MAAM,OAAO,OAAO;EAAE;EAAU;EAAa"}