feat(skills): add create-website skill for zero-build responsive SPAs and showcases
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user