Files
wild-pc/cli/src/castle_cli/commands/dev.py
payneio b720a96c6e feat: Add castle build command
Added castle build [project] command following the same pattern as castle test and castle lint. Stack handlers already had build methods — this just wires up the CLI to expose them.

Supports building a single project or all projects, providing feature parity with existing project-level commands.
2026-04-27 20:48:35 -07:00

127 lines
3.6 KiB
Python

"""castle build / castle test / castle lint - run dev commands across projects."""
from __future__ import annotations
import argparse
import asyncio
from castle_core.stacks import get_handler
from castle_cli.config import CastleConfig, load_config
def _run_action(config: CastleConfig, project_name: str, action: str) -> bool:
"""Run a stack action for a single project. Returns True on success."""
if project_name not in config.programs:
print(f"Unknown component: {project_name}")
return False
comp = config.programs[project_name]
if not comp.source:
print(f" {project_name}: no source directory, skipping")
return True
handler = get_handler(comp.stack)
if handler is None:
print(f" {project_name}: unsupported stack '{comp.stack}', skipping")
return True
method_name = action.replace("-", "_")
method = getattr(handler, method_name, None)
if method is None:
print(f" {project_name}: action '{action}' not supported")
return False
print(f"\n{'' * 40}")
print(f" {action}: {project_name}")
print(f"{'' * 40}")
result = asyncio.run(method(project_name, comp, config.root))
if result.output:
print(result.output)
return result.status == "ok"
def run_test(args: argparse.Namespace) -> int:
"""Run tests for one or all projects."""
config = load_config()
if args.project:
success = _run_action(config, args.project, "test")
return 0 if success else 1
# Run all
all_passed = True
for name, comp in config.programs.items():
if not comp.source:
continue
handler = get_handler(comp.stack)
if handler is None:
continue
# Skip projects without a tests directory (python) or test script (node)
source_dir = config.root / comp.source
if comp.stack in ("python-cli", "python-fastapi"):
if not (source_dir / "tests").exists():
continue
if not _run_action(config, name, "test"):
all_passed = False
if all_passed:
print("\nAll tests passed.")
else:
print("\nSome tests failed.")
return 0 if all_passed else 1
def run_lint(args: argparse.Namespace) -> int:
"""Run linter for one or all projects."""
config = load_config()
if args.project:
success = _run_action(config, args.project, "lint")
return 0 if success else 1
# Run all
all_passed = True
for name, comp in config.programs.items():
if not comp.source:
continue
handler = get_handler(comp.stack)
if handler is None:
continue
if not _run_action(config, name, "lint"):
all_passed = False
if all_passed:
print("\nAll lint checks passed.")
else:
print("\nSome lint checks failed.")
return 0 if all_passed else 1
def run_build(args: argparse.Namespace) -> int:
"""Run build for one or all projects."""
config = load_config()
if args.project:
success = _run_action(config, args.project, "build")
return 0 if success else 1
# Run all buildable projects
all_passed = True
for name, comp in config.programs.items():
if not comp.source:
continue
handler = get_handler(comp.stack)
if handler is None:
continue
if not _run_action(config, name, "build"):
all_passed = False
if all_passed:
print("\nAll builds succeeded.")
else:
print("\nSome builds failed.")
return 0 if all_passed else 1