#!/usr/bin/env python3 # /// script # dependencies = [ # "boto3>=1.34.0", # ] # /// """ Publish single-page websites to hosting.haumdaucher.de with validation and clean URL reporting. Integrates with the existing 'hosting' skill publisher. """ import argparse import os import re import subprocess import sys from pathlib import Path def validate_html(content: str) -> list[str]: """Basic pre-flight checks to ensure the generated website isn't malformed.""" warnings = [] content_lower = content.lower() if "" not in content_lower: warnings.append("Missing declaration.") if "viewport" not in content_lower: warnings.append("Missing responsive viewport meta tag.") if "cdn.tailwindcss.com" not in content_lower and "tailwindcss" not in content_lower: warnings.append("Tailwind CSS CDN not detected in HTML.") if "alpine" not in content_lower: warnings.append("Alpine.js script not detected (interactivity might be missing).") if "lucide" in content_lower and "lucide.createicons" not in content_lower: warnings.append("Lucide icons included, but 'lucide.createIcons()' initialization is missing.") return warnings def resolve_hosting_script() -> Path: """Finds the publish_file.py script relative to this script or in standard locations.""" current_dir = Path(__file__).resolve().parent # Standard relative location in mop-skills sibling_hosting = current_dir.parent.parent / "hosting" / "scripts" / "publish_file.py" if sibling_hosting.exists(): return sibling_hosting # Fallback in container pod_hosting = Path("/home/node/mop-skills/mop-skills/skills/hosting/scripts/publish_file.py") if pod_hosting.exists(): return pod_hosting raise FileNotFoundError(f"Cannot find publish_file.py in {sibling_hosting} or {pod_hosting}") def main(): parser = argparse.ArgumentParser(description="Publish websites to hosting.haumdaucher.de") parser.add_argument("--file", "-f", help="Local HTML file to upload") parser.add_argument("--content", "-c", help="Inline HTML content string") parser.add_argument("--slug", "-s", required=True, help="Slug identifier for URL (e.g. creami-calculator)") parser.add_argument("--category", default="showcase", help="Category folder (default: showcase)") parser.add_argument("--skip-validation", action="store_true", help="Skip HTML quality checks") parser.add_argument("--url-only", action="store_true", help="Print only public URL") args = parser.parse_args() if not args.file and args.content is None: parser.error("You must provide either --file or --content.") # Read content for validation html_content = "" if args.file: file_path = Path(args.file) if not file_path.exists(): sys.exit(f"Error: File not found: {args.file}") html_content = file_path.read_text(encoding="utf-8", errors="replace") else: html_content = args.content # Quality validation if not args.skip_validation: warnings = validate_html(html_content) if warnings: print("⚠️ Pre-flight HTML Warnings:", file=sys.stderr) for w in warnings: print(f" - {w}", file=sys.stderr) # Delegate to hosting/scripts/publish_file.py publisher_script = resolve_hosting_script() cmd = [ sys.executable, str(publisher_script), "--category", args.category, "--slug", args.slug, "--ext", "html", ] if args.file: cmd.extend(["--file", str(args.file)]) else: cmd.extend(["--content", args.content]) if args.url_only: cmd.append("--url-only") result = subprocess.run(cmd, capture_output=True, text=True) if result.returncode != 0: sys.stderr.write(result.stderr) sys.exit(result.returncode) print(result.stdout.strip()) if __name__ == "__main__": main()