Rustelo/scripts/build/build-theme.js

193 lines
5.4 KiB
JavaScript
Raw Normal View History

2026-02-08 20:18:46 +00:00
#!/usr/bin/env node
/**
* Theme Build Script
2026-02-08 20:37:49 +00:00
*
2026-02-08 20:18:46 +00:00
* This script generates CSS variables from TOML theme configurations.
* It can be run manually or integrated into the build pipeline.
*/
const fs = require('fs');
const path = require('path');
// Simple TOML parser for basic key-value pairs
function parseSimpleToml(content) {
const result = {};
let currentSection = null;
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
const lines = content.split('\n');
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
for (const line of lines) {
const trimmed = line.trim();
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Skip empty lines and comments
if (!trimmed || trimmed.startsWith('#')) continue;
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Section headers [section]
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
currentSection = trimmed.slice(1, -1);
if (!result[currentSection]) {
result[currentSection] = {};
}
continue;
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Key-value pairs
if (trimmed.includes('=')) {
const [key, ...valueParts] = trimmed.split('=');
let value = valueParts.join('=').trim();
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Handle quoted values vs unquoted values
if (value.startsWith('"') && value.includes('"', 1)) {
// Extract value between first and last quotes, ignoring comments after closing quote
const firstQuote = value.indexOf('"');
const lastQuote = value.indexOf('"', firstQuote + 1);
if (lastQuote !== -1) {
value = value.substring(firstQuote + 1, lastQuote);
}
} else {
// For unquoted values, remove inline comments
if (value.includes('#')) {
value = value.split('#')[0].trim();
}
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
if (currentSection) {
result[currentSection][key.trim()] = value;
} else {
result[key.trim()] = value;
}
}
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
return result;
}
// Generate CSS variables from theme config
function generateCssVariables(themeConfig) {
let css = `:root {\n`;
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Colors
if (themeConfig.colors) {
css += ` /* Colors */\n`;
for (const [key, value] of Object.entries(themeConfig.colors)) {
const cssVar = key.replace(/_/g, '-');
css += ` --color-${cssVar}: ${value};\n`;
}
css += `\n`;
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Typography
if (themeConfig.typography) {
css += ` /* Typography */\n`;
for (const [key, value] of Object.entries(themeConfig.typography)) {
const cssVar = key.replace(/_/g, '-');
css += ` --${cssVar}: ${value};\n`;
}
css += `\n`;
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Spacing
if (themeConfig.spacing) {
css += ` /* Spacing */\n`;
for (const [key, value] of Object.entries(themeConfig.spacing)) {
const cssVar = key.replace(/_/g, '-');
css += ` --space-${cssVar}: ${value};\n`;
}
css += `\n`;
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Border Radius
if (themeConfig.radius) {
css += ` /* Border Radius */\n`;
for (const [key, value] of Object.entries(themeConfig.radius)) {
const cssVar = key.replace(/_/g, '-');
css += ` --radius-${cssVar}: ${value};\n`;
}
css += `\n`;
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Component specific
if (themeConfig.components) {
css += ` /* Component Tokens */\n`;
if (themeConfig.components.button) {
css += ` --btn-border-radius: ${themeConfig.components.button.border_radius};\n`;
}
if (themeConfig.components.card) {
css += ` --card-border-radius: ${themeConfig.components.card.border_radius};\n`;
}
if (themeConfig.components.input) {
css += ` --input-border-radius: ${themeConfig.components.input.border_radius};\n`;
}
css += `\n`;
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Animations
if (themeConfig.animations) {
css += ` /* Animations */\n`;
for (const [key, value] of Object.entries(themeConfig.animations)) {
const cssVar = key.replace(/_/g, '-');
css += ` --${cssVar}: ${value};\n`;
}
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
css += `}\n`;
return css;
}
// Main function
function buildTheme(themeName = 'default') {
try {
console.log(`Building theme: ${themeName}`);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Read theme TOML file from new assets location
const themePath = path.join(__dirname, '..', 'assets', 'styles', 'themes', `${themeName}.toml`);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
if (!fs.existsSync(themePath)) {
console.error(`Theme file not found: ${themePath}`);
process.exit(1);
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
const themeContent = fs.readFileSync(themePath, 'utf8');
const themeConfig = parseSimpleToml(themeContent);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Generate CSS
const css = generateCssVariables(themeConfig);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Write CSS file
const outputPath = path.join(__dirname, '..', 'public', 'styles', `theme-${themeName}.css`);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Ensure directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Write file with header
const header = `/* Theme Variables - ${themeName} */\n/* Generated from ${path.basename(themePath)} */\n/* Do not edit manually */\n\n`;
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
fs.writeFileSync(outputPath, header + css);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
console.log(`✅ Theme built successfully: ${outputPath}`);
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
// Also update the main theme variables file if this is the default theme
if (themeName === 'default') {
const mainThemePath = path.join(__dirname, '..', 'public', 'styles', 'theme-variables.css');
fs.writeFileSync(mainThemePath, header + css);
console.log(`✅ Updated main theme variables: ${mainThemePath}`);
}
2026-02-08 20:37:49 +00:00
2026-02-08 20:18:46 +00:00
} catch (error) {
console.error('Error building theme:', error.message);
process.exit(1);
}
}
// CLI handling
if (require.main === module) {
const themeName = process.argv[2] || 'default';
buildTheme(themeName);
}
2026-02-08 20:37:49 +00:00
module.exports = { buildTheme, generateCssVariables, parseSimpleToml };