feat: add CLI commands (build, clean, graph)
- build: executes all or specific target with dependency resolution - clean: removes generated artifacts and state file, preserves inputs - graph: prints dependency graph with build stages - Config discovery: finds single *.bulkgen.yaml in working directory
This commit is contained in:
parent
bb4b2e2b86
commit
1d98c0010a
1 changed files with 101 additions and 0 deletions
101
bulkgen/cli.py
Normal file
101
bulkgen/cli.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
"""Typer CLI for bulkgen: build, clean, graph commands."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
||||
from bulkgen.builder import run_build
|
||||
from bulkgen.config import load_config
|
||||
from bulkgen.graph import build_graph, get_build_order
|
||||
|
||||
app = typer.Typer(name="bulkgen", help="AI artifact build tool.")
|
||||
|
||||
|
||||
def _find_config(directory: Path) -> Path:
|
||||
"""Find the single ``*.bulkgen.yaml`` file in *directory*."""
|
||||
candidates = list(directory.glob("*.bulkgen.yaml"))
|
||||
if len(candidates) == 0:
|
||||
typer.echo("Error: No .bulkgen.yaml file found in current directory", err=True)
|
||||
raise typer.Exit(code=1)
|
||||
if len(candidates) > 1:
|
||||
names = ", ".join(str(c.name) for c in candidates)
|
||||
typer.echo(f"Error: Multiple .bulkgen.yaml files found: {names}", err=True)
|
||||
raise typer.Exit(code=1)
|
||||
return candidates[0]
|
||||
|
||||
|
||||
@app.command()
|
||||
def build(
|
||||
target: Annotated[
|
||||
str | None, typer.Argument(help="Specific target to build.")
|
||||
] = None,
|
||||
) -> None:
|
||||
"""Build all targets (or a specific target) in dependency order."""
|
||||
project_dir = Path.cwd()
|
||||
config_path = _find_config(project_dir)
|
||||
config = load_config(config_path)
|
||||
|
||||
result = asyncio.run(run_build(config, project_dir, target))
|
||||
|
||||
if result.built:
|
||||
typer.echo(f"\nBuilt {len(result.built)} target(s)")
|
||||
if result.skipped:
|
||||
typer.echo(f"Skipped {len(result.skipped)} target(s) (up to date)")
|
||||
if result.failed:
|
||||
typer.echo(f"Failed {len(result.failed)} target(s):", err=True)
|
||||
for name, err in result.failed.items():
|
||||
typer.echo(f" {name}: {err}", err=True)
|
||||
raise typer.Exit(code=1)
|
||||
|
||||
|
||||
@app.command()
|
||||
def clean() -> None:
|
||||
"""Remove generated artifacts (targets only, not input files)."""
|
||||
project_dir = Path.cwd()
|
||||
config_path = _find_config(project_dir)
|
||||
config = load_config(config_path)
|
||||
|
||||
removed = 0
|
||||
for target_name in config.targets:
|
||||
target_path = project_dir / target_name
|
||||
if target_path.exists():
|
||||
target_path.unlink()
|
||||
typer.echo(f"Removed: {target_name}")
|
||||
removed += 1
|
||||
|
||||
state_path = project_dir / ".bulkgen.state.yaml"
|
||||
if state_path.exists():
|
||||
state_path.unlink()
|
||||
typer.echo("Removed: .bulkgen.state.yaml")
|
||||
|
||||
typer.echo(f"Cleaned {removed} artifact(s)")
|
||||
|
||||
|
||||
@app.command()
|
||||
def graph() -> None:
|
||||
"""Print the dependency graph with build stages."""
|
||||
project_dir = Path.cwd()
|
||||
config_path = _find_config(project_dir)
|
||||
config = load_config(config_path)
|
||||
|
||||
dep_graph = build_graph(config, project_dir)
|
||||
generations = get_build_order(dep_graph)
|
||||
target_names = set(config.targets)
|
||||
|
||||
for i, gen in enumerate(generations):
|
||||
targets_in_gen = [n for n in gen if n in target_names]
|
||||
externals_in_gen = [n for n in gen if n not in target_names]
|
||||
|
||||
if externals_in_gen:
|
||||
typer.echo(f"Stage {i} (inputs): {', '.join(externals_in_gen)}")
|
||||
if targets_in_gen:
|
||||
typer.echo(f"Stage {i} (targets): {', '.join(targets_in_gen)}")
|
||||
|
||||
for node in gen:
|
||||
predecessors = list(dep_graph.predecessors(node))
|
||||
if predecessors:
|
||||
typer.echo(f" {node} <- {', '.join(predecessors)}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue