mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-05-04 00:32:36 +00:00
refactor: replace regex with tomlkit in devtools CLI
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
This commit is contained in:
@@ -11,7 +11,7 @@ classifiers = ["Private :: Do Not Upload"]
|
||||
private = true
|
||||
dependencies = [
|
||||
"click~=8.1.7",
|
||||
"toml~=0.10.2",
|
||||
"tomlkit~=0.13.2",
|
||||
"openai>=1.83.0,<3",
|
||||
"python-dotenv~=1.1.1",
|
||||
"pygithub~=1.59.1",
|
||||
@@ -25,6 +25,10 @@ release = "crewai_devtools.cli:release"
|
||||
docs-check = "crewai_devtools.docs_check:docs_check"
|
||||
devtools = "crewai_devtools.cli:main"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
addopts = "--noconftest"
|
||||
|
||||
[tool.uv]
|
||||
exclude-newer = "3 days"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Development tools for version bumping and git automation."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
@@ -18,6 +18,7 @@ from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import Confirm
|
||||
import tomlkit
|
||||
|
||||
from crewai_devtools.docs_check import docs_check
|
||||
from crewai_devtools.prompts import RELEASE_NOTES_PROMPT, TRANSLATE_RELEASE_NOTES_PROMPT
|
||||
@@ -169,18 +170,17 @@ def update_pyproject_version(file_path: Path, new_version: str) -> bool:
|
||||
if not file_path.exists():
|
||||
return False
|
||||
|
||||
content = file_path.read_text()
|
||||
new_content = re.sub(
|
||||
r'^(version\s*=\s*")[^"]+(")',
|
||||
rf"\g<1>{new_version}\2",
|
||||
content,
|
||||
count=1,
|
||||
flags=re.MULTILINE,
|
||||
)
|
||||
if new_content != content:
|
||||
file_path.write_text(new_content)
|
||||
return True
|
||||
return False
|
||||
doc = tomlkit.parse(file_path.read_text())
|
||||
project = doc.get("project")
|
||||
if project is None:
|
||||
return False
|
||||
old_version = project.get("version")
|
||||
if old_version is None or old_version == new_version:
|
||||
return False
|
||||
|
||||
project["version"] = new_version
|
||||
file_path.write_text(tomlkit.dumps(doc))
|
||||
return True
|
||||
|
||||
|
||||
_DEFAULT_WORKSPACE_PACKAGES: Final[list[str]] = [
|
||||
@@ -473,6 +473,14 @@ def update_changelog(
|
||||
return True
|
||||
|
||||
|
||||
def _is_crewai_dep(spec: str) -> bool:
|
||||
"""Return True if *spec* is a ``crewai`` or ``crewai[...]`` dependency."""
|
||||
if not spec.startswith("crewai"):
|
||||
return False
|
||||
rest = spec[6:] # after "crewai"
|
||||
return len(rest) > 0 and rest[0] in ("[", "=", ">", "<", "~", "!")
|
||||
|
||||
|
||||
def _pin_crewai_deps(content: str, version: str) -> str:
|
||||
"""Replace crewai dependency version pins in a pyproject.toml string.
|
||||
|
||||
@@ -486,11 +494,21 @@ def _pin_crewai_deps(content: str, version: str) -> str:
|
||||
Returns:
|
||||
Transformed content.
|
||||
"""
|
||||
return re.sub(
|
||||
r'"crewai(\[tools\])?(==|>=)[^"]*"',
|
||||
lambda m: f'"crewai{(m.group(1) or "")!s}=={version}"',
|
||||
content,
|
||||
)
|
||||
doc = tomlkit.parse(content)
|
||||
for key in ("dependencies", "optional-dependencies"):
|
||||
deps = doc.get("project", {}).get(key)
|
||||
if deps is None:
|
||||
continue
|
||||
# optional-dependencies is a table of lists; dependencies is a list
|
||||
dep_lists = deps.values() if isinstance(deps, Mapping) else [deps]
|
||||
for dep_list in dep_lists:
|
||||
for i, dep in enumerate(dep_list):
|
||||
s = str(dep)
|
||||
if not _is_crewai_dep(s) or ("==" not in s and ">=" not in s):
|
||||
continue
|
||||
extras = s[6 : s.index("]") + 1] if "[" in s[6:7] else ""
|
||||
dep_list[i] = f"crewai{extras}=={version}"
|
||||
return tomlkit.dumps(doc)
|
||||
|
||||
|
||||
def update_template_dependencies(templates_dir: Path, new_version: str) -> list[Path]:
|
||||
@@ -1049,6 +1067,11 @@ _ENTERPRISE_EXTRA_PACKAGES: Final[tuple[str, ...]] = tuple(
|
||||
for p in os.getenv("ENTERPRISE_EXTRA_PACKAGES", "").split(",")
|
||||
if p.strip()
|
||||
)
|
||||
_ENTERPRISE_WORKFLOW_PATHS: Final[tuple[str, ...]] = tuple(
|
||||
p.strip()
|
||||
for p in os.getenv("ENTERPRISE_WORKFLOW_PATHS", "").split(",")
|
||||
if p.strip()
|
||||
)
|
||||
|
||||
|
||||
def _update_enterprise_crewai_dep(pyproject_path: Path, version: str) -> bool:
|
||||
@@ -1072,6 +1095,86 @@ def _update_enterprise_crewai_dep(pyproject_path: Path, version: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _update_enterprise_workflows(repo_dir: Path, version: str) -> list[Path]:
|
||||
"""Update crewai version pins in enterprise CI workflow files.
|
||||
|
||||
Applies ``_repin_crewai_install`` line-by-line on the raw file so
|
||||
only version numbers change and all formatting is preserved.
|
||||
|
||||
Args:
|
||||
repo_dir: Root of the cloned enterprise repo.
|
||||
version: New crewai version string.
|
||||
|
||||
Returns:
|
||||
List of workflow paths that were modified.
|
||||
"""
|
||||
updated: list[Path] = []
|
||||
for rel_path in _ENTERPRISE_WORKFLOW_PATHS:
|
||||
workflow = repo_dir / rel_path
|
||||
if not workflow.exists():
|
||||
continue
|
||||
|
||||
raw = workflow.read_text()
|
||||
lines = raw.splitlines(keepends=True)
|
||||
changed = False
|
||||
for i, line in enumerate(lines):
|
||||
if "crewai[" not in line:
|
||||
continue
|
||||
new_line = _repin_crewai_install(line, version)
|
||||
if new_line != line:
|
||||
lines[i] = new_line
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
new_raw = "".join(lines)
|
||||
else:
|
||||
new_raw = raw
|
||||
|
||||
if new_raw != raw:
|
||||
workflow.write_text(new_raw)
|
||||
updated.append(workflow)
|
||||
|
||||
return updated
|
||||
|
||||
|
||||
def _repin_crewai_install(run_value: str, version: str) -> str:
|
||||
"""Rewrite ``crewai[extras]==old`` pins in a shell command string.
|
||||
|
||||
Splits on the known ``crewai[`` prefix and reconstructs the pin
|
||||
with the new version, avoiding regex.
|
||||
|
||||
Args:
|
||||
run_value: The ``run:`` string from a workflow step.
|
||||
version: New version to pin to.
|
||||
|
||||
Returns:
|
||||
The updated string.
|
||||
"""
|
||||
result: list[str] = []
|
||||
remainder = run_value
|
||||
marker = "crewai["
|
||||
while marker in remainder:
|
||||
before, _, after = remainder.partition(marker)
|
||||
result.append(before)
|
||||
# after looks like: a2a]==1.14.0" ...
|
||||
bracket_end = after.index("]")
|
||||
extras = after[:bracket_end]
|
||||
rest = after[bracket_end + 1 :]
|
||||
if rest.startswith("=="):
|
||||
# Find end of version — next quote or whitespace
|
||||
ver_start = 2 # len("==")
|
||||
ver_end = ver_start
|
||||
while ver_end < len(rest) and rest[ver_end] not in ('"', "'", " ", "\n"):
|
||||
ver_end += 1
|
||||
result.append(f"crewai[{extras}]=={version}")
|
||||
remainder = rest[ver_end:]
|
||||
else:
|
||||
result.append(f"crewai[{extras}]")
|
||||
remainder = rest
|
||||
result.append(remainder)
|
||||
return "".join(result)
|
||||
|
||||
|
||||
_DEPLOYMENT_TEST_REPO: Final[str] = "crewAIInc/crew_deployment_test"
|
||||
|
||||
_PYPI_POLL_INTERVAL: Final[int] = 15
|
||||
@@ -1099,11 +1202,7 @@ def _update_deployment_test_repo(version: str, is_prerelease: bool) -> None:
|
||||
|
||||
pyproject = repo_dir / "pyproject.toml"
|
||||
content = pyproject.read_text()
|
||||
new_content = re.sub(
|
||||
r'"crewai\[tools\]==[^"]+"',
|
||||
f'"crewai[tools]=={version}"',
|
||||
content,
|
||||
)
|
||||
new_content = _pin_crewai_deps(content, version)
|
||||
if new_content == content:
|
||||
console.print(
|
||||
"[yellow]Warning:[/yellow] No crewai[tools] pin found to update"
|
||||
@@ -1262,6 +1361,12 @@ def _release_enterprise(version: str, is_prerelease: bool, dry_run: bool) -> Non
|
||||
f"[green]✓[/green] Updated crewai[tools] dep in {enterprise_dep_path}"
|
||||
)
|
||||
|
||||
# --- update crewai pins in CI workflows ---
|
||||
for wf in _update_enterprise_workflows(repo_dir, version):
|
||||
console.print(
|
||||
f"[green]✓[/green] Updated crewai pin in {wf.relative_to(repo_dir)}"
|
||||
)
|
||||
|
||||
_wait_for_pypi("crewai", version)
|
||||
|
||||
console.print("\nSyncing workspace...")
|
||||
|
||||
0
lib/devtools/tests/__init__.py
Normal file
0
lib/devtools/tests/__init__.py
Normal file
225
lib/devtools/tests/test_toml_updates.py
Normal file
225
lib/devtools/tests/test_toml_updates.py
Normal file
@@ -0,0 +1,225 @@
|
||||
"""Tests for TOML-based version and dependency update functions."""
|
||||
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
from crewai_devtools.cli import (
|
||||
_pin_crewai_deps,
|
||||
_repin_crewai_install,
|
||||
update_pyproject_version,
|
||||
)
|
||||
|
||||
|
||||
# --- update_pyproject_version ---
|
||||
|
||||
|
||||
class TestUpdatePyprojectVersion:
|
||||
def test_updates_version(self, tmp_path: Path) -> None:
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(
|
||||
dedent("""\
|
||||
[project]
|
||||
name = "my-pkg"
|
||||
version = "1.0.0"
|
||||
""")
|
||||
)
|
||||
|
||||
assert update_pyproject_version(pyproject, "2.0.0") is True
|
||||
assert 'version = "2.0.0"' in pyproject.read_text()
|
||||
|
||||
def test_returns_false_when_already_current(self, tmp_path: Path) -> None:
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(
|
||||
dedent("""\
|
||||
[project]
|
||||
name = "my-pkg"
|
||||
version = "1.0.0"
|
||||
""")
|
||||
)
|
||||
|
||||
assert update_pyproject_version(pyproject, "1.0.0") is False
|
||||
|
||||
def test_returns_false_when_no_project_section(self, tmp_path: Path) -> None:
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text("[tool.ruff]\nline-length = 88\n")
|
||||
|
||||
assert update_pyproject_version(pyproject, "1.0.0") is False
|
||||
|
||||
def test_returns_false_when_version_is_dynamic(self, tmp_path: Path) -> None:
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(
|
||||
dedent("""\
|
||||
[project]
|
||||
name = "my-pkg"
|
||||
dynamic = ["version"]
|
||||
""")
|
||||
)
|
||||
|
||||
assert update_pyproject_version(pyproject, "1.0.0") is False
|
||||
assert 'version = "1.0.0"' not in pyproject.read_text()
|
||||
|
||||
def test_returns_false_for_missing_file(self, tmp_path: Path) -> None:
|
||||
assert update_pyproject_version(tmp_path / "nope.toml", "1.0.0") is False
|
||||
|
||||
def test_preserves_comments_and_formatting(self, tmp_path: Path) -> None:
|
||||
content = dedent("""\
|
||||
# This is important
|
||||
[project]
|
||||
name = "my-pkg"
|
||||
version = "1.0.0" # current version
|
||||
description = "A package"
|
||||
""")
|
||||
pyproject = tmp_path / "pyproject.toml"
|
||||
pyproject.write_text(content)
|
||||
|
||||
update_pyproject_version(pyproject, "2.0.0")
|
||||
result = pyproject.read_text()
|
||||
|
||||
assert "# This is important" in result
|
||||
assert 'description = "A package"' in result
|
||||
|
||||
|
||||
# --- _pin_crewai_deps ---
|
||||
|
||||
|
||||
class TestPinCrewaiDeps:
|
||||
def test_pins_exact_version(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai==1.0.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai==2.0.0"' in result
|
||||
|
||||
def test_pins_minimum_version(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai>=1.0.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai==2.0.0"' in result
|
||||
assert ">=" not in result
|
||||
|
||||
def test_pins_with_tools_extra(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai[tools]==1.0.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai[tools]==2.0.0"' in result
|
||||
|
||||
def test_leaves_unrelated_deps_alone(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"requests>=2.0",
|
||||
"crewai==1.0.0",
|
||||
"click~=8.1",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"requests>=2.0"' in result
|
||||
assert '"click~=8.1"' in result
|
||||
|
||||
def test_handles_optional_dependencies(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = []
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai[tools]>=1.0.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "3.0.0")
|
||||
assert '"crewai[tools]==3.0.0"' in result
|
||||
|
||||
def test_handles_multiple_crewai_entries(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai==1.0.0",
|
||||
"crewai[tools]==1.0.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai==2.0.0"' in result
|
||||
assert '"crewai[tools]==2.0.0"' in result
|
||||
|
||||
def test_preserves_arbitrary_extras(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai[a2a]==1.0.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai[a2a]==2.0.0"' in result
|
||||
|
||||
def test_no_deps_returns_unchanged(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
name = "empty"
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert "empty" in result
|
||||
|
||||
def test_skips_crewai_without_version_specifier(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai-tools~=1.0",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai-tools~=1.0"' in result
|
||||
|
||||
def test_skips_crewai_extras_without_pin(self) -> None:
|
||||
content = dedent("""\
|
||||
[project]
|
||||
dependencies = [
|
||||
"crewai[tools]",
|
||||
]
|
||||
""")
|
||||
result = _pin_crewai_deps(content, "2.0.0")
|
||||
assert '"crewai[tools]"' in result
|
||||
assert "==" not in result
|
||||
|
||||
|
||||
# --- _repin_crewai_install ---
|
||||
|
||||
|
||||
class TestRepinCrewaiInstall:
|
||||
def test_repins_a2a_extra(self) -> None:
|
||||
result = _repin_crewai_install('uv pip install "crewai[a2a]==1.14.0"', "2.0.0")
|
||||
assert result == 'uv pip install "crewai[a2a]==2.0.0"'
|
||||
|
||||
def test_repins_tools_extra(self) -> None:
|
||||
result = _repin_crewai_install('uv pip install "crewai[tools]==1.0.0"', "3.0.0")
|
||||
assert result == 'uv pip install "crewai[tools]==3.0.0"'
|
||||
|
||||
def test_leaves_unrelated_commands_alone(self) -> None:
|
||||
cmd = "uv pip install requests"
|
||||
assert _repin_crewai_install(cmd, "2.0.0") == cmd
|
||||
|
||||
def test_handles_multiple_pins(self) -> None:
|
||||
cmd = 'pip install "crewai[a2a]==1.0.0" "crewai[tools]==1.0.0"'
|
||||
result = _repin_crewai_install(cmd, "2.0.0")
|
||||
assert result == 'pip install "crewai[a2a]==2.0.0" "crewai[tools]==2.0.0"'
|
||||
|
||||
def test_preserves_surrounding_text(self) -> None:
|
||||
cmd = 'echo hello && uv pip install "crewai[a2a]==1.14.0" && echo done'
|
||||
result = _repin_crewai_install(cmd, "2.0.0")
|
||||
assert (
|
||||
result == 'echo hello && uv pip install "crewai[a2a]==2.0.0" && echo done'
|
||||
)
|
||||
|
||||
def test_no_version_specifier_unchanged(self) -> None:
|
||||
cmd = 'pip install "crewai[tools]>=1.0"'
|
||||
assert _repin_crewai_install(cmd, "2.0.0") == cmd
|
||||
Reference in New Issue
Block a user