Files
composiohq--composio/python/scripts/bump.py
T
2026-07-13 12:38:34 +08:00

183 lines
4.9 KiB
Python

import typing as t
from enum import Enum
from itertools import chain
from pathlib import Path
import click
import tomli
from semver import VersionInfo
CWD = Path.cwd()
SKIPDIR = (".venv", "venv", ".nox", ".tox", "temp", "node_modules", "site-packages")
PYPROJECT = "pyproject.toml"
SETUP = "setup.py"
BumpTypes = {
"M": "major",
"m": "minor",
"p": "patch",
"b": "post",
"c": "pre",
"s": "skip",
}
BumpTypesPrompt = " | ".join([f"{k}-{v.title()}" for k, v in BumpTypes.items()])
class BumpType(Enum):
MAJOR = "major"
MINOR = "minor"
PATCH = "patch"
PRE = "pre"
POST = "post"
def _get_bumped_version(current: VersionInfo, bump_type: BumpType) -> VersionInfo:
if bump_type == BumpType.MAJOR:
return current.next_version("major")
if bump_type == BumpType.MINOR:
return current.next_version("minor")
if bump_type == BumpType.PATCH:
return current.next_version("patch")
if bump_type == BumpType.PRE:
return current.next_version("prerelease")
return current.bump_build(token="post")
def _skip(file: Path) -> bool:
return any(skipdir in file.parts for skipdir in SKIPDIR)
def _find_packages():
files = []
for file in chain(CWD.glob(f"**/{PYPROJECT}"), CWD.glob(f"**/{SETUP}")):
if _skip(file=file):
continue
files.append(file)
return files
def _get_version_update(
package: str,
version: str,
bump_type_selector: t.Optional[str] = None,
) -> t.Optional[str]:
pre = None
if "-" in version:
version, pre = version.split("-", maxsplit=1)
if "rc" in version:
version, marker, number = version.partition("rc")
pre = f"{marker}{number}"
# TODO: Add support for post builds
current_version = VersionInfo.parse(version=version)
current_version._prerelease = pre
bump_type_selector = t.cast(
str,
bump_type_selector
or click.prompt(f"* Bump {package}@{version} -> {BumpTypesPrompt}"),
)
if bump_type_selector == "s":
return None
try:
_bump_type = BumpType(BumpTypes[bump_type_selector])
except (ValueError, KeyError) as e:
raise click.ClickException(f"Invalid bump type: {e}")
return str(_get_bumped_version(current=current_version, bump_type=_bump_type))
def _bump_setup_py(file: Path, bump_type: t.Optional[str] = None):
# Load config
content = file.read_text(encoding="utf-8")
_, _, arguments = content.partition("setup(")
package, version = None, None
for line in arguments.split("\n"):
if "=" not in line:
continue
arg, val = line.strip().split("=", maxsplit=1)
val = val.replace('"', "").replace(",", "").strip()
if arg == "name":
package = val
if arg == "version":
version = val
# Skip the main setup.py
if package == "composio":
return
if package is None or version is None:
raise ValueError(f"Package or version not found in {file}")
# Bump version
next_version = _get_version_update(
package=package,
version=version,
bump_type_selector=bump_type,
)
if next_version is None:
click.echo(f"* Skipping {package}")
return
click.echo(f"* Bumping {package} {version} -> {next_version}")
content = content.replace(f'version="{version}"', f'version="{next_version}"')
file.write_text(content, encoding="utf-8")
def _bump_pyproject_toml(file: Path, bump_type: t.Optional[str] = None):
# Load config
content = file.read_text(encoding="utf-8")
config = tomli.loads(content)
project = t.cast(dict, config["project"])
# Extract meta
package = project["name"]
version = project["version"]
# Bump version
next_version = _get_version_update(
package=package,
version=version,
bump_type_selector=bump_type,
)
if next_version is None:
click.echo(f"* Skipping {package}")
return
click.echo(f"* Bumping {package} {version} -> {next_version}")
content = content.replace(f'version = "{version}"', f'version = "{next_version}"')
file.write_text(content, encoding="utf-8")
def _bump_package(file: Path, bump_type: t.Optional[str] = None):
if file.name == PYPROJECT:
return _bump_pyproject_toml(file=file, bump_type=bump_type)
return _bump_setup_py(file=file, bump_type=bump_type)
@click.command("bump")
@click.option("--pre", "bump_type", flag_value="c")
@click.option("--post", "bump_type", flag_value="b")
@click.option("--patch", "bump_type", flag_value="p")
@click.option("--minor", "bump_type", flag_value="m")
@click.option("--major", "bump_type", flag_value="M")
def bump(bump_type: t.Optional[str]):
packages = _find_packages()
for package in packages:
try:
_bump_package(file=package, bump_type=bump_type)
except Exception as e:
raise e.__class__(f"Error bumping {package}")
if __name__ == "__main__":
bump()