@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.
@@ -2,6 +2,29 @@ import path from "node:path";
2
2
  import fsSync from "node:fs";
3
3
  import fg from "fast-glob";
4
4
  import matter from "gray-matter";
5
+ //#region src/config.ts
6
+ /**
7
+ * SSG Configuration helper
8
+ */
9
+ /**
10
+ * Define SSG configuration with type safety
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // ssg.config.ts
15
+ * import { defineSSGConfig } from '@sigx/ssg';
16
+ *
17
+ * export default defineSSGConfig({
18
+ * pages: 'src/pages',
19
+ * layouts: 'src/layouts',
20
+ * theme: '@sigx/ssg-theme-daisyui',
21
+ * site: {
22
+ * title: 'My Site',
23
+ * description: 'A SignalX-powered static site'
24
+ * }
25
+ * });
26
+ * ```
27
+ */
5
28
  function defineSSGConfig(config) {
6
29
  return {
7
30
  pages: "src/pages",
@@ -28,6 +51,9 @@ function defineSSGConfig(config) {
28
51
  }
29
52
  };
30
53
  }
54
+ /**
55
+ * Load SSG config from file
56
+ */
31
57
  async function loadConfig(configPath) {
32
58
  const fsPath = await import("node:path");
33
59
  const fs = await import("node:fs");
@@ -77,6 +103,9 @@ async function loadConfig(configPath) {
77
103
  return defineSSGConfig({});
78
104
  }
79
105
  }
106
+ /**
107
+ * Resolve paths in config to absolute paths
108
+ */
80
109
  function resolveConfigPaths(config, root) {
81
110
  return {
82
111
  ...config,
@@ -86,6 +115,16 @@ function resolveConfigPaths(config, root) {
86
115
  outDir: path.resolve(root, config.outDir || "dist")
87
116
  };
88
117
  }
118
+ //#endregion
119
+ //#region src/mdx/frontmatter.ts
120
+ /**
121
+ * Frontmatter parser
122
+ *
123
+ * Extracts and parses YAML frontmatter from Markdown/MDX files.
124
+ */
125
+ /**
126
+ * Parse frontmatter from content
127
+ */
89
128
  function parseFrontmatter(source) {
90
129
  const { data, content, matter: raw } = matter(source);
91
130
  return {
@@ -95,6 +134,9 @@ function parseFrontmatter(source) {
95
134
  hasFrontmatter: !!raw
96
135
  };
97
136
  }
137
+ /**
138
+ * Normalize frontmatter data to PageMeta
139
+ */
98
140
  function normalizeFrontmatter(data) {
99
141
  const meta = {};
100
142
  if (typeof data.title === "string") meta.title = data.title;
@@ -110,16 +152,38 @@ function normalizeFrontmatter(data) {
110
152
  for (const [key, value] of Object.entries(data)) if (!(key in meta)) meta[key] = value;
111
153
  return meta;
112
154
  }
155
+ /**
156
+ * Extract title from markdown content if not in frontmatter
157
+ *
158
+ * Looks for the first H1 heading
159
+ */
113
160
  function extractTitleFromContent(content) {
114
161
  const h1Match = content.match(/^#\s+(.+)$/m);
115
162
  return h1Match ? h1Match[1].trim() : null;
116
163
  }
164
+ //#endregion
165
+ //#region src/routing/scanner.ts
166
+ /**
167
+ * File-based route scanner
168
+ *
169
+ * Scans a pages directory and generates route definitions from file paths.
170
+ * Supports dynamic routes with [param] and catch-all with [...slug] patterns.
171
+ */
172
+ /**
173
+ * File extensions to treat as pages
174
+ */
117
175
  var PAGE_EXTENSIONS = [
118
176
  ".tsx",
119
177
  ".jsx",
120
178
  ".mdx",
121
179
  ".md"
122
180
  ];
181
+ /**
182
+ * Files/directories to exclude from routing
183
+ *
184
+ * Only exclude common non-page folders at the ROOT of pages directory.
185
+ * Nested folders like /docs/components/ are valid routes.
186
+ */
123
187
  var EXCLUDED_PATTERNS = [
124
188
  "components/**",
125
189
  "hooks/**",
@@ -129,6 +193,9 @@ var EXCLUDED_PATTERNS = [
129
193
  "**/*.test.*",
130
194
  "**/*.spec.*"
131
195
  ];
196
+ /**
197
+ * Scan pages directory and generate routes
198
+ */
132
199
  async function scanPages(config, root) {
133
200
  const pagesDir = path.resolve(root, config.pages || "src/pages");
134
201
  const files = await fg(PAGE_EXTENSIONS.map((ext) => `**/*${ext}`), {
@@ -144,6 +211,9 @@ async function scanPages(config, root) {
144
211
  }
145
212
  return sortRoutes(routes);
146
213
  }
214
+ /**
215
+ * Convert a file path to a route definition with frontmatter metadata
216
+ */
147
217
  async function fileToRouteWithMeta(filePath, pagesDir) {
148
218
  const route = fileToRoute(filePath, pagesDir);
149
219
  if (!route) return null;
@@ -154,6 +224,9 @@ async function fileToRouteWithMeta(filePath, pagesDir) {
154
224
  } catch (err) {}
155
225
  return route;
156
226
  }
227
+ /**
228
+ * Convert a file path to a route definition
229
+ */
157
230
  function fileToRoute(filePath, pagesDir) {
158
231
  const ext = path.extname(filePath);
159
232
  let routePath = filePath.slice(0, -ext.length);
@@ -166,6 +239,14 @@ function fileToRoute(filePath, pagesDir) {
166
239
  name
167
240
  };
168
241
  }
242
+ /**
243
+ * Convert file path patterns to route path patterns
244
+ *
245
+ * Examples:
246
+ * blog/[slug] -> /blog/:slug
247
+ * docs/[...path] -> /docs/*path
248
+ * users/[id]/posts -> /users/:id/posts
249
+ */
169
250
  function filePathToRoutePath(filePath) {
170
251
  return filePath.split("/").map((segment) => {
171
252
  if (segment.startsWith("[...") && segment.endsWith("]")) return `*${segment.slice(4, -1)}`;
@@ -175,10 +256,29 @@ function filePathToRoutePath(filePath) {
175
256
  return segment;
176
257
  }).join("/");
177
258
  }
259
+ /**
260
+ * Convert route path to a route name
261
+ *
262
+ * Examples:
263
+ * / -> index
264
+ * /about -> about
265
+ * /blog/:slug -> blog-slug
266
+ * /docs/*path -> docs-path
267
+ */
178
268
  function pathToRouteName(routePath) {
179
269
  if (routePath === "/" || routePath === "") return "index";
180
270
  return routePath.replace(/^\//, "").replace(/\//g, "-").replace(/:/g, "").replace(/\*/g, "").replace(/\?/g, "").replace(/-+/g, "-").replace(/-$/, "");
181
271
  }
272
+ /**
273
+ * Sort routes by specificity
274
+ *
275
+ * Order:
276
+ * 1. Static routes (most specific)
277
+ * 2. Dynamic param routes
278
+ * 3. Catch-all routes (least specific)
279
+ *
280
+ * Within each category, longer paths come first
281
+ */
182
282
  function sortRoutes(routes) {
183
283
  return routes.sort((a, b) => {
184
284
  const scoreA = getRouteScore(a.path);
@@ -187,6 +287,9 @@ function sortRoutes(routes) {
187
287
  return b.path.length - a.path.length;
188
288
  });
189
289
  }
290
+ /**
291
+ * Calculate specificity score for a route
292
+ */
190
293
  function getRouteScore(routePath) {
191
294
  const segments = routePath.split("/").filter(Boolean);
192
295
  let score = 0;
@@ -195,9 +298,15 @@ function getRouteScore(routePath) {
195
298
  else score += 100;
196
299
  return score;
197
300
  }
301
+ /**
302
+ * Check if a route has dynamic segments
303
+ */
198
304
  function isDynamicRoute(route) {
199
305
  return route.path.includes(":") || route.path.includes("*");
200
306
  }
307
+ /**
308
+ * Extract parameter names from a route path
309
+ */
201
310
  function extractParams(routePath) {
202
311
  const params = [];
203
312
  const segments = routePath.split("/");
@@ -205,6 +314,9 @@ function extractParams(routePath) {
205
314
  else if (segment.startsWith("*")) params.push(segment.slice(1));
206
315
  return params;
207
316
  }
317
+ /**
318
+ * Generate all static paths for a route using getStaticPaths
319
+ */
208
320
  function expandDynamicRoute(route, staticPaths) {
209
321
  const paths = [];
210
322
  for (const { params } of staticPaths) {
@@ -217,11 +329,22 @@ function expandDynamicRoute(route, staticPaths) {
217
329
  }
218
330
  return paths;
219
331
  }
220
- const VIRTUAL_ROUTES_ID = "virtual:ssg-routes";
221
- const RESOLVED_VIRTUAL_ROUTES_ID = "\0" + VIRTUAL_ROUTES_ID;
332
+ //#endregion
333
+ //#region src/routing/virtual.ts
334
+ /**
335
+ * Virtual module ID for SSG routes
336
+ */
337
+ var VIRTUAL_ROUTES_ID = "virtual:ssg-routes";
338
+ var RESOLVED_VIRTUAL_ROUTES_ID = "\0" + VIRTUAL_ROUTES_ID;
339
+ /**
340
+ * Normalize file path to use forward slashes (for ES module imports)
341
+ */
222
342
  function normalizePath$1(filePath) {
223
343
  return filePath.replace(/\\/g, "/");
224
344
  }
345
+ /**
346
+ * Generate the virtual routes module code
347
+ */
225
348
  function generateRoutesModule(routes, config) {
226
349
  const imports = [];
227
350
  const routeDefinitions = [];
@@ -252,6 +375,9 @@ const routes = [${routeDefinitions.join(",")}
252
375
  export default routes;
253
376
  `;
254
377
  }
378
+ /**
379
+ * Generate lazy-loading routes module (for development with HMR)
380
+ */
255
381
  function generateLazyRoutesModule(routes, config) {
256
382
  const imports = [];
257
383
  const routeDefinitions = [];
@@ -281,6 +407,16 @@ const routes = [${routeDefinitions.join(",")}
281
407
  export default routes;
282
408
  `;
283
409
  }
410
+ //#endregion
411
+ //#region src/routing/navigation.ts
412
+ /**
413
+ * Generate navigation structure from routes for a specific collection path
414
+ *
415
+ * @param routes - Scanned SSG routes
416
+ * @param collectionPath - Path prefix for the collection (e.g., '/docs')
417
+ * @param showDrafts - Whether to show draft pages
418
+ * @param isDev - Whether running in development mode
419
+ */
284
420
  function generateNavigation(routes, collectionPath, showDrafts, isDev) {
285
421
  const navRoutes = routes.filter((route) => {
286
422
  if (!route.path.startsWith(collectionPath)) return false;
@@ -312,6 +448,9 @@ function generateNavigation(routes, collectionPath, showDrafts, isDev) {
312
448
  }
313
449
  return { sidebar: buildSections(context) };
314
450
  }
451
+ /**
452
+ * Add an item to a nested category structure
453
+ */
315
454
  function addToCategory(categories, path, item) {
316
455
  if (path.length === 0) return;
317
456
  const [first, ...rest] = path;
@@ -328,6 +467,10 @@ function addToCategory(categories, path, item) {
328
467
  if (rest.length === 0) category.items.push(item);
329
468
  else addToCategory(category.children, rest, item);
330
469
  }
470
+ /**
471
+ * Common section ordering for navigation
472
+ * Used for both top-level sections and nested categories
473
+ */
331
474
  var SECTION_ORDER = {
332
475
  "Getting Started": 10,
333
476
  "Core Concepts": 20,
@@ -349,10 +492,16 @@ var SECTION_ORDER = {
349
492
  "Mockup": 160,
350
493
  "Other": 999
351
494
  };
495
+ /**
496
+ * Get sort order for a section/category title
497
+ */
352
498
  function getSectionOrder(title, explicitOrder) {
353
499
  if (explicitOrder !== void 0) return explicitOrder;
354
500
  return SECTION_ORDER[title] ?? 50;
355
501
  }
502
+ /**
503
+ * Build NavSection array from context
504
+ */
356
505
  function buildSections(context) {
357
506
  const sections = [];
358
507
  for (const [, category] of context.categories) sections.push(buildSection(category));
@@ -370,6 +519,9 @@ function buildSections(context) {
370
519
  });
371
520
  return sections;
372
521
  }
522
+ /**
523
+ * Build a NavSection from a category
524
+ */
373
525
  function buildSection(category) {
374
526
  const sortedItems = category.items.sort((a, b) => a.order - b.order || a.title.localeCompare(b.title)).map((item) => ({
375
527
  title: item.title,
@@ -388,6 +540,9 @@ function buildSection(category) {
388
540
  order: category.order
389
541
  };
390
542
  }
543
+ /**
544
+ * Build a nested NavItem from a child category
545
+ */
391
546
  function buildNestedSection(category) {
392
547
  const sortedItems = category.items.sort((a, b) => a.order - b.order || a.title.localeCompare(b.title)).map((item) => ({
393
548
  title: item.title,
@@ -402,10 +557,24 @@ function buildNestedSection(category) {
402
557
  order: category.order
403
558
  };
404
559
  }
560
+ /**
561
+ * Convert route path to a display title
562
+ *
563
+ * @example '/docs/getting-started' -> 'Getting Started'
564
+ * @example '/docs/api/router' -> 'Router'
565
+ */
405
566
  function routeToTitle(routePath) {
406
567
  const segments = routePath.split("/").filter(Boolean);
407
568
  return (segments[segments.length - 1] || "Home").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
408
569
  }
570
+ /**
571
+ * Generate navigation for all collections
572
+ *
573
+ * @param routes - Scanned SSG routes
574
+ * @param config - SSG configuration
575
+ * @param isDev - Whether running in development mode
576
+ * @returns Record mapping collection names to their navigation
577
+ */
409
578
  function generateAllCollections(routes, config, isDev) {
410
579
  const collections = config.collections || {};
411
580
  const result = {};
@@ -415,8 +584,20 @@ function generateAllCollections(routes, config, isDev) {
415
584
  }
416
585
  return result;
417
586
  }
418
- const VIRTUAL_NAVIGATION_ID = "virtual:ssg-navigation";
419
- const RESOLVED_VIRTUAL_NAVIGATION_ID = "\0" + VIRTUAL_NAVIGATION_ID;
587
+ //#endregion
588
+ //#region src/routing/virtual-navigation.ts
589
+ /**
590
+ * Virtual module ID for SSG navigation
591
+ */
592
+ var VIRTUAL_NAVIGATION_ID = "virtual:ssg-navigation";
593
+ var RESOLVED_VIRTUAL_NAVIGATION_ID = "\0" + VIRTUAL_NAVIGATION_ID;
594
+ /**
595
+ * Generate the virtual navigation module code
596
+ *
597
+ * @param routes - Scanned SSG routes with metadata
598
+ * @param config - SSG configuration
599
+ * @param isDev - Whether running in development mode
600
+ */
420
601
  function generateNavigationModule(routes, config, isDev) {
421
602
  const navigation = generateAllCollections(routes, config, isDev);
422
603
  const collections = config.collections || {};
@@ -478,7 +659,21 @@ export function getSidebar(name) {
478
659
  export default { navigation, collections, getCollectionNav, detectCollection, getSidebar };
479
660
  `;
480
661
  }
662
+ //#endregion
663
+ //#region src/layouts/resolver.ts
664
+ /**
665
+ * Layout resolver
666
+ *
667
+ * Resolves layouts from local layouts directory or theme packages.
668
+ * Layouts wrap page content and provide consistent structure.
669
+ */
670
+ /**
671
+ * Layout extensions to search for
672
+ */
481
673
  var LAYOUT_EXTENSIONS = [".tsx", ".jsx"];
674
+ /**
675
+ * Scan local layouts directory
676
+ */
482
677
  async function scanLocalLayouts(config, root) {
483
678
  const layoutsDir = path.resolve(root, config.layouts || "src/layouts");
484
679
  if (!fsSync.existsSync(layoutsDir)) return [];
@@ -495,6 +690,9 @@ async function scanLocalLayouts(config, root) {
495
690
  };
496
691
  });
497
692
  }
693
+ /**
694
+ * Load layouts from a theme package
695
+ */
498
696
  async function loadThemeLayouts(themeName, root) {
499
697
  try {
500
698
  const { createRequire } = await import("node:module");
@@ -515,6 +713,13 @@ async function loadThemeLayouts(themeName, root) {
515
713
  return [];
516
714
  }
517
715
  }
716
+ /**
717
+ * Discover all available layouts
718
+ *
719
+ * Priority order:
720
+ * 1. Local layouts (can override theme)
721
+ * 2. Theme layouts
722
+ */
518
723
  async function discoverLayouts(config, root) {
519
724
  const layouts = /* @__PURE__ */ new Map();
520
725
  if (config.theme) {
@@ -525,11 +730,22 @@ async function discoverLayouts(config, root) {
525
730
  for (const layout of localLayouts) layouts.set(layout.name, layout);
526
731
  return Array.from(layouts.values());
527
732
  }
528
- const VIRTUAL_LAYOUTS_ID = "virtual:generated-layouts";
529
- const RESOLVED_VIRTUAL_LAYOUTS_ID = "\0virtual:generated-layouts";
733
+ //#endregion
734
+ //#region src/layouts/virtual.ts
735
+ /**
736
+ * Virtual module ID for generated layouts
737
+ */
738
+ var VIRTUAL_LAYOUTS_ID = "virtual:generated-layouts";
739
+ var RESOLVED_VIRTUAL_LAYOUTS_ID = "\0virtual:generated-layouts";
740
+ /**
741
+ * Normalize file path to use forward slashes (for ES module imports)
742
+ */
530
743
  function normalizePath(filePath) {
531
744
  return filePath.replace(/\\/g, "/");
532
745
  }
746
+ /**
747
+ * Generate the virtual layouts module code
748
+ */
533
749
  function generateLayoutsModule(layouts, config) {
534
750
  const imports = [];
535
751
  const layoutEntries = [];
@@ -805,11 +1021,26 @@ export function setupLayouts(routes) {
805
1021
  export default layouts;
806
1022
  `;
807
1023
  }
808
- const VIRTUAL_CLIENT_ID = "virtual:ssg-client";
809
- const RESOLVED_VIRTUAL_CLIENT_ID = "\0" + VIRTUAL_CLIENT_ID + ".tsx";
810
- const SSG_CLIENT_ENTRY_PATH = "/@ssg/client.tsx";
811
- const VIRTUAL_SERVER_ID = "virtual:ssg-server";
812
- const RESOLVED_VIRTUAL_SERVER_ID = "\0" + VIRTUAL_SERVER_ID + ".tsx";
1024
+ //#endregion
1025
+ //#region src/vite/virtual-entries.ts
1026
+ /**
1027
+ * Virtual Entry Point Generation
1028
+ *
1029
+ * Generates auto-configured client and server entry points for zero-config mode.
1030
+ * Users can override by providing their own entry files.
1031
+ */
1032
+ /**
1033
+ * Virtual module IDs
1034
+ * Note: We use a special path format that Vite can resolve from HTML
1035
+ */
1036
+ var VIRTUAL_CLIENT_ID = "virtual:ssg-client";
1037
+ var RESOLVED_VIRTUAL_CLIENT_ID = "\0" + VIRTUAL_CLIENT_ID + ".tsx";
1038
+ var SSG_CLIENT_ENTRY_PATH = "/@ssg/client.tsx";
1039
+ var VIRTUAL_SERVER_ID = "virtual:ssg-server";
1040
+ var RESOLVED_VIRTUAL_SERVER_ID = "\0" + VIRTUAL_SERVER_ID + ".tsx";
1041
+ /**
1042
+ * Detect if user has custom entry points
1043
+ */
813
1044
  function detectCustomEntries(root, config) {
814
1045
  const clientPaths = [
815
1046
  "src/main.tsx",
@@ -871,6 +1102,9 @@ function detectCustomEntries(root, config) {
871
1102
  globalCssPath
872
1103
  };
873
1104
  }
1105
+ /**
1106
+ * Generate virtual client entry code
1107
+ */
874
1108
  function generateClientEntry(config, detection) {
875
1109
  const cssImport = detection.globalCssPath ? `import '${detection.globalCssPath}';\n` : "";
876
1110
  const additionalImports = (config.clientImports ?? []).map((imp) => `import '${imp}';`).join("\n");
@@ -914,6 +1148,9 @@ ${prefetchEnabled ? `// Enable link prefetching for faster navigation
914
1148
  setupPrefetch({ delay: ${prefetchDelay} });` : ""}
915
1149
  `;
916
1150
  }
1151
+ /**
1152
+ * Generate virtual server entry code
1153
+ */
917
1154
  function generateServerEntry(config) {
918
1155
  return `/**
919
1156
  * Auto-generated server entry point
@@ -948,6 +1185,9 @@ export async function render(url, context) {
948
1185
  }
949
1186
  `;
950
1187
  }
1188
+ /**
1189
+ * Generate virtual HTML template
1190
+ */
951
1191
  function generateHtmlTemplate(config) {
952
1192
  const site = config.site || {};
953
1193
  const lang = site.lang || "en";
@@ -998,6 +1238,10 @@ function generateHtmlTemplate(config) {
998
1238
  </html>
999
1239
  `;
1000
1240
  }
1241
+ /**
1242
+ * Generate HTML template for production build
1243
+ * Uses the actual client entry path instead of virtual module
1244
+ */
1001
1245
  function generateProductionHtmlTemplate(config, clientEntryPath) {
1002
1246
  const site = config.site || {};
1003
1247
  const lang = site.lang || "en";
@@ -1048,6 +1292,7 @@ function generateProductionHtmlTemplate(config, clientEntryPath) {
1048
1292
  </html>
1049
1293
  `;
1050
1294
  }
1295
+ //#endregion
1051
1296
  export { resolveConfigPaths as A, extractParams as C, parseFrontmatter as D, extractTitleFromContent as E, defineSSGConfig as O, expandDynamicRoute as S, scanPages as T, generateNavigationModule as _, VIRTUAL_SERVER_ID as a, generateLazyRoutesModule as b, generateHtmlTemplate as c, RESOLVED_VIRTUAL_LAYOUTS_ID as d, VIRTUAL_LAYOUTS_ID as f, VIRTUAL_NAVIGATION_ID as g, RESOLVED_VIRTUAL_NAVIGATION_ID as h, VIRTUAL_CLIENT_ID as i, loadConfig as k, generateProductionHtmlTemplate as l, discoverLayouts as m, RESOLVED_VIRTUAL_SERVER_ID as n, detectCustomEntries as o, generateLayoutsModule as p, SSG_CLIENT_ENTRY_PATH as r, generateClientEntry as s, RESOLVED_VIRTUAL_CLIENT_ID as t, generateServerEntry as u, RESOLVED_VIRTUAL_ROUTES_ID as v, isDynamicRoute as w, generateRoutesModule as x, VIRTUAL_ROUTES_ID as y };
1052
1297
 
1053
- //# sourceMappingURL=virtual-entries-sVkqKMAM.js.map
1298
+ //# sourceMappingURL=virtual-entries-TuNN2It1.js.map