@zyrab/domo-ssg 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zyrab/domo-ssg",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A Static Site Generator (SSG) for Domo-based projects.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/config.js CHANGED
@@ -1,37 +1,44 @@
1
1
  // src/config.js
2
2
  import path from "path";
3
+ import { pathToFileURL } from "url";
4
+
5
+ let mergedConfig = null;
6
+
7
+ export async function loadConfig() {
8
+ if (mergedConfig) return mergedConfig;
9
+ const userConfigPath = path.resolve(process.cwd(), "domo.config.js");
3
10
 
4
- /**
5
- * Loads and processes the SSG configuration.
6
- * @param {string} configFilePath - Absolute path to the user's config file.
7
- * @returns {Promise<object>} The resolved configuration object.
8
- */
9
- export async function loadConfig(configFilePath) {
10
11
  // Default configuration values
11
12
  const defaultConfig = {
12
13
  outDir: "./dist",
13
- routesFile: "./routes.js", // Assuming a common name for routes
14
- layout: "./layout.js", // Assuming a common name for layout
14
+ routesFile: "./routes.js",
15
+ layout: "./layout.js",
16
+ lang: "en",
17
+ author: "Domo",
15
18
  exclude: ["css", "js", "assets", "robots.txt", "admin"],
16
- baseUrl: "http://localhost:3000", // Default base URL for sitemap
19
+ baseUrl: "http://localhost:3000",
17
20
  };
18
21
 
19
22
  let userConfig = {};
20
23
  try {
21
- const importedConfig = await import(configFilePath);
24
+ const importedConfig = await import(pathToFileURL(userConfigPath).href);
22
25
  userConfig = importedConfig.default || importedConfig;
23
26
  } catch (error) {
24
27
  console.warn(`⚠️ No custom config file found at ${configFilePath}. Using default settings.`);
25
28
  }
29
+ mergedConfig = {
30
+ ...defaultConfig,
31
+ ...userConfig,
32
+ outDir: path.resolve(process.cwd(), userConfig.outDir || defaultConfig.outDir),
33
+ routesFile: path.resolve(process.cwd(), userConfig.routesFile || defaultConfig.routesFile),
34
+ layout: path.resolve(process.cwd(), userConfig.layout || defaultConfig.layout),
35
+ };
36
+ return mergedConfig;
37
+ }
26
38
 
27
- // Merge user config with defaults
28
- const mergedConfig = { ...defaultConfig, ...userConfig };
29
-
30
- // Resolve paths relative to the current working directory of the build script
31
- // This assumes the config file itself specifies paths relative to its own location
32
- mergedConfig.outDir = path.resolve(process.cwd(), mergedConfig.outDir);
33
- mergedConfig.routesFile = path.resolve(process.cwd(), mergedConfig.routesFile);
34
- mergedConfig.layout = path.resolve(process.cwd(), mergedConfig.layout);
35
-
39
+ export function getConfig() {
40
+ if (!mergedConfig) {
41
+ throw new Error("Config has not been loaded yet. Call loadConfig() first.");
42
+ }
36
43
  return mergedConfig;
37
44
  }
package/src/index.js CHANGED
@@ -1,19 +1,12 @@
1
- // src/index.js (formerly build.mjs)
1
+ // src/index.js
2
2
  import { pathToFileURL } from "url";
3
- import path from "path";
4
3
  import { loadConfig } from "./config.js";
5
4
  import { cleanOutputDir } from "./file-utils.js";
6
5
  import { generateSitemap } from "./sitemap.js";
7
6
  import { buildRoutes } from "./route-traversal.js";
8
7
 
9
- // __filename and __dirname equivalents for ES Modules
10
-
11
8
  async function main() {
12
- // Determine where the user's config file should be
13
- // Assumes config is at the root of the project where the script is run
14
- const userConfigPath = path.resolve(process.cwd(), "domo.config.js");
15
-
16
- const config = await loadConfig(pathToFileURL(userConfigPath).href);
9
+ const config = await loadConfig();
17
10
 
18
11
  // Import layout and route tree using pathToFileURL and .href for dynamic imports
19
12
  const { routes } = await import(pathToFileURL(config.routesFile).href);
@@ -28,7 +21,7 @@ async function main() {
28
21
  cleanOutputDir(config.outDir, config.exclude);
29
22
 
30
23
  // 2. Build all routes recursively
31
- await buildRoutes(routes, "", {}, renderLayout, config.outDir);
24
+ await buildRoutes(routes, renderLayout);
32
25
 
33
26
  // 3. Generate sitemap
34
27
  generateSitemap(config.outDir, config.baseUrl, config.exclude);
@@ -1,5 +1,6 @@
1
1
  // src/route-handler.js
2
2
  import Router from "@zyrab/domo-router";
3
+ import { getConfig } from "./config.js";
3
4
  import { writeHTML } from "./file-utils.js";
4
5
  import { writeJs } from "./event-utils.js";
5
6
 
@@ -16,7 +17,6 @@ export function joinPaths(...segments) {
16
17
 
17
18
  return "/" + pathStr.replace(/\/+/g, "/");
18
19
  }
19
-
20
20
  /**
21
21
  * Handles the rendering and writing of a single route's HTML file.
22
22
  * @param {object} routeConfig - Configuration for the current route.
@@ -26,42 +26,55 @@ export function joinPaths(...segments) {
26
26
  * @param {string} [routeConfig.script] - Optional script to include in the layout.
27
27
  * @param {object} [routeConfig.meta={}] - Optional metadata for the page (title, description).
28
28
  * @param {Function} renderLayout - The layout rendering function from the user's config.
29
- * @param {string} outputDir - The base output directory for generated files.
30
29
  * @returns {Promise<void>}
31
30
  */
32
31
 
33
- export async function handleRoute({ path, props, component, script, meta = {} }, renderLayout, outputDir) {
32
+ export async function handleRoute({ path, props, component, scripts, styles, fonts, meta = {} }, renderLayout) {
33
+ const config = getConfig();
34
34
  try {
35
35
  // Set router info for server-side context
36
36
  Router.setInfo(path, props);
37
37
 
38
- // Calculate base depth for relative paths in layout if needed
39
- const baseDepth = path === "/" ? 0 : path.split("/").filter(Boolean).length;
40
-
41
38
  // Render the component content
42
39
  const content = await component(props);
43
40
 
44
41
  // --- Write JS file ---
45
- const eScript = writeJs(content, outputDir, path);
46
- let allScript = [];
47
- if (Array.isArray(script) && script.length > 0) {
48
- allScript.push(...script);
49
- }
50
- if (eScript && typeof eScript === "string" && eScript.trim() !== "") {
51
- allScript.push(eScript);
52
- }
42
+ const embededScript = writeJs(content, config.outDir, path);
53
43
 
44
+ const fontPaths = normalizeAssets([fonts, config.assets.fonts]);
45
+ const stylePaths = normalizeAssets([styles, config.assets.styles]);
46
+ const scriptPaths = normalizeAssets([embededScript, scripts, config.assets.scripts]);
54
47
  // Render the full HTML layout
55
48
  const html = await renderLayout(content, {
56
49
  title: meta.title || "",
57
50
  description: meta.description || "",
58
- script: allScript,
59
- baseDepth,
51
+ descriptionOG: meta.descriptionOG,
52
+ canonical: meta.canonical,
53
+ type: meta.type,
54
+ scripts: scriptPaths,
55
+ styles: stylePaths,
56
+ fonts: fontPaths,
57
+ favicon: config?.assets?.favicon,
58
+ baseUrl: config?.baseUrl,
59
+ lang: config?.lang,
60
+ author: config?.author,
61
+ theme: config?.theme,
60
62
  });
61
63
 
62
64
  // Write the generated HTML to a file
63
- writeHTML(outputDir, path, html);
65
+ writeHTML(config.outDir, path, html);
64
66
  } catch (e) {
65
67
  console.warn(`⚠️ Error rendering ${path}:\n${e.stack}`);
66
68
  }
67
69
  }
70
+ function normalizeAssets(arr) {
71
+ let result = [];
72
+ for (const el of arr) {
73
+ if (Array.isArray(el) && el.length > 0) {
74
+ result.push(...el);
75
+ } else if (typeof el === "string" && el.trim() !== "") {
76
+ result.push(el);
77
+ }
78
+ }
79
+ return result;
80
+ }
@@ -4,77 +4,58 @@ import { handleRoute, joinPaths } from "./route-handler.js";
4
4
  /**
5
5
  * Recursively builds HTML files for all defined routes, including nested and dynamic routes.
6
6
  * @param {object} routes - The route configuration object.
7
+ * @param {Function} renderLayout - The layout rendering function.
7
8
  * @param {string} [parentPath=""] - The path accumulated from parent routes.
8
9
  * @param {object} [props={}] - Accumulated properties from parent dynamic routes.
9
- * @param {Function} renderLayout - The layout rendering function.
10
- * @param {string} outputDir - The base output directory.
11
- * @returns {Promise<void>}
12
10
  */
13
- export async function buildRoutes(routes, parentPath = "", props = {}, renderLayout, outputDir) {
14
- for (const routeKey in routes) {
15
- const r = routes[routeKey];
11
+ export async function buildRoutes(routes, renderLayout, parentPath = "", props = {}) {
12
+ for (const routeSegment in routes) {
13
+ const routeNode = routes[routeSegment];
16
14
  // Skip '/' if it's a child of another path (handled by parent's segment)
17
- if (parentPath !== "" && routeKey === "/") continue;
15
+ if (parentPath !== "" && routeSegment === "/") continue;
18
16
 
19
- // Handle dynamic routes with getDinamicList
20
- if (r.getDinamicList) {
17
+ const currentRoute = joinPaths(parentPath, routeSegment);
18
+ // Handle dynamic routes with routeParams
19
+ if (routeNode.routeParams) {
21
20
  try {
22
21
  // Extract parameter name from dynamic segment (e.g., ":slug" -> "slug")
23
- const paramKey = routeKey.split(":").filter(Boolean).pop();
22
+ const paramName = routeSegment.split(":").filter(Boolean).pop();
24
23
  // The last segment of the parent path is the slug for nested dynamic lists
25
- const slugParam = parentPath.split("/").filter(Boolean).pop();
24
+ const parentRouteName = parentPath.split("/").filter(Boolean).pop();
26
25
 
27
- const list = await r.getDinamicList(slugParam);
26
+ const resolvedParams = await routeNode.routeParams(parentRouteName);
28
27
 
29
- if (!Array.isArray(list) || list.length === 0) {
30
- console.warn(`⚠️ No items returned for dynamic route at ${joinPaths(parentPath, routeKey)}`);
28
+ if (!Array.isArray(resolvedParams) || resolvedParams.length === 0) {
29
+ console.warn(`⚠️ No items returned for dynamic route at ${currentRoute}`);
31
30
  continue;
32
31
  }
33
32
 
34
- for (const item of list) {
35
- if (!item[paramKey]) {
36
- console.warn(
37
- `⚠️ Missing required parameter '${paramKey}' in item for dynamic route at ${joinPaths(
38
- parentPath,
39
- routeKey
40
- )}`
41
- );
33
+ for (const item of resolvedParams) {
34
+ if (!item[paramName]) {
35
+ console.warn(`⚠️ Missing required parameter '${paramName}' in item for dynamic route at ${currentRoute}`);
42
36
  continue;
43
37
  }
44
38
 
45
- const segment = item[paramKey]; // Use the actual value from the item for the URL segment
46
- const meta = { title: item.title, description: item.description, ...item.meta }; // Merge item's meta with route's meta
39
+ const segment = item[paramName]; // Use the actual value from the item for the URL segment
40
+ const meta = {
41
+ title: item.title,
42
+ description: item.description,
43
+ type: item?.type,
44
+ canonical: item?.canonical,
45
+ ogImage: item?.ogImage,
46
+ descriptionOG: item?.descriptionOG,
47
+ }; // Merge item's meta with route's meta
47
48
  const routePath = joinPaths(parentPath, segment); // Full path for this specific dynamic item
48
- const childProps = { ...props, [paramKey]: segment, itemData: item }; // Pass item data as prop
49
+ const childProps = { ...props, [paramName]: segment, itemData: item }; // Pass item data as prop
49
50
 
50
- if (r.component) {
51
- await handleRoute(
52
- {
53
- path: routePath,
54
- props: childProps,
55
- script: r.script,
56
- component: r.component,
57
- meta,
58
- },
59
- renderLayout,
60
- outputDir
61
- );
62
- } else if (r.children?.["/"]?.component) {
51
+ if (routeNode.component) {
52
+ await handleRoute({ ...routeNode, path: routePath, props: childProps, meta }, renderLayout);
53
+ } else if (routeNode.children?.["/"]?.component) {
63
54
  // If dynamic route has children with a default component
64
- await handleRoute(
65
- {
66
- path: routePath,
67
- props: childProps,
68
- script: r.children["/"].script,
69
- component: r.children["/"].component,
70
- meta: r.children["/"].meta || meta, // Children's meta takes precedence
71
- },
72
- renderLayout,
73
- outputDir
74
- );
55
+ await handleRoute({ ...routeNode.children["/"], path: routePath, props: childProps, meta }, renderLayout);
75
56
  // Recursively build children of this dynamic item if they exist
76
- if (r.children) {
77
- await buildRoutes(r.children, routePath, childProps, renderLayout, outputDir);
57
+ if (routeNode.children) {
58
+ await buildRoutes(routeNode.children, renderLayout, routePath, childProps);
78
59
  }
79
60
  } else {
80
61
  console.warn(
@@ -83,53 +64,27 @@ export async function buildRoutes(routes, parentPath = "", props = {}, renderLay
83
64
  }
84
65
  }
85
66
  } catch (e) {
86
- console.warn(`⚠️ Skipped dynamic route generation for ${joinPaths(parentPath, routeKey)}: ${e.message}`);
67
+ console.warn(`⚠️ Skipped dynamic route generation for ${currentRoute}: ${e.message}`);
87
68
  }
88
69
  continue; // Move to the next route key after handling dynamic list
89
70
  }
90
71
 
91
72
  // Handle static routes with a component
92
- if (r.component) {
93
- const routePath = joinPaths(parentPath, routeKey);
94
- await handleRoute(
95
- {
96
- path: routePath,
97
- props,
98
- script: r.script,
99
- component: r.component,
100
- meta: r.meta,
101
- },
102
- renderLayout,
103
- outputDir
104
- );
73
+ if (routeNode.component) {
74
+ await handleRoute({ path: currentRoute, ...routeNode, props }, renderLayout);
105
75
  // Continue to children if they exist for this static route
106
- if (r.children) {
107
- await buildRoutes(r.children, routePath, { ...props }, renderLayout, outputDir);
76
+ if (routeNode.children) {
77
+ await buildRoutes(routeNode.children, renderLayout, currentRoute, { ...props });
108
78
  }
109
79
  continue;
110
80
  }
111
-
112
- // Handle routes with only children (e.g., a folder route with no direct component, but an index component)
113
- if (r.children) {
114
- const routePath = joinPaths(parentPath, routeKey);
115
- // Render the default child component ('/') for this path if it exists
116
- if (r.children["/"]?.component) {
117
- await handleRoute(
118
- {
119
- path: routePath,
120
- props,
121
- script: r.children["/"].script,
122
- component: r.children["/"].component,
123
- meta: r.children["/"].meta,
124
- },
125
- renderLayout,
126
- outputDir
127
- );
128
- } else {
129
- console.warn(`⚠️ Route at ${routePath} has children but no default component ('/')`);
130
- }
131
- // Recursively build the children
132
- await buildRoutes(r.children, routePath, { ...props }, renderLayout, outputDir);
81
+ // Render the default child component ('/') for this path if it exists
82
+ if (routeNode.children["/"]?.component) {
83
+ await handleRoute({ path: currentRoute, ...routeNode.children["/"], props }, renderLayout);
84
+ } else if (routeNode.children) {
85
+ console.warn(`⚠️ Route at ${currentRoute} has children but no default component ('/')`);
133
86
  }
87
+ // Recursively build the children
88
+ await buildRoutes(routeNode.children, renderLayout, currentRoute, { ...props });
134
89
  }
135
90
  }