111 lines
3.9 KiB
Python
Executable File
111 lines
3.9 KiB
Python
Executable File
#!/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 "<!doctype html>" not in content_lower:
|
|
warnings.append("Missing <!DOCTYPE html> 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()
|