From 749df446e9fa5f1ec5f4ffe59369879ec209d96d Mon Sep 17 00:00:00 2001 From: Ole-Morten Duesund Date: Mon, 18 Aug 2025 20:13:04 +0200 Subject: [PATCH] Add Justfile and fix server implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Server improvements: - Complete rewrite with robust IPv4/IPv6 support - Graceful fallback from IPv6 dual-stack to IPv4-only - Better error handling and informative startup messages - Proper socket cleanup and server shutdown - Custom logging with timestamps Justfile additions: - serve/serve-bg/stop - Server management - lint-all/format-all/check-all - Code quality commands - install/setup/clean - Project management - validate/info/status - Development helpers - icons-png/icons-browser - Icon generation - commit/quick-commit - Git workflow helpers The server now works reliably across different network configurations and the justfile provides a comprehensive development workflow. šŸ¤– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- justfile | 138 +++++++++++++++++++++++++++++++++++++++++++++++++ server.py | 151 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 257 insertions(+), 32 deletions(-) create mode 100644 justfile diff --git a/justfile b/justfile new file mode 100644 index 0000000..e463c0d --- /dev/null +++ b/justfile @@ -0,0 +1,138 @@ +# GlitchCraft Development Commands +# Install just: https://github.com/casey/just + +# Default recipe - show available commands +default: + @just --list + +# Development server management +# Start the development server +serve: + python3 server.py + +# Start server in background +serve-bg: + python3 server.py & + @echo "Server started in background" + @echo "Use 'just stop' to stop it" + +# Stop background server +stop: + @echo "Stopping GlitchCraft server..." + pkill -f "python.*server.py" || echo "No server process found" + +# Code quality and linting +# Lint all JavaScript files +lint-js: + bun run lint:js + +# Lint and auto-fix JavaScript +fix-js: + bun run lint:js:fix + +# Format all code with Prettier +format: + bun run format + +# Check code formatting without modifying files +check-format: + bun run check + +# Lint Python code +lint-python: + black --check server.py + pylint server.py + +# Format Python code +format-python: + black server.py + +# Lint and format everything +lint-all: lint-js lint-python + @echo "āœ“ All linting completed" + +# Format everything +format-all: format-python format + @echo "āœ“ All formatting completed" + +# Check everything (linting + formatting) +check-all: lint-all check-format + @echo "āœ“ All checks passed" + +# PWA icon generation +# Generate PNG icons (requires canvas package) +icons-png: + bun add canvas + node app/create-icons.js + +# Open browser-based icon generator +icons-browser: + @echo "Opening app/generate-icons.html..." + @echo "Right-click each canvas to save icons" + python3 -c "import webbrowser; webbrowser.open('app/generate-icons.html')" + +# Development workflow +# Install dependencies +install: + bun install + +# Clean up generated files +clean: + rm -rf node_modules/ + rm -f bun.lockb + find . -name "*.pyc" -delete + find . -name "__pycache__" -type d -exec rm -rf {} + + +# Full setup from scratch +setup: install icons-png + @echo "āœ“ GlitchCraft setup complete!" + @echo "Run 'just serve' to start development" + +# Testing and validation +# Validate HTML files +validate-html: + bunx vnu-jar app/index.html app/generate-icons.html + +# Test PWA manifest +validate-manifest: + python3 -m json.tool app/manifest.json > /dev/null && echo "āœ“ Manifest is valid JSON" + +# Run all validations +validate: validate-html validate-manifest check-all + @echo "āœ“ All validations passed" + +# Git helpers +# Create a commit with all changes +commit message: + git add -A + git commit -m "{{message}}\n\nšŸ¤– Generated with [Claude Code](https://claude.ai/code)\n\nCo-Authored-By: Claude " + +# Quick commit with lint/format +quick-commit message: format-all lint-all + just commit "{{message}}" + +# Development info +# Show project info +info: + @echo "šŸŽØ GlitchCraft - Artisanal text corruption, served fresh!" + @echo "" + @echo "šŸ“ Project structure:" + @echo " app/ - Main application (deploy this directory)" + @echo " server.py - Development server (IPv4/IPv6)" + @echo " justfile - Development commands" + @echo "" + @echo "šŸš€ Quick start:" + @echo " just install - Install dependencies" + @echo " just serve - Start development server" + @echo " just check-all - Run all linting and validation" + @echo "" + @echo "šŸ”— Server URLs:" + @echo " http://localhost:8000/" + @echo " http://127.0.0.1:8000/ (IPv4)" + @echo " http://[::1]:8000/ (IPv6)" + +# Show server status +status: + @echo "Checking server status..." + @pgrep -f "python.*server.py" > /dev/null && echo "āœ“ Server is running" || echo "āœ— Server is not running" + @echo "Use 'just serve' to start or 'just stop' to stop" \ No newline at end of file diff --git a/server.py b/server.py index c61a8e7..a87b8bb 100644 --- a/server.py +++ b/server.py @@ -2,60 +2,147 @@ """ Simple HTTP server for testing the GlitchCraft PWA Run with: python3 server.py -Then open http://localhost:8000/ in your browser +Supports both IPv4 and IPv6 connections """ import http.server import socketserver import socket import os +import sys +import time + PORT = 8000 # Serve from the app directory DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app") -class DualStackServer(socketserver.TCPServer): - """TCP server that supports both IPv4 and IPv6.""" - - address_family = socket.AF_INET6 - - def __init__(self, *args, **kwargs): - # Disable dual stack to make it work on all systems - # Some systems have it enabled by default, some don't - super().__init__(*args, **kwargs) - # Enable dual stack - listen on both IPv4 and IPv6 - self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) - - -class MyHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): +class GlitchCraftHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): """Custom HTTP request handler for serving the GlitchCraft PWA.""" def __init__(self, *args, **kwargs): super().__init__(*args, directory=DIRECTORY, **kwargs) def end_headers(self): - # Add headers for PWA and CORS + # Add headers for PWA functionality self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") self.send_header("Service-Worker-Allowed", "/") self.send_header("X-Content-Type-Options", "nosniff") + # Add CORS headers for development + self.send_header("Access-Control-Allow-Origin", "*") + self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + self.send_header("Access-Control-Allow-Headers", "*") super().end_headers() + def log_message(self, fmt, *args): + # Custom log format with timestamp + timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + print(f"[{timestamp}] {fmt % args}") + + +class DualStackTCPServer(socketserver.TCPServer): + """TCP server that attempts to support both IPv4 and IPv6.""" + + allow_reuse_address = True + + def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): + # Try IPv6 first (with dual-stack if available) + self.address_family = socket.AF_INET6 + try: + super().__init__( + server_address, RequestHandlerClass, bind_and_activate=False + ) + # Try to enable dual-stack (works on most modern systems) + try: + self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) + self._is_dual_stack = True + except (AttributeError, OSError): + # Dual-stack not supported, IPv6 only + self._is_dual_stack = False + + if bind_and_activate: + self.server_bind() + self.server_activate() + + except (OSError, socket.error): + # IPv6 failed, fall back to IPv4 + self.server_close() + self.address_family = socket.AF_INET + self._is_dual_stack = False + # Convert IPv6 address to IPv4 if needed + if server_address[0] == "::": + server_address = ("0.0.0.0", server_address[1]) + super().__init__(server_address, RequestHandlerClass, bind_and_activate) + + +def main(): + """Start the development server.""" + print("Starting GlitchCraft development server...") + print(f"Serving files from: {DIRECTORY}") + print(f"Port: {PORT}") + + # Check if app directory exists + if not os.path.exists(DIRECTORY): + print(f"Error: Directory {DIRECTORY} does not exist!") + sys.exit(1) + + # Try different host configurations + hosts_to_try = [ + ("::", "IPv6 dual-stack"), + ("0.0.0.0", "IPv4 all interfaces"), + ("127.0.0.1", "IPv4 localhost only"), + ] + + server = None + for host, description in hosts_to_try: + try: + server = DualStackTCPServer((host, PORT), GlitchCraftHTTPRequestHandler) + print(f"āœ“ Server started using {description}") + + # Print access URLs based on server configuration + print("\nAccess your app at:") + if server.address_family == socket.AF_INET6: + if hasattr(server, "_is_dual_stack") and getattr( + server, "_is_dual_stack", False + ): + print(f" http://localhost:{PORT}/") + print(f" http://127.0.0.1:{PORT}/ (IPv4)") + print(f" http://[::1]:{PORT}/ (IPv6)") + print(f" http://:{PORT}/ (network)") + else: + print(f" http://[::1]:{PORT}/ (IPv6 only)") + print(f" http://localhost:{PORT}/ (if supported)") + else: + if host == "0.0.0.0": + print(f" http://localhost:{PORT}/") + print(f" http://127.0.0.1:{PORT}/ (IPv4)") + print(f" http://:{PORT}/ (network)") + else: + print(f" http://localhost:{PORT}/ (localhost only)") + + print("\nPress Ctrl+C to stop the server") + break + + except OSError as e: + print(f"āœ— Failed to start server on {host}: {e}") + if "Address already in use" in str(e): + print( + f" Port {PORT} is already in use. Try a different port or stop other servers." + ) + continue + + if server is None: + print("Failed to start server on any interface!") + sys.exit(1) + + try: + server.serve_forever() + except KeyboardInterrupt: + print("\nShutting down server...") + finally: + server.server_close() + if __name__ == "__main__": - # Listen on all interfaces (:: for IPv6, which also handles IPv4 with dual stack) - HOST = "::" # Listen on all interfaces (IPv6 and IPv4) - - with DualStackServer((HOST, PORT), MyHTTPRequestHandler) as httpd: - print(f"Server running on all interfaces at port {PORT}") - print(f"Access at:") - print(f" http://localhost:{PORT}/") - print(f" http://127.0.0.1:{PORT}/ (IPv4)") - print(f" http://[::1]:{PORT}/ (IPv6)") - print(f" http://:{PORT}/") - print(f"Serving files from: {DIRECTORY}") - print("Press Ctrl+C to stop the server") - try: - httpd.serve_forever() - except KeyboardInterrupt: - print("\nServer stopped.") + main()