fix(cli): skip project install for json crew sync

This commit is contained in:
Joao Moura
2026-06-15 09:44:01 -07:00
parent c4027a1fb4
commit bfc817bbd3
4 changed files with 124 additions and 9 deletions

View File

@@ -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).

View File

@@ -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:

View File

@@ -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)

View File

@@ -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)