feat: migrate Hugo Bootstrap theme to latest Hugo with Tailwind CSS and refactor codebase
* replace Bootstrap-based styling with Tailwind CSS * update theme compatibility for latest Hugo version * refactor templates and partials * fix outdated code and broken components * improve project structure and maintainability * optimize styling and frontend build setup
This commit is contained in:
217
scripts/themeGenerator.js
Normal file
217
scripts/themeGenerator.js
Normal file
@@ -0,0 +1,217 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user