@sigx/ssg 0.1.26 → 0.2.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.
@@ -1,8 +1,18 @@
1
- import { A as resolveConfigPaths, C as extractParams, S as expandDynamicRoute, T as scanPages, k as loadConfig, l as generateProductionHtmlTemplate, m as discoverLayouts, o as detectCustomEntries, s as generateClientEntry, u as generateServerEntry, w as isDynamicRoute } from "./virtual-entries-sVkqKMAM.js";
1
+ import { A as resolveConfigPaths, C as extractParams, S as expandDynamicRoute, T as scanPages, k as loadConfig, l as generateProductionHtmlTemplate, m as discoverLayouts, o as detectCustomEntries, s as generateClientEntry, u as generateServerEntry, w as isDynamicRoute } from "./virtual-entries-TuNN2It1.js";
2
2
  import path from "node:path";
3
3
  import fs from "node:fs/promises";
4
4
  import fsSync from "node:fs";
5
5
  import { pathToFileURL } from "node:url";
6
+ //#region src/sitemap.ts
7
+ /**
8
+ * Sitemap Generation
9
+ *
10
+ * Generates XML sitemaps for SSG sites following the sitemap protocol.
11
+ * https://www.sitemaps.org/protocol.html
12
+ */
13
+ /**
14
+ * Generate sitemap XML content
15
+ */
6
16
  function generateSitemap(entries, config) {
7
17
  const siteUrl = config.site?.url?.replace(/\/$/, "") || "";
8
18
  const base = config.base?.replace(/\/$/, "") || "";
@@ -20,6 +30,9 @@ ${entries.map((entry) => {
20
30
  }).join("\n")}
21
31
  </urlset>`;
22
32
  }
33
+ /**
34
+ * Generate robots.txt content
35
+ */
23
36
  function generateRobotsTxt(config, sitemapPath = "/sitemap.xml") {
24
37
  return `User-agent: *
25
38
  Allow: /
@@ -27,6 +40,9 @@ Allow: /
27
40
  Sitemap: ${config.site?.url?.replace(/\/$/, "") || ""}${config.base?.replace(/\/$/, "") || ""}${sitemapPath}
28
41
  `;
29
42
  }
43
+ /**
44
+ * Convert page build results to sitemap entries
45
+ */
30
46
  function pagesToSitemapEntries(pages, options = {}) {
31
47
  const { exclude = [], defaultChangefreq = "weekly", defaultPriority = .5 } = options;
32
48
  return pages.filter((page) => {
@@ -47,6 +63,9 @@ function pagesToSitemapEntries(pages, options = {}) {
47
63
  };
48
64
  });
49
65
  }
66
+ /**
67
+ * Write sitemap and robots.txt to output directory
68
+ */
50
69
  async function writeSitemap(pages, config, outDir, options = {}) {
51
70
  const entries = pagesToSitemapEntries(pages, options);
52
71
  if (options.additionalUrls) entries.push(...options.additionalUrls);
@@ -61,9 +80,28 @@ async function writeSitemap(pages, config, outDir, options = {}) {
61
80
  robotsPath
62
81
  };
63
82
  }
83
+ /**
84
+ * Escape special XML characters
85
+ */
64
86
  function escapeXml(str) {
65
87
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
66
88
  }
89
+ //#endregion
90
+ //#region src/build.ts
91
+ /**
92
+ * SSG Build CLI
93
+ *
94
+ * Static site generation build process:
95
+ * 1. Load configuration
96
+ * 2. Scan routes and expand dynamic paths
97
+ * 3. Build with Vite for production
98
+ * 4. Render each route to static HTML
99
+ * 5. Write output files
100
+ * 6. Generate sitemap and robots.txt
101
+ */
102
+ /**
103
+ * Build static site
104
+ */
67
105
  async function build(options = {}) {
68
106
  const startTime = Date.now();
69
107
  const root = process.cwd();
@@ -223,6 +261,9 @@ async function build(options = {}) {
223
261
  warnings
224
262
  };
225
263
  }
264
+ /**
265
+ * Collect all paths to render, expanding dynamic routes
266
+ */
226
267
  async function collectPaths(routes, root, warnings) {
227
268
  const paths = [];
228
269
  for (const route of routes) if (isDynamicRoute(route)) try {
@@ -253,6 +294,9 @@ async function collectPaths(routes, root, warnings) {
253
294
  });
254
295
  return paths;
255
296
  }
297
+ /**
298
+ * Generate head tags for a page
299
+ */
256
300
  function generateHeadTags(pathInfo, config) {
257
301
  const tags = [];
258
302
  const meta = pathInfo.route.meta || {};
@@ -266,12 +310,19 @@ function generateHeadTags(pathInfo, config) {
266
310
  }
267
311
  return tags.join("\n ");
268
312
  }
313
+ /**
314
+ * Get output file path for a URL path
315
+ */
269
316
  function getOutputPath(urlPath, outDir) {
270
317
  let normalized = urlPath.replace(/^\//, "").replace(/\/$/, "");
271
318
  if (!normalized) normalized = "index";
272
319
  if (!normalized.endsWith(".html")) normalized = path.join(normalized, "index.html");
273
320
  return path.join(outDir, normalized);
274
321
  }
322
+ /**
323
+ * Get SSR entry point
324
+ * Returns virtual module ID if no custom entry exists
325
+ */
275
326
  async function getSSREntryPoint(config, root) {
276
327
  const detection = detectCustomEntries(root, config);
277
328
  if (!detection.useVirtualServer && detection.customServerPath) return detection.customServerPath;
@@ -280,6 +331,10 @@ async function getSSREntryPoint(config, root) {
280
331
  fsSync.writeFileSync(tempServerPath, virtualServerCode, "utf-8");
281
332
  return tempServerPath;
282
333
  }
334
+ /**
335
+ * Get client entry point
336
+ * Returns virtual module ID if no custom entry exists
337
+ */
283
338
  async function getClientEntryPoint(config, root) {
284
339
  const detection = detectCustomEntries(root, config);
285
340
  if (!detection.useVirtualClient && detection.customClientPath) return detection.customClientPath;
@@ -288,12 +343,18 @@ async function getClientEntryPoint(config, root) {
288
343
  fsSync.writeFileSync(tempClientPath, virtualClientCode, "utf-8");
289
344
  return tempClientPath;
290
345
  }
346
+ /**
347
+ * Clean up temporary entry files
348
+ */
291
349
  async function cleanupTempEntries(root) {
292
350
  const tempFiles = [path.join(root, ".ssg-temp-entry-server.tsx"), path.join(root, ".ssg-temp-entry-client.tsx")];
293
351
  for (const file of tempFiles) try {
294
352
  await fs.unlink(file);
295
353
  } catch {}
296
354
  }
355
+ /**
356
+ * Get or generate HTML template
357
+ */
297
358
  async function getHtmlTemplate(config, root, clientEntryPath) {
298
359
  const detection = detectCustomEntries(root, config);
299
360
  if (!detection.useVirtualHtml && detection.customHtmlPath) {
@@ -304,14 +365,21 @@ async function getHtmlTemplate(config, root, clientEntryPath) {
304
365
  }
305
366
  return generateProductionHtmlTemplate(config, clientEntryPath);
306
367
  }
368
+ /**
369
+ * Format bytes to human-readable string
370
+ */
307
371
  function formatBytes(bytes) {
308
372
  if (bytes < 1024) return `${bytes}B`;
309
373
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
310
374
  return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
311
375
  }
376
+ /**
377
+ * Escape HTML special characters
378
+ */
312
379
  function escapeHtml(str) {
313
380
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
314
381
  }
382
+ //#endregion
315
383
  export { writeSitemap as a, pagesToSitemapEntries as i, generateRobotsTxt as n, generateSitemap as r, build as t };
316
384
 
317
- //# sourceMappingURL=build-CfOi93wa.js.map
385
+ //# sourceMappingURL=build-qmtK32gt.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-CfOi93wa.js","names":[],"sources":["../src/sitemap.ts","../src/build.ts"],"sourcesContent":["/**\r\n * Sitemap Generation\r\n *\r\n * Generates XML sitemaps for SSG sites following the sitemap protocol.\r\n * https://www.sitemaps.org/protocol.html\r\n */\r\n\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport type { SSGConfig, PageBuildResult } from './types';\r\n\r\n/**\r\n * Sitemap entry with optional metadata\r\n */\r\nexport interface SitemapEntry {\r\n /** URL path (relative to site base) */\r\n path: string;\r\n /** Last modification date */\r\n lastmod?: Date | string;\r\n /** Change frequency hint */\r\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\r\n /** Priority relative to other pages (0.0 to 1.0) */\r\n priority?: number;\r\n}\r\n\r\n/**\r\n * Sitemap generation options\r\n */\r\nexport interface SitemapOptions {\r\n /** Include all built pages automatically */\r\n includePages?: boolean;\r\n /** Additional URLs to include */\r\n additionalUrls?: SitemapEntry[];\r\n /** URLs to exclude (glob patterns or exact matches) */\r\n exclude?: string[];\r\n /** Default change frequency */\r\n defaultChangefreq?: SitemapEntry['changefreq'];\r\n /** Default priority */\r\n defaultPriority?: number;\r\n}\r\n\r\n/**\r\n * Generate sitemap XML content\r\n */\r\nexport function generateSitemap(\r\n entries: SitemapEntry[],\r\n config: SSGConfig\r\n): string {\r\n const siteUrl = config.site?.url?.replace(/\\/$/, '') || '';\r\n const base = config.base?.replace(/\\/$/, '') || '';\r\n\r\n const urlEntries = entries.map((entry) => {\r\n const loc = `${siteUrl}${base}${entry.path}`;\r\n const lastmod = entry.lastmod\r\n ? typeof entry.lastmod === 'string'\r\n ? entry.lastmod\r\n : entry.lastmod.toISOString().split('T')[0]\r\n : undefined;\r\n\r\n return ` <url>\r\n <loc>${escapeXml(loc)}</loc>${lastmod ? `\r\n <lastmod>${lastmod}</lastmod>` : ''}${entry.changefreq ? `\r\n <changefreq>${entry.changefreq}</changefreq>` : ''}${entry.priority !== undefined ? `\r\n <priority>${entry.priority.toFixed(1)}</priority>` : ''}\r\n </url>`;\r\n });\r\n\r\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\r\n${urlEntries.join('\\n')}\r\n</urlset>`;\r\n}\r\n\r\n/**\r\n * Generate robots.txt content\r\n */\r\nexport function generateRobotsTxt(config: SSGConfig, sitemapPath = '/sitemap.xml'): string {\r\n const siteUrl = config.site?.url?.replace(/\\/$/, '') || '';\r\n const base = config.base?.replace(/\\/$/, '') || '';\r\n\r\n return `User-agent: *\r\nAllow: /\r\n\r\nSitemap: ${siteUrl}${base}${sitemapPath}\r\n`;\r\n}\r\n\r\n/**\r\n * Convert page build results to sitemap entries\r\n */\r\nexport function pagesToSitemapEntries(\r\n pages: PageBuildResult[],\r\n options: SitemapOptions = {}\r\n): SitemapEntry[] {\r\n const {\r\n exclude = [],\r\n defaultChangefreq = 'weekly',\r\n defaultPriority = 0.5,\r\n } = options;\r\n\r\n return pages\r\n .filter((page) => {\r\n // Filter out excluded paths\r\n for (const pattern of exclude) {\r\n if (pattern.includes('*')) {\r\n // Simple glob matching\r\n const regex = new RegExp(\r\n '^' + pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.') + '$'\r\n );\r\n if (regex.test(page.path)) return false;\r\n } else if (page.path === pattern) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n })\r\n .map((page) => {\r\n // Determine priority based on path depth\r\n const depth = page.path.split('/').filter(Boolean).length;\r\n let priority = defaultPriority;\r\n \r\n if (page.path === '/') {\r\n priority = 1.0; // Homepage highest priority\r\n } else if (depth === 1) {\r\n priority = 0.8; // Top-level pages\r\n } else if (depth === 2) {\r\n priority = 0.6; // Second-level pages\r\n }\r\n\r\n return {\r\n path: page.path,\r\n changefreq: defaultChangefreq,\r\n priority,\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * Write sitemap and robots.txt to output directory\r\n */\r\nexport async function writeSitemap(\r\n pages: PageBuildResult[],\r\n config: SSGConfig,\r\n outDir: string,\r\n options: SitemapOptions = {}\r\n): Promise<{ sitemapPath: string; robotsPath: string }> {\r\n // Generate entries from pages\r\n const entries = pagesToSitemapEntries(pages, options);\r\n\r\n // Add additional URLs if provided\r\n if (options.additionalUrls) {\r\n entries.push(...options.additionalUrls);\r\n }\r\n\r\n // Generate sitemap XML\r\n const sitemapContent = generateSitemap(entries, config);\r\n const sitemapPath = path.join(outDir, 'sitemap.xml');\r\n await fs.writeFile(sitemapPath, sitemapContent, 'utf-8');\r\n\r\n // Generate robots.txt\r\n const robotsContent = generateRobotsTxt(config);\r\n const robotsPath = path.join(outDir, 'robots.txt');\r\n await fs.writeFile(robotsPath, robotsContent, 'utf-8');\r\n\r\n return { sitemapPath, robotsPath };\r\n}\r\n\r\n/**\r\n * Escape special XML characters\r\n */\r\nfunction escapeXml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&apos;');\r\n}\r\n","/**\r\n * SSG Build CLI\r\n *\r\n * Static site generation build process:\r\n * 1. Load configuration\r\n * 2. Scan routes and expand dynamic paths\r\n * 3. Build with Vite for production\r\n * 4. Render each route to static HTML\r\n * 5. Write output files\r\n * 6. Generate sitemap and robots.txt\r\n */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs/promises';\r\nimport fsSync from 'node:fs';\r\nimport { createRequire } from 'node:module';\r\nimport { pathToFileURL } from 'node:url';\r\nimport type { SSGConfig, BuildOptions, BuildResult, PageBuildResult, SSGRoute, PageModule } from './types';\r\nimport { loadConfig, resolveConfigPaths } from './config';\r\nimport { scanPages, isDynamicRoute, extractParams, expandDynamicRoute } from './routing/index';\r\nimport { discoverLayouts } from './layouts/index';\r\nimport { writeSitemap } from './sitemap';\r\nimport {\r\n detectCustomEntries,\r\n generateClientEntry,\r\n generateServerEntry,\r\n generateProductionHtmlTemplate,\r\n VIRTUAL_CLIENT_ID,\r\n VIRTUAL_SERVER_ID,\r\n} from './vite/virtual-entries';\r\n\r\n/**\r\n * Build static site\r\n */\r\nexport async function build(options: BuildOptions = {}): Promise<BuildResult> {\r\n const startTime = Date.now();\r\n const root = process.cwd();\r\n const warnings: string[] = [];\r\n const pages: PageBuildResult[] = [];\r\n\r\n console.log('\\n🚀 @sigx/ssg - Building static site...\\n');\r\n\r\n // Step 1: Load configuration\r\n console.log('📦 Loading configuration...');\r\n const config = await loadConfig(options.configPath);\r\n const resolvedConfig = resolveConfigPaths(config, root);\r\n\r\n // Step 2: Scan routes\r\n console.log('🔍 Scanning pages...');\r\n const routes = await scanPages(resolvedConfig, root);\r\n console.log(` Found ${routes.length} page(s)`);\r\n\r\n // Step 3: Discover layouts\r\n console.log('📐 Discovering layouts...');\r\n const layouts = await discoverLayouts(resolvedConfig, root);\r\n console.log(` Found ${layouts.length} layout(s)`);\r\n\r\n // Step 4: Detect entry points\r\n const entryDetection = detectCustomEntries(root, resolvedConfig);\r\n if (entryDetection.useVirtualClient || entryDetection.useVirtualServer) {\r\n console.log('📦 Using zero-config mode');\r\n if (entryDetection.useVirtualClient) console.log(' → Virtual client entry');\r\n if (entryDetection.useVirtualServer) console.log(' → Virtual server entry');\r\n if (entryDetection.useVirtualHtml) console.log(' → Virtual HTML template');\r\n }\r\n\r\n // Get entry points (may create temp files for virtual entries)\r\n const clientEntry = await getClientEntryPoint(resolvedConfig, root);\r\n const ssrEntry = await getSSREntryPoint(resolvedConfig, root);\r\n\r\n // Always write HTML template for the build (either generated or updated from custom)\r\n // This ensures Vite processes it and outputs index.html\r\n const htmlTemplatePath = path.join(root, 'index.html');\r\n let cleanupHtml = false;\r\n let originalHtmlContent: string | null = null;\r\n \r\n // Save original HTML content if we're modifying a custom one\r\n if (!entryDetection.useVirtualHtml && fsSync.existsSync(htmlTemplatePath)) {\r\n originalHtmlContent = fsSync.readFileSync(htmlTemplatePath, 'utf-8');\r\n }\r\n \r\n const htmlContent = await getHtmlTemplate(resolvedConfig, root, clientEntry);\r\n fsSync.writeFileSync(htmlTemplatePath, htmlContent, 'utf-8');\r\n cleanupHtml = entryDetection.useVirtualHtml; // Only cleanup (delete) if we generated it\r\n\r\n // Step 5: Build with Vite\r\n console.log('🔨 Building with Vite...');\r\n const vite = await import('vite');\r\n\r\n try {\r\n // Build client bundle\r\n // Note: We don't empty the outDir since vite build may have already run\r\n // Always use HTML as input so Vite outputs index.html properly\r\n const clientInput = htmlTemplatePath;\r\n \r\n await vite.build({\r\n root,\r\n mode: 'production',\r\n build: {\r\n outDir: resolvedConfig.outDir,\r\n emptyOutDir: false,\r\n ssrManifest: true,\r\n rollupOptions: {\r\n input: clientInput,\r\n },\r\n },\r\n logLevel: options.verbose ? 'info' : 'warn',\r\n });\r\n\r\n // Build SSR bundle\r\n const ssrOutDir = path.join(resolvedConfig.outDir!, '.ssg');\r\n await vite.build({\r\n root,\r\n mode: 'production',\r\n build: {\r\n outDir: ssrOutDir,\r\n ssr: true,\r\n rollupOptions: {\r\n input: ssrEntry,\r\n },\r\n },\r\n logLevel: options.verbose ? 'info' : 'warn',\r\n });\r\n\r\n // Step 6: Collect all paths to render\r\n console.log('📝 Collecting paths to render...');\r\n const pathsToRender = await collectPaths(routes, root, warnings);\r\n console.log(` ${pathsToRender.length} path(s) to render`);\r\n\r\n // Pre-create all output directories to avoid mkdir contention during parallel rendering\r\n const outputDirs = new Set<string>();\r\n for (const pathInfo of pathsToRender) {\r\n const outputPath = getOutputPath(pathInfo.path, resolvedConfig.outDir!);\r\n outputDirs.add(path.dirname(outputPath));\r\n }\r\n await Promise.all(\r\n Array.from(outputDirs).map(dir => fs.mkdir(dir, { recursive: true }))\r\n );\r\n\r\n // Step 7: Render each path to HTML\r\n console.log('🎨 Rendering pages...');\r\n\r\n // Determine the SSR entry output name (based on input file name)\r\n const ssrEntryBasename = path.basename(ssrEntry, path.extname(ssrEntry));\r\n const ssrEntryName = ssrEntryBasename + '.js';\r\n\r\n // Pre-load the SSR module once for all pages\r\n const entryPath = path.join(ssrOutDir, ssrEntryName);\r\n const entryModule = await import(pathToFileURL(entryPath).href);\r\n\r\n // Load HTML template once\r\n const templatePath = path.join(resolvedConfig.outDir!, 'index.html');\r\n const template = await fs.readFile(templatePath, 'utf-8');\r\n\r\n // Parallel rendering configuration - higher concurrency since rendering is CPU-bound\r\n const CONCURRENCY = options.concurrency ?? 20; // Number of pages to render in parallel\r\n const verbose = options.verbose ?? false;\r\n\r\n interface RenderResult {\r\n pathInfo: PathToRender;\r\n html: string;\r\n outputPath: string;\r\n renderTime: number;\r\n }\r\n\r\n // Render a single page (CPU-bound, no I/O)\r\n async function renderPage(pathInfo: PathToRender): Promise<RenderResult | null> {\r\n const renderStart = Date.now();\r\n\r\n try {\r\n // Render the app\r\n const appHtml = await entryModule.render(pathInfo.path, {\r\n params: pathInfo.params,\r\n props: pathInfo.props,\r\n });\r\n\r\n // Inject into template\r\n let html = template.replace('<!--app-html-->', appHtml);\r\n const headTags = generateHeadTags(pathInfo, resolvedConfig);\r\n html = html.replace('<!--head-tags-->', headTags);\r\n\r\n const outputPath = getOutputPath(pathInfo.path, resolvedConfig.outDir!);\r\n const renderTime = Date.now() - renderStart;\r\n\r\n return { pathInfo, html, outputPath, renderTime };\r\n } catch (err) {\r\n const errorMessage = err instanceof Error ? err.message : String(err);\r\n console.error(` ❌ ${pathInfo.path}: ${errorMessage}`);\r\n warnings.push(`Failed to render ${pathInfo.path}: ${errorMessage}`);\r\n return null;\r\n }\r\n }\r\n\r\n // Render all pages in parallel batches (CPU-bound, no I/O)\r\n console.log(' Phase 1: Rendering...');\r\n const renderPhaseStart = Date.now();\r\n const renderResults: RenderResult[] = [];\r\n \r\n for (let i = 0; i < pathsToRender.length; i += CONCURRENCY) {\r\n const batch = pathsToRender.slice(i, i + CONCURRENCY);\r\n const results = await Promise.all(batch.map(renderPage));\r\n \r\n for (const result of results) {\r\n if (result) {\r\n renderResults.push(result);\r\n }\r\n }\r\n }\r\n const renderPhaseDuration = Date.now() - renderPhaseStart;\r\n console.log(` Phase 1 complete: ${renderResults.length} pages in ${renderPhaseDuration}ms (${Math.round(renderPhaseDuration / renderResults.length)}ms avg)`);\r\n\r\n // Write all files in parallel with limited concurrency\r\n console.log(' Phase 2: Writing files...');\r\n const writePhaseStart = Date.now();\r\n const WRITE_CONCURRENCY = 10; // Limit parallel writes to reduce I/O contention\r\n \r\n for (let i = 0; i < renderResults.length; i += WRITE_CONCURRENCY) {\r\n const batch = renderResults.slice(i, i + WRITE_CONCURRENCY);\r\n await Promise.all(batch.map(async (result) => {\r\n await fs.writeFile(result.outputPath, result.html, 'utf-8');\r\n const size = Buffer.byteLength(result.html, 'utf-8');\r\n\r\n pages.push({\r\n path: result.pathInfo.path,\r\n file: result.outputPath,\r\n time: result.renderTime,\r\n size,\r\n });\r\n \r\n if (verbose) {\r\n console.log(` ✓ ${result.pathInfo.path} (${result.renderTime}ms, ${formatBytes(size)})`);\r\n }\r\n }));\r\n }\r\n const writePhaseDuration = Date.now() - writePhaseStart;\r\n console.log(` Phase 2 complete: ${renderResults.length} files in ${writePhaseDuration}ms`);\r\n \r\n // Summary (non-verbose)\r\n if (!verbose) {\r\n console.log(` ✓ Rendered ${renderResults.length} pages`);\r\n }\r\n\r\n // Step 8: Clean up SSR build\r\n await fs.rm(ssrOutDir, { recursive: true, force: true });\r\n\r\n // Step 9: Generate sitemap and robots.txt\r\n if (pages.length > 0) {\r\n console.log('🗺️ Generating sitemap...');\r\n await writeSitemap(pages, resolvedConfig, resolvedConfig.outDir!);\r\n console.log(' ✓ sitemap.xml');\r\n console.log(' ✓ robots.txt');\r\n }\r\n\r\n } finally {\r\n // Clean up temporary entry files\r\n await cleanupTempEntries(root);\r\n \r\n // Clean up or restore HTML template\r\n if (cleanupHtml) {\r\n // We generated a virtual HTML, remove it\r\n try {\r\n await fs.unlink(htmlTemplatePath);\r\n } catch {\r\n // Ignore\r\n }\r\n } else if (originalHtmlContent !== null) {\r\n // We modified a custom HTML, restore the original\r\n try {\r\n await fs.writeFile(htmlTemplatePath, originalHtmlContent, 'utf-8');\r\n } catch {\r\n // Ignore\r\n }\r\n }\r\n }\r\n\r\n // Done\r\n const totalTime = Date.now() - startTime;\r\n\r\n console.log(`\\n✅ Built ${pages.length} page(s) in ${totalTime}ms`);\r\n\r\n if (warnings.length > 0) {\r\n console.log(`\\n⚠️ ${warnings.length} warning(s):`);\r\n for (const warning of warnings) {\r\n console.log(` - ${warning}`);\r\n }\r\n }\r\n\r\n console.log(`\\n📁 Output: ${resolvedConfig.outDir}\\n`);\r\n\r\n return {\r\n pages,\r\n totalTime,\r\n warnings,\r\n };\r\n}\r\n\r\n/**\r\n * Path information for rendering\r\n */\r\ninterface PathToRender {\r\n path: string;\r\n route: SSGRoute;\r\n params: Record<string, string>;\r\n props?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Collect all paths to render, expanding dynamic routes\r\n */\r\nasync function collectPaths(\r\n routes: SSGRoute[],\r\n root: string,\r\n warnings: string[]\r\n): Promise<PathToRender[]> {\r\n const paths: PathToRender[] = [];\r\n\r\n for (const route of routes) {\r\n if (isDynamicRoute(route)) {\r\n // Load module and call getStaticPaths\r\n try {\r\n const moduleUrl = pathToFileURL(route.file).href;\r\n const pageModule = (await import(moduleUrl)) as PageModule;\r\n\r\n if (!pageModule.getStaticPaths) {\r\n const params = extractParams(route.path).join(', ');\r\n console.warn(\r\n `\\n⚠️ SSG102: Dynamic route missing getStaticPaths()\\n` +\r\n ` 📁 ${route.file}\\n` +\r\n ` Route: ${route.path} (params: ${params})\\n` +\r\n ` 💡 Export getStaticPaths() to generate static pages:\\n\\n` +\r\n ` export async function getStaticPaths() {\\n` +\r\n ` return [{ params: { ${params.split(', ')[0]}: 'value' } }];\\n` +\r\n ` }\\n`\r\n );\r\n warnings.push(\r\n `Route ${route.path} has dynamic segments [${params}] but no getStaticPaths() export. Skipping.`\r\n );\r\n continue;\r\n }\r\n\r\n const staticPaths = await pageModule.getStaticPaths();\r\n\r\n for (const staticPath of staticPaths) {\r\n const expandedPaths = expandDynamicRoute(route, [staticPath]);\r\n for (const expandedPath of expandedPaths) {\r\n paths.push({\r\n path: expandedPath,\r\n route,\r\n params: staticPath.params,\r\n props: staticPath.props,\r\n });\r\n }\r\n }\r\n } catch (err) {\r\n warnings.push(`Failed to load ${route.file}: ${err}`);\r\n }\r\n } else {\r\n paths.push({\r\n path: route.path,\r\n route,\r\n params: {},\r\n });\r\n }\r\n }\r\n\r\n return paths;\r\n}\r\n\r\n/**\r\n * Render a page to HTML\r\n */\r\nasync function renderPage(\r\n pathInfo: PathToRender,\r\n config: SSGConfig,\r\n ssrOutDir: string,\r\n ssrEntryName: string\r\n): Promise<string> {\r\n // Load the SSR entry module\r\n // This should export a render function that returns HTML string\r\n const entryPath = path.join(ssrOutDir, ssrEntryName);\r\n const entryModule = await import(pathToFileURL(entryPath).href);\r\n\r\n // Render the app - the entry module's render function handles SSR\r\n const appHtml = await entryModule.render(pathInfo.path, {\r\n params: pathInfo.params,\r\n props: pathInfo.props,\r\n });\r\n\r\n // Load HTML template\r\n const templatePath = path.join(config.outDir!, 'index.html');\r\n let template = await fs.readFile(templatePath, 'utf-8');\r\n\r\n // Inject app HTML\r\n template = template.replace('<!--app-html-->', appHtml);\r\n\r\n // Inject head tags if any\r\n const headTags = generateHeadTags(pathInfo, config);\r\n template = template.replace('<!--head-tags-->', headTags);\r\n\r\n return template;\r\n}\r\n\r\n/**\r\n * Generate head tags for a page\r\n */\r\nfunction generateHeadTags(pathInfo: PathToRender, config: SSGConfig): string {\r\n const tags: string[] = [];\r\n const meta = pathInfo.route.meta || {};\r\n\r\n // Title\r\n const title = meta.title || config.site?.title;\r\n if (title) {\r\n tags.push(`<title>${escapeHtml(title)}</title>`);\r\n }\r\n\r\n // Description\r\n const description = meta.description || config.site?.description;\r\n if (description) {\r\n tags.push(`<meta name=\"description\" content=\"${escapeHtml(description)}\">`);\r\n }\r\n\r\n // Canonical URL\r\n if (config.site?.url) {\r\n const canonical = new URL(pathInfo.path, config.site.url).href;\r\n tags.push(`<link rel=\"canonical\" href=\"${canonical}\">`);\r\n }\r\n\r\n return tags.join('\\n ');\r\n}\r\n\r\n/**\r\n * Get output file path for a URL path\r\n */\r\nfunction getOutputPath(urlPath: string, outDir: string): string {\r\n // Normalize path\r\n let normalized = urlPath.replace(/^\\//, '').replace(/\\/$/, '');\r\n\r\n if (!normalized) {\r\n normalized = 'index';\r\n }\r\n\r\n // Add .html extension or /index.html for directories\r\n if (!normalized.endsWith('.html')) {\r\n normalized = path.join(normalized, 'index.html');\r\n }\r\n\r\n return path.join(outDir, normalized);\r\n}\r\n\r\n/**\r\n * Get SSR entry point\r\n * Returns virtual module ID if no custom entry exists\r\n */\r\nasync function getSSREntryPoint(config: SSGConfig, root: string): Promise<string> {\r\n // Check for custom entry points\r\n const detection = detectCustomEntries(root, config);\r\n \r\n if (!detection.useVirtualServer && detection.customServerPath) {\r\n return detection.customServerPath;\r\n }\r\n\r\n // Use virtual server entry - we need to write a temp file for the build\r\n // because Rollup input must be a real file path\r\n const virtualServerCode = generateServerEntry(config);\r\n const tempServerPath = path.join(root, '.ssg-temp-entry-server.tsx');\r\n fsSync.writeFileSync(tempServerPath, virtualServerCode, 'utf-8');\r\n \r\n return tempServerPath;\r\n}\r\n\r\n/**\r\n * Get client entry point\r\n * Returns virtual module ID if no custom entry exists\r\n */\r\nasync function getClientEntryPoint(config: SSGConfig, root: string): Promise<string> {\r\n const detection = detectCustomEntries(root, config);\r\n \r\n if (!detection.useVirtualClient && detection.customClientPath) {\r\n return detection.customClientPath;\r\n }\r\n\r\n // Use virtual client entry - write a temp file\r\n const virtualClientCode = generateClientEntry(config, detection);\r\n const tempClientPath = path.join(root, '.ssg-temp-entry-client.tsx');\r\n fsSync.writeFileSync(tempClientPath, virtualClientCode, 'utf-8');\r\n \r\n return tempClientPath;\r\n}\r\n\r\n/**\r\n * Clean up temporary entry files\r\n */\r\nasync function cleanupTempEntries(root: string): Promise<void> {\r\n const tempFiles = [\r\n path.join(root, '.ssg-temp-entry-server.tsx'),\r\n path.join(root, '.ssg-temp-entry-client.tsx'),\r\n ];\r\n \r\n for (const file of tempFiles) {\r\n try {\r\n await fs.unlink(file);\r\n } catch {\r\n // Ignore if file doesn't exist\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Get or generate HTML template\r\n */\r\nasync function getHtmlTemplate(config: SSGConfig, root: string, clientEntryPath: string): Promise<string> {\r\n const detection = detectCustomEntries(root, config);\r\n \r\n if (!detection.useVirtualHtml && detection.customHtmlPath) {\r\n // Read custom HTML and update the script src to point to the temp entry file\r\n let html = await fs.readFile(detection.customHtmlPath, 'utf-8');\r\n // Replace any virtual SSG client paths with the actual temp entry path\r\n // Use relative path from root for the script src\r\n const relativePath = './' + path.relative(root, clientEntryPath).replace(/\\\\/g, '/');\r\n html = html.replace(\r\n /<script([^>]*)\\s+src=[\"']?\\/@ssg\\/client\\.tsx[\"']?/g,\r\n `<script$1 src=\"${relativePath}\"`\r\n );\r\n return html;\r\n }\r\n\r\n // Generate virtual HTML template\r\n return generateProductionHtmlTemplate(config, clientEntryPath);\r\n}\r\n\r\n/**\r\n * Format bytes to human-readable string\r\n */\r\nfunction formatBytes(bytes: number): string {\r\n if (bytes < 1024) return `${bytes}B`;\r\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\r\n return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\r\n}\r\n\r\n/**\r\n * Escape HTML special characters\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#39;');\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"build-qmtK32gt.js","names":[],"sources":["../src/sitemap.ts","../src/build.ts"],"sourcesContent":["/**\r\n * Sitemap Generation\r\n *\r\n * Generates XML sitemaps for SSG sites following the sitemap protocol.\r\n * https://www.sitemaps.org/protocol.html\r\n */\r\n\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport type { SSGConfig, PageBuildResult } from './types';\r\n\r\n/**\r\n * Sitemap entry with optional metadata\r\n */\r\nexport interface SitemapEntry {\r\n /** URL path (relative to site base) */\r\n path: string;\r\n /** Last modification date */\r\n lastmod?: Date | string;\r\n /** Change frequency hint */\r\n changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';\r\n /** Priority relative to other pages (0.0 to 1.0) */\r\n priority?: number;\r\n}\r\n\r\n/**\r\n * Sitemap generation options\r\n */\r\nexport interface SitemapOptions {\r\n /** Include all built pages automatically */\r\n includePages?: boolean;\r\n /** Additional URLs to include */\r\n additionalUrls?: SitemapEntry[];\r\n /** URLs to exclude (glob patterns or exact matches) */\r\n exclude?: string[];\r\n /** Default change frequency */\r\n defaultChangefreq?: SitemapEntry['changefreq'];\r\n /** Default priority */\r\n defaultPriority?: number;\r\n}\r\n\r\n/**\r\n * Generate sitemap XML content\r\n */\r\nexport function generateSitemap(\r\n entries: SitemapEntry[],\r\n config: SSGConfig\r\n): string {\r\n const siteUrl = config.site?.url?.replace(/\\/$/, '') || '';\r\n const base = config.base?.replace(/\\/$/, '') || '';\r\n\r\n const urlEntries = entries.map((entry) => {\r\n const loc = `${siteUrl}${base}${entry.path}`;\r\n const lastmod = entry.lastmod\r\n ? typeof entry.lastmod === 'string'\r\n ? entry.lastmod\r\n : entry.lastmod.toISOString().split('T')[0]\r\n : undefined;\r\n\r\n return ` <url>\r\n <loc>${escapeXml(loc)}</loc>${lastmod ? `\r\n <lastmod>${lastmod}</lastmod>` : ''}${entry.changefreq ? `\r\n <changefreq>${entry.changefreq}</changefreq>` : ''}${entry.priority !== undefined ? `\r\n <priority>${entry.priority.toFixed(1)}</priority>` : ''}\r\n </url>`;\r\n });\r\n\r\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\r\n${urlEntries.join('\\n')}\r\n</urlset>`;\r\n}\r\n\r\n/**\r\n * Generate robots.txt content\r\n */\r\nexport function generateRobotsTxt(config: SSGConfig, sitemapPath = '/sitemap.xml'): string {\r\n const siteUrl = config.site?.url?.replace(/\\/$/, '') || '';\r\n const base = config.base?.replace(/\\/$/, '') || '';\r\n\r\n return `User-agent: *\r\nAllow: /\r\n\r\nSitemap: ${siteUrl}${base}${sitemapPath}\r\n`;\r\n}\r\n\r\n/**\r\n * Convert page build results to sitemap entries\r\n */\r\nexport function pagesToSitemapEntries(\r\n pages: PageBuildResult[],\r\n options: SitemapOptions = {}\r\n): SitemapEntry[] {\r\n const {\r\n exclude = [],\r\n defaultChangefreq = 'weekly',\r\n defaultPriority = 0.5,\r\n } = options;\r\n\r\n return pages\r\n .filter((page) => {\r\n // Filter out excluded paths\r\n for (const pattern of exclude) {\r\n if (pattern.includes('*')) {\r\n // Simple glob matching\r\n const regex = new RegExp(\r\n '^' + pattern.replace(/\\*/g, '.*').replace(/\\?/g, '.') + '$'\r\n );\r\n if (regex.test(page.path)) return false;\r\n } else if (page.path === pattern) {\r\n return false;\r\n }\r\n }\r\n return true;\r\n })\r\n .map((page) => {\r\n // Determine priority based on path depth\r\n const depth = page.path.split('/').filter(Boolean).length;\r\n let priority = defaultPriority;\r\n \r\n if (page.path === '/') {\r\n priority = 1.0; // Homepage highest priority\r\n } else if (depth === 1) {\r\n priority = 0.8; // Top-level pages\r\n } else if (depth === 2) {\r\n priority = 0.6; // Second-level pages\r\n }\r\n\r\n return {\r\n path: page.path,\r\n changefreq: defaultChangefreq,\r\n priority,\r\n };\r\n });\r\n}\r\n\r\n/**\r\n * Write sitemap and robots.txt to output directory\r\n */\r\nexport async function writeSitemap(\r\n pages: PageBuildResult[],\r\n config: SSGConfig,\r\n outDir: string,\r\n options: SitemapOptions = {}\r\n): Promise<{ sitemapPath: string; robotsPath: string }> {\r\n // Generate entries from pages\r\n const entries = pagesToSitemapEntries(pages, options);\r\n\r\n // Add additional URLs if provided\r\n if (options.additionalUrls) {\r\n entries.push(...options.additionalUrls);\r\n }\r\n\r\n // Generate sitemap XML\r\n const sitemapContent = generateSitemap(entries, config);\r\n const sitemapPath = path.join(outDir, 'sitemap.xml');\r\n await fs.writeFile(sitemapPath, sitemapContent, 'utf-8');\r\n\r\n // Generate robots.txt\r\n const robotsContent = generateRobotsTxt(config);\r\n const robotsPath = path.join(outDir, 'robots.txt');\r\n await fs.writeFile(robotsPath, robotsContent, 'utf-8');\r\n\r\n return { sitemapPath, robotsPath };\r\n}\r\n\r\n/**\r\n * Escape special XML characters\r\n */\r\nfunction escapeXml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&apos;');\r\n}\r\n","/**\r\n * SSG Build CLI\r\n *\r\n * Static site generation build process:\r\n * 1. Load configuration\r\n * 2. Scan routes and expand dynamic paths\r\n * 3. Build with Vite for production\r\n * 4. Render each route to static HTML\r\n * 5. Write output files\r\n * 6. Generate sitemap and robots.txt\r\n */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs/promises';\r\nimport fsSync from 'node:fs';\r\nimport { createRequire } from 'node:module';\r\nimport { pathToFileURL } from 'node:url';\r\nimport type { SSGConfig, BuildOptions, BuildResult, PageBuildResult, SSGRoute, PageModule } from './types';\r\nimport { loadConfig, resolveConfigPaths } from './config';\r\nimport { scanPages, isDynamicRoute, extractParams, expandDynamicRoute } from './routing/index';\r\nimport { discoverLayouts } from './layouts/index';\r\nimport { writeSitemap } from './sitemap';\r\nimport {\r\n detectCustomEntries,\r\n generateClientEntry,\r\n generateServerEntry,\r\n generateProductionHtmlTemplate,\r\n VIRTUAL_CLIENT_ID,\r\n VIRTUAL_SERVER_ID,\r\n} from './vite/virtual-entries';\r\n\r\n/**\r\n * Build static site\r\n */\r\nexport async function build(options: BuildOptions = {}): Promise<BuildResult> {\r\n const startTime = Date.now();\r\n const root = process.cwd();\r\n const warnings: string[] = [];\r\n const pages: PageBuildResult[] = [];\r\n\r\n console.log('\\n🚀 @sigx/ssg - Building static site...\\n');\r\n\r\n // Step 1: Load configuration\r\n console.log('📦 Loading configuration...');\r\n const config = await loadConfig(options.configPath);\r\n const resolvedConfig = resolveConfigPaths(config, root);\r\n\r\n // Step 2: Scan routes\r\n console.log('🔍 Scanning pages...');\r\n const routes = await scanPages(resolvedConfig, root);\r\n console.log(` Found ${routes.length} page(s)`);\r\n\r\n // Step 3: Discover layouts\r\n console.log('📐 Discovering layouts...');\r\n const layouts = await discoverLayouts(resolvedConfig, root);\r\n console.log(` Found ${layouts.length} layout(s)`);\r\n\r\n // Step 4: Detect entry points\r\n const entryDetection = detectCustomEntries(root, resolvedConfig);\r\n if (entryDetection.useVirtualClient || entryDetection.useVirtualServer) {\r\n console.log('📦 Using zero-config mode');\r\n if (entryDetection.useVirtualClient) console.log(' → Virtual client entry');\r\n if (entryDetection.useVirtualServer) console.log(' → Virtual server entry');\r\n if (entryDetection.useVirtualHtml) console.log(' → Virtual HTML template');\r\n }\r\n\r\n // Get entry points (may create temp files for virtual entries)\r\n const clientEntry = await getClientEntryPoint(resolvedConfig, root);\r\n const ssrEntry = await getSSREntryPoint(resolvedConfig, root);\r\n\r\n // Always write HTML template for the build (either generated or updated from custom)\r\n // This ensures Vite processes it and outputs index.html\r\n const htmlTemplatePath = path.join(root, 'index.html');\r\n let cleanupHtml = false;\r\n let originalHtmlContent: string | null = null;\r\n \r\n // Save original HTML content if we're modifying a custom one\r\n if (!entryDetection.useVirtualHtml && fsSync.existsSync(htmlTemplatePath)) {\r\n originalHtmlContent = fsSync.readFileSync(htmlTemplatePath, 'utf-8');\r\n }\r\n \r\n const htmlContent = await getHtmlTemplate(resolvedConfig, root, clientEntry);\r\n fsSync.writeFileSync(htmlTemplatePath, htmlContent, 'utf-8');\r\n cleanupHtml = entryDetection.useVirtualHtml; // Only cleanup (delete) if we generated it\r\n\r\n // Step 5: Build with Vite\r\n console.log('🔨 Building with Vite...');\r\n const vite = await import('vite');\r\n\r\n try {\r\n // Build client bundle\r\n // Note: We don't empty the outDir since vite build may have already run\r\n // Always use HTML as input so Vite outputs index.html properly\r\n const clientInput = htmlTemplatePath;\r\n \r\n await vite.build({\r\n root,\r\n mode: 'production',\r\n build: {\r\n outDir: resolvedConfig.outDir,\r\n emptyOutDir: false,\r\n ssrManifest: true,\r\n rollupOptions: {\r\n input: clientInput,\r\n },\r\n },\r\n logLevel: options.verbose ? 'info' : 'warn',\r\n });\r\n\r\n // Build SSR bundle\r\n const ssrOutDir = path.join(resolvedConfig.outDir!, '.ssg');\r\n await vite.build({\r\n root,\r\n mode: 'production',\r\n build: {\r\n outDir: ssrOutDir,\r\n ssr: true,\r\n rollupOptions: {\r\n input: ssrEntry,\r\n },\r\n },\r\n logLevel: options.verbose ? 'info' : 'warn',\r\n });\r\n\r\n // Step 6: Collect all paths to render\r\n console.log('📝 Collecting paths to render...');\r\n const pathsToRender = await collectPaths(routes, root, warnings);\r\n console.log(` ${pathsToRender.length} path(s) to render`);\r\n\r\n // Pre-create all output directories to avoid mkdir contention during parallel rendering\r\n const outputDirs = new Set<string>();\r\n for (const pathInfo of pathsToRender) {\r\n const outputPath = getOutputPath(pathInfo.path, resolvedConfig.outDir!);\r\n outputDirs.add(path.dirname(outputPath));\r\n }\r\n await Promise.all(\r\n Array.from(outputDirs).map(dir => fs.mkdir(dir, { recursive: true }))\r\n );\r\n\r\n // Step 7: Render each path to HTML\r\n console.log('🎨 Rendering pages...');\r\n\r\n // Determine the SSR entry output name (based on input file name)\r\n const ssrEntryBasename = path.basename(ssrEntry, path.extname(ssrEntry));\r\n const ssrEntryName = ssrEntryBasename + '.js';\r\n\r\n // Pre-load the SSR module once for all pages\r\n const entryPath = path.join(ssrOutDir, ssrEntryName);\r\n const entryModule = await import(pathToFileURL(entryPath).href);\r\n\r\n // Load HTML template once\r\n const templatePath = path.join(resolvedConfig.outDir!, 'index.html');\r\n const template = await fs.readFile(templatePath, 'utf-8');\r\n\r\n // Parallel rendering configuration - higher concurrency since rendering is CPU-bound\r\n const CONCURRENCY = options.concurrency ?? 20; // Number of pages to render in parallel\r\n const verbose = options.verbose ?? false;\r\n\r\n interface RenderResult {\r\n pathInfo: PathToRender;\r\n html: string;\r\n outputPath: string;\r\n renderTime: number;\r\n }\r\n\r\n // Render a single page (CPU-bound, no I/O)\r\n async function renderPage(pathInfo: PathToRender): Promise<RenderResult | null> {\r\n const renderStart = Date.now();\r\n\r\n try {\r\n // Render the app\r\n const appHtml = await entryModule.render(pathInfo.path, {\r\n params: pathInfo.params,\r\n props: pathInfo.props,\r\n });\r\n\r\n // Inject into template\r\n let html = template.replace('<!--app-html-->', appHtml);\r\n const headTags = generateHeadTags(pathInfo, resolvedConfig);\r\n html = html.replace('<!--head-tags-->', headTags);\r\n\r\n const outputPath = getOutputPath(pathInfo.path, resolvedConfig.outDir!);\r\n const renderTime = Date.now() - renderStart;\r\n\r\n return { pathInfo, html, outputPath, renderTime };\r\n } catch (err) {\r\n const errorMessage = err instanceof Error ? err.message : String(err);\r\n console.error(` ❌ ${pathInfo.path}: ${errorMessage}`);\r\n warnings.push(`Failed to render ${pathInfo.path}: ${errorMessage}`);\r\n return null;\r\n }\r\n }\r\n\r\n // Render all pages in parallel batches (CPU-bound, no I/O)\r\n console.log(' Phase 1: Rendering...');\r\n const renderPhaseStart = Date.now();\r\n const renderResults: RenderResult[] = [];\r\n \r\n for (let i = 0; i < pathsToRender.length; i += CONCURRENCY) {\r\n const batch = pathsToRender.slice(i, i + CONCURRENCY);\r\n const results = await Promise.all(batch.map(renderPage));\r\n \r\n for (const result of results) {\r\n if (result) {\r\n renderResults.push(result);\r\n }\r\n }\r\n }\r\n const renderPhaseDuration = Date.now() - renderPhaseStart;\r\n console.log(` Phase 1 complete: ${renderResults.length} pages in ${renderPhaseDuration}ms (${Math.round(renderPhaseDuration / renderResults.length)}ms avg)`);\r\n\r\n // Write all files in parallel with limited concurrency\r\n console.log(' Phase 2: Writing files...');\r\n const writePhaseStart = Date.now();\r\n const WRITE_CONCURRENCY = 10; // Limit parallel writes to reduce I/O contention\r\n \r\n for (let i = 0; i < renderResults.length; i += WRITE_CONCURRENCY) {\r\n const batch = renderResults.slice(i, i + WRITE_CONCURRENCY);\r\n await Promise.all(batch.map(async (result) => {\r\n await fs.writeFile(result.outputPath, result.html, 'utf-8');\r\n const size = Buffer.byteLength(result.html, 'utf-8');\r\n\r\n pages.push({\r\n path: result.pathInfo.path,\r\n file: result.outputPath,\r\n time: result.renderTime,\r\n size,\r\n });\r\n \r\n if (verbose) {\r\n console.log(` ✓ ${result.pathInfo.path} (${result.renderTime}ms, ${formatBytes(size)})`);\r\n }\r\n }));\r\n }\r\n const writePhaseDuration = Date.now() - writePhaseStart;\r\n console.log(` Phase 2 complete: ${renderResults.length} files in ${writePhaseDuration}ms`);\r\n \r\n // Summary (non-verbose)\r\n if (!verbose) {\r\n console.log(` ✓ Rendered ${renderResults.length} pages`);\r\n }\r\n\r\n // Step 8: Clean up SSR build\r\n await fs.rm(ssrOutDir, { recursive: true, force: true });\r\n\r\n // Step 9: Generate sitemap and robots.txt\r\n if (pages.length > 0) {\r\n console.log('🗺️ Generating sitemap...');\r\n await writeSitemap(pages, resolvedConfig, resolvedConfig.outDir!);\r\n console.log(' ✓ sitemap.xml');\r\n console.log(' ✓ robots.txt');\r\n }\r\n\r\n } finally {\r\n // Clean up temporary entry files\r\n await cleanupTempEntries(root);\r\n \r\n // Clean up or restore HTML template\r\n if (cleanupHtml) {\r\n // We generated a virtual HTML, remove it\r\n try {\r\n await fs.unlink(htmlTemplatePath);\r\n } catch {\r\n // Ignore\r\n }\r\n } else if (originalHtmlContent !== null) {\r\n // We modified a custom HTML, restore the original\r\n try {\r\n await fs.writeFile(htmlTemplatePath, originalHtmlContent, 'utf-8');\r\n } catch {\r\n // Ignore\r\n }\r\n }\r\n }\r\n\r\n // Done\r\n const totalTime = Date.now() - startTime;\r\n\r\n console.log(`\\n✅ Built ${pages.length} page(s) in ${totalTime}ms`);\r\n\r\n if (warnings.length > 0) {\r\n console.log(`\\n⚠️ ${warnings.length} warning(s):`);\r\n for (const warning of warnings) {\r\n console.log(` - ${warning}`);\r\n }\r\n }\r\n\r\n console.log(`\\n📁 Output: ${resolvedConfig.outDir}\\n`);\r\n\r\n return {\r\n pages,\r\n totalTime,\r\n warnings,\r\n };\r\n}\r\n\r\n/**\r\n * Path information for rendering\r\n */\r\ninterface PathToRender {\r\n path: string;\r\n route: SSGRoute;\r\n params: Record<string, string>;\r\n props?: Record<string, unknown>;\r\n}\r\n\r\n/**\r\n * Collect all paths to render, expanding dynamic routes\r\n */\r\nasync function collectPaths(\r\n routes: SSGRoute[],\r\n root: string,\r\n warnings: string[]\r\n): Promise<PathToRender[]> {\r\n const paths: PathToRender[] = [];\r\n\r\n for (const route of routes) {\r\n if (isDynamicRoute(route)) {\r\n // Load module and call getStaticPaths\r\n try {\r\n const moduleUrl = pathToFileURL(route.file).href;\r\n const pageModule = (await import(moduleUrl)) as PageModule;\r\n\r\n if (!pageModule.getStaticPaths) {\r\n const params = extractParams(route.path).join(', ');\r\n console.warn(\r\n `\\n⚠️ SSG102: Dynamic route missing getStaticPaths()\\n` +\r\n ` 📁 ${route.file}\\n` +\r\n ` Route: ${route.path} (params: ${params})\\n` +\r\n ` 💡 Export getStaticPaths() to generate static pages:\\n\\n` +\r\n ` export async function getStaticPaths() {\\n` +\r\n ` return [{ params: { ${params.split(', ')[0]}: 'value' } }];\\n` +\r\n ` }\\n`\r\n );\r\n warnings.push(\r\n `Route ${route.path} has dynamic segments [${params}] but no getStaticPaths() export. Skipping.`\r\n );\r\n continue;\r\n }\r\n\r\n const staticPaths = await pageModule.getStaticPaths();\r\n\r\n for (const staticPath of staticPaths) {\r\n const expandedPaths = expandDynamicRoute(route, [staticPath]);\r\n for (const expandedPath of expandedPaths) {\r\n paths.push({\r\n path: expandedPath,\r\n route,\r\n params: staticPath.params,\r\n props: staticPath.props,\r\n });\r\n }\r\n }\r\n } catch (err) {\r\n warnings.push(`Failed to load ${route.file}: ${err}`);\r\n }\r\n } else {\r\n paths.push({\r\n path: route.path,\r\n route,\r\n params: {},\r\n });\r\n }\r\n }\r\n\r\n return paths;\r\n}\r\n\r\n/**\r\n * Render a page to HTML\r\n */\r\nasync function renderPage(\r\n pathInfo: PathToRender,\r\n config: SSGConfig,\r\n ssrOutDir: string,\r\n ssrEntryName: string\r\n): Promise<string> {\r\n // Load the SSR entry module\r\n // This should export a render function that returns HTML string\r\n const entryPath = path.join(ssrOutDir, ssrEntryName);\r\n const entryModule = await import(pathToFileURL(entryPath).href);\r\n\r\n // Render the app - the entry module's render function handles SSR\r\n const appHtml = await entryModule.render(pathInfo.path, {\r\n params: pathInfo.params,\r\n props: pathInfo.props,\r\n });\r\n\r\n // Load HTML template\r\n const templatePath = path.join(config.outDir!, 'index.html');\r\n let template = await fs.readFile(templatePath, 'utf-8');\r\n\r\n // Inject app HTML\r\n template = template.replace('<!--app-html-->', appHtml);\r\n\r\n // Inject head tags if any\r\n const headTags = generateHeadTags(pathInfo, config);\r\n template = template.replace('<!--head-tags-->', headTags);\r\n\r\n return template;\r\n}\r\n\r\n/**\r\n * Generate head tags for a page\r\n */\r\nfunction generateHeadTags(pathInfo: PathToRender, config: SSGConfig): string {\r\n const tags: string[] = [];\r\n const meta = pathInfo.route.meta || {};\r\n\r\n // Title\r\n const title = meta.title || config.site?.title;\r\n if (title) {\r\n tags.push(`<title>${escapeHtml(title)}</title>`);\r\n }\r\n\r\n // Description\r\n const description = meta.description || config.site?.description;\r\n if (description) {\r\n tags.push(`<meta name=\"description\" content=\"${escapeHtml(description)}\">`);\r\n }\r\n\r\n // Canonical URL\r\n if (config.site?.url) {\r\n const canonical = new URL(pathInfo.path, config.site.url).href;\r\n tags.push(`<link rel=\"canonical\" href=\"${canonical}\">`);\r\n }\r\n\r\n return tags.join('\\n ');\r\n}\r\n\r\n/**\r\n * Get output file path for a URL path\r\n */\r\nfunction getOutputPath(urlPath: string, outDir: string): string {\r\n // Normalize path\r\n let normalized = urlPath.replace(/^\\//, '').replace(/\\/$/, '');\r\n\r\n if (!normalized) {\r\n normalized = 'index';\r\n }\r\n\r\n // Add .html extension or /index.html for directories\r\n if (!normalized.endsWith('.html')) {\r\n normalized = path.join(normalized, 'index.html');\r\n }\r\n\r\n return path.join(outDir, normalized);\r\n}\r\n\r\n/**\r\n * Get SSR entry point\r\n * Returns virtual module ID if no custom entry exists\r\n */\r\nasync function getSSREntryPoint(config: SSGConfig, root: string): Promise<string> {\r\n // Check for custom entry points\r\n const detection = detectCustomEntries(root, config);\r\n \r\n if (!detection.useVirtualServer && detection.customServerPath) {\r\n return detection.customServerPath;\r\n }\r\n\r\n // Use virtual server entry - we need to write a temp file for the build\r\n // because Rollup input must be a real file path\r\n const virtualServerCode = generateServerEntry(config);\r\n const tempServerPath = path.join(root, '.ssg-temp-entry-server.tsx');\r\n fsSync.writeFileSync(tempServerPath, virtualServerCode, 'utf-8');\r\n \r\n return tempServerPath;\r\n}\r\n\r\n/**\r\n * Get client entry point\r\n * Returns virtual module ID if no custom entry exists\r\n */\r\nasync function getClientEntryPoint(config: SSGConfig, root: string): Promise<string> {\r\n const detection = detectCustomEntries(root, config);\r\n \r\n if (!detection.useVirtualClient && detection.customClientPath) {\r\n return detection.customClientPath;\r\n }\r\n\r\n // Use virtual client entry - write a temp file\r\n const virtualClientCode = generateClientEntry(config, detection);\r\n const tempClientPath = path.join(root, '.ssg-temp-entry-client.tsx');\r\n fsSync.writeFileSync(tempClientPath, virtualClientCode, 'utf-8');\r\n \r\n return tempClientPath;\r\n}\r\n\r\n/**\r\n * Clean up temporary entry files\r\n */\r\nasync function cleanupTempEntries(root: string): Promise<void> {\r\n const tempFiles = [\r\n path.join(root, '.ssg-temp-entry-server.tsx'),\r\n path.join(root, '.ssg-temp-entry-client.tsx'),\r\n ];\r\n \r\n for (const file of tempFiles) {\r\n try {\r\n await fs.unlink(file);\r\n } catch {\r\n // Ignore if file doesn't exist\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Get or generate HTML template\r\n */\r\nasync function getHtmlTemplate(config: SSGConfig, root: string, clientEntryPath: string): Promise<string> {\r\n const detection = detectCustomEntries(root, config);\r\n \r\n if (!detection.useVirtualHtml && detection.customHtmlPath) {\r\n // Read custom HTML and update the script src to point to the temp entry file\r\n let html = await fs.readFile(detection.customHtmlPath, 'utf-8');\r\n // Replace any virtual SSG client paths with the actual temp entry path\r\n // Use relative path from root for the script src\r\n const relativePath = './' + path.relative(root, clientEntryPath).replace(/\\\\/g, '/');\r\n html = html.replace(\r\n /<script([^>]*)\\s+src=[\"']?\\/@ssg\\/client\\.tsx[\"']?/g,\r\n `<script$1 src=\"${relativePath}\"`\r\n );\r\n return html;\r\n }\r\n\r\n // Generate virtual HTML template\r\n return generateProductionHtmlTemplate(config, clientEntryPath);\r\n}\r\n\r\n/**\r\n * Format bytes to human-readable string\r\n */\r\nfunction formatBytes(bytes: number): string {\r\n if (bytes < 1024) return `${bytes}B`;\r\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;\r\n return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;\r\n}\r\n\r\n/**\r\n * Escape HTML special characters\r\n */\r\nfunction escapeHtml(str: string): string {\r\n return str\r\n .replace(/&/g, '&amp;')\r\n .replace(/</g, '&lt;')\r\n .replace(/>/g, '&gt;')\r\n .replace(/\"/g, '&quot;')\r\n .replace(/'/g, '&#39;');\r\n}\r\n"],"mappings":"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"}
package/dist/build.js CHANGED
@@ -1,3 +1,2 @@
1
- import "./virtual-entries-sVkqKMAM.js";
2
- import { t as build } from "./build-CfOi93wa.js";
1
+ import { t as build } from "./build-qmtK32gt.js";
3
2
  export { build };
package/dist/client.js CHANGED
@@ -1,10 +1,17 @@
1
1
  import { ssrClientPlugin } from "@sigx/server-renderer/client";
2
+ //#region src/client.ts
3
+ /**
4
+ * Prefetch a route's assets
5
+ */
2
6
  function prefetch(path) {
3
7
  const link = document.createElement("link");
4
8
  link.rel = "prefetch";
5
9
  link.href = path;
6
10
  document.head.appendChild(link);
7
11
  }
12
+ /**
13
+ * Setup prefetch on link hover
14
+ */
8
15
  function setupPrefetch(options = {}) {
9
16
  const { delay = 100 } = options;
10
17
  const prefetched = /* @__PURE__ */ new Set();
@@ -21,9 +28,15 @@ function setupPrefetch(options = {}) {
21
28
  anchor.addEventListener("mouseleave", () => clearTimeout(timeoutId), { once: true });
22
29
  });
23
30
  }
31
+ /**
32
+ * Check if the page was statically generated
33
+ */
24
34
  function isStaticPage() {
25
35
  return !document.documentElement.hasAttribute("data-ssr");
26
36
  }
37
+ /**
38
+ * Get initial state embedded in the page
39
+ */
27
40
  function getInitialState() {
28
41
  const script = document.getElementById("__SSG_STATE__");
29
42
  if (!script) return null;
@@ -33,6 +46,7 @@ function getInitialState() {
33
46
  return null;
34
47
  }
35
48
  }
49
+ //#endregion
36
50
  export { getInitialState, isStaticPage, prefetch, setupPrefetch, ssrClientPlugin };
37
51
 
38
52
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\r\n * SSG Client Runtime\r\n *\r\n * Client-side utilities for SSG sites:\r\n * - Hydration plugin\r\n * - Route prefetching\r\n * - Client-side navigation\r\n */\r\n\r\nexport { ssrClientPlugin } from '@sigx/server-renderer/client';\r\n\r\n/**\r\n * Prefetch a route's assets\r\n */\r\nexport function prefetch(path: string): void {\r\n // Create a link element for prefetching\r\n const link = document.createElement('link');\r\n link.rel = 'prefetch';\r\n link.href = path;\r\n document.head.appendChild(link);\r\n}\r\n\r\n/**\r\n * Setup prefetch on link hover\r\n */\r\nexport function setupPrefetch(options: { delay?: number } = {}): void {\r\n const { delay = 100 } = options;\r\n const prefetched = new Set<string>();\r\n\r\n document.addEventListener('mouseover', (event) => {\r\n const target = event.target as HTMLElement;\r\n const anchor = target.closest('a');\r\n\r\n if (!anchor) return;\r\n\r\n const href = anchor.getAttribute('href');\r\n if (!href || href.startsWith('http') || href.startsWith('#')) return;\r\n if (prefetched.has(href)) return;\r\n\r\n // Delay prefetch slightly to avoid unnecessary requests\r\n const timeoutId = setTimeout(() => {\r\n prefetched.add(href);\r\n prefetch(href);\r\n }, delay);\r\n\r\n anchor.addEventListener(\r\n 'mouseleave',\r\n () => clearTimeout(timeoutId),\r\n { once: true }\r\n );\r\n });\r\n}\r\n\r\n/**\r\n * Check if the page was statically generated\r\n */\r\nexport function isStaticPage(): boolean {\r\n return !document.documentElement.hasAttribute('data-ssr');\r\n}\r\n\r\n/**\r\n * Get initial state embedded in the page\r\n */\r\nexport function getInitialState<T = Record<string, unknown>>(): T | null {\r\n const script = document.getElementById('__SSG_STATE__');\r\n if (!script) return null;\r\n\r\n try {\r\n return JSON.parse(script.textContent || '');\r\n } catch {\r\n return null;\r\n }\r\n}\r\n"],"mappings":";AAcA,SAAgB,SAAS,MAAoB;CAEzC,MAAM,OAAO,SAAS,cAAc,OAAO;AAC3C,MAAK,MAAM;AACX,MAAK,OAAO;AACZ,UAAS,KAAK,YAAY,KAAK;;AAMnC,SAAgB,cAAc,UAA8B,EAAE,EAAQ;CAClE,MAAM,EAAE,QAAQ,QAAQ;CACxB,MAAM,6BAAa,IAAI,KAAa;AAEpC,UAAS,iBAAiB,cAAc,UAAU;EAE9C,MAAM,SADS,MAAM,OACC,QAAQ,IAAI;AAElC,MAAI,CAAC,OAAQ;EAEb,MAAM,OAAO,OAAO,aAAa,OAAO;AACxC,MAAI,CAAC,QAAQ,KAAK,WAAW,OAAO,IAAI,KAAK,WAAW,IAAI,CAAE;AAC9D,MAAI,WAAW,IAAI,KAAK,CAAE;EAG1B,MAAM,YAAY,iBAAiB;AAC/B,cAAW,IAAI,KAAK;AACpB,YAAS,KAAK;KACf,MAAM;AAET,SAAO,iBACH,oBACM,aAAa,UAAU,EAC7B,EAAE,MAAM,MAAM,CACjB;GACH;;AAMN,SAAgB,eAAwB;AACpC,QAAO,CAAC,SAAS,gBAAgB,aAAa,WAAW;;AAM7D,SAAgB,kBAAyD;CACrE,MAAM,SAAS,SAAS,eAAe,gBAAgB;AACvD,KAAI,CAAC,OAAQ,QAAO;AAEpB,KAAI;AACA,SAAO,KAAK,MAAM,OAAO,eAAe,GAAG;SACvC;AACJ,SAAO"}
1
+ {"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\r\n * SSG Client Runtime\r\n *\r\n * Client-side utilities for SSG sites:\r\n * - Hydration plugin\r\n * - Route prefetching\r\n * - Client-side navigation\r\n */\r\n\r\nexport { ssrClientPlugin } from '@sigx/server-renderer/client';\r\n\r\n/**\r\n * Prefetch a route's assets\r\n */\r\nexport function prefetch(path: string): void {\r\n // Create a link element for prefetching\r\n const link = document.createElement('link');\r\n link.rel = 'prefetch';\r\n link.href = path;\r\n document.head.appendChild(link);\r\n}\r\n\r\n/**\r\n * Setup prefetch on link hover\r\n */\r\nexport function setupPrefetch(options: { delay?: number } = {}): void {\r\n const { delay = 100 } = options;\r\n const prefetched = new Set<string>();\r\n\r\n document.addEventListener('mouseover', (event) => {\r\n const target = event.target as HTMLElement;\r\n const anchor = target.closest('a');\r\n\r\n if (!anchor) return;\r\n\r\n const href = anchor.getAttribute('href');\r\n if (!href || href.startsWith('http') || href.startsWith('#')) return;\r\n if (prefetched.has(href)) return;\r\n\r\n // Delay prefetch slightly to avoid unnecessary requests\r\n const timeoutId = setTimeout(() => {\r\n prefetched.add(href);\r\n prefetch(href);\r\n }, delay);\r\n\r\n anchor.addEventListener(\r\n 'mouseleave',\r\n () => clearTimeout(timeoutId),\r\n { once: true }\r\n );\r\n });\r\n}\r\n\r\n/**\r\n * Check if the page was statically generated\r\n */\r\nexport function isStaticPage(): boolean {\r\n return !document.documentElement.hasAttribute('data-ssr');\r\n}\r\n\r\n/**\r\n * Get initial state embedded in the page\r\n */\r\nexport function getInitialState<T = Record<string, unknown>>(): T | null {\r\n const script = document.getElementById('__SSG_STATE__');\r\n if (!script) return null;\r\n\r\n try {\r\n return JSON.parse(script.textContent || '');\r\n } catch {\r\n return null;\r\n }\r\n}\r\n"],"mappings":";;;;;AAcA,SAAgB,SAAS,MAAoB;CAEzC,MAAM,OAAO,SAAS,cAAc,OAAO;AAC3C,MAAK,MAAM;AACX,MAAK,OAAO;AACZ,UAAS,KAAK,YAAY,KAAK;;;;;AAMnC,SAAgB,cAAc,UAA8B,EAAE,EAAQ;CAClE,MAAM,EAAE,QAAQ,QAAQ;CACxB,MAAM,6BAAa,IAAI,KAAa;AAEpC,UAAS,iBAAiB,cAAc,UAAU;EAE9C,MAAM,SADS,MAAM,OACC,QAAQ,IAAI;AAElC,MAAI,CAAC,OAAQ;EAEb,MAAM,OAAO,OAAO,aAAa,OAAO;AACxC,MAAI,CAAC,QAAQ,KAAK,WAAW,OAAO,IAAI,KAAK,WAAW,IAAI,CAAE;AAC9D,MAAI,WAAW,IAAI,KAAK,CAAE;EAG1B,MAAM,YAAY,iBAAiB;AAC/B,cAAW,IAAI,KAAK;AACpB,YAAS,KAAK;KACf,MAAM;AAET,SAAO,iBACH,oBACM,aAAa,UAAU,EAC7B,EAAE,MAAM,MAAM,CACjB;GACH;;;;;AAMN,SAAgB,eAAwB;AACpC,QAAO,CAAC,SAAS,gBAAgB,aAAa,WAAW;;;;;AAM7D,SAAgB,kBAAyD;CACrE,MAAM,SAAS,SAAS,eAAe,gBAAgB;AACvD,KAAI,CAAC,OAAQ,QAAO;AAEpB,KAAI;AACA,SAAO,KAAK,MAAM,OAAO,eAAe,GAAG;SACvC;AACJ,SAAO"}
package/dist/dev.js CHANGED
@@ -1,7 +1,17 @@
1
- import { k as loadConfig } from "./virtual-entries-sVkqKMAM.js";
2
- import { n as ssgPlugin } from "./plugin-WQzgfl9i.js";
1
+ import { k as loadConfig } from "./virtual-entries-TuNN2It1.js";
2
+ import { t as ssgPlugin } from "./plugin-Bik0HMne.js";
3
3
  import path from "node:path";
4
4
  import fsSync from "node:fs";
5
+ //#region src/dev.ts
6
+ /**
7
+ * SSG Dev Server
8
+ *
9
+ * Starts a Vite development server with SSG plugins pre-configured.
10
+ * Provides a unified `ssg dev` command for zero-config development.
11
+ */
12
+ /**
13
+ * Start the SSG development server
14
+ */
5
15
  async function dev(options = {}) {
6
16
  const root = process.cwd();
7
17
  console.log("\n🚀 @sigx/ssg - Starting development server...\n");
@@ -53,6 +63,9 @@ async function dev(options = {}) {
53
63
  server.printUrls();
54
64
  }
55
65
  }
66
+ /**
67
+ * Preview the production build
68
+ */
56
69
  async function preview(options = {}) {
57
70
  const root = process.cwd();
58
71
  console.log("\n👀 @sigx/ssg - Preview server...\n");
@@ -66,6 +79,7 @@ async function preview(options = {}) {
66
79
  })).printUrls();
67
80
  console.log("\n");
68
81
  }
82
+ //#endregion
69
83
  export { dev, preview };
70
84
 
71
85
  //# sourceMappingURL=dev.js.map
package/dist/dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","names":[],"sources":["../src/dev.ts"],"sourcesContent":["/**\r\n * SSG Dev Server\r\n *\r\n * Starts a Vite development server with SSG plugins pre-configured.\r\n * Provides a unified `ssg dev` command for zero-config development.\r\n */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport type { SSGConfig } from './types';\r\nimport { loadConfig } from './config';\r\nimport { ssgPlugin } from './vite/plugin';\r\n\r\n/**\r\n * Dev server options\r\n */\r\nexport interface DevOptions {\r\n /**\r\n * Path to ssg.config.ts\r\n */\r\n configPath?: string;\r\n\r\n /**\r\n * Port to run dev server on\r\n * @default 5173\r\n */\r\n port?: number;\r\n\r\n /**\r\n * Host to bind to\r\n * @default 'localhost'\r\n */\r\n host?: string | boolean;\r\n\r\n /**\r\n * Open browser automatically\r\n * @default false\r\n */\r\n open?: boolean;\r\n\r\n /**\r\n * Enable verbose logging\r\n */\r\n verbose?: boolean;\r\n}\r\n\r\n/**\r\n * Start the SSG development server\r\n */\r\nexport async function dev(options: DevOptions = {}): Promise<void> {\r\n const root = process.cwd();\r\n\r\n console.log('\\n🚀 @sigx/ssg - Starting development server...\\n');\r\n\r\n // Load SSG config\r\n const ssgConfig = await loadConfig(options.configPath);\r\n\r\n // Check if user has a vite.config.ts\r\n const hasViteConfig = fs.existsSync(path.join(root, 'vite.config.ts')) ||\r\n fs.existsSync(path.join(root, 'vite.config.js')) ||\r\n fs.existsSync(path.join(root, 'vite.config.mjs'));\r\n\r\n // Import Vite\r\n const vite = await import('vite');\r\n\r\n if (hasViteConfig) {\r\n // User has their own vite.config - use it directly\r\n // They should have ssgPlugin() configured already\r\n console.log('📦 Using existing vite.config\\n');\r\n\r\n const server = await vite.createServer({\r\n root,\r\n server: {\r\n port: options.port,\r\n host: options.host,\r\n open: options.open,\r\n },\r\n });\r\n\r\n await server.listen();\r\n server.printUrls();\r\n } else {\r\n // Zero-config mode - configure everything automatically\r\n console.log('📦 Zero-config mode enabled\\n');\r\n\r\n // Try to import @sigx/vite plugin\r\n let sigxPlugin: ((...args: any[]) => any) | undefined;\r\n try {\r\n const sigxVite = await import('@sigx/vite');\r\n sigxPlugin = sigxVite.sigxPlugin;\r\n } catch {\r\n console.warn('⚠️ @sigx/vite not found, JSX transform may not work');\r\n console.warn(' Install with: npm install @sigx/vite\\n');\r\n }\r\n\r\n // Build plugins array\r\n const plugins: any[] = [];\r\n\r\n // Add Tailwind CSS if available\r\n try {\r\n // @ts-expect-error - Optional dependency\r\n const tailwind = await import('@tailwindcss/vite');\r\n plugins.push(tailwind.default());\r\n } catch {\r\n // Tailwind not installed, skip\r\n }\r\n\r\n // Add SignalX plugin if available\r\n if (sigxPlugin) {\r\n plugins.push(sigxPlugin());\r\n }\r\n\r\n // Add SSG plugin\r\n plugins.push(...ssgPlugin({ configPath: options.configPath }));\r\n\r\n const server = await vite.createServer({\r\n root,\r\n plugins,\r\n // Vite 8 uses oxc instead of esbuild for JSX transforms\r\n oxc: {\r\n jsx: {\r\n runtime: 'automatic',\r\n importSource: 'sigx',\r\n }\r\n },\r\n server: {\r\n port: options.port ?? 5173,\r\n host: options.host,\r\n open: options.open,\r\n },\r\n });\r\n\r\n await server.listen();\r\n server.printUrls();\r\n }\r\n}\r\n\r\n/**\r\n * Preview the production build\r\n */\r\nexport async function preview(options: DevOptions = {}): Promise<void> {\r\n const root = process.cwd();\r\n\r\n console.log('\\n👀 @sigx/ssg - Preview server...\\n');\r\n\r\n const vite = await import('vite');\r\n\r\n const server = await vite.preview({\r\n root,\r\n preview: {\r\n port: options.port ?? 4173,\r\n host: options.host,\r\n open: options.open,\r\n },\r\n });\r\n\r\n server.printUrls();\r\n console.log('\\n');\r\n}\r\n"],"mappings":";;;;AAiDA,eAAsB,IAAI,UAAsB,EAAE,EAAiB;CAC/D,MAAM,OAAO,QAAQ,KAAK;AAE1B,SAAQ,IAAI,oDAAoD;AAG9C,OAAM,WAAW,QAAQ,WAAW;CAGtD,MAAM,gBAAgB,OAAG,WAAW,KAAK,KAAK,MAAM,iBAAiB,CAAC,IAClE,OAAG,WAAW,KAAK,KAAK,MAAM,iBAAiB,CAAC,IAChD,OAAG,WAAW,KAAK,KAAK,MAAM,kBAAkB,CAAC;CAGrD,MAAM,OAAO,MAAM,OAAO;AAE1B,KAAI,eAAe;AAGf,UAAQ,IAAI,kCAAkC;EAE9C,MAAM,SAAS,MAAM,KAAK,aAAa;GACnC;GACA,QAAQ;IACJ,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,MAAM,QAAQ;IACjB;GACJ,CAAC;AAEF,QAAM,OAAO,QAAQ;AACrB,SAAO,WAAW;QACf;AAEH,UAAQ,IAAI,gCAAgC;EAG5C,IAAI;AACJ,MAAI;AAEA,iBADiB,MAAM,OAAO,eACR;UAClB;AACJ,WAAQ,KAAK,uDAAuD;AACpE,WAAQ,KAAK,4CAA4C;;EAI7D,MAAM,UAAiB,EAAE;AAGzB,MAAI;GAEA,MAAM,WAAW,MAAM,OAAO;AAC9B,WAAQ,KAAK,SAAS,SAAS,CAAC;UAC5B;AAKR,MAAI,WACA,SAAQ,KAAK,YAAY,CAAC;AAI9B,UAAQ,KAAK,GAAG,UAAU,EAAE,YAAY,QAAQ,YAAY,CAAC,CAAC;EAE9D,MAAM,SAAS,MAAM,KAAK,aAAa;GACnC;GACA;GAEA,KAAK,EACD,KAAK;IACD,SAAS;IACT,cAAc;IACjB,EACJ;GACD,QAAQ;IACJ,MAAM,QAAQ,QAAQ;IACtB,MAAM,QAAQ;IACd,MAAM,QAAQ;IACjB;GACJ,CAAC;AAEF,QAAM,OAAO,QAAQ;AACrB,SAAO,WAAW;;;AAO1B,eAAsB,QAAQ,UAAsB,EAAE,EAAiB;CACnE,MAAM,OAAO,QAAQ,KAAK;AAE1B,SAAQ,IAAI,uCAAuC;AAanD,EATe,OAFF,MAAM,OAAO,SAEA,QAAQ;EAC9B;EACA,SAAS;GACL,MAAM,QAAQ,QAAQ;GACtB,MAAM,QAAQ;GACd,MAAM,QAAQ;GACjB;EACJ,CAAC,EAEK,WAAW;AAClB,SAAQ,IAAI,KAAK"}
1
+ {"version":3,"file":"dev.js","names":[],"sources":["../src/dev.ts"],"sourcesContent":["/**\r\n * SSG Dev Server\r\n *\r\n * Starts a Vite development server with SSG plugins pre-configured.\r\n * Provides a unified `ssg dev` command for zero-config development.\r\n */\r\n\r\nimport path from 'node:path';\r\nimport fs from 'node:fs';\r\nimport type { SSGConfig } from './types';\r\nimport { loadConfig } from './config';\r\nimport { ssgPlugin } from './vite/plugin';\r\n\r\n/**\r\n * Dev server options\r\n */\r\nexport interface DevOptions {\r\n /**\r\n * Path to ssg.config.ts\r\n */\r\n configPath?: string;\r\n\r\n /**\r\n * Port to run dev server on\r\n * @default 5173\r\n */\r\n port?: number;\r\n\r\n /**\r\n * Host to bind to\r\n * @default 'localhost'\r\n */\r\n host?: string | boolean;\r\n\r\n /**\r\n * Open browser automatically\r\n * @default false\r\n */\r\n open?: boolean;\r\n\r\n /**\r\n * Enable verbose logging\r\n */\r\n verbose?: boolean;\r\n}\r\n\r\n/**\r\n * Start the SSG development server\r\n */\r\nexport async function dev(options: DevOptions = {}): Promise<void> {\r\n const root = process.cwd();\r\n\r\n console.log('\\n🚀 @sigx/ssg - Starting development server...\\n');\r\n\r\n // Load SSG config\r\n const ssgConfig = await loadConfig(options.configPath);\r\n\r\n // Check if user has a vite.config.ts\r\n const hasViteConfig = fs.existsSync(path.join(root, 'vite.config.ts')) ||\r\n fs.existsSync(path.join(root, 'vite.config.js')) ||\r\n fs.existsSync(path.join(root, 'vite.config.mjs'));\r\n\r\n // Import Vite\r\n const vite = await import('vite');\r\n\r\n if (hasViteConfig) {\r\n // User has their own vite.config - use it directly\r\n // They should have ssgPlugin() configured already\r\n console.log('📦 Using existing vite.config\\n');\r\n\r\n const server = await vite.createServer({\r\n root,\r\n server: {\r\n port: options.port,\r\n host: options.host,\r\n open: options.open,\r\n },\r\n });\r\n\r\n await server.listen();\r\n server.printUrls();\r\n } else {\r\n // Zero-config mode - configure everything automatically\r\n console.log('📦 Zero-config mode enabled\\n');\r\n\r\n // Try to import @sigx/vite plugin\r\n let sigxPlugin: ((...args: any[]) => any) | undefined;\r\n try {\r\n const sigxVite = await import('@sigx/vite');\r\n sigxPlugin = sigxVite.sigxPlugin;\r\n } catch {\r\n console.warn('⚠️ @sigx/vite not found, JSX transform may not work');\r\n console.warn(' Install with: npm install @sigx/vite\\n');\r\n }\r\n\r\n // Build plugins array\r\n const plugins: any[] = [];\r\n\r\n // Add Tailwind CSS if available\r\n try {\r\n // @ts-expect-error - Optional dependency\r\n const tailwind = await import('@tailwindcss/vite');\r\n plugins.push(tailwind.default());\r\n } catch {\r\n // Tailwind not installed, skip\r\n }\r\n\r\n // Add SignalX plugin if available\r\n if (sigxPlugin) {\r\n plugins.push(sigxPlugin());\r\n }\r\n\r\n // Add SSG plugin\r\n plugins.push(...ssgPlugin({ configPath: options.configPath }));\r\n\r\n const server = await vite.createServer({\r\n root,\r\n plugins,\r\n // Vite 8 uses oxc instead of esbuild for JSX transforms\r\n oxc: {\r\n jsx: {\r\n runtime: 'automatic',\r\n importSource: 'sigx',\r\n }\r\n },\r\n server: {\r\n port: options.port ?? 5173,\r\n host: options.host,\r\n open: options.open,\r\n },\r\n });\r\n\r\n await server.listen();\r\n server.printUrls();\r\n }\r\n}\r\n\r\n/**\r\n * Preview the production build\r\n */\r\nexport async function preview(options: DevOptions = {}): Promise<void> {\r\n const root = process.cwd();\r\n\r\n console.log('\\n👀 @sigx/ssg - Preview server...\\n');\r\n\r\n const vite = await import('vite');\r\n\r\n const server = await vite.preview({\r\n root,\r\n preview: {\r\n port: options.port ?? 4173,\r\n host: options.host,\r\n open: options.open,\r\n },\r\n });\r\n\r\n server.printUrls();\r\n console.log('\\n');\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AAiDA,eAAsB,IAAI,UAAsB,EAAE,EAAiB;CAC/D,MAAM,OAAO,QAAQ,KAAK;AAE1B,SAAQ,IAAI,oDAAoD;AAG9C,OAAM,WAAW,QAAQ,WAAW;CAGtD,MAAM,gBAAgB,OAAG,WAAW,KAAK,KAAK,MAAM,iBAAiB,CAAC,IAClE,OAAG,WAAW,KAAK,KAAK,MAAM,iBAAiB,CAAC,IAChD,OAAG,WAAW,KAAK,KAAK,MAAM,kBAAkB,CAAC;CAGrD,MAAM,OAAO,MAAM,OAAO;AAE1B,KAAI,eAAe;AAGf,UAAQ,IAAI,kCAAkC;EAE9C,MAAM,SAAS,MAAM,KAAK,aAAa;GACnC;GACA,QAAQ;IACJ,MAAM,QAAQ;IACd,MAAM,QAAQ;IACd,MAAM,QAAQ;IACjB;GACJ,CAAC;AAEF,QAAM,OAAO,QAAQ;AACrB,SAAO,WAAW;QACf;AAEH,UAAQ,IAAI,gCAAgC;EAG5C,IAAI;AACJ,MAAI;AAEA,iBADiB,MAAM,OAAO,eACR;UAClB;AACJ,WAAQ,KAAK,uDAAuD;AACpE,WAAQ,KAAK,4CAA4C;;EAI7D,MAAM,UAAiB,EAAE;AAGzB,MAAI;GAEA,MAAM,WAAW,MAAM,OAAO;AAC9B,WAAQ,KAAK,SAAS,SAAS,CAAC;UAC5B;AAKR,MAAI,WACA,SAAQ,KAAK,YAAY,CAAC;AAI9B,UAAQ,KAAK,GAAG,UAAU,EAAE,YAAY,QAAQ,YAAY,CAAC,CAAC;EAE9D,MAAM,SAAS,MAAM,KAAK,aAAa;GACnC;GACA;GAEA,KAAK,EACD,KAAK;IACD,SAAS;IACT,cAAc;IACjB,EACJ;GACD,QAAQ;IACJ,MAAM,QAAQ,QAAQ;IACtB,MAAM,QAAQ;IACd,MAAM,QAAQ;IACjB;GACJ,CAAC;AAEF,QAAM,OAAO,QAAQ;AACrB,SAAO,WAAW;;;;;;AAO1B,eAAsB,QAAQ,UAAsB,EAAE,EAAiB;CACnE,MAAM,OAAO,QAAQ,KAAK;AAE1B,SAAQ,IAAI,uCAAuC;AAanD,EATe,OAFF,MAAM,OAAO,SAEA,QAAQ;EAC9B;EACA,SAAS;GACL,MAAM,QAAQ,QAAQ;GACtB,MAAM,QAAQ;GACd,MAAM,QAAQ;GACjB;EACJ,CAAC,EAEK,WAAW;AAClB,SAAQ,IAAI,KAAK"}
package/dist/index.js CHANGED
@@ -1,8 +1,17 @@
1
- import { O as defineSSGConfig } from "./virtual-entries-sVkqKMAM.js";
2
- import { a as writeSitemap, i as pagesToSitemapEntries, n as generateRobotsTxt, r as generateSitemap, t as build } from "./build-CfOi93wa.js";
3
- import "./plugin-WQzgfl9i.js";
1
+ import { O as defineSSGConfig } from "./virtual-entries-TuNN2It1.js";
2
+ import { a as writeSitemap, i as pagesToSitemapEntries, n as generateRobotsTxt, r as generateSitemap, t as build } from "./build-qmtK32gt.js";
4
3
  import { dev, preview } from "./dev.js";
5
4
  import path from "node:path";
5
+ //#region src/errors.ts
6
+ /**
7
+ * SSG Error Classes and Utilities
8
+ *
9
+ * Provides structured error messages with file locations and suggestions
10
+ * for common issues encountered during development and build.
11
+ */
12
+ /**
13
+ * Base SSG error with enhanced formatting
14
+ */
6
15
  var SSGError = class extends Error {
7
16
  code;
8
17
  file;
@@ -17,6 +26,9 @@ var SSGError = class extends Error {
17
26
  this.suggestion = options.suggestion;
18
27
  this.cause = options.cause;
19
28
  }
29
+ /**
30
+ * Format error for console output with colors
31
+ */
20
32
  format(root) {
21
33
  const lines = [];
22
34
  lines.push(`\n❌ ${this.code}: ${this.message}\n`);
@@ -30,7 +42,10 @@ var SSGError = class extends Error {
30
42
  return lines.join("\n");
31
43
  }
32
44
  };
33
- const ErrorCodes = {
45
+ /**
46
+ * Error codes for categorization
47
+ */
48
+ var ErrorCodes = {
34
49
  CONFIG_NOT_FOUND: "SSG001",
35
50
  CONFIG_INVALID: "SSG002",
36
51
  CONFIG_THEME_NOT_FOUND: "SSG003",
@@ -43,6 +58,9 @@ const ErrorCodes = {
43
58
  MDX_PARSE_ERROR: "SSG400",
44
59
  MDX_FRONTMATTER_ERROR: "SSG401"
45
60
  };
61
+ /**
62
+ * Format and log an SSG error with full context
63
+ */
46
64
  function handleError(error, root) {
47
65
  if (error instanceof SSGError) {
48
66
  console.error(error.format(root));
@@ -53,6 +71,7 @@ function handleError(error, root) {
53
71
  } else console.error("\n❌ Unknown error:", error);
54
72
  process.exit(1);
55
73
  }
74
+ //#endregion
56
75
  export { ErrorCodes, SSGError, build, defineSSGConfig as defineConfig, defineSSGConfig, dev, generateRobotsTxt, generateSitemap, handleError, pagesToSitemapEntries, preview, writeSitemap };
57
76
 
58
77
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\r\n * SSG Error Classes and Utilities\r\n *\r\n * Provides structured error messages with file locations and suggestions\r\n * for common issues encountered during development and build.\r\n */\r\n\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Base SSG error with enhanced formatting\r\n */\r\nexport class SSGError extends Error {\r\n readonly code: string;\r\n readonly file?: string;\r\n readonly line?: number;\r\n readonly suggestion?: string;\r\n\r\n constructor(\r\n message: string,\r\n options: {\r\n code: string;\r\n file?: string;\r\n line?: number;\r\n suggestion?: string;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(message);\r\n this.name = 'SSGError';\r\n this.code = options.code;\r\n this.file = options.file;\r\n this.line = options.line;\r\n this.suggestion = options.suggestion;\r\n this.cause = options.cause;\r\n }\r\n\r\n /**\r\n * Format error for console output with colors\r\n */\r\n format(root?: string): string {\r\n const lines: string[] = [];\r\n \r\n // Header with error code\r\n lines.push(`\\n❌ ${this.code}: ${this.message}\\n`);\r\n \r\n // File location\r\n if (this.file) {\r\n const displayPath = root ? path.relative(root, this.file) : this.file;\r\n if (this.line) {\r\n lines.push(` 📁 ${displayPath}:${this.line}`);\r\n } else {\r\n lines.push(` 📁 ${displayPath}`);\r\n }\r\n }\r\n \r\n // Suggestion\r\n if (this.suggestion) {\r\n lines.push(`\\n 💡 ${this.suggestion}`);\r\n }\r\n \r\n lines.push('');\r\n return lines.join('\\n');\r\n }\r\n}\r\n\r\n/**\r\n * Error codes for categorization\r\n */\r\nexport const ErrorCodes = {\r\n // Config errors\r\n CONFIG_NOT_FOUND: 'SSG001',\r\n CONFIG_INVALID: 'SSG002',\r\n CONFIG_THEME_NOT_FOUND: 'SSG003',\r\n \r\n // Route/Page errors\r\n PAGE_NOT_FOUND: 'SSG100',\r\n PAGE_INVALID_EXPORT: 'SSG101',\r\n DYNAMIC_ROUTE_NO_PATHS: 'SSG102',\r\n LAYOUT_NOT_FOUND: 'SSG103',\r\n \r\n // Build errors\r\n BUILD_RENDER_FAILED: 'SSG300',\r\n BUILD_VITE_FAILED: 'SSG301',\r\n \r\n // MDX errors\r\n MDX_PARSE_ERROR: 'SSG400',\r\n MDX_FRONTMATTER_ERROR: 'SSG401',\r\n} as const;\r\n\r\n// ============================================================================\r\n// Config Errors\r\n// ============================================================================\r\n\r\nexport function configNotFoundError(searchedPaths: string[]): SSGError {\r\n return new SSGError(\r\n 'No SSG configuration file found',\r\n {\r\n code: ErrorCodes.CONFIG_NOT_FOUND,\r\n suggestion: `Create ssg.config.ts in your project root:\\n\\n` +\r\n ` import { defineSSGConfig } from '@sigx/ssg';\\n` +\r\n ` export default defineSSGConfig({ site: { title: 'My Site' } });`,\r\n }\r\n );\r\n}\r\n\r\nexport function themeNotFoundError(themeName: string): SSGError {\r\n return new SSGError(\r\n `Theme package \"${themeName}\" not found`,\r\n {\r\n code: ErrorCodes.CONFIG_THEME_NOT_FOUND,\r\n suggestion: `Install the theme package:\\n\\n npm install ${themeName}`,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Route/Page Errors\r\n// ============================================================================\r\n\r\nexport function layoutNotFoundError(layoutName: string, pagePath: string, availableLayouts: string[]): SSGError {\r\n const available = availableLayouts.length > 0\r\n ? `Available layouts: ${availableLayouts.join(', ')}`\r\n : 'No layouts found. Create one in src/layouts/';\r\n \r\n return new SSGError(\r\n `Layout \"${layoutName}\" not found`,\r\n {\r\n code: ErrorCodes.LAYOUT_NOT_FOUND,\r\n file: pagePath,\r\n suggestion: `${available}\\n\\n To use a layout, set it in frontmatter:\\n ---\\n layout: default\\n ---`,\r\n }\r\n );\r\n}\r\n\r\nexport function dynamicRouteNoPaths(filePath: string, routePath: string): SSGError {\r\n return new SSGError(\r\n `Dynamic route has no getStaticPaths export`,\r\n {\r\n code: ErrorCodes.DYNAMIC_ROUTE_NO_PATHS,\r\n file: filePath,\r\n suggestion: `Dynamic routes like \"${routePath}\" require a getStaticPaths export:\\n\\n` +\r\n ` export async function getStaticPaths() {\\n` +\r\n ` return [{ params: { slug: 'example' } }];\\n` +\r\n ` }`,\r\n }\r\n );\r\n}\r\n\r\nexport function pageInvalidExport(filePath: string): SSGError {\r\n return new SSGError(\r\n `Page file has no default export`,\r\n {\r\n code: ErrorCodes.PAGE_INVALID_EXPORT,\r\n file: filePath,\r\n suggestion: `Pages must export a default component:\\n\\n` +\r\n ` export default component(() => {\\n` +\r\n ` return () => <div>Page content</div>;\\n` +\r\n ` });`,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Build Errors\r\n// ============================================================================\r\n\r\nexport function buildRenderError(path: string, error: Error): SSGError {\r\n return new SSGError(\r\n `Failed to render page: ${path}`,\r\n {\r\n code: ErrorCodes.BUILD_RENDER_FAILED,\r\n suggestion: `Check the error details below. Common causes:\\n` +\r\n ` - Runtime error in component code\\n` +\r\n ` - Missing dependencies during SSR\\n` +\r\n ` - Browser APIs used during server render`,\r\n cause: error,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// MDX Errors\r\n// ============================================================================\r\n\r\nexport function mdxParseError(file: string, error: Error, line?: number): SSGError {\r\n return new SSGError(\r\n `Failed to parse MDX file`,\r\n {\r\n code: ErrorCodes.MDX_PARSE_ERROR,\r\n file,\r\n line,\r\n suggestion: `Check the MDX syntax. Common issues:\\n` +\r\n ` - Unclosed JSX tags\\n` +\r\n ` - Invalid JavaScript expressions\\n` +\r\n ` - Mixing HTML with JSX incorrectly`,\r\n cause: error,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Error Handler\r\n// ============================================================================\r\n\r\n/**\r\n * Format and log an SSG error with full context\r\n */\r\nexport function handleError(error: unknown, root?: string): never {\r\n if (error instanceof SSGError) {\r\n console.error(error.format(root));\r\n if (error.cause) {\r\n console.error(' Caused by:', error.cause);\r\n }\r\n } else if (error instanceof Error) {\r\n console.error(`\\n❌ ${error.name}: ${error.message}\\n`);\r\n if (error.stack) {\r\n console.error(error.stack);\r\n }\r\n } else {\r\n console.error('\\n❌ Unknown error:', error);\r\n }\r\n \r\n process.exit(1);\r\n}\r\n\r\n/**\r\n * Wrap an async function with error handling\r\n */\r\nexport function withErrorHandling<T>(\r\n fn: () => Promise<T>,\r\n root?: string\r\n): Promise<T> {\r\n return fn().catch((error) => {\r\n handleError(error, root);\r\n });\r\n}\r\n"],"mappings":";;;;;AAYA,IAAa,WAAb,cAA8B,MAAM;CAChC;CACA;CACA;CACA;CAEA,YACI,SACA,SAOF;AACE,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,OAAO,QAAQ;AACpB,OAAK,OAAO,QAAQ;AACpB,OAAK,OAAO,QAAQ;AACpB,OAAK,aAAa,QAAQ;AAC1B,OAAK,QAAQ,QAAQ;;CAMzB,OAAO,MAAuB;EAC1B,MAAM,QAAkB,EAAE;AAG1B,QAAM,KAAK,OAAO,KAAK,KAAK,IAAI,KAAK,QAAQ,IAAI;AAGjD,MAAI,KAAK,MAAM;GACX,MAAM,cAAc,OAAO,KAAK,SAAS,MAAM,KAAK,KAAK,GAAG,KAAK;AACjE,OAAI,KAAK,KACL,OAAM,KAAK,SAAS,YAAY,GAAG,KAAK,OAAO;OAE/C,OAAM,KAAK,SAAS,cAAc;;AAK1C,MAAI,KAAK,WACL,OAAM,KAAK,WAAW,KAAK,aAAa;AAG5C,QAAM,KAAK,GAAG;AACd,SAAO,MAAM,KAAK,KAAK;;;AAO/B,MAAa,aAAa;CAEtB,kBAAkB;CAClB,gBAAgB;CAChB,wBAAwB;CAGxB,gBAAgB;CAChB,qBAAqB;CACrB,wBAAwB;CACxB,kBAAkB;CAGlB,qBAAqB;CACrB,mBAAmB;CAGnB,iBAAiB;CACjB,uBAAuB;CAC1B;AAwHD,SAAgB,YAAY,OAAgB,MAAsB;AAC9D,KAAI,iBAAiB,UAAU;AAC3B,UAAQ,MAAM,MAAM,OAAO,KAAK,CAAC;AACjC,MAAI,MAAM,MACN,SAAQ,MAAM,iBAAiB,MAAM,MAAM;YAExC,iBAAiB,OAAO;AAC/B,UAAQ,MAAM,OAAO,MAAM,KAAK,IAAI,MAAM,QAAQ,IAAI;AACtD,MAAI,MAAM,MACN,SAAQ,MAAM,MAAM,MAAM;OAG9B,SAAQ,MAAM,sBAAsB,MAAM;AAG9C,SAAQ,KAAK,EAAE"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\r\n * SSG Error Classes and Utilities\r\n *\r\n * Provides structured error messages with file locations and suggestions\r\n * for common issues encountered during development and build.\r\n */\r\n\r\nimport path from 'node:path';\r\n\r\n/**\r\n * Base SSG error with enhanced formatting\r\n */\r\nexport class SSGError extends Error {\r\n readonly code: string;\r\n readonly file?: string;\r\n readonly line?: number;\r\n readonly suggestion?: string;\r\n\r\n constructor(\r\n message: string,\r\n options: {\r\n code: string;\r\n file?: string;\r\n line?: number;\r\n suggestion?: string;\r\n cause?: Error;\r\n }\r\n ) {\r\n super(message);\r\n this.name = 'SSGError';\r\n this.code = options.code;\r\n this.file = options.file;\r\n this.line = options.line;\r\n this.suggestion = options.suggestion;\r\n this.cause = options.cause;\r\n }\r\n\r\n /**\r\n * Format error for console output with colors\r\n */\r\n format(root?: string): string {\r\n const lines: string[] = [];\r\n \r\n // Header with error code\r\n lines.push(`\\n❌ ${this.code}: ${this.message}\\n`);\r\n \r\n // File location\r\n if (this.file) {\r\n const displayPath = root ? path.relative(root, this.file) : this.file;\r\n if (this.line) {\r\n lines.push(` 📁 ${displayPath}:${this.line}`);\r\n } else {\r\n lines.push(` 📁 ${displayPath}`);\r\n }\r\n }\r\n \r\n // Suggestion\r\n if (this.suggestion) {\r\n lines.push(`\\n 💡 ${this.suggestion}`);\r\n }\r\n \r\n lines.push('');\r\n return lines.join('\\n');\r\n }\r\n}\r\n\r\n/**\r\n * Error codes for categorization\r\n */\r\nexport const ErrorCodes = {\r\n // Config errors\r\n CONFIG_NOT_FOUND: 'SSG001',\r\n CONFIG_INVALID: 'SSG002',\r\n CONFIG_THEME_NOT_FOUND: 'SSG003',\r\n \r\n // Route/Page errors\r\n PAGE_NOT_FOUND: 'SSG100',\r\n PAGE_INVALID_EXPORT: 'SSG101',\r\n DYNAMIC_ROUTE_NO_PATHS: 'SSG102',\r\n LAYOUT_NOT_FOUND: 'SSG103',\r\n \r\n // Build errors\r\n BUILD_RENDER_FAILED: 'SSG300',\r\n BUILD_VITE_FAILED: 'SSG301',\r\n \r\n // MDX errors\r\n MDX_PARSE_ERROR: 'SSG400',\r\n MDX_FRONTMATTER_ERROR: 'SSG401',\r\n} as const;\r\n\r\n// ============================================================================\r\n// Config Errors\r\n// ============================================================================\r\n\r\nexport function configNotFoundError(searchedPaths: string[]): SSGError {\r\n return new SSGError(\r\n 'No SSG configuration file found',\r\n {\r\n code: ErrorCodes.CONFIG_NOT_FOUND,\r\n suggestion: `Create ssg.config.ts in your project root:\\n\\n` +\r\n ` import { defineSSGConfig } from '@sigx/ssg';\\n` +\r\n ` export default defineSSGConfig({ site: { title: 'My Site' } });`,\r\n }\r\n );\r\n}\r\n\r\nexport function themeNotFoundError(themeName: string): SSGError {\r\n return new SSGError(\r\n `Theme package \"${themeName}\" not found`,\r\n {\r\n code: ErrorCodes.CONFIG_THEME_NOT_FOUND,\r\n suggestion: `Install the theme package:\\n\\n npm install ${themeName}`,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Route/Page Errors\r\n// ============================================================================\r\n\r\nexport function layoutNotFoundError(layoutName: string, pagePath: string, availableLayouts: string[]): SSGError {\r\n const available = availableLayouts.length > 0\r\n ? `Available layouts: ${availableLayouts.join(', ')}`\r\n : 'No layouts found. Create one in src/layouts/';\r\n \r\n return new SSGError(\r\n `Layout \"${layoutName}\" not found`,\r\n {\r\n code: ErrorCodes.LAYOUT_NOT_FOUND,\r\n file: pagePath,\r\n suggestion: `${available}\\n\\n To use a layout, set it in frontmatter:\\n ---\\n layout: default\\n ---`,\r\n }\r\n );\r\n}\r\n\r\nexport function dynamicRouteNoPaths(filePath: string, routePath: string): SSGError {\r\n return new SSGError(\r\n `Dynamic route has no getStaticPaths export`,\r\n {\r\n code: ErrorCodes.DYNAMIC_ROUTE_NO_PATHS,\r\n file: filePath,\r\n suggestion: `Dynamic routes like \"${routePath}\" require a getStaticPaths export:\\n\\n` +\r\n ` export async function getStaticPaths() {\\n` +\r\n ` return [{ params: { slug: 'example' } }];\\n` +\r\n ` }`,\r\n }\r\n );\r\n}\r\n\r\nexport function pageInvalidExport(filePath: string): SSGError {\r\n return new SSGError(\r\n `Page file has no default export`,\r\n {\r\n code: ErrorCodes.PAGE_INVALID_EXPORT,\r\n file: filePath,\r\n suggestion: `Pages must export a default component:\\n\\n` +\r\n ` export default component(() => {\\n` +\r\n ` return () => <div>Page content</div>;\\n` +\r\n ` });`,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Build Errors\r\n// ============================================================================\r\n\r\nexport function buildRenderError(path: string, error: Error): SSGError {\r\n return new SSGError(\r\n `Failed to render page: ${path}`,\r\n {\r\n code: ErrorCodes.BUILD_RENDER_FAILED,\r\n suggestion: `Check the error details below. Common causes:\\n` +\r\n ` - Runtime error in component code\\n` +\r\n ` - Missing dependencies during SSR\\n` +\r\n ` - Browser APIs used during server render`,\r\n cause: error,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// MDX Errors\r\n// ============================================================================\r\n\r\nexport function mdxParseError(file: string, error: Error, line?: number): SSGError {\r\n return new SSGError(\r\n `Failed to parse MDX file`,\r\n {\r\n code: ErrorCodes.MDX_PARSE_ERROR,\r\n file,\r\n line,\r\n suggestion: `Check the MDX syntax. Common issues:\\n` +\r\n ` - Unclosed JSX tags\\n` +\r\n ` - Invalid JavaScript expressions\\n` +\r\n ` - Mixing HTML with JSX incorrectly`,\r\n cause: error,\r\n }\r\n );\r\n}\r\n\r\n// ============================================================================\r\n// Error Handler\r\n// ============================================================================\r\n\r\n/**\r\n * Format and log an SSG error with full context\r\n */\r\nexport function handleError(error: unknown, root?: string): never {\r\n if (error instanceof SSGError) {\r\n console.error(error.format(root));\r\n if (error.cause) {\r\n console.error(' Caused by:', error.cause);\r\n }\r\n } else if (error instanceof Error) {\r\n console.error(`\\n❌ ${error.name}: ${error.message}\\n`);\r\n if (error.stack) {\r\n console.error(error.stack);\r\n }\r\n } else {\r\n console.error('\\n❌ Unknown error:', error);\r\n }\r\n \r\n process.exit(1);\r\n}\r\n\r\n/**\r\n * Wrap an async function with error handling\r\n */\r\nexport function withErrorHandling<T>(\r\n fn: () => Promise<T>,\r\n root?: string\r\n): Promise<T> {\r\n return fn().catch((error) => {\r\n handleError(error, root);\r\n });\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;AAYA,IAAa,WAAb,cAA8B,MAAM;CAChC;CACA;CACA;CACA;CAEA,YACI,SACA,SAOF;AACE,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,OAAO,QAAQ;AACpB,OAAK,OAAO,QAAQ;AACpB,OAAK,OAAO,QAAQ;AACpB,OAAK,aAAa,QAAQ;AAC1B,OAAK,QAAQ,QAAQ;;;;;CAMzB,OAAO,MAAuB;EAC1B,MAAM,QAAkB,EAAE;AAG1B,QAAM,KAAK,OAAO,KAAK,KAAK,IAAI,KAAK,QAAQ,IAAI;AAGjD,MAAI,KAAK,MAAM;GACX,MAAM,cAAc,OAAO,KAAK,SAAS,MAAM,KAAK,KAAK,GAAG,KAAK;AACjE,OAAI,KAAK,KACL,OAAM,KAAK,SAAS,YAAY,GAAG,KAAK,OAAO;OAE/C,OAAM,KAAK,SAAS,cAAc;;AAK1C,MAAI,KAAK,WACL,OAAM,KAAK,WAAW,KAAK,aAAa;AAG5C,QAAM,KAAK,GAAG;AACd,SAAO,MAAM,KAAK,KAAK;;;;;;AAO/B,IAAa,aAAa;CAEtB,kBAAkB;CAClB,gBAAgB;CAChB,wBAAwB;CAGxB,gBAAgB;CAChB,qBAAqB;CACrB,wBAAwB;CACxB,kBAAkB;CAGlB,qBAAqB;CACrB,mBAAmB;CAGnB,iBAAiB;CACjB,uBAAuB;CAC1B;;;;AAwHD,SAAgB,YAAY,OAAgB,MAAsB;AAC9D,KAAI,iBAAiB,UAAU;AAC3B,UAAQ,MAAM,MAAM,OAAO,KAAK,CAAC;AACjC,MAAI,MAAM,MACN,SAAQ,MAAM,iBAAiB,MAAM,MAAM;YAExC,iBAAiB,OAAO;AAC/B,UAAQ,MAAM,OAAO,MAAM,KAAK,IAAI,MAAM,QAAQ,IAAI;AACtD,MAAI,MAAM,MACN,SAAQ,MAAM,MAAM,MAAM;OAG9B,SAAQ,MAAM,sBAAsB,MAAM;AAG9C,SAAQ,KAAK,EAAE"}