#!/usr/bin/env node /** * Dog Calculator Build System - ORGANIZED VERSION * * This build script generates iframe.html and sundog-dog-food-calculator.js * from organized source files in the src/ directory. * * Source structure: * - src/index.html - HTML template * - src/css/main.css - Main styles * - src/css/themes.css - Theme-specific styles * - src/js/config.js - Configuration constants * - src/js/calculator.js - JavaScript functionality * * Output files: * - iframe.html - Standalone calculator page * - sundog-dog-food-calculator.js - Embeddable widget * * Usage: node build.js */ const fs = require('fs'); const path = require('path'); console.log('🎯 Dog Calculator Build System - ORGANIZED VERSION'); console.log(''); /** * Read organized components from src directory */ function readSourceComponents() { const srcDir = 'src'; // Check if src directory exists if (!fs.existsSync(srcDir)) { throw new Error('src directory not found'); } // Read CSS files const mainCssPath = path.join(srcDir, 'css', 'main.css'); const themesCssPath = path.join(srcDir, 'css', 'themes.css'); if (!fs.existsSync(mainCssPath)) { throw new Error('src/css/main.css not found'); } if (!fs.existsSync(themesCssPath)) { throw new Error('src/css/themes.css not found'); } const mainCss = fs.readFileSync(mainCssPath, 'utf8').trim(); const themesCss = fs.readFileSync(themesCssPath, 'utf8').trim(); const css = mainCss + '\n\n' + themesCss; // Read HTML template const htmlPath = path.join(srcDir, 'index.html'); if (!fs.existsSync(htmlPath)) { throw new Error('src/index.html not found'); } const html = fs.readFileSync(htmlPath, 'utf8').trim(); // Read JavaScript files const configPath = path.join(srcDir, 'js', 'config.js'); const calculatorPath = path.join(srcDir, 'js', 'calculator.js'); if (!fs.existsSync(configPath)) { throw new Error('src/js/config.js not found'); } if (!fs.existsSync(calculatorPath)) { throw new Error('src/js/calculator.js not found'); } const config = fs.readFileSync(configPath, 'utf8').trim(); const calculator = fs.readFileSync(calculatorPath, 'utf8').trim(); const js = config + '\n\n' + calculator; return { css, html, js }; } /** * Backup existing files */ function backupFiles() { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filesToBackup = ['iframe.html', 'sundog-dog-food-calculator.js']; filesToBackup.forEach(file => { if (fs.existsSync(file)) { const backupName = `${file}.backup-${timestamp}`; fs.copyFileSync(file, backupName); console.log(`📦 Backed up ${file} to: ${backupName}`); } }); } /** * Generate iframe.html from modular components */ function generateIframe(css, html, js) { const content = ` Dog Calorie Calculator - Canine Nutrition and Wellness ${html} `; return content; } /** * Create production-ready widget JavaScript */ function createWidgetJS(css, html, js) { // Transform the JavaScript from calculator.js to work as a widget // Replace the iframe's DOMContentLoaded listener with widget initialization let transformedJS = js // Replace the iframe class name with widget class name .replace(/class DogCalorieCalculator/g, 'class DogCalorieCalculatorWidget') // Replace document.getElementById with scoped selectors within the widget .replace(/document\.getElementById\('([^']+)'\)/g, 'this.container.querySelector(\'#$1\')') // Replace direct document queries in the class with container-scoped queries .replace(/document\.querySelector\(/g, 'this.container.querySelector(') .replace(/document\.querySelectorAll\(/g, 'this.container.querySelectorAll(') // Remove the DOMContentLoaded listener and class instantiation - we'll handle this in the widget wrapper .replace(/document\.addEventListener\('DOMContentLoaded'.*?\n.*?new DogCalorieCalculator.*?\n.*?\}\);/s, '') // Remove duplicate theme/scale assignments that override options .replace(/this\.theme = this\.getThemeFromURL\(\) \|\| 'system';\s*\n\s*this\.scale = this\.getScaleFromURL\(\) \|\| 1\.0;/g, '') // Add widget initialization methods .replace(/constructor\(\) \{/, `constructor(container, options = {}) { this.container = container; this.options = { theme: options.theme || this.getThemeFromURL() || 'system', scale: options.scale || this.getScaleFromURL() || 1.0, ...options }; this.theme = this.options.theme; this.scale = this.options.scale;`) // Replace the init() method to inject HTML and apply widget settings .replace(/init\(\) \{/, `init() { // Inject the calculator HTML into the container this.container.innerHTML = \`${html}\`; // Apply widget-specific settings this.applyTheme(); this.applyScale(); // Continue with original init logic`) // Remove duplicate applyTheme/applyScale calls .replace(/this\.applyTheme\(\);\s*\n\s*this\.applyScale\(\);\s*\n\s*this\.bindEvents/g, 'this.bindEvents'); // Add widget-specific methods before the class closing brace transformedJS = transformedJS.replace(/(\s+)(\}\s*$)/, `$1 getThemeFromURL() { const urlParams = new URLSearchParams(window.location.search); const theme = urlParams.get('theme'); return ['light', 'dark', 'system'].includes(theme) ? theme : null; } getScaleFromURL() { const urlParams = new URLSearchParams(window.location.search); const scale = parseFloat(urlParams.get('scale')); return (!isNaN(scale) && scale >= 0.5 && scale <= 2.0) ? scale : null; } applyTheme() { const calculatorContainer = this.container.querySelector('#dogCalculator'); if (calculatorContainer) { // Remove existing theme classes calculatorContainer.classList.remove('theme-light', 'theme-dark', 'theme-system'); // Add the selected theme class if (['light', 'dark', 'system'].includes(this.options.theme)) { calculatorContainer.classList.add('theme-' + this.options.theme); } } } applyScale() { const scale = Math.max(0.5, Math.min(2.0, this.options.scale)); if (scale !== 1.0) { this.container.style.transform = \`scale(\${scale})\`; this.container.style.transformOrigin = 'top left'; } }$2`); // Add CSS variables to widget CSS const widgetCSS = `/* Sundog Dog Food Calorie Calculator Styles */ /* CSS Variables for theming */ ${css}`; const widgetCode = `/** * Dog Calorie Calculator Widget * Embeddable JavaScript widget for websites * * THIS CODE IS AUTO-GENERATED FROM src/ FILES - DO NOT EDIT MANUALLY * Edit files in src/ directory and run 'node build.js' to update * * Usage: * *
* * Or with options: *
* * By Canine Nutrition and Wellness * https://caninenutritionandwellness.com */ (function() { 'use strict'; // Inject widget styles const CSS_STYLES = \`${widgetCSS}\`; function injectStyles() { if (document.getElementById('dog-calculator-styles')) return; const style = document.createElement('style'); style.id = 'dog-calculator-styles'; style.textContent = CSS_STYLES; document.head.appendChild(style); } // JavaScript from src/calculator.js (transformed for widget use) ${transformedJS} // Auto-initialize widgets on page load function initializeWidget() { injectStyles(); const containers = document.querySelectorAll('#dog-calorie-calculator, .dog-calorie-calculator'); containers.forEach(container => { if (container.dataset.initialized) return; const options = { theme: container.dataset.theme || 'system', scale: parseFloat(container.dataset.scale) || 1.0 }; new DogCalorieCalculatorWidget(container, options); container.dataset.initialized = 'true'; }); } // Initialize when DOM is ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initializeWidget); } else { initializeWidget(); } // Export for manual initialization window.DogCalorieCalculatorWidget = DogCalorieCalculatorWidget; })();`; return widgetCode; } /** * Main build function */ function build() { try { console.log('📖 Reading organized components from src/ directory...'); const { css, html, js } = readSourceComponents(); console.log(' ✅ src/index.html (' + html.split('\n').length + ' lines)'); console.log(' ✅ src/css/main.css + themes.css (' + css.split('\n').length + ' lines)'); console.log(' ✅ src/js/config.js + calculator.js (' + js.split('\n').length + ' lines)'); console.log(''); console.log('📦 Backing up existing files...'); backupFiles(); console.log(''); console.log('🏗️ Building output files...'); // Generate iframe.html const iframeContent = generateIframe(css, html, js); fs.writeFileSync('iframe.html', iframeContent); console.log(' ✅ Generated iframe.html'); // Generate widget const widgetCode = createWidgetJS(css, html, js); fs.writeFileSync('sundog-dog-food-calculator.js', widgetCode); console.log(' ✅ Generated sundog-dog-food-calculator.js'); console.log(''); console.log('🎉 Build completed successfully!'); console.log(''); console.log('📋 Summary:'); console.log(' Source structure:'); console.log(' • src/index.html - HTML structure'); console.log(' • src/css/main.css - Core styles'); console.log(' • src/css/themes.css - Theme variations'); console.log(' • src/js/config.js - Configuration'); console.log(' • src/js/calculator.js - Main logic'); console.log(''); console.log(' Generated files:'); console.log(' • iframe.html - Standalone calculator'); console.log(' • sundog-dog-food-calculator.js - Embeddable widget'); console.log(''); console.log('🔄 Your workflow:'); console.log(' 1. Edit organized files in src/'); console.log(' 2. Run: node build.js'); console.log(' 3. Both output files are regenerated!'); console.log(''); console.log('💡 Clean, organized structure - easy to maintain!'); } catch (error) { console.error('❌ Build failed:', error.message); console.error(error.stack); process.exit(1); } } // Run build if called directly if (require.main === module) { build(); } module.exports = { build };