- Nx 22.7 monorepo (pnpm 11.1, TypeScript 5.9, Node 24) - apps/api: NestJS 11 (CJS conforme CODING-RULES.md PGD-DB-004) - apps/web: React 19 + Vite 8 (ESM) - libs/shared/api-interface: Zod contract base - Docker Compose dev: Postgres 18, Valkey 8, MinIO, Mailpit - WDS artifacts: - design-artifacts/A-Product-Brief/ (5 docs canônicos + 16 dialogs) - design-artifacts/B-Trigger-Map/ (hub + 4 personas + feature impact) - Stack canon: STACK.md v2.2 + CODING-RULES.md v2.0 + brand.md - AGENTS.md + README.md como entrada para devs/agentes Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
116 lines
3.6 KiB
Python
116 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
# /// script
|
|
# requires-python = ">=3.9"
|
|
# ///
|
|
"""Detect Docker and build the bmad-eval-runner image when needed.
|
|
|
|
Usage:
|
|
python3 docker_setup.py --check # exit 0 if image is ready, 1 otherwise
|
|
python3 docker_setup.py --build # build the image (no-op if present)
|
|
python3 docker_setup.py --rebuild # force rebuild
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
IMAGE_TAG = "bmad-eval-runner:latest"
|
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
DOCKERFILE = SCRIPT_DIR.parent / "assets" / "Dockerfile"
|
|
|
|
|
|
def docker_available() -> tuple[bool, str]:
|
|
if shutil.which("docker") is None:
|
|
return False, "docker CLI not found on PATH"
|
|
try:
|
|
result = subprocess.run(
|
|
["docker", "info"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
)
|
|
if result.returncode != 0:
|
|
return False, f"`docker info` failed: {result.stderr.strip().splitlines()[-1] if result.stderr.strip() else 'unknown'}"
|
|
return True, "ok"
|
|
except subprocess.TimeoutExpired:
|
|
return False, "`docker info` timed out"
|
|
except Exception as e:
|
|
return False, f"docker check error: {e}"
|
|
|
|
|
|
def image_present(tag: str = IMAGE_TAG) -> bool:
|
|
try:
|
|
result = subprocess.run(
|
|
["docker", "image", "inspect", tag],
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
timeout=10,
|
|
)
|
|
return result.returncode == 0
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def build_image(tag: str = IMAGE_TAG, force: bool = False, verbose: bool = True) -> int:
|
|
if not DOCKERFILE.is_file():
|
|
print(f"Dockerfile missing at {DOCKERFILE}", file=sys.stderr)
|
|
return 2
|
|
|
|
cmd = ["docker", "build", "-t", tag, "-f", str(DOCKERFILE), str(DOCKERFILE.parent)]
|
|
if force:
|
|
cmd.insert(2, "--no-cache")
|
|
|
|
if verbose:
|
|
print(f"Building {tag} from {DOCKERFILE} ...", file=sys.stderr)
|
|
|
|
proc = subprocess.run(cmd, stdout=sys.stderr if verbose else subprocess.DEVNULL, stderr=sys.stderr)
|
|
return proc.returncode
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Manage the bmad-eval-runner Docker image")
|
|
group = parser.add_mutually_exclusive_group(required=True)
|
|
group.add_argument("--check", action="store_true", help="Report status as JSON; exit 0 if image is ready")
|
|
group.add_argument("--build", action="store_true", help="Build the image (no-op if already present)")
|
|
group.add_argument("--rebuild", action="store_true", help="Force rebuild")
|
|
parser.add_argument("--quiet", action="store_true")
|
|
args = parser.parse_args()
|
|
|
|
available, reason = docker_available()
|
|
present = image_present() if available else False
|
|
|
|
if args.check:
|
|
print(json.dumps({
|
|
"docker_available": available,
|
|
"docker_reason": reason,
|
|
"image_present": present,
|
|
"image_tag": IMAGE_TAG,
|
|
}, indent=2))
|
|
return 0 if (available and present) else 1
|
|
|
|
if not available:
|
|
print(f"Docker is not available: {reason}", file=sys.stderr)
|
|
return 3
|
|
|
|
if args.rebuild:
|
|
return build_image(force=True, verbose=not args.quiet)
|
|
|
|
if args.build:
|
|
if present:
|
|
if not args.quiet:
|
|
print(f"{IMAGE_TAG} already present; skipping build (use --rebuild to force).", file=sys.stderr)
|
|
return 0
|
|
return build_image(force=False, verbose=not args.quiet)
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|