import os import platform import subprocess import tempfile from pathlib import Path import requests from cognee.shared.logging_utils import get_logger logger = get_logger() def get_nvm_dir() -> Path: """ Get the nvm directory path following standard nvm installation logic. Uses XDG_CONFIG_HOME if set, otherwise falls back to ~/.nvm. """ xdg_config_home = os.environ.get("XDG_CONFIG_HOME") if xdg_config_home: return Path(xdg_config_home) / "nvm" return Path.home() / ".nvm" def get_nvm_sh_path() -> Path: """ Get the path to nvm.sh following standard nvm installation logic. """ return get_nvm_dir() / "nvm.sh" def check_nvm_installed() -> bool: """ Check if nvm (Node Version Manager) is installed. """ try: # Check if nvm is available in the shell # nvm is typically sourced in shell config files, so we need to check via shell if platform.system() == "Windows": # On Windows, nvm-windows uses a different approach result = subprocess.run( ["nvm", "version"], capture_output=True, text=True, timeout=10, shell=True, ) else: # On Unix-like systems, nvm is a shell function, so we need to source it # First check if nvm.sh exists nvm_path = get_nvm_sh_path() if not nvm_path.exists(): logger.debug(f"nvm.sh not found at {nvm_path}") return False # Try to source nvm and check version, capturing errors result = subprocess.run( ["bash", "-c", f"source {nvm_path} && nvm --version"], capture_output=True, text=True, timeout=10, ) if result.returncode != 0: # Log the error to help diagnose configuration issues if result.stderr: logger.debug(f"nvm check failed: {result.stderr.strip()}") return False return result.returncode == 0 except Exception as e: logger.debug(f"Exception checking nvm: {str(e)}") return False def install_nvm() -> bool: """ Install nvm (Node Version Manager) on Unix-like systems. """ if platform.system() == "Windows": logger.error("nvm installation on Windows requires nvm-windows.") logger.error( "Please install nvm-windows manually from: https://github.com/coreybutler/nvm-windows" ) return False logger.info("Installing nvm (Node Version Manager)...") try: # Download and install nvm nvm_install_script = "https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh" logger.info(f"Downloading nvm installer from {nvm_install_script}...") response = requests.get(nvm_install_script, timeout=60) response.raise_for_status() # Create a temporary script file with tempfile.NamedTemporaryFile(mode="w", suffix=".sh", delete=False) as f: f.write(response.text) install_script_path = f.name try: # Make the script executable and run it os.chmod(install_script_path, 0o755) result = subprocess.run( ["bash", install_script_path], capture_output=True, text=True, timeout=120, ) if result.returncode == 0: logger.info("✓ nvm installed successfully") # Source nvm in current shell session nvm_dir = get_nvm_dir() if nvm_dir.exists(): return True else: logger.warning( f"nvm installation completed but nvm directory not found at {nvm_dir}" ) return False else: logger.error(f"nvm installation failed: {result.stderr}") return False finally: # Clean up temporary script try: os.unlink(install_script_path) except Exception: pass except requests.exceptions.RequestException as e: logger.error(f"Failed to download nvm installer: {str(e)}") return False except Exception as e: logger.error(f"Failed to install nvm: {str(e)}") return False def install_node_with_nvm() -> bool: """ Install the latest Node.js version using nvm. Returns True if installation succeeds, False otherwise. """ if platform.system() == "Windows": logger.error("Node.js installation via nvm on Windows requires nvm-windows.") logger.error("Please install Node.js manually from: https://nodejs.org/") return False logger.info("Installing latest Node.js version using nvm...") try: # Source nvm and install latest Node.js nvm_path = get_nvm_sh_path() if not nvm_path.exists(): logger.error(f"nvm.sh not found at {nvm_path}. nvm may not be properly installed.") return False nvm_source_cmd = f"source {nvm_path}" install_cmd = f"{nvm_source_cmd} && nvm install node" result = subprocess.run( ["bash", "-c", install_cmd], capture_output=True, text=True, timeout=300, # 5 minutes timeout for Node.js installation ) if result.returncode == 0: logger.info("✓ Node.js installed successfully via nvm") # Set as default version use_cmd = f"{nvm_source_cmd} && nvm alias default node" subprocess.run( ["bash", "-c", use_cmd], capture_output=True, text=True, timeout=30, ) # Add nvm to PATH for current session # This ensures node/npm are available in subsequent commands nvm_dir = get_nvm_dir() if nvm_dir.exists(): # Update PATH for current process nvm_bin = nvm_dir / "versions" / "node" # Find the latest installed version if nvm_bin.exists(): versions = sorted(nvm_bin.iterdir(), reverse=True) if versions: latest_node_bin = versions[0] / "bin" if latest_node_bin.exists(): current_path = os.environ.get("PATH", "") os.environ["PATH"] = f"{latest_node_bin}:{current_path}" return True else: logger.error(f"Failed to install Node.js: {result.stderr}") return False except subprocess.TimeoutExpired: logger.error("Timeout installing Node.js (this can take several minutes)") return False except Exception as e: logger.error(f"Error installing Node.js: {str(e)}") return False def check_node_npm() -> tuple[bool, str]: # (is_available, error_message) """ Check if Node.js and npm are available. If not available, attempts to install nvm and Node.js automatically. """ try: # Check Node.js - try direct command first, then with nvm if needed result = subprocess.run(["node", "--version"], capture_output=True, text=True, timeout=10) if result.returncode != 0: # If direct command fails, try with nvm sourced (in case nvm is installed but not in PATH) nvm_path = get_nvm_sh_path() if nvm_path.exists(): result = subprocess.run( ["bash", "-c", f"source {nvm_path} && node --version"], capture_output=True, text=True, timeout=10, ) if result.returncode != 0 and result.stderr: logger.debug(f"Failed to source nvm or run node: {result.stderr.strip()}") if result.returncode != 0: # Node.js is not installed, try to install it logger.info("Node.js is not installed. Attempting to install automatically...") # Check if nvm is installed if not check_nvm_installed(): logger.info("nvm is not installed. Installing nvm first...") if not install_nvm(): return ( False, "Failed to install nvm. Please install Node.js manually from https://nodejs.org/", ) # Install Node.js using nvm if not install_node_with_nvm(): return ( False, "Failed to install Node.js. Please install Node.js manually from https://nodejs.org/", ) # Verify installation after automatic setup # Try with nvm sourced first nvm_path = get_nvm_sh_path() if nvm_path.exists(): result = subprocess.run( ["bash", "-c", f"source {nvm_path} && node --version"], capture_output=True, text=True, timeout=10, ) if result.returncode != 0 and result.stderr: logger.debug( f"Failed to verify node after installation: {result.stderr.strip()}" ) else: result = subprocess.run( ["node", "--version"], capture_output=True, text=True, timeout=10 ) if result.returncode != 0: nvm_path = get_nvm_sh_path() return ( False, f"Node.js installation completed but node command is not available. Please restart your terminal or source {nvm_path}", ) node_version = result.stdout.strip() logger.debug(f"Found Node.js version: {node_version}") # Check npm - handle Windows PowerShell scripts if platform.system() == "Windows": # On Windows, npm might be a PowerShell script, so we need to use shell=True result = subprocess.run( ["npm", "--version"], capture_output=True, text=True, timeout=10, shell=True ) else: # On Unix-like systems, if we just installed via nvm, we may need to source nvm # Try direct command first result = subprocess.run( ["npm", "--version"], capture_output=True, text=True, timeout=10 ) if result.returncode != 0: # Try with nvm sourced nvm_path = get_nvm_sh_path() if nvm_path.exists(): result = subprocess.run( ["bash", "-c", f"source {nvm_path} && npm --version"], capture_output=True, text=True, timeout=10, ) if result.returncode != 0 and result.stderr: logger.debug(f"Failed to source nvm or run npm: {result.stderr.strip()}") if result.returncode != 0: return False, "npm is not installed or not in PATH" npm_version = result.stdout.strip() logger.debug(f"Found npm version: {npm_version}") return True, f"Node.js {node_version}, npm {npm_version}" except subprocess.TimeoutExpired: return False, "Timeout checking Node.js/npm installation" except FileNotFoundError: # Node.js is not installed, try to install it logger.info("Node.js is not found. Attempting to install automatically...") # Check if nvm is installed if not check_nvm_installed(): logger.info("nvm is not installed. Installing nvm first...") if not install_nvm(): return ( False, "Failed to install nvm. Please install Node.js manually from https://nodejs.org/", ) # Install Node.js using nvm if not install_node_with_nvm(): return ( False, "Failed to install Node.js. Please install Node.js manually from https://nodejs.org/", ) # Retry checking Node.js after installation try: result = subprocess.run( ["node", "--version"], capture_output=True, text=True, timeout=10 ) if result.returncode == 0: node_version = result.stdout.strip() # Check npm nvm_path = get_nvm_sh_path() if nvm_path.exists(): result = subprocess.run( ["bash", "-c", f"source {nvm_path} && npm --version"], capture_output=True, text=True, timeout=10, ) if result.returncode == 0: npm_version = result.stdout.strip() return True, f"Node.js {node_version}, npm {npm_version}" elif result.stderr: logger.debug(f"Failed to source nvm or run npm: {result.stderr.strip()}") except Exception as e: logger.debug(f"Exception retrying node/npm check: {str(e)}") return False, "Node.js/npm not found. Please install Node.js from https://nodejs.org/" except Exception as e: return False, f"Error checking Node.js/npm: {str(e)}"