#!/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 backupDir = 'backup';
// Create backup directory if it doesn't exist
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir);
}
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 = path.join(backupDir, `${file}.backup-${timestamp}`);
fs.copyFileSync(file, backupName);
console.log(` 📦 Backed up ${file}`);
}
});
}
/**
* 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 };