diff --git a/src/server/main.py b/src/server/main.py index 6816b0d..7c2b63d 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -559,6 +559,31 @@ async def game_loop(): print(f"Game loop error: {e}") get_logger().logger.error(f"Game loop error: {e}", exc_info=True) + +def ensure_npm_dependencies(web_dir: str) -> bool: + """ + 确保 npm 依赖是最新的。 + + Args: + web_dir: web 目录路径。 + + Returns: + True 如果安装成功,False 如果失败。 + """ + import platform + print("📦 正在检查前端依赖...") + try: + if platform.system() == "Windows": + subprocess.run("npm install", cwd=web_dir, shell=True, check=True) + else: + subprocess.run(["npm", "install"], cwd=web_dir, shell=False, check=True) + print("✅ 前端依赖已就绪") + return True + except subprocess.CalledProcessError as e: + print(f"⚠️ npm install 失败: {e},继续启动...") + return False + + @asynccontextmanager async def lifespan(app: FastAPI): # 初始化语言设置 @@ -603,6 +628,9 @@ async def lifespan(app: FastAPI): project_root = os.path.abspath(os.path.join(current_dir, '..', '..')) web_dir = os.path.join(project_root, 'web') + # 确保 npm 依赖是最新的(npm install 会自动跳过已安装的包,通常 <1s)。 + ensure_npm_dependencies(web_dir) + print(f"正在启动前端开发服务 (npm run dev) 于: {web_dir}") # 跨平台兼容:Windows 用 shell=True + 字符串,macOS/Linux 用 shell=False + 列表。 try: diff --git a/tests/test_dev_mode.py b/tests/test_dev_mode.py new file mode 100644 index 0000000..e23ab84 --- /dev/null +++ b/tests/test_dev_mode.py @@ -0,0 +1,76 @@ +"""Tests for dev mode functionality in the server.""" + +import subprocess +from unittest.mock import patch, MagicMock +import pytest + + +class TestEnsureNpmDependencies: + """Tests for ensure_npm_dependencies function.""" + + def test_npm_install_success_unix(self): + """Test npm install succeeds on Unix-like systems.""" + from src.server.main import ensure_npm_dependencies + + with patch("platform.system", return_value="Darwin"), \ + patch("subprocess.run") as mock_run: + mock_run.return_value = MagicMock(returncode=0) + + result = ensure_npm_dependencies("/fake/web/dir") + + assert result is True + mock_run.assert_called_once_with( + ["npm", "install"], + cwd="/fake/web/dir", + shell=False, + check=True + ) + + def test_npm_install_success_windows(self): + """Test npm install succeeds on Windows.""" + from src.server.main import ensure_npm_dependencies + + with patch("platform.system", return_value="Windows"), \ + patch("subprocess.run") as mock_run: + mock_run.return_value = MagicMock(returncode=0) + + result = ensure_npm_dependencies("/fake/web/dir") + + assert result is True + mock_run.assert_called_once_with( + "npm install", + cwd="/fake/web/dir", + shell=True, + check=True + ) + + def test_npm_install_failure_returns_false(self): + """Test npm install failure returns False but doesn't raise.""" + from src.server.main import ensure_npm_dependencies + + with patch("platform.system", return_value="Darwin"), \ + patch("subprocess.run") as mock_run: + mock_run.side_effect = subprocess.CalledProcessError(1, "npm install") + + result = ensure_npm_dependencies("/fake/web/dir") + + assert result is False + + def test_npm_install_linux(self): + """Test npm install on Linux uses same path as macOS.""" + from src.server.main import ensure_npm_dependencies + + with patch("platform.system", return_value="Linux"), \ + patch("subprocess.run") as mock_run: + mock_run.return_value = MagicMock(returncode=0) + + result = ensure_npm_dependencies("/fake/web/dir") + + assert result is True + # Linux should use the same non-shell approach as macOS. + mock_run.assert_called_once_with( + ["npm", "install"], + cwd="/fake/web/dir", + shell=False, + check=True + )