@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.
- package/dist/{build-CfOi93wa.js → build-qmtK32gt.js} +70 -2
- package/dist/{build-CfOi93wa.js.map → build-qmtK32gt.js.map} +1 -1
- package/dist/build.js +1 -2
- package/dist/client.js +14 -0
- package/dist/client.js.map +1 -1
- package/dist/dev.js +16 -2
- package/dist/dev.js.map +1 -1
- package/dist/index.js +23 -4
- package/dist/index.js.map +1 -1
- package/dist/{plugin-WQzgfl9i.js → plugin-Bik0HMne.js} +89 -4
- package/dist/{plugin-WQzgfl9i.js.map → plugin-Bik0HMne.js.map} +1 -1
- package/dist/{virtual-entries-sVkqKMAM.js → virtual-entries-TuNN2It1.js} +257 -12
- package/dist/{virtual-entries-sVkqKMAM.js.map → virtual-entries-TuNN2It1.js.map} +1 -1
- package/dist/vite/plugin.js +2 -3
- package/package.json +7 -7
|
@@ -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-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
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-
|
|
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, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\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, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n}\r\n"],"mappings":";;;;;AA4CA,SAAgB,gBACZ,SACA,QACM;CACN,MAAM,UAAU,OAAO,MAAM,KAAK,QAAQ,OAAO,GAAG,IAAI;CACxD,MAAM,OAAO,OAAO,MAAM,QAAQ,OAAO,GAAG,IAAI;AAkBhD,QAAO;;EAhBY,QAAQ,KAAK,UAAU;EACtC,MAAM,MAAM,GAAG,UAAU,OAAO,MAAM;EACtC,MAAM,UAAU,MAAM,UAChB,OAAO,MAAM,YAAY,WACrB,MAAM,UACN,MAAM,QAAQ,aAAa,CAAC,MAAM,IAAI,CAAC,KAC3C,KAAA;AAEN,SAAO;WACJ,UAAU,IAAI,CAAC,QAAQ,UAAU;eAC7B,QAAQ,cAAc,KAAK,MAAM,aAAa;kBAC3C,MAAM,WAAW,iBAAiB,KAAK,MAAM,aAAa,KAAA,IAAY;gBACxE,MAAM,SAAS,QAAQ,EAAE,CAAC,eAAe,GAAG;;GAEtD,CAIO,KAAK,KAAK,CAAC;;;AAOxB,SAAgB,kBAAkB,QAAmB,cAAc,gBAAwB;AAIvF,QAAO;;;WAHS,OAAO,MAAM,KAAK,QAAQ,OAAO,GAAG,IAAI,KAC3C,OAAO,MAAM,QAAQ,OAAO,GAAG,IAAI,KAKxB,YAAY;;;AAOxC,SAAgB,sBACZ,OACA,UAA0B,EAAE,EACd;CACd,MAAM,EACF,UAAU,EAAE,EACZ,oBAAoB,UACpB,kBAAkB,OAClB;AAEJ,QAAO,MACF,QAAQ,SAAS;AAEd,OAAK,MAAM,WAAW,QAClB,KAAI,QAAQ,SAAS,IAAI;OAEP,IAAI,OACd,MAAM,QAAQ,QAAQ,OAAO,KAAK,CAAC,QAAQ,OAAO,IAAI,GAAG,IAC5D,CACS,KAAK,KAAK,KAAK,CAAE,QAAO;aAC3B,KAAK,SAAS,QACrB,QAAO;AAGf,SAAO;GACT,CACD,KAAK,SAAS;EAEX,MAAM,QAAQ,KAAK,KAAK,MAAM,IAAI,CAAC,OAAO,QAAQ,CAAC;EACnD,IAAI,WAAW;AAEf,MAAI,KAAK,SAAS,IACd,YAAW;WACJ,UAAU,EACjB,YAAW;WACJ,UAAU,EACjB,YAAW;AAGf,SAAO;GACH,MAAM,KAAK;GACX,YAAY;GACZ;GACH;GACH;;AAMV,eAAsB,aAClB,OACA,QACA,QACA,UAA0B,EAAE,EACwB;CAEpD,MAAM,UAAU,sBAAsB,OAAO,QAAQ;AAGrD,KAAI,QAAQ,eACR,SAAQ,KAAK,GAAG,QAAQ,eAAe;CAI3C,MAAM,iBAAiB,gBAAgB,SAAS,OAAO;CACvD,MAAM,cAAc,KAAK,KAAK,QAAQ,cAAc;AACpD,OAAM,GAAG,UAAU,aAAa,gBAAgB,QAAQ;CAGxD,MAAM,gBAAgB,kBAAkB,OAAO;CAC/C,MAAM,aAAa,KAAK,KAAK,QAAQ,aAAa;AAClD,OAAM,GAAG,UAAU,YAAY,eAAe,QAAQ;AAEtD,QAAO;EAAE;EAAa;EAAY;;AAMtC,SAAS,UAAU,KAAqB;AACpC,QAAO,IACF,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,SAAS;;AC9IhC,eAAsB,MAAM,UAAwB,EAAE,EAAwB;CAC1E,MAAM,YAAY,KAAK,KAAK;CAC5B,MAAM,OAAO,QAAQ,KAAK;CAC1B,MAAM,WAAqB,EAAE;CAC7B,MAAM,QAA2B,EAAE;AAEnC,SAAQ,IAAI,6CAA6C;AAGzD,SAAQ,IAAI,8BAA8B;CAE1C,MAAM,iBAAiB,mBADR,MAAM,WAAW,QAAQ,WAAW,EACD,KAAK;AAGvD,SAAQ,IAAI,uBAAuB;CACnC,MAAM,SAAS,MAAM,UAAU,gBAAgB,KAAK;AACpD,SAAQ,IAAI,YAAY,OAAO,OAAO,UAAU;AAGhD,SAAQ,IAAI,4BAA4B;CACxC,MAAM,UAAU,MAAM,gBAAgB,gBAAgB,KAAK;AAC3D,SAAQ,IAAI,YAAY,QAAQ,OAAO,YAAY;CAGnD,MAAM,iBAAiB,oBAAoB,MAAM,eAAe;AAChE,KAAI,eAAe,oBAAoB,eAAe,kBAAkB;AACpE,UAAQ,IAAI,4BAA4B;AACxC,MAAI,eAAe,iBAAkB,SAAQ,IAAI,4BAA4B;AAC7E,MAAI,eAAe,iBAAkB,SAAQ,IAAI,4BAA4B;AAC7E,MAAI,eAAe,eAAgB,SAAQ,IAAI,6BAA6B;;CAIhF,MAAM,cAAc,MAAM,oBAAoB,gBAAgB,KAAK;CACnE,MAAM,WAAW,MAAM,iBAAiB,gBAAgB,KAAK;CAI7D,MAAM,mBAAmB,KAAK,KAAK,MAAM,aAAa;CACtD,IAAI,cAAc;CAClB,IAAI,sBAAqC;AAGzC,KAAI,CAAC,eAAe,kBAAkB,OAAO,WAAW,iBAAiB,CACrE,uBAAsB,OAAO,aAAa,kBAAkB,QAAQ;CAGxE,MAAM,cAAc,MAAM,gBAAgB,gBAAgB,MAAM,YAAY;AAC5E,QAAO,cAAc,kBAAkB,aAAa,QAAQ;AAC5D,eAAc,eAAe;AAG7B,SAAQ,IAAI,2BAA2B;CACvC,MAAM,OAAO,MAAM,OAAO;AAE1B,KAAI;EAIA,MAAM,cAAc;AAEpB,QAAM,KAAK,MAAM;GACb;GACA,MAAM;GACN,OAAO;IACH,QAAQ,eAAe;IACvB,aAAa;IACb,aAAa;IACb,eAAe,EACX,OAAO,aACV;IACJ;GACD,UAAU,QAAQ,UAAU,SAAS;GACxC,CAAC;EAGF,MAAM,YAAY,KAAK,KAAK,eAAe,QAAS,OAAO;AAC3D,QAAM,KAAK,MAAM;GACb;GACA,MAAM;GACN,OAAO;IACH,QAAQ;IACR,KAAK;IACL,eAAe,EACX,OAAO,UACV;IACJ;GACD,UAAU,QAAQ,UAAU,SAAS;GACxC,CAAC;AAGF,UAAQ,IAAI,mCAAmC;EAC/C,MAAM,gBAAgB,MAAM,aAAa,QAAQ,MAAM,SAAS;AAChE,UAAQ,IAAI,MAAM,cAAc,OAAO,oBAAoB;EAG3D,MAAM,6BAAa,IAAI,KAAa;AACpC,OAAK,MAAM,YAAY,eAAe;GAClC,MAAM,aAAa,cAAc,SAAS,MAAM,eAAe,OAAQ;AACvE,cAAW,IAAI,KAAK,QAAQ,WAAW,CAAC;;AAE5C,QAAM,QAAQ,IACV,MAAM,KAAK,WAAW,CAAC,KAAI,QAAO,GAAG,MAAM,KAAK,EAAE,WAAW,MAAM,CAAC,CAAC,CACxE;AAGD,UAAQ,IAAI,wBAAwB;EAIpC,MAAM,eADmB,KAAK,SAAS,UAAU,KAAK,QAAQ,SAAS,CAAC,GAChC;EAIxC,MAAM,cAAc,MAAM,OAAO,cADf,KAAK,KAAK,WAAW,aAAa,CACK,CAAC;EAG1D,MAAM,eAAe,KAAK,KAAK,eAAe,QAAS,aAAa;EACpE,MAAM,WAAW,MAAM,GAAG,SAAS,cAAc,QAAQ;EAGzD,MAAM,cAAc,QAAQ,eAAe;EAC3C,MAAM,UAAU,QAAQ,WAAW;EAUnC,eAAe,WAAW,UAAsD;GAC5E,MAAM,cAAc,KAAK,KAAK;AAE9B,OAAI;IAEA,MAAM,UAAU,MAAM,YAAY,OAAO,SAAS,MAAM;KACpD,QAAQ,SAAS;KACjB,OAAO,SAAS;KACnB,CAAC;IAGF,IAAI,OAAO,SAAS,QAAQ,mBAAmB,QAAQ;IACvD,MAAM,WAAW,iBAAiB,UAAU,eAAe;AAC3D,WAAO,KAAK,QAAQ,oBAAoB,SAAS;IAEjD,MAAM,aAAa,cAAc,SAAS,MAAM,eAAe,OAAQ;IACvE,MAAM,aAAa,KAAK,KAAK,GAAG;AAEhC,WAAO;KAAE;KAAU;KAAM;KAAY;KAAY;YAC5C,KAAK;IACV,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AACrE,YAAQ,MAAM,QAAQ,SAAS,KAAK,IAAI,eAAe;AACvD,aAAS,KAAK,oBAAoB,SAAS,KAAK,IAAI,eAAe;AACnE,WAAO;;;AAKf,UAAQ,IAAI,2BAA2B;EACvC,MAAM,mBAAmB,KAAK,KAAK;EACnC,MAAM,gBAAgC,EAAE;AAExC,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,aAAa;GACxD,MAAM,QAAQ,cAAc,MAAM,GAAG,IAAI,YAAY;GACrD,MAAM,UAAU,MAAM,QAAQ,IAAI,MAAM,IAAI,WAAW,CAAC;AAExD,QAAK,MAAM,UAAU,QACjB,KAAI,OACA,eAAc,KAAK,OAAO;;EAItC,MAAM,sBAAsB,KAAK,KAAK,GAAG;AACzC,UAAQ,IAAI,wBAAwB,cAAc,OAAO,YAAY,oBAAoB,MAAM,KAAK,MAAM,sBAAsB,cAAc,OAAO,CAAC,SAAS;AAG/J,UAAQ,IAAI,+BAA+B;EAC3C,MAAM,kBAAkB,KAAK,KAAK;EAClC,MAAM,oBAAoB;AAE1B,OAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK,mBAAmB;GAC9D,MAAM,QAAQ,cAAc,MAAM,GAAG,IAAI,kBAAkB;AAC3D,SAAM,QAAQ,IAAI,MAAM,IAAI,OAAO,WAAW;AAC1C,UAAM,GAAG,UAAU,OAAO,YAAY,OAAO,MAAM,QAAQ;IAC3D,MAAM,OAAO,OAAO,WAAW,OAAO,MAAM,QAAQ;AAEpD,UAAM,KAAK;KACP,MAAM,OAAO,SAAS;KACtB,MAAM,OAAO;KACb,MAAM,OAAO;KACb;KACH,CAAC;AAEF,QAAI,QACA,SAAQ,IAAI,QAAQ,OAAO,SAAS,KAAK,IAAI,OAAO,WAAW,MAAM,YAAY,KAAK,CAAC,GAAG;KAEhG,CAAC;;EAEP,MAAM,qBAAqB,KAAK,KAAK,GAAG;AACxC,UAAQ,IAAI,wBAAwB,cAAc,OAAO,YAAY,mBAAmB,IAAI;AAG5F,MAAI,CAAC,QACD,SAAQ,IAAI,iBAAiB,cAAc,OAAO,QAAQ;AAI9D,QAAM,GAAG,GAAG,WAAW;GAAE,WAAW;GAAM,OAAO;GAAM,CAAC;AAGxD,MAAI,MAAM,SAAS,GAAG;AAClB,WAAQ,IAAI,6BAA6B;AACzC,SAAM,aAAa,OAAO,gBAAgB,eAAe,OAAQ;AACjE,WAAQ,IAAI,mBAAmB;AAC/B,WAAQ,IAAI,kBAAkB;;WAG5B;AAEN,QAAM,mBAAmB,KAAK;AAG9B,MAAI,YAEA,KAAI;AACA,SAAM,GAAG,OAAO,iBAAiB;UAC7B;WAGD,wBAAwB,KAE/B,KAAI;AACA,SAAM,GAAG,UAAU,kBAAkB,qBAAqB,QAAQ;UAC9D;;CAOhB,MAAM,YAAY,KAAK,KAAK,GAAG;AAE/B,SAAQ,IAAI,aAAa,MAAM,OAAO,cAAc,UAAU,IAAI;AAElE,KAAI,SAAS,SAAS,GAAG;AACrB,UAAQ,IAAI,SAAS,SAAS,OAAO,cAAc;AACnD,OAAK,MAAM,WAAW,SAClB,SAAQ,IAAI,QAAQ,UAAU;;AAItC,SAAQ,IAAI,gBAAgB,eAAe,OAAO,IAAI;AAEtD,QAAO;EACH;EACA;EACA;EACH;;AAgBL,eAAe,aACX,QACA,MACA,UACuB;CACvB,MAAM,QAAwB,EAAE;AAEhC,MAAK,MAAM,SAAS,OAChB,KAAI,eAAe,MAAM,CAErB,KAAI;EAEA,MAAM,aAAc,MAAM,OADR,cAAc,MAAM,KAAK,CAAC;AAG5C,MAAI,CAAC,WAAW,gBAAgB;GAC5B,MAAM,SAAS,cAAc,MAAM,KAAK,CAAC,KAAK,KAAK;AACnD,WAAQ,KACJ,+DACS,MAAM,KAAK,cACP,MAAM,KAAK,YAAY,OAAO,8IAGV,OAAO,MAAM,KAAK,CAAC,GAAG,4BAE1D;AACD,YAAS,KACL,SAAS,MAAM,KAAK,yBAAyB,OAAO,6CACvD;AACD;;EAGJ,MAAM,cAAc,MAAM,WAAW,gBAAgB;AAErD,OAAK,MAAM,cAAc,aAAa;GAClC,MAAM,gBAAgB,mBAAmB,OAAO,CAAC,WAAW,CAAC;AAC7D,QAAK,MAAM,gBAAgB,cACvB,OAAM,KAAK;IACP,MAAM;IACN;IACA,QAAQ,WAAW;IACnB,OAAO,WAAW;IACrB,CAAC;;UAGL,KAAK;AACV,WAAS,KAAK,kBAAkB,MAAM,KAAK,IAAI,MAAM;;KAGzD,OAAM,KAAK;EACP,MAAM,MAAM;EACZ;EACA,QAAQ,EAAE;EACb,CAAC;AAIV,QAAO;;AAwCX,SAAS,iBAAiB,UAAwB,QAA2B;CACzE,MAAM,OAAiB,EAAE;CACzB,MAAM,OAAO,SAAS,MAAM,QAAQ,EAAE;CAGtC,MAAM,QAAQ,KAAK,SAAS,OAAO,MAAM;AACzC,KAAI,MACA,MAAK,KAAK,UAAU,WAAW,MAAM,CAAC,UAAU;CAIpD,MAAM,cAAc,KAAK,eAAe,OAAO,MAAM;AACrD,KAAI,YACA,MAAK,KAAK,qCAAqC,WAAW,YAAY,CAAC,IAAI;AAI/E,KAAI,OAAO,MAAM,KAAK;EAClB,MAAM,YAAY,IAAI,IAAI,SAAS,MAAM,OAAO,KAAK,IAAI,CAAC;AAC1D,OAAK,KAAK,+BAA+B,UAAU,IAAI;;AAG3D,QAAO,KAAK,KAAK,SAAS;;AAM9B,SAAS,cAAc,SAAiB,QAAwB;CAE5D,IAAI,aAAa,QAAQ,QAAQ,OAAO,GAAG,CAAC,QAAQ,OAAO,GAAG;AAE9D,KAAI,CAAC,WACD,cAAa;AAIjB,KAAI,CAAC,WAAW,SAAS,QAAQ,CAC7B,cAAa,KAAK,KAAK,YAAY,aAAa;AAGpD,QAAO,KAAK,KAAK,QAAQ,WAAW;;AAOxC,eAAe,iBAAiB,QAAmB,MAA+B;CAE9E,MAAM,YAAY,oBAAoB,MAAM,OAAO;AAEnD,KAAI,CAAC,UAAU,oBAAoB,UAAU,iBACzC,QAAO,UAAU;CAKrB,MAAM,oBAAoB,oBAAoB,OAAO;CACrD,MAAM,iBAAiB,KAAK,KAAK,MAAM,6BAA6B;AACpE,QAAO,cAAc,gBAAgB,mBAAmB,QAAQ;AAEhE,QAAO;;AAOX,eAAe,oBAAoB,QAAmB,MAA+B;CACjF,MAAM,YAAY,oBAAoB,MAAM,OAAO;AAEnD,KAAI,CAAC,UAAU,oBAAoB,UAAU,iBACzC,QAAO,UAAU;CAIrB,MAAM,oBAAoB,oBAAoB,QAAQ,UAAU;CAChE,MAAM,iBAAiB,KAAK,KAAK,MAAM,6BAA6B;AACpE,QAAO,cAAc,gBAAgB,mBAAmB,QAAQ;AAEhE,QAAO;;AAMX,eAAe,mBAAmB,MAA6B;CAC3D,MAAM,YAAY,CACd,KAAK,KAAK,MAAM,6BAA6B,EAC7C,KAAK,KAAK,MAAM,6BAA6B,CAChD;AAED,MAAK,MAAM,QAAQ,UACf,KAAI;AACA,QAAM,GAAG,OAAO,KAAK;SACjB;;AAShB,eAAe,gBAAgB,QAAmB,MAAc,iBAA0C;CACtG,MAAM,YAAY,oBAAoB,MAAM,OAAO;AAEnD,KAAI,CAAC,UAAU,kBAAkB,UAAU,gBAAgB;EAEvD,IAAI,OAAO,MAAM,GAAG,SAAS,UAAU,gBAAgB,QAAQ;EAG/D,MAAM,eAAe,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAC,QAAQ,OAAO,IAAI;AACpF,SAAO,KAAK,QACR,uDACA,kBAAkB,aAAa,GAClC;AACD,SAAO;;AAIX,QAAO,+BAA+B,QAAQ,gBAAgB;;AAMlE,SAAS,YAAY,OAAuB;AACxC,KAAI,QAAQ,KAAM,QAAO,GAAG,MAAM;AAClC,KAAI,QAAQ,OAAO,KAAM,QAAO,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;AAC7D,QAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,EAAE,CAAC;;AAMjD,SAAS,WAAW,KAAqB;AACrC,QAAO,IACF,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ"}
|
|
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, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\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, '&')\r\n .replace(/</g, '<')\r\n .replace(/>/g, '>')\r\n .replace(/\"/g, '"')\r\n .replace(/'/g, ''');\r\n}\r\n"],"mappings":"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"}
|
package/dist/build.js
CHANGED
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
|
package/dist/client.js.map
CHANGED
|
@@ -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":"
|
|
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-
|
|
2
|
-
import {
|
|
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":"
|
|
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-
|
|
2
|
-
import { a as writeSitemap, i as pagesToSitemapEntries, n as generateRobotsTxt, r as generateSitemap, t as build } from "./build-
|
|
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
|
-
|
|
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":"
|
|
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"}
|