diff --git a/lib/devtools/src/crewai_devtools/cli.py b/lib/devtools/src/crewai_devtools/cli.py index 2decccc7b..538c69503 100644 --- a/lib/devtools/src/crewai_devtools/cli.py +++ b/lib/devtools/src/crewai_devtools/cli.py @@ -1507,7 +1507,8 @@ def _workflow_run_commands(content: str) -> list[str]: break block.append(block_line.strip()) index += 1 - commands.append("\n".join(block)) + separator = " " if value.startswith(">") else "\n" + commands.append(separator.join(block)) return commands @@ -1515,8 +1516,9 @@ def _workflow_crewai_requirements(content: str) -> list[tuple[str, str]]: """Collect CrewAI requirements from executable workflow install commands.""" requirements: list[tuple[str, str]] = [] for command in _workflow_run_commands(content): - normalized = command.replace("\\\n", " ").replace("\n", " ; ") - lexer = shlex.shlex(normalized, posix=True, punctuation_chars=";&|") + normalized = command.replace("\\\n", " ") + lexer = shlex.shlex(normalized, posix=True, punctuation_chars=";&|\n") + lexer.whitespace = " \t\r" lexer.whitespace_split = True lexer.commenters = "#" try: @@ -1552,7 +1554,13 @@ def _workflow_crewai_requirements(content: str) -> list[tuple[str, str]]: continue index += install_length - while index < len(tokens) and tokens[index] not in {";", "&&", "||", "|"}: + while index < len(tokens) and tokens[index] not in { + ";", + "&&", + "||", + "|", + "\n", + }: argument = tokens[index] pin = _crewai_requirement_pin(argument) if pin is not None: @@ -1572,8 +1580,10 @@ def _validate_deployment_repo_crewai_pin( workflows_dir = repo_dir / ".github" / "workflows" if workflows_dir.exists(): for workflow in workflows_dir.iterdir(): - if workflow.suffix in (".yml", ".yaml"): - requirements.extend(_workflow_crewai_requirements(workflow.read_text())) + if workflow.is_file() and workflow.suffix in (".yml", ".yaml"): + requirements.extend( + _workflow_crewai_requirements(workflow.read_text(encoding="utf-8")) + ) if not requirements: raise RuntimeError(f"No effective CrewAI dependency found in {repo_dir.name}") diff --git a/lib/devtools/tests/test_toml_updates.py b/lib/devtools/tests/test_toml_updates.py index 6f61a6af2..6bf05a590 100644 --- a/lib/devtools/tests/test_toml_updates.py +++ b/lib/devtools/tests/test_toml_updates.py @@ -75,6 +75,17 @@ def test_deployment_repo_validation_ignores_comments_and_echo(tmp_path: Path) -> 'run: echo "crewai==2.0.0"\n# run: pip install crewai==2.0.0\n' ) + with pytest.raises(RuntimeError, match="No effective CrewAI dependency"): + _validate_deployment_repo_crewai_pin( + tmp_path, + '[project]\ndependencies = ["requests>=2"]\n', + "2.0.0", + ) + + +def test_deployment_repo_validation_ignores_pyproject_comment_pin( + tmp_path: Path, +) -> None: with pytest.raises(RuntimeError, match=r"must all pin 2\.0\.0"): _validate_deployment_repo_crewai_pin( tmp_path, @@ -120,6 +131,66 @@ def test_deployment_repo_validation_reads_multiline_workflow_install( ) +def test_deployment_repo_validation_reads_install_after_comment( + tmp_path: Path, +) -> None: + workflows = tmp_path / ".github" / "workflows" + workflows.mkdir(parents=True) + (workflows / "test.yml").write_text( + "steps:\n" + " - name: Install\n" + " run: |\n" + " # Install the canary dependency\n" + ' uv pip install "crewai==2.0.0"\n', + encoding="utf-8", + ) + + _validate_deployment_repo_crewai_pin( + tmp_path, + '[project]\ndependencies = ["requests>=2"]\n', + "2.0.0", + ) + + +def test_deployment_repo_validation_reads_folded_workflow_install( + tmp_path: Path, +) -> None: + workflows = tmp_path / ".github" / "workflows" + workflows.mkdir(parents=True) + (workflows / "test.yml").write_text( + "steps:\n" + " - name: Install\n" + " run: >\n" + " uv pip install\n" + ' "crewai==2.0.0"\n', + encoding="utf-8", + ) + + _validate_deployment_repo_crewai_pin( + tmp_path, + '[project]\ndependencies = ["requests>=2"]\n', + "2.0.0", + ) + + +def test_deployment_repo_validation_skips_non_file_workflow_entries( + tmp_path: Path, +) -> None: + workflows = tmp_path / ".github" / "workflows" + workflows.mkdir(parents=True) + (workflows / "ignored.yml").mkdir() + (workflows / "test.yaml").write_text( + '# UTF-8 workflow: déploiement\nrun: uv pip install "crewai==2.0.0"\n', + encoding="utf-8", + ) + + _validate_deployment_repo_crewai_pin( + tmp_path, + '[project]\ndependencies = ["requests>=2"]\n', + "2.0.0", + ) + + class TestUpdatePyprojectVersion: def test_updates_version(self, tmp_path: Path) -> None: pyproject = tmp_path / "pyproject.toml"