Add Justfile and fix server implementation

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 <noreply@anthropic.com>
This commit is contained in:
Ole-Morten Duesund 2025-08-18 20:13:04 +02:00
commit 749df446e9
2 changed files with 257 additions and 32 deletions

138
justfile Normal file
View file

@ -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 <noreply@anthropic.com>"
# 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"

151
server.py
View file

@ -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://<your-ip>:{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://<your-ip>:{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://<your-ip>:{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()