From 2fa0be52ef61d4711f7270a41e7a9a40387b9f62 Mon Sep 17 00:00:00 2001 From: Tobias O Date: Thu, 2 Oct 2025 15:51:23 +1300 Subject: [PATCH] Update commands to automatically run pnpm for relevant commands Signed-off-by: Tobias O --- epdb/management/commands/collectstatic.py | 67 +++++++++++++++++++ epdb/management/commands/runserver.py | 81 +++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 epdb/management/commands/collectstatic.py create mode 100644 epdb/management/commands/runserver.py diff --git a/epdb/management/commands/collectstatic.py b/epdb/management/commands/collectstatic.py new file mode 100644 index 00000000..355f4b38 --- /dev/null +++ b/epdb/management/commands/collectstatic.py @@ -0,0 +1,67 @@ +""" +Custom collectstatic command that automatically builds CSS first. +Overrides Django's default collectstatic to include pnpm build. +""" + +import subprocess +import sys +from pathlib import Path +from django.conf import settings +from django.contrib.staticfiles.management.commands.collectstatic import ( + Command as CollectstaticCommand, +) + + +class Command(CollectstaticCommand): + help = "Collect static files (automatically builds CSS first)" + + def handle(self, *args, **options): + """Build CSS before collecting static files.""" + self.stdout.write(self.style.SUCCESS("Building CSS with pnpm...")) + + try: + # Run pnpm build + result = subprocess.run( + ["pnpm", "run", "build"], + cwd=settings.BASE_DIR, + capture_output=True, + text=True, + timeout=60, # 60 second timeout + ) + + if result.returncode != 0: + self.stdout.write(self.style.ERROR("✗ CSS build failed:")) + self.stdout.write(result.stderr) + sys.exit(1) + + # Verify output.css was created + output_css = Path(settings.BASE_DIR) / "static" / "css" / "output.css" + if not output_css.exists(): + self.stdout.write( + self.style.ERROR("✗ CSS build failed: output.css not generated") + ) + sys.exit(1) + + # Show file size + size_kb = output_css.stat().st_size / 1024 + self.stdout.write( + self.style.SUCCESS(f"✓ CSS built successfully ({size_kb:.1f}KB)\n") + ) + + except FileNotFoundError: + self.stdout.write( + self.style.ERROR( + "✗ Error: pnpm not found. Install pnpm to build CSS.\n" + "See README.md for setup instructions." + ) + ) + sys.exit(1) + except subprocess.TimeoutExpired: + self.stdout.write(self.style.ERROR("✗ CSS build timed out (>60s)")) + sys.exit(1) + except Exception as e: + self.stdout.write(self.style.ERROR(f"✗ CSS build error: {e}")) + sys.exit(1) + + # Run normal collectstatic + super().handle(*args, **options) diff --git a/epdb/management/commands/runserver.py b/epdb/management/commands/runserver.py new file mode 100644 index 00000000..d761a228 --- /dev/null +++ b/epdb/management/commands/runserver.py @@ -0,0 +1,81 @@ +""" +Custom runserver command that automatically starts CSS watcher. +Overrides Django's default runserver to include pnpm dev. +""" + +import signal +import subprocess +import sys +from django.conf import settings +from django.contrib.staticfiles.management.commands.runserver import ( + Command as RunserverCommand, +) + + +class Command(RunserverCommand): + help = "Run development server with automatic CSS building" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.css_process = None + + def handle(self, *args, **options): + """Start CSS watcher before running Django dev server.""" + self.stdout.write(self.style.SUCCESS("Starting CSS watcher (pnpm dev)...")) + + # Start pnpm dev in background + try: + self.css_process = subprocess.Popen( + ["pnpm", "run", "dev"], + cwd=settings.BASE_DIR, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + self.stdout.write(self.style.SUCCESS("✓ CSS watcher started\n")) + except FileNotFoundError: + self.stdout.write( + self.style.WARNING( + "Warning: pnpm not found. CSS will not be rebuilt automatically.\n" + 'Install pnpm or run "pnpm run dev" manually in another terminal.\n' + ) + ) + except Exception as e: + self.stdout.write( + self.style.WARNING(f"Warning: Could not start CSS watcher: {e}\n") + ) + + # Register cleanup handler + original_sigint = signal.getsignal(signal.SIGINT) + original_sigterm = signal.getsignal(signal.SIGTERM) + + def cleanup(signum, frame): + self.stdout.write("\nShutting down...") + if self.css_process: + self.css_process.terminate() + try: + self.css_process.wait(timeout=5) + self.stdout.write(self.style.SUCCESS("✓ CSS watcher stopped")) + except subprocess.TimeoutExpired: + self.css_process.kill() + # Call original handler + if signum == signal.SIGINT and callable(original_sigint): + original_sigint(signum, frame) + elif signum == signal.SIGTERM and callable(original_sigterm): + original_sigterm(signum, frame) + sys.exit(0) + + signal.signal(signal.SIGINT, cleanup) + signal.signal(signal.SIGTERM, cleanup) + + # Run Django dev server + try: + super().handle(*args, **options) + finally: + # Cleanup on normal exit + if self.css_process: + self.css_process.terminate() + try: + self.css_process.wait(timeout=5) + except subprocess.TimeoutExpired: + self.css_process.kill()