"""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", )