"""bincio render — build or serve the Astro static site.""" import os import subprocess import sys from pathlib import Path from typing import Optional import click from rich.console import Console console = Console() def _find_site_dir(explicit: Optional[str]) -> Path: """Locate the Astro project directory.""" if explicit: p = Path(explicit).expanduser().resolve() if not (p / "package.json").exists(): raise click.UsageError(f"No package.json found in --site-dir {p}") return p # Search upward from cwd: ./site, ../site (for when cwd is bincio_data/) for candidate in [Path.cwd() / "site", Path.cwd().parent / "site"]: if (candidate / "package.json").exists(): return candidate raise click.UsageError( "Could not find the Astro site directory. " "Run from the project root or pass --site-dir." ) def _find_data_dir(explicit: Optional[str], config_path: Optional[str]) -> Path: """Resolve the BAS data directory.""" if explicit: return Path(explicit).expanduser().resolve() if config_path and Path(config_path).exists(): import yaml raw = yaml.safe_load(Path(config_path).read_text()) out = raw.get("output", {}).get("dir") if out: return Path(out).expanduser().resolve() # Default: ./bincio_data next to cwd default = Path.cwd() / "bincio_data" if default.exists(): return default raise click.UsageError( "Could not find the BAS data directory. " "Run `bincio extract` first, or pass --data-dir." ) def _ensure_npm(site: Path) -> None: """Run `npm install` if node_modules is missing or stale.""" if not (site / "node_modules").exists(): console.print("Running [cyan]npm install[/cyan]…") subprocess.run(["npm", "install"], cwd=site, check=True) def _merge_edits(data: Path) -> None: """Run the sidecar merge step, producing data/_merged/.""" from bincio.render.merge import merge_all n = merge_all(data) if n: console.print(f"Merged [cyan]{n}[/cyan] sidecar edit(s) into _merged/") else: console.print("No sidecars found — _merged/ mirrors extracted data.") def _link_data(site: Path, data: Path) -> None: """Symlink site/public/data → data/_merged/ (the post-merge output).""" merged = data / "_merged" target = merged if merged.exists() else data public_data = site / "public" / "data" public_data.parent.mkdir(parents=True, exist_ok=True) if public_data.is_symlink(): if public_data.resolve() == target.resolve(): return # already correct public_data.unlink() elif public_data.exists(): console.print( f"[yellow]Warning:[/yellow] {public_data} exists and is not a symlink — " "remove it manually if you want bincio to manage it." ) return public_data.symlink_to(target) console.print(f"Linked data: [cyan]{target}[/cyan] → [cyan]{public_data}[/cyan]") @click.command() @click.option("--config", "config_path", default=None, help="Path to extract_config.yaml (reads output.dir from it).") @click.option("--data-dir", default=None, help="BAS data store directory (output of bincio extract).") @click.option("--site-dir", default=None, help="Astro project directory (default: ./site).") @click.option("--out", "out_dir", default=None, help="Build output directory (default: site/dist).") @click.option("--serve", is_flag=True, help="Start dev server with hot reload instead of building.") @click.option("--deploy", default=None, metavar="TARGET", help="Deploy after build. Currently supports: github.") def render( config_path: Optional[str], data_dir: Optional[str], site_dir: Optional[str], out_dir: Optional[str], serve: bool, deploy: Optional[str], ) -> None: """Build (or serve) the BincioActivity static site from a BAS data store.""" site = _find_site_dir(site_dir) data = _find_data_dir(data_dir, config_path) console.print(f"Site: [cyan]{site}[/cyan]") console.print(f"Data: [cyan]{data}[/cyan]") _ensure_npm(site) _merge_edits(data) _link_data(site, data) env = {**os.environ, "BINCIO_DATA_DIR": str(data)} if serve: console.print("Starting [cyan]astro dev[/cyan]…") subprocess.run(["npm", "run", "dev"], cwd=site, env=env) return # Build cmd = ["npm", "run", "build"] if out_dir: # Pass outDir via Astro CLI flag cmd = ["npx", "astro", "build", "--outDir", str(Path(out_dir).resolve())] console.print("Running [cyan]astro build[/cyan]…") result = subprocess.run(cmd, cwd=site, env=env) if result.returncode != 0: console.print("[red]Build failed.[/red]") sys.exit(result.returncode) dist = Path(out_dir).resolve() if out_dir else site / "dist" console.print(f"\n[green]Build complete.[/green] Output: [cyan]{dist}[/cyan]") if deploy == "github": _deploy_github(site, dist) def _deploy_github(site: Path, dist: Path) -> None: """Push dist/ to the gh-pages branch.""" console.print("Deploying to [cyan]GitHub Pages[/cyan]…") # Requires npx gh-pages or git subtree push result = subprocess.run( ["npx", "gh-pages", "-d", str(dist)], cwd=site, ) if result.returncode != 0: console.print( "[yellow]Tip:[/yellow] install gh-pages with `npm install -g gh-pages`" )