imajin/scripts/run/install_command.py

326 lines
9.6 KiB
Python

"""Install command handler for script runner."""
import argparse
import subprocess
import sys
from pathlib import Path
# TypeScript packages to install
TYPESCRIPT_PACKAGES = [
"packages/imajin-app",
"packages/imajin-react",
"packages/imajin-electron",
"packages/imajin-client",
"packages/imajin-config",
]
# Python services to install
PYTHON_SERVICES = [
"services/imajin-aesthetic",
"services/imajin-diffusion",
"services/imajin-identity",
"services/imajin-moderator",
"services/imajin-processing",
"services/imajin-prompt",
"services/imajin-prompt-generator",
"services/imajin-request-classifier",
"services/imajin-semantic",
]
def install_command(args, workspace_root: Path):
"""Install dependencies for all packages and services.
Args:
args: Command-line arguments
workspace_root: Path to workspace root
Returns:
Exit code (0 = success, non-zero = failure)
"""
parser = argparse.ArgumentParser(
prog="./run install",
description="Install dependencies for @image workspace",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
./run install # Install everything
./run install --ts # Install only TypeScript packages
./run install --py # Install only Python services
./run install --build # Install and build everything
./run install --test # Install, build, and test
What this does:
TypeScript: npm install in each package/monorepo
Python: Create .venv and pip install -e . in each service
Note: This can take 5-10 minutes on first run.
""",
)
parser.add_argument(
"--ts",
"--typescript",
action="store_true",
dest="typescript",
help="Install only TypeScript packages",
)
parser.add_argument(
"--py",
"--python",
action="store_true",
dest="python",
help="Install only Python services",
)
parser.add_argument(
"--build",
action="store_true",
help="Build TypeScript packages after installing",
)
parser.add_argument(
"--test",
action="store_true",
help="Run tests after installing (implies --build)",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Verbose output",
)
parsed_args = parser.parse_args(args)
# If no flags, install both
if not parsed_args.typescript and not parsed_args.python:
parsed_args.typescript = True
parsed_args.python = True
# --test implies --build
if parsed_args.test:
parsed_args.build = True
print("@image workspace install\n")
failed = []
succeeded = []
# Install root workspace (if exists)
root_package_json = workspace_root / "package.json"
if root_package_json.exists() and parsed_args.typescript:
print("Root Workspace")
print("" * 50)
print("▶ Installing: workspace root")
result = subprocess.run(
["pnpm", "install"],
cwd=workspace_root,
capture_output=not parsed_args.verbose,
check=False,
)
if result.returncode == 0:
print("✓ PASS: workspace root")
succeeded.append("workspace:root")
else:
print("✗ FAIL: workspace root")
if not parsed_args.verbose and result.stderr:
print(f" Error: {result.stderr.decode()[:300]}")
failed.append("workspace:root")
print()
# Install TypeScript packages
if parsed_args.typescript:
print("TypeScript Packages")
print("" * 50)
for pkg in TYPESCRIPT_PACKAGES:
pkg_path = workspace_root / pkg
if not pkg_path.exists():
print(f"⊘ SKIP: {pkg} (directory not found)")
continue
package_json = pkg_path / "package.json"
if not package_json.exists():
print(f"⊘ SKIP: {pkg} (no package.json)")
continue
print(f"▶ Installing: {pkg}")
# Run pnpm install
install_cmd = ["pnpm", "install"]
result = subprocess.run(
install_cmd,
cwd=pkg_path,
capture_output=not parsed_args.verbose,
check=False,
)
if result.returncode == 0:
print(f"✓ PASS: {pkg}")
succeeded.append(f"ts:{pkg}")
else:
print(f"✗ FAIL: {pkg}")
if not parsed_args.verbose and result.stderr:
print(f" Error: {result.stderr.decode()[:300]}")
failed.append(f"ts:{pkg}")
print()
print()
# Install Python services
if parsed_args.python:
print("Python Services")
print("" * 50)
for service in PYTHON_SERVICES:
service_path = workspace_root / service
if not service_path.exists():
print(f"⊘ SKIP: {service} (directory not found)")
continue
# Check for pyproject.toml or setup.py
has_pyproject = (service_path / "pyproject.toml").exists()
has_setup = (service_path / "setup.py").exists()
if not has_pyproject and not has_setup:
print(f"⊘ SKIP: {service} (no pyproject.toml or setup.py)")
continue
print(f"▶ Installing: {service}")
venv_path = service_path / ".venv"
# Create venv if it doesn't exist
if not venv_path.exists():
print(f" Creating virtual environment...")
venv_cmd = ["python3", "-m", "venv", ".venv"]
result = subprocess.run(
venv_cmd,
cwd=service_path,
capture_output=not parsed_args.verbose,
check=False,
)
if result.returncode != 0:
print(f"✗ FAIL: {service} (venv creation failed)")
if not parsed_args.verbose and result.stderr:
print(f" Error: {result.stderr.decode()[:300]}")
failed.append(f"py:{service}")
print()
continue
# Install package in editable mode
print(f" Installing dependencies...")
activate_script = venv_path / "bin" / "activate"
# Install package in editable mode
pip_cmd = f"source {activate_script} && pip install -e ."
# Also install dev dependencies if available
if has_pyproject:
# Check if there are dev/test extras
pip_cmd += " && pip install -e '.[dev]' 2>/dev/null || true"
result = subprocess.run(
["bash", "-c", pip_cmd],
cwd=service_path,
capture_output=not parsed_args.verbose,
check=False,
)
if result.returncode == 0:
print(f"✓ PASS: {service}")
succeeded.append(f"py:{service}")
else:
print(f"✗ FAIL: {service}")
if not parsed_args.verbose and result.stderr:
print(f" Error: {result.stderr.decode()[:300]}")
failed.append(f"py:{service}")
print()
print()
# Summary
total = len(succeeded) + len(failed)
print("" * 50)
print(f"Installed: {len(succeeded)}/{total} succeeded")
if failed:
print(f"\nFailed:")
for item in failed:
print(f"{item}")
return 1
print("\n✓ All dependencies installed successfully")
# Build if requested
if parsed_args.build and parsed_args.typescript:
print("\n" + "" * 50)
print("Building TypeScript packages...")
print("" * 50 + "\n")
# Import and call build command
import importlib.util
script_path = Path(__file__).resolve()
build_cmd_path = script_path.parent / "build_command.py"
spec = importlib.util.spec_from_file_location("build_command", build_cmd_path)
build_cmd = importlib.util.module_from_spec(spec)
spec.loader.exec_module(build_cmd)
build_args = []
if parsed_args.verbose:
build_args.append("-v")
result = build_cmd.build_command(build_args, workspace_root)
if result != 0:
print("\n✗ Build failed")
return result
# Test if requested
if parsed_args.test:
print("\n" + "" * 50)
print("Running tests...")
print("" * 50 + "\n")
# Import and call test command
import importlib.util
script_path = Path(__file__).resolve()
test_cmd_path = script_path.parent / "test_command.py"
spec = importlib.util.spec_from_file_location("test_command", test_cmd_path)
test_cmd = importlib.util.module_from_spec(spec)
spec.loader.exec_module(test_cmd)
test_args = ["--unit"] # Run unit tests only by default
if parsed_args.verbose:
test_args.append("-v")
result = test_cmd.test_command(test_args, workspace_root)
if result != 0:
print("\n✗ Tests failed")
return result
return 0
def register_install_command(runner):
"""Register the install command with the script runner.
Args:
runner: ScriptRunner instance
"""
runner.register_command(
"install",
install_command,
"Install dependencies for all packages",
)