mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-01-10 16:48:30 +00:00
feat: add base devtooling
This commit is contained in:
0
lib/devtools/README.md
Normal file
0
lib/devtools/README.md
Normal file
32
lib/devtools/pyproject.toml
Normal file
32
lib/devtools/pyproject.toml
Normal file
@@ -0,0 +1,32 @@
|
||||
[project]
|
||||
name = "crewai-devtools"
|
||||
dynamic = ["version"]
|
||||
description = "Development tools for version bumping and git automation"
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
{ name = "Greyson R. LaLonde", email = "greyson@crewai.com" },
|
||||
]
|
||||
requires-python = ">=3.10, <3.14"
|
||||
classifiers = ["Private :: Do Not Upload"]
|
||||
private = true
|
||||
dependencies = [
|
||||
"click>=8.3.0",
|
||||
"toml>=0.10.2",
|
||||
"openai>=1.0.0",
|
||||
"python-dotenv>=1.1.1",
|
||||
"pygithub>=1.59.1",
|
||||
"rich>=13.9.4",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
bump-version = "crewai_devtools.cli:bump"
|
||||
tag = "crewai_devtools.cli:tag"
|
||||
devtools = "crewai_devtools.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/crewai_devtools/__init__.py"
|
||||
|
||||
3
lib/devtools/src/crewai_devtools/__init__.py
Normal file
3
lib/devtools/src/crewai_devtools/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""CrewAI development tools."""
|
||||
|
||||
__version__ = "1.0.0a2"
|
||||
622
lib/devtools/src/crewai_devtools/cli.py
Normal file
622
lib/devtools/src/crewai_devtools/cli.py
Normal file
@@ -0,0 +1,622 @@
|
||||
"""Development tools for version bumping and git automation."""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import click
|
||||
from dotenv import load_dotenv
|
||||
from github import Github
|
||||
from openai import OpenAI
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
from rich.panel import Panel
|
||||
from rich.prompt import Confirm
|
||||
|
||||
from crewai_devtools.prompts import RELEASE_NOTES_PROMPT
|
||||
|
||||
|
||||
load_dotenv()
|
||||
|
||||
console = Console()
|
||||
|
||||
|
||||
def run_command(cmd: list[str], cwd: Path | None = None) -> str:
|
||||
"""Run a shell command and return output.
|
||||
|
||||
Args:
|
||||
cmd: Command to run as list of strings.
|
||||
cwd: Working directory for command.
|
||||
|
||||
Returns:
|
||||
Command output as string.
|
||||
|
||||
Raises:
|
||||
subprocess.CalledProcessError: If command fails.
|
||||
"""
|
||||
result = subprocess.run( # noqa: S603
|
||||
cmd,
|
||||
cwd=cwd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return result.stdout.strip()
|
||||
|
||||
|
||||
def check_gh_installed() -> None:
|
||||
"""Check if GitHub CLI is installed and offer to install it.
|
||||
|
||||
Raises:
|
||||
SystemExit: If gh is not installed and user declines installation.
|
||||
"""
|
||||
try:
|
||||
run_command(["gh", "--version"])
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
console.print("[yellow]Warning:[/yellow] GitHub CLI (gh) is not installed")
|
||||
import platform
|
||||
|
||||
if platform.system() == "Darwin":
|
||||
try:
|
||||
run_command(["brew", "--version"])
|
||||
from rich.prompt import Confirm
|
||||
|
||||
if Confirm.ask(
|
||||
"\n[bold]Would you like to install GitHub CLI via Homebrew?[/bold]",
|
||||
default=True,
|
||||
):
|
||||
try:
|
||||
console.print("\nInstalling GitHub CLI...")
|
||||
subprocess.run(
|
||||
["brew", "install", "gh"], # noqa: S607
|
||||
check=True,
|
||||
)
|
||||
console.print(
|
||||
"[green]✓[/green] GitHub CLI installed successfully"
|
||||
)
|
||||
console.print("\nAuthenticating with GitHub...")
|
||||
subprocess.run(
|
||||
["gh", "auth", "login"], # noqa: S607
|
||||
check=True,
|
||||
)
|
||||
console.print("[green]✓[/green] GitHub authentication complete")
|
||||
return
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(
|
||||
f"[red]Error:[/red] Failed to install or authenticate gh: {e}"
|
||||
)
|
||||
console.print(
|
||||
"\nYou can try running [bold]gh auth login[/bold] manually"
|
||||
)
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
console.print("\nPlease install GitHub CLI from: https://cli.github.com/")
|
||||
console.print("\nInstallation instructions:")
|
||||
console.print(" macOS: brew install gh")
|
||||
console.print(
|
||||
" Linux: https://github.com/cli/cli/blob/trunk/docs/install_linux.md"
|
||||
)
|
||||
console.print(" Windows: winget install --id GitHub.cli")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_git_clean() -> None:
|
||||
"""Check if git working directory is clean.
|
||||
|
||||
Raises:
|
||||
SystemExit: If there are uncommitted changes.
|
||||
"""
|
||||
try:
|
||||
status = run_command(["git", "status", "--porcelain"])
|
||||
if status:
|
||||
console.print(
|
||||
"[red]Error:[/red] You have uncommitted changes. Please commit or stash them first."
|
||||
)
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"[red]Error checking git status:[/red] {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def update_version_in_file(file_path: Path, new_version: str) -> bool:
|
||||
"""Update __version__ attribute in a Python file.
|
||||
|
||||
Args:
|
||||
file_path: Path to Python file.
|
||||
new_version: New version string.
|
||||
|
||||
Returns:
|
||||
True if version was updated, False otherwise.
|
||||
"""
|
||||
if not file_path.exists():
|
||||
return False
|
||||
|
||||
content = file_path.read_text()
|
||||
lines = content.splitlines()
|
||||
updated = False
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith("__version__"):
|
||||
lines[i] = f'__version__ = "{new_version}"'
|
||||
updated = True
|
||||
break
|
||||
|
||||
if updated:
|
||||
file_path.write_text("\n".join(lines) + "\n")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def find_version_files(base_path: Path) -> list[Path]:
|
||||
"""Find all __init__.py files that contain __version__.
|
||||
|
||||
Args:
|
||||
base_path: Base directory to search in.
|
||||
|
||||
Returns:
|
||||
List of paths to files containing __version__.
|
||||
"""
|
||||
return [
|
||||
init_file
|
||||
for init_file in base_path.rglob("__init__.py")
|
||||
if "__version__" in init_file.read_text()
|
||||
]
|
||||
|
||||
|
||||
def get_packages(lib_dir: Path) -> list[Path]:
|
||||
"""Get all packages from lib/ directory.
|
||||
|
||||
Args:
|
||||
lib_dir: Path to lib/ directory.
|
||||
|
||||
Returns:
|
||||
List of package directory paths.
|
||||
|
||||
Raises:
|
||||
SystemExit: If lib/ doesn't exist or no packages found.
|
||||
"""
|
||||
if not lib_dir.exists():
|
||||
console.print("[red]Error:[/red] lib/ directory not found")
|
||||
sys.exit(1)
|
||||
|
||||
packages = [p for p in lib_dir.iterdir() if p.is_dir()]
|
||||
|
||||
if not packages:
|
||||
console.print("[red]Error:[/red] No packages found in lib/")
|
||||
sys.exit(1)
|
||||
|
||||
return packages
|
||||
|
||||
|
||||
def get_commits_from_last_tag(tag_name: str, version: str) -> tuple[str, str]:
|
||||
"""Get commits from the last tag, excluding current version.
|
||||
|
||||
Args:
|
||||
tag_name: Current tag name (e.g., "v1.0.0").
|
||||
version: Current version (e.g., "1.0.0").
|
||||
|
||||
Returns:
|
||||
Tuple of (commit_range, commits) where commits is newline-separated.
|
||||
"""
|
||||
try:
|
||||
all_tags = run_command(["git", "tag", "--sort=-version:refname"]).split("\n")
|
||||
prev_tags = [t for t in all_tags if t and t != tag_name and t != f"v{version}"]
|
||||
|
||||
if prev_tags:
|
||||
last_tag = prev_tags[0]
|
||||
commit_range = f"{last_tag}..HEAD"
|
||||
commits = run_command(["git", "log", commit_range, "--pretty=format:%s"])
|
||||
else:
|
||||
commit_range = "HEAD"
|
||||
commits = run_command(["git", "log", "--pretty=format:%s"])
|
||||
except subprocess.CalledProcessError:
|
||||
commit_range = "HEAD"
|
||||
commits = run_command(["git", "log", "--pretty=format:%s"])
|
||||
|
||||
return commit_range, commits
|
||||
|
||||
|
||||
def get_github_contributors(commit_range: str) -> list[str]:
|
||||
"""Get GitHub usernames from commit range using GitHub API.
|
||||
|
||||
Args:
|
||||
commit_range: Git commit range (e.g., "abc123..HEAD").
|
||||
|
||||
Returns:
|
||||
List of GitHub usernames sorted alphabetically.
|
||||
"""
|
||||
try:
|
||||
# Get GitHub token from gh CLI
|
||||
try:
|
||||
gh_token = run_command(["gh", "auth", "token"])
|
||||
except subprocess.CalledProcessError:
|
||||
gh_token = None
|
||||
|
||||
g = Github(login_or_token=gh_token) if gh_token else Github()
|
||||
github_repo = g.get_repo("crewAIInc/crewAI")
|
||||
|
||||
commit_shas = run_command(
|
||||
["git", "log", commit_range, "--pretty=format:%H"]
|
||||
).split("\n")
|
||||
|
||||
contributors = set()
|
||||
for sha in commit_shas:
|
||||
if not sha:
|
||||
continue
|
||||
try:
|
||||
commit = github_repo.get_commit(sha)
|
||||
if commit.author and commit.author.login:
|
||||
contributors.add(commit.author.login)
|
||||
|
||||
if commit.commit.message:
|
||||
for line in commit.commit.message.split("\n"):
|
||||
if line.strip().startswith("Co-authored-by:"):
|
||||
if "<" in line and ">" in line:
|
||||
email_part = line.split("<")[1].split(">")[0]
|
||||
if "@users.noreply.github.com" in email_part:
|
||||
username = email_part.split("+")[-1].split("@")[0]
|
||||
contributors.add(username)
|
||||
except Exception: # noqa: S112
|
||||
continue
|
||||
|
||||
return sorted(list(contributors))
|
||||
|
||||
except Exception as e:
|
||||
console.print(
|
||||
f"[yellow]Warning:[/yellow] Could not fetch GitHub contributors: {e}"
|
||||
)
|
||||
return []
|
||||
|
||||
|
||||
@click.group()
|
||||
def cli() -> None:
|
||||
"""Development tools for version bumping and git automation."""
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("version")
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Show what would be done without making changes"
|
||||
)
|
||||
@click.option("--no-push", is_flag=True, help="Don't push changes to remote")
|
||||
def bump(version: str, dry_run: bool, no_push: bool) -> None:
|
||||
"""Bump version across all packages in lib/.
|
||||
|
||||
Args:
|
||||
version: New version to set (e.g., 1.0.0, 1.0.0a1).
|
||||
dry_run: Show what would be done without making changes.
|
||||
no_push: Don't push changes to remote.
|
||||
"""
|
||||
try:
|
||||
# Check prerequisites
|
||||
check_gh_installed()
|
||||
|
||||
cwd = Path.cwd()
|
||||
lib_dir = cwd / "lib"
|
||||
|
||||
if not dry_run:
|
||||
console.print("Checking git status...")
|
||||
check_git_clean()
|
||||
console.print("[green]✓[/green] Working directory is clean")
|
||||
else:
|
||||
console.print("[dim][DRY RUN][/dim] Would check git status")
|
||||
|
||||
packages = get_packages(lib_dir)
|
||||
|
||||
console.print(f"\nFound {len(packages)} package(s) to update:")
|
||||
for pkg in packages:
|
||||
console.print(f" - {pkg.name}")
|
||||
|
||||
console.print(f"\nUpdating version to {version}...")
|
||||
updated_files = []
|
||||
|
||||
for pkg in packages:
|
||||
version_files = find_version_files(pkg)
|
||||
for vfile in version_files:
|
||||
if dry_run:
|
||||
console.print(
|
||||
f"[dim][DRY RUN][/dim] Would update: {vfile.relative_to(cwd)}"
|
||||
)
|
||||
else:
|
||||
if update_version_in_file(vfile, version):
|
||||
console.print(
|
||||
f"[green]✓[/green] Updated: {vfile.relative_to(cwd)}"
|
||||
)
|
||||
updated_files.append(vfile)
|
||||
else:
|
||||
console.print(
|
||||
f"[red]✗[/red] Failed to update: {vfile.relative_to(cwd)}"
|
||||
)
|
||||
|
||||
if not updated_files and not dry_run:
|
||||
console.print(
|
||||
"[yellow]Warning:[/yellow] No __version__ attributes found to update"
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
console.print("\nUpdating uv.lock...")
|
||||
run_command(["uv", "lock"])
|
||||
console.print("[green]✓[/green] Lock file updated")
|
||||
else:
|
||||
console.print("[dim][DRY RUN][/dim] Would run: uv lock")
|
||||
|
||||
branch_name = f"feat/bump-version-{version}"
|
||||
if not dry_run:
|
||||
console.print(f"\nCreating branch {branch_name}...")
|
||||
run_command(["git", "checkout", "-b", branch_name])
|
||||
console.print("[green]✓[/green] Branch created")
|
||||
|
||||
console.print("\nCommitting changes...")
|
||||
run_command(["git", "add", "."])
|
||||
run_command(["git", "commit", "-m", f"feat: bump versions to {version}"])
|
||||
console.print("[green]✓[/green] Changes committed")
|
||||
|
||||
if not no_push:
|
||||
console.print("\nPushing branch...")
|
||||
run_command(["git", "push", "-u", "origin", branch_name])
|
||||
console.print("[green]✓[/green] Branch pushed")
|
||||
else:
|
||||
console.print(f"[dim][DRY RUN][/dim] Would create branch: {branch_name}")
|
||||
console.print(
|
||||
f"[dim][DRY RUN][/dim] Would commit: feat: bump versions to {version}"
|
||||
)
|
||||
if not no_push:
|
||||
console.print(f"[dim][DRY RUN][/dim] Would push branch: {branch_name}")
|
||||
|
||||
if not dry_run and not no_push:
|
||||
console.print("\nCreating pull request...")
|
||||
run_command(
|
||||
[
|
||||
"gh",
|
||||
"pr",
|
||||
"create",
|
||||
"--base",
|
||||
"release/v1.0.0",
|
||||
"--title",
|
||||
f"feat: bump versions to {version}",
|
||||
"--body",
|
||||
"",
|
||||
]
|
||||
)
|
||||
console.print("[green]✓[/green] Pull request created")
|
||||
elif dry_run:
|
||||
console.print(
|
||||
f"[dim][DRY RUN][/dim] Would create PR: feat: bump versions to {version}"
|
||||
)
|
||||
else:
|
||||
console.print("\nSkipping PR creation (--no-push flag set)")
|
||||
|
||||
console.print(f"\n[green]✓[/green] Version bump to {version} complete!")
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"[red]Error running command:[/red] {e}")
|
||||
if e.stderr:
|
||||
console.print(e.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error:[/red] {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.option(
|
||||
"--dry-run", is_flag=True, help="Show what would be done without making changes"
|
||||
)
|
||||
@click.option("--no-edit", is_flag=True, help="Skip editing release notes")
|
||||
def tag(dry_run: bool, no_edit: bool) -> None:
|
||||
"""Create and push a version tag on main branch.
|
||||
|
||||
Run this after the version bump PR has been merged.
|
||||
Automatically detects version from __version__ in packages.
|
||||
|
||||
Args:
|
||||
dry_run: Show what would be done without making changes.
|
||||
no_edit: Skip editing release notes.
|
||||
"""
|
||||
try:
|
||||
cwd = Path.cwd()
|
||||
lib_dir = cwd / "lib"
|
||||
|
||||
packages = get_packages(lib_dir)
|
||||
|
||||
with console.status("[cyan]Validating package versions..."):
|
||||
versions = {}
|
||||
for pkg in packages:
|
||||
version_files = find_version_files(pkg)
|
||||
for vfile in version_files:
|
||||
content = vfile.read_text()
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith("__version__"):
|
||||
ver = line.split("=")[1].strip().strip('"').strip("'")
|
||||
versions[vfile.relative_to(cwd)] = ver
|
||||
break
|
||||
|
||||
if not versions:
|
||||
console.print(
|
||||
"[red]✗[/red] Validated package versions: Could not find __version__ in any package"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
unique_versions = set(versions.values())
|
||||
if len(unique_versions) > 1:
|
||||
console.print(
|
||||
"[red]✗[/red] Validated package versions: Version mismatch detected"
|
||||
)
|
||||
for file, ver in versions.items():
|
||||
console.print(f" {file}: {ver}")
|
||||
sys.exit(1)
|
||||
|
||||
version = unique_versions.pop()
|
||||
console.print(f"[green]✓[/green] Validated packages @ [bold]{version}[/bold]")
|
||||
tag_name = f"v{version}"
|
||||
|
||||
if not dry_run:
|
||||
with console.status("[cyan]Checking out release/v1.0.0 branch..."):
|
||||
try:
|
||||
run_command(["git", "checkout", "release/v1.0.0"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(
|
||||
f"[red]✗[/red] Checked out release/v1.0.0 branch: {e}"
|
||||
)
|
||||
sys.exit(1)
|
||||
console.print("[green]✓[/green] On release/v1.0.0 branch")
|
||||
|
||||
with console.status("[cyan]Pulling latest changes..."):
|
||||
try:
|
||||
run_command(["git", "pull"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"[red]✗[/red] Pulled latest changes: {e}")
|
||||
sys.exit(1)
|
||||
console.print("[green]✓[/green] release/v1.0.0 branch up to date")
|
||||
|
||||
release_notes = f"Release {version}"
|
||||
commits = ""
|
||||
|
||||
with console.status("[cyan]Generating release notes..."):
|
||||
try:
|
||||
prev_bump_commit = run_command(
|
||||
[
|
||||
"git",
|
||||
"log",
|
||||
"--grep=^feat: bump versions to",
|
||||
"--format=%H",
|
||||
"-n",
|
||||
"2",
|
||||
]
|
||||
)
|
||||
commits_list = prev_bump_commit.strip().split("\n")
|
||||
|
||||
if len(commits_list) > 1:
|
||||
prev_commit = commits_list[1]
|
||||
commit_range = f"{prev_commit}..HEAD"
|
||||
commits = run_command(
|
||||
["git", "log", commit_range, "--pretty=format:%s"]
|
||||
)
|
||||
|
||||
commit_lines = [
|
||||
line
|
||||
for line in commits.split("\n")
|
||||
if not line.startswith("feat: bump versions to")
|
||||
]
|
||||
commits = "\n".join(commit_lines)
|
||||
else:
|
||||
commit_range, commits = get_commits_from_last_tag(tag_name, version)
|
||||
|
||||
except subprocess.CalledProcessError:
|
||||
commit_range, commits = get_commits_from_last_tag(tag_name, version)
|
||||
|
||||
github_contributors = get_github_contributors(commit_range)
|
||||
|
||||
if commits.strip():
|
||||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
contributors_section = ""
|
||||
if github_contributors:
|
||||
contributors_section = f"\n\n## Contributors\n\n{', '.join([f'@{u}' for u in github_contributors])}"
|
||||
|
||||
prompt = RELEASE_NOTES_PROMPT.substitute(
|
||||
version=version,
|
||||
commits=commits,
|
||||
contributors_section=contributors_section,
|
||||
)
|
||||
|
||||
response = client.chat.completions.create(
|
||||
model="gpt-4o-mini",
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful assistant that generates clear, concise release notes.",
|
||||
},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.7,
|
||||
)
|
||||
|
||||
release_notes = (
|
||||
response.choices[0].message.content or f"Release {version}"
|
||||
)
|
||||
|
||||
console.print("[green]✓[/green] Generated release notes")
|
||||
|
||||
if commits.strip():
|
||||
try:
|
||||
console.print()
|
||||
md = Markdown(release_notes, justify="left")
|
||||
console.print(
|
||||
Panel(
|
||||
md,
|
||||
title="[bold cyan]Generated Release Notes[/bold cyan]",
|
||||
border_style="cyan",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
console.print(
|
||||
f"[yellow]Warning:[/yellow] Could not generate release notes with OpenAI: {e}"
|
||||
)
|
||||
console.print("Using default release notes")
|
||||
|
||||
if not no_edit:
|
||||
if Confirm.ask(
|
||||
"\n[bold]Would you like to edit the release notes?[/bold]", default=True
|
||||
):
|
||||
edited_notes = click.edit(release_notes)
|
||||
if edited_notes is not None:
|
||||
release_notes = edited_notes.strip()
|
||||
console.print("\n[green]✓[/green] Release notes updated")
|
||||
else:
|
||||
console.print("\n[green]✓[/green] Using original release notes")
|
||||
else:
|
||||
console.print(
|
||||
"\n[green]✓[/green] Using generated release notes without editing"
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
"\n[green]✓[/green] Using generated release notes without editing"
|
||||
)
|
||||
|
||||
if not dry_run:
|
||||
with console.status(f"[cyan]Creating tag {tag_name}..."):
|
||||
try:
|
||||
run_command(["git", "tag", "-a", tag_name, "-m", release_notes])
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"[red]✗[/red] Created tag {tag_name}: {e}")
|
||||
sys.exit(1)
|
||||
console.print(f"[green]✓[/green] Created tag {tag_name}")
|
||||
|
||||
with console.status(f"[cyan]Pushing tag {tag_name}..."):
|
||||
try:
|
||||
run_command(["git", "push", "origin", tag_name])
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"[red]✗[/red] Pushed tag {tag_name}: {e}")
|
||||
sys.exit(1)
|
||||
console.print(f"[green]✓[/green] Pushed tag {tag_name}")
|
||||
|
||||
console.print(
|
||||
f"\n[green]✓[/green] Packages @ [bold]{version}[/bold] tagged successfully!"
|
||||
)
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
console.print(f"[red]Error running command:[/red] {e}")
|
||||
if e.stderr:
|
||||
console.print(e.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
console.print(f"[red]Error:[/red] {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
cli.add_command(bump)
|
||||
cli.add_command(tag)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Entry point for the CLI."""
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
45
lib/devtools/src/crewai_devtools/prompts.py
Normal file
45
lib/devtools/src/crewai_devtools/prompts.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Prompt templates for AI-generated content."""
|
||||
|
||||
from string import Template
|
||||
|
||||
|
||||
RELEASE_NOTES_PROMPT = Template(
|
||||
"""Generate concise release notes for version $version based on these commits:
|
||||
|
||||
$commits
|
||||
|
||||
The commits follow the Conventional Commits standard (feat:, fix:, chore:, etc.).
|
||||
|
||||
Use this exact template format:
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Features
|
||||
- [List feat: commits here, using imperative mood like "Add X", "Implement Y"]
|
||||
|
||||
### Bug Fixes
|
||||
- [List fix: commits here, using imperative mood like "Fix X", "Resolve Y"]
|
||||
|
||||
### Documentation
|
||||
- [List docs: commits here, using imperative mood like "Update X", "Add Y"]
|
||||
|
||||
### Performance
|
||||
- [List perf: commits here, using imperative mood like "Improve X", "Optimize Y"]
|
||||
|
||||
### Refactoring
|
||||
- [List refactor: commits here, using imperative mood like "Refactor X", "Simplify Y"]
|
||||
|
||||
### Breaking Changes
|
||||
- [List commits with BREAKING CHANGE in footer or ! after type, using imperative mood]$contributors_section
|
||||
|
||||
Instructions:
|
||||
- Parse conventional commit format (type: description or type(scope): description)
|
||||
- Only include sections that have relevant changes from the commits
|
||||
- Skip chore:, ci:, test:, and style: commits unless significant
|
||||
- Convert commit messages to imperative mood if needed (e.g., "adds" → "Add")
|
||||
- Be concise but informative
|
||||
- Focus on user-facing changes
|
||||
- Use the exact Contributors list provided above, do not modify it
|
||||
|
||||
Keep it professional and clear."""
|
||||
)
|
||||
Reference in New Issue
Block a user