const fs = require("fs"); const path = require("path"); /** * Determine the paths based on setup mode (theme vs project) * @returns {Object} Configuration object with paths and mode info */ function determinePaths() { const rootHugoToml = path.join(__dirname, "../hugo.toml"); const exampleSiteHugoToml = path.join(__dirname, "../exampleSite/hugo.toml"); if (fs.existsSync(exampleSiteHugoToml)) { // Theme setup mode - exampleSite structure exists return { hugoTomlPath: exampleSiteHugoToml, themePath: path.join(__dirname, "../exampleSite/data/theme.json"), outputPath: path.join(__dirname, "../assets/css/generated-theme.css"), isThemeSetup: true, }; } else if (fs.existsSync(rootHugoToml)) { // Project setup mode - hugo.toml at root try { const hugoTomlContent = fs.readFileSync(rootHugoToml, "utf8"); const themeNameMatch = hugoTomlContent.match( /^theme\s*=\s*["'\[]?"?([^"'\]]+)"?[\]"]?/m, ); if (!themeNameMatch || !themeNameMatch[1]) { throw new Error("Could not extract theme name from hugo.toml"); } const themeName = themeNameMatch[1]; return { hugoTomlPath: rootHugoToml, themePath: path.join(__dirname, "../data/theme.json"), outputPath: path.join( __dirname, `../themes/${themeName}/assets/css/generated-theme.css`, ), isThemeSetup: false, }; } catch (error) { throw new Error(`Failed to determine paths: ${error.message}`); } } else { throw new Error( "Could not determine setup mode: neither exampleSite/hugo.toml nor root hugo.toml found", ); } } const { themePath, outputPath } = determinePaths(); // Helper to convert color name from snake_case to kebab-case const toKebab = (str) => str.replace(/_/g, "-"); // Helper to extract a clean font name const findFont = (fontStr) => fontStr.replace(/\+/g, " ").replace(/:[^:]+/g, ""); /** * Add color entries to CSS array * @param {Array} cssLines - Array of CSS lines to append to * @param {Object} colors - Color object to process * @param {string} prefix - Optional prefix for color variable names */ function addColorsToCss(cssLines, colors, prefix = "") { Object.entries(colors).forEach(([key, value]) => { const colorName = prefix ? `--color-${prefix}-${toKebab(key)}` : `--color-${toKebab(key)}`; cssLines.push(` ${colorName}: ${value};`); }); } /** * Generate theme CSS from theme.json configuration * @throws {Error} If theme.json is missing or invalid */ function generateThemeCSS() { // Validate that theme.json exists if (!fs.existsSync(themePath)) { throw new Error(`Theme configuration not found: ${themePath}`); } try { // Read and parse theme configuration const themeConfig = JSON.parse(fs.readFileSync(themePath, "utf8")); // Validate required theme structure if (!themeConfig.colors || !themeConfig.fonts) { throw new Error( "Invalid theme.json: missing 'colors' or 'fonts' section", ); } // Build CSS using array for better performance const cssLines = [ "/**", ' * Auto-generated from "data/theme.json"', " * DO NOT EDIT THIS FILE MANUALLY", " * Run: node scripts/themeGenerator.js", " */", "", "@theme {", " /* === Colors === */", ]; // Add default theme colors if (themeConfig.colors.default?.theme_color) { addColorsToCss(cssLines, themeConfig.colors.default.theme_color); } // Add default text colors if (themeConfig.colors.default?.text_color) { addColorsToCss(cssLines, themeConfig.colors.default.text_color); } // Add darkmode colors (if available) if (themeConfig.colors.darkmode) { cssLines.push("", " /* === Darkmode Colors === */"); if (themeConfig.colors.darkmode.theme_color) { addColorsToCss( cssLines, themeConfig.colors.darkmode.theme_color, "darkmode", ); } if (themeConfig.colors.darkmode.text_color) { addColorsToCss( cssLines, themeConfig.colors.darkmode.text_color, "darkmode", ); } } // Add font families cssLines.push("", " /* === Font Families === */"); const fontFamily = themeConfig.fonts.font_family || {}; Object.entries(fontFamily) .filter(([key]) => !key.includes("type")) .forEach(([key, font]) => { const fontFallback = fontFamily[`${key}_type`] || "sans-serif"; const fontValue = `${findFont(font)}, ${fontFallback}`; cssLines.push(` --font-${toKebab(key)}: ${fontValue};`); }); // Add font sizes cssLines.push("", " /* === Font Sizes === */"); const baseSize = Number(themeConfig.fonts.font_size?.base || 16); const scale = Number(themeConfig.fonts.font_size?.scale || 1.25); cssLines.push(` --text-base: ${baseSize}px;`); cssLines.push(` --text-base-sm: ${baseSize * 0.8}px;`); let currentSize = scale; for (let i = 6; i >= 1; i--) { cssLines.push(` --text-h${i}: ${currentSize}rem;`); cssLines.push(` --text-h${i}-sm: ${currentSize * 0.9}rem;`); currentSize *= scale; } cssLines.push("}"); // Ensure output directory exists const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } // Write the file fs.writeFileSync(outputPath, cssLines.join("\n") + "\n"); console.log("āœ… Theme CSS generated successfully at:", outputPath); } catch (error) { throw new Error(`Failed to generate theme CSS: ${error.message}`); } } // Generate CSS on startup try { generateThemeCSS(); } catch (error) { console.error("āŒ Error:", error.message); process.exit(1); } // Check for --watch flag if (process.argv.includes("--watch")) { let debounceTimer; const watcher = fs.watch(themePath, (eventType) => { if (eventType === "change") { // Debounce to avoid multiple triggers clearTimeout(debounceTimer); debounceTimer = setTimeout(() => { try { generateThemeCSS(); } catch (error) { console.error("āŒ Error regenerating theme CSS:", error.message); } }, 300); } }); // Handle graceful shutdown process.on("SIGINT", () => { clearTimeout(debounceTimer); watcher.close(); console.log("\nšŸ‘‹ Watcher stopped"); process.exit(0); }); console.log("šŸ‘ļø Watching for changes to:", themePath); }