Major Changes: - Moved legacy system to Legacy/ folder for archival - Built new modular architecture with strict separation of concerns - Created core system: Module, EventBus, ModuleLoader, Router - Added Application bootstrap with auto-start functionality - Implemented development server with ES6 modules support - Created comprehensive documentation and project context - Converted SBS-7-8 content to JSON format - Copied all legacy games and content to new structure New Architecture Features: - Sealed modules with WeakMap private data - Strict dependency injection system - Event-driven communication only - Inviolable responsibility patterns - Auto-initialization without commands - Component-based UI foundation ready Technical Stack: - Vanilla JS/HTML/CSS only - ES6 modules with proper imports/exports - HTTP development server (no file:// protocol) - Modular CSS with component scoping - Comprehensive error handling and debugging Ready for Phase 2: Converting legacy modules to new architecture 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
197 lines
6.1 KiB
JavaScript
197 lines
6.1 KiB
JavaScript
/**
|
|
* Development Server - Simple HTTP server for local development
|
|
* Handles static files, CORS, and development features
|
|
*/
|
|
|
|
import { createServer } from 'http';
|
|
import { readFile, stat } from 'fs/promises';
|
|
import { join, extname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname } from 'path';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
const HOST = process.env.HOST || 'localhost';
|
|
|
|
// MIME types for different file extensions
|
|
const MIME_TYPES = {
|
|
'.html': 'text/html',
|
|
'.css': 'text/css',
|
|
'.js': 'application/javascript',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.jpeg': 'image/jpeg',
|
|
'.gif': 'image/gif',
|
|
'.svg': 'image/svg+xml',
|
|
'.ico': 'image/x-icon',
|
|
'.woff': 'font/woff',
|
|
'.woff2': 'font/woff2',
|
|
'.ttf': 'font/ttf',
|
|
'.mp3': 'audio/mpeg',
|
|
'.wav': 'audio/wav',
|
|
'.mp4': 'video/mp4'
|
|
};
|
|
|
|
const server = createServer(async (req, res) => {
|
|
try {
|
|
// Parse URL and remove query parameters
|
|
const urlPath = new URL(req.url, `http://${req.headers.host}`).pathname;
|
|
|
|
// Default to index.html for root requests
|
|
const filePath = urlPath === '/' ? 'index.html' : urlPath.slice(1);
|
|
const fullPath = join(__dirname, filePath);
|
|
|
|
console.log(`${new Date().toISOString()} - ${req.method} ${urlPath}`);
|
|
|
|
// Set CORS headers for all requests
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
|
|
// Handle preflight requests
|
|
if (req.method === 'OPTIONS') {
|
|
res.writeHead(200);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
// Check if file exists
|
|
try {
|
|
const stats = await stat(fullPath);
|
|
|
|
if (stats.isDirectory()) {
|
|
// Try to serve index.html from directory
|
|
const indexPath = join(fullPath, 'index.html');
|
|
try {
|
|
await stat(indexPath);
|
|
return serveFile(indexPath, res);
|
|
} catch {
|
|
return send404(res, `Directory listing not allowed for ${urlPath}`);
|
|
}
|
|
}
|
|
|
|
return serveFile(fullPath, res);
|
|
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') {
|
|
return send404(res, `File not found: ${urlPath}`);
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Server error:', error);
|
|
|
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
res.end('Internal Server Error');
|
|
}
|
|
});
|
|
|
|
async function serveFile(filePath, res) {
|
|
try {
|
|
const ext = extname(filePath).toLowerCase();
|
|
const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
|
|
// Set content type
|
|
res.setHeader('Content-Type', mimeType);
|
|
|
|
// Set cache headers for static assets
|
|
if (['.css', '.js', '.png', '.jpg', '.gif', '.svg', '.ico', '.woff', '.woff2'].includes(ext)) {
|
|
res.setHeader('Cache-Control', 'public, max-age=3600'); // 1 hour
|
|
} else {
|
|
res.setHeader('Cache-Control', 'no-cache'); // No cache for HTML and other files
|
|
}
|
|
|
|
// Add security headers
|
|
if (ext === '.js') {
|
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
}
|
|
|
|
// Read and serve file
|
|
const content = await readFile(filePath);
|
|
|
|
res.writeHead(200);
|
|
res.end(content);
|
|
|
|
console.log(` ✅ Served ${filePath} (${content.length} bytes, ${mimeType})`);
|
|
|
|
} catch (error) {
|
|
console.error(`Error serving file ${filePath}:`, error);
|
|
|
|
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
res.end('Error reading file');
|
|
}
|
|
}
|
|
|
|
function send404(res, message = 'Not Found') {
|
|
const html404 = `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>404 - Not Found</title>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
text-align: center;
|
|
padding: 2rem;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
}
|
|
h1 { font-size: 3rem; margin-bottom: 1rem; }
|
|
p { font-size: 1.2rem; opacity: 0.8; }
|
|
a { color: white; text-decoration: underline; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>404</h1>
|
|
<p>${message}</p>
|
|
<p><a href="/">← Back to Class Generator</a></p>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
res.end(html404);
|
|
|
|
console.log(` ❌ 404: ${message}`);
|
|
}
|
|
|
|
// Start server
|
|
server.listen(PORT, HOST, () => {
|
|
console.log('\n🚀 Class Generator Development Server');
|
|
console.log('=====================================');
|
|
console.log(`📍 Local: http://${HOST}:${PORT}/`);
|
|
console.log(`🌐 Network: http://localhost:${PORT}/`);
|
|
console.log('📁 Serving files from:', __dirname);
|
|
console.log('\n✨ Features:');
|
|
console.log(' • ES6 modules support');
|
|
console.log(' • CORS enabled');
|
|
console.log(' • Static file serving');
|
|
console.log(' • Development-friendly caching');
|
|
console.log('\n🔥 Ready for development!');
|
|
console.log('Press Ctrl+C to stop\n');
|
|
});
|
|
|
|
// Graceful shutdown
|
|
process.on('SIGINT', () => {
|
|
console.log('\n👋 Shutting down server...');
|
|
server.close(() => {
|
|
console.log('✅ Server stopped');
|
|
process.exit(0);
|
|
});
|
|
});
|
|
|
|
process.on('SIGTERM', () => {
|
|
console.log('\n👋 Received SIGTERM, shutting down...');
|
|
server.close(() => {
|
|
console.log('✅ Server stopped');
|
|
process.exit(0);
|
|
});
|
|
}); |