diff --git a/lib/cli/src/crewai_cli/install_crew.py b/lib/cli/src/crewai_cli/install_crew.py index 8a0d4382e..52877e605 100644 --- a/lib/cli/src/crewai_cli/install_crew.py +++ b/lib/cli/src/crewai_cli/install_crew.py @@ -1,20 +1,63 @@ +from pathlib import Path import subprocess import click -from crewai_cli.utils import build_env_with_all_tool_credentials +from crewai_cli.utils import build_env_with_all_tool_credentials, parse_toml + + +def _find_json_crew_file(project_root: Path | None = None) -> Path | None: + """Return the JSON crew definition path when present.""" + root = project_root or Path.cwd() + for filename in ("crew.jsonc", "crew.json"): + crew_path = root / filename + if crew_path.is_file(): + return crew_path + return None + + +def _is_json_crew_project(project_root: Path | None = None) -> bool: + """Return True for JSON crew projects that do not need package install.""" + root = project_root or Path.cwd() + if _find_json_crew_file(root) is None: + return False + + pyproject_path = root / "pyproject.toml" + if not pyproject_path.is_file(): + return True + + try: + pyproject = parse_toml(pyproject_path.read_text()) + except Exception: + return True + + declared_type: str | None = ( + (pyproject.get("tool") or {}).get("crewai", {}).get("type") + ) + return declared_type != "flow" # Be mindful about changing this. # on some environments we don't use this command but instead uv sync directly # so if you expect this to support more things you will need to replicate it there # ask @joaomdmoura if you are unsure -def install_crew(proxy_options: list[str], *, raise_on_error: bool = False) -> None: +def install_crew( + proxy_options: list[str], + *, + raise_on_error: bool = False, + install_project: bool | None = None, +) -> None: """ Install the crew by running the UV command to lock and install. """ try: - command = ["uv", "sync", *proxy_options] + if install_project is None: + install_project = not _is_json_crew_project() + + command = ["uv", "sync"] + if not install_project and "--no-install-project" not in proxy_options: + command.append("--no-install-project") + command.extend(proxy_options) # Inject tool repository credentials so uv can authenticate # against private package indexes (e.g. crewai tool repository). diff --git a/lib/cli/src/crewai_cli/run_crew.py b/lib/cli/src/crewai_cli/run_crew.py index 1f828b970..34f82e68e 100644 --- a/lib/cli/src/crewai_cli/run_crew.py +++ b/lib/cli/src/crewai_cli/run_crew.py @@ -231,7 +231,7 @@ def _install_json_crew_dependencies() -> None: try: click.echo("Installing dependencies...") - install_crew([], raise_on_error=True) + install_crew([], raise_on_error=True, install_project=False) except subprocess.CalledProcessError as e: raise SystemExit(e.returncode) from e except Exception as e: diff --git a/lib/cli/tests/test_install_crew.py b/lib/cli/tests/test_install_crew.py new file mode 100644 index 000000000..d15d8229d --- /dev/null +++ b/lib/cli/tests/test_install_crew.py @@ -0,0 +1,66 @@ +from pathlib import Path + +import pytest + +import crewai_cli.install_crew as install_crew_module + + +@pytest.fixture(autouse=True) +def _tool_credentials(monkeypatch): + monkeypatch.setattr( + install_crew_module, + "build_env_with_all_tool_credentials", + lambda: {"CREWAI_TEST": "1"}, + ) + + +def test_install_crew_json_project_skips_project_install( + fp, monkeypatch, tmp_path: Path +): + monkeypatch.chdir(tmp_path) + (tmp_path / "pyproject.toml").write_text( + """ +[project] +name = "json_crew" + +[tool.crewai] +type = "crew" +""".strip() + ) + (tmp_path / "crew.jsonc").write_text("{}\n") + fp.register(["uv", "sync", "--no-install-project"], stdout="") + + install_crew_module.install_crew([]) + + +def test_install_crew_flow_project_installs_project(fp, monkeypatch, tmp_path: Path): + monkeypatch.chdir(tmp_path) + (tmp_path / "pyproject.toml").write_text( + """ +[project] +name = "flow_project" + +[tool.crewai] +type = "flow" +""".strip() + ) + (tmp_path / "crew.jsonc").write_text("{}\n") + fp.register(["uv", "sync"], stdout="") + + install_crew_module.install_crew([]) + + +def test_install_crew_classic_project_installs_project( + fp, monkeypatch, tmp_path: Path +): + monkeypatch.chdir(tmp_path) + (tmp_path / "pyproject.toml").write_text("[project]\nname = 'classic'\n") + fp.register(["uv", "sync"], stdout="") + + install_crew_module.install_crew([]) + + +def test_install_crew_install_project_false_adds_no_install_project(fp): + fp.register(["uv", "sync", "--no-install-project", "--frozen"], stdout="") + + install_crew_module.install_crew(["--frozen"], install_project=False) diff --git a/lib/cli/tests/test_run_crew.py b/lib/cli/tests/test_run_crew.py index 6764cf614..3ebbe89a9 100644 --- a/lib/cli/tests/test_run_crew.py +++ b/lib/cli/tests/test_run_crew.py @@ -127,14 +127,16 @@ def test_json_run_installs_dependencies_when_pyproject_exists( (tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n") calls = [] - def fake_install_crew(proxy_options, *, raise_on_error=False): - calls.append((proxy_options, raise_on_error)) + def fake_install_crew( + proxy_options, *, raise_on_error=False, install_project=None + ): + calls.append((proxy_options, raise_on_error, install_project)) monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew) run_crew_module._install_json_crew_dependencies() - assert calls == [([], True)] + assert calls == [([], True, False)] def test_json_run_skips_dependency_install_without_pyproject( @@ -143,7 +145,9 @@ def test_json_run_skips_dependency_install_without_pyproject( monkeypatch.chdir(tmp_path) calls = [] - def fake_install_crew(proxy_options, *, raise_on_error=False): + def fake_install_crew( + proxy_options, *, raise_on_error=False, install_project=None + ): calls.append((proxy_options, raise_on_error)) monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew) @@ -157,7 +161,9 @@ def test_json_run_install_failure_exits_nonzero(monkeypatch, tmp_path: Path): monkeypatch.chdir(tmp_path) (tmp_path / "pyproject.toml").write_text("[project]\nname = 'demo'\n") - def fake_install_crew(proxy_options, *, raise_on_error=False): + def fake_install_crew( + proxy_options, *, raise_on_error=False, install_project=None + ): raise subprocess.CalledProcessError(42, ["uv", "sync"]) monkeypatch.setattr("crewai_cli.install_crew.install_crew", fake_install_crew)