All checks were successful
Deploy Fly.io Proxy / deploy (push) Successful in 2m51s
## Summary Monthly tooling dependency update per [[update-tooling-dependencies]]. - **Prek hooks:** trufflehog v3.93.4→v3.94.0, ruff v0.15.2→v0.15.7, shfmt v3.12.0-2→v3.13.0-1, ansible-lint floor→26.3.0, ansible-core floor→2.18 - **Fly.io proxy:** nginx 1.28.2→1.29.6, Grafana Alloy v1.13.1→v1.14.1 - **Forgejo workflows:** actions/checkout v4.3.1→v6.0.2 (SHA-pinned across all 5 workflows) - **Mise tasks:** tightened Python lower bounds — rich≥14.0.0, typer≥0.24.0, httpx≥0.28.1, pyyaml≥6.0.2 ## Test plan - [x] `prek run --all-files` passes - [ ] Verify Fly.io deploy succeeds after merge (nginx minor bump + Alloy bump) - [ ] Spot-check a workflow run with the new actions/checkout v6 🤖 Generated with [Claude Code](https://claude.com/claude-code) Reviewed-on: #307
140 lines
4.6 KiB
Text
Executable file
140 lines
4.6 KiB
Text
Executable file
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# requires-python = ">=3.12"
|
|
# dependencies = ["pyyaml>=6.0.2", "rich>=14.0.0", "typer>=0.24.0"]
|
|
# ///
|
|
#MISE description="Build docs with Dagger and serve locally, opening to a specific card"
|
|
#USAGE arg "<card>" help="Card path relative to docs/, e.g. how-to/knowledgebase/review-documentation"
|
|
#USAGE flag "--port <port>" default="8484" help="Port for preview server (default 8484)"
|
|
"""Build the full Quartz docs site and serve locally for visual preview.
|
|
|
|
Builds the documentation using Dagger's build_docs function, extracts the
|
|
result, and serves it in the same quartz container used in production
|
|
(image parsed from the ArgoCD kustomization). Opens the browser directly
|
|
to the specified card. The container auto-removes after 1 hour.
|
|
|
|
Usage: mise run docs-preview how-to/knowledgebase/review-documentation
|
|
"""
|
|
|
|
import shutil
|
|
import subprocess
|
|
import tarfile
|
|
import tempfile
|
|
import webbrowser
|
|
from pathlib import Path
|
|
from typing import Annotated
|
|
|
|
import typer
|
|
import yaml
|
|
from rich.console import Console
|
|
|
|
REPO_ROOT = Path(__file__).parent.parent
|
|
CONTAINER_NAME = "docs-preview"
|
|
|
|
|
|
def get_quartz_image() -> str:
|
|
"""Parse the quartz container image from the ArgoCD kustomization."""
|
|
kustomization = REPO_ROOT / "argocd" / "manifests" / "docs" / "kustomization.yaml"
|
|
data = yaml.safe_load(kustomization.read_text())
|
|
for img in data.get("images", []):
|
|
if img["name"] == "registry.ops.eblu.me/blumeops/quartz":
|
|
return f"{img['name']}:{img['newTag']}"
|
|
raise RuntimeError("Could not find quartz image in kustomization.yaml")
|
|
|
|
|
|
def main(
|
|
card: Annotated[str, typer.Argument(help="Card path relative to docs/")],
|
|
port: Annotated[int, typer.Option(help="Port for preview server")] = 8484,
|
|
) -> None:
|
|
console = Console()
|
|
|
|
# Normalize: accept with or without .md suffix
|
|
card_stem = card.removesuffix(".md")
|
|
# Try exact path first (e.g. "docs/how-to/..."), then inside docs/
|
|
exact_file = REPO_ROOT / f"{card_stem}.md"
|
|
docs_file = REPO_ROOT / "docs" / f"{card_stem}.md"
|
|
if exact_file.exists() and card_stem.startswith("docs/"):
|
|
card_stem = card_stem.removeprefix("docs/")
|
|
card_file = exact_file
|
|
elif docs_file.exists():
|
|
card_file = docs_file
|
|
else:
|
|
console.print(f"[bold red]Card not found:[/bold red] {docs_file}")
|
|
raise typer.Exit(code=1)
|
|
|
|
url_path = "/" + card_stem
|
|
image = get_quartz_image()
|
|
console.print(f"[dim]Using image: {image}[/dim]")
|
|
|
|
# Clean up any previous preview container and its docroot
|
|
subprocess.run(
|
|
["docker", "rm", "-f", CONTAINER_NAME],
|
|
capture_output=True,
|
|
)
|
|
docroot = Path(tempfile.gettempdir()) / "docs-preview"
|
|
if docroot.exists():
|
|
shutil.rmtree(docroot)
|
|
docroot.mkdir()
|
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
tarball = Path(tmpdir) / "docs-preview.tar.gz"
|
|
|
|
console.print("[bold]Building docs with Dagger...[/bold]")
|
|
subprocess.run(
|
|
[
|
|
"dagger",
|
|
"call",
|
|
"build-docs",
|
|
"--src=.",
|
|
"--version=preview",
|
|
"export",
|
|
f"--path={tarball}",
|
|
],
|
|
cwd=REPO_ROOT,
|
|
check=True,
|
|
)
|
|
|
|
console.print("[bold]Extracting docs...[/bold]")
|
|
with tarfile.open(tarball, "r:gz") as tf:
|
|
tf.extractall(docroot, filter="data")
|
|
|
|
console.print("[bold]Starting preview container...[/bold]")
|
|
subprocess.run(
|
|
[
|
|
"docker",
|
|
"run",
|
|
"-d",
|
|
"--rm",
|
|
"--name", CONTAINER_NAME,
|
|
"--stop-timeout", "0",
|
|
"-p", f"{port}:80",
|
|
"-v", f"{docroot}:/usr/share/nginx/html:ro",
|
|
"--entrypoint", "nginx",
|
|
image,
|
|
"-g", "daemon off;",
|
|
],
|
|
check=True,
|
|
)
|
|
|
|
url = f"http://localhost:{port}{url_path}"
|
|
console.print(f"\n[bold green]Preview running at http://localhost:{port}[/bold green]")
|
|
console.print(f"[bold cyan]Opening {url}[/bold cyan]\n")
|
|
webbrowser.open(url)
|
|
|
|
console.print(f"[yellow]Container will auto-stop in 1 hour.[/yellow]")
|
|
console.print(f"[yellow]To stop sooner: docker rm -f {CONTAINER_NAME}[/yellow]\n")
|
|
|
|
# Schedule auto-cleanup after 1 hour (container + docroot)
|
|
subprocess.Popen(
|
|
[
|
|
"sh", "-c",
|
|
f"sleep 3600 && docker rm -f {CONTAINER_NAME} 2>/dev/null && rm -rf {docroot}",
|
|
],
|
|
start_new_session=True,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
typer.run(main)
|