diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 91cec2b..638b5b3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,6 +18,7 @@ repos: hooks: - id: pylint args: [--rcfile=pyproject.toml] + additional_dependencies: [colorama] - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.11.0 hooks: diff --git a/Makefile b/Makefile index ddcacb3..5a87bdd 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,9 @@ check: poetry run mypy custom_components poetry run ruff check . +update: + @./scripts/release.py update-hass + clean: find . -type d -name "__pycache__" -exec rm -rf {} + find . -type f -name "*.pyc" -delete diff --git a/pyproject.toml b/pyproject.toml index 6119735..f12ec58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,13 +21,14 @@ packages = [{ include = "custom_components" }] python = ">=3.12,<3.13" homeassistant = ">=2024.6.3" signalrgb = "0.9.6" +colorama = "^0.4.6" [tool.poetry.group.dev.dependencies] pytest = "^8.2" pytest-cov = "^5.0" pytest-asyncio = "^0.23.0" pytest-homeassistant-custom-component = "^0.13" -black = "^23.3.0" +black = "^24.4" isort = "^5.12.0" mypy = "^1.11" pylint = "^3.2" @@ -58,7 +59,7 @@ exclude_lines = [ [tool.black] line-length = 88 -target-version = ['py312'] +target-version = ['py311'] include = '\.pyi?$' extend-exclude = ''' /( diff --git a/scripts/release.py b/scripts/release.py index 3278c35..cd9e3f7 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,83 +1,187 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 +import argparse +import json import os import shutil -import json import subprocess -import argparse +import sys from collections import OrderedDict -BASE_PATH = "/home/bliss/dev" -PROJECT_LINK = "https://github.com/hyperb1iss/signalrgb-homeassistant" -ISSUE_TRACKER = f"{PROJECT_LINK}/issues" - - -def copy_integration(src_path, dest_path): - if os.path.exists(dest_path): - shutil.rmtree(dest_path) - shutil.copytree(src_path, dest_path) - print(f"Copied integration from {src_path} to {dest_path}") - +import colorama +from colorama import Fore, Style -def update_manifest_version(manifest_path, new_version): - with open(manifest_path, "r") as f: - manifest = json.load(f) +# Initialize colorama for cross-platform colored output +colorama.init(autoreset=True) - # Update version and links - manifest["version"] = new_version - manifest["documentation"] = PROJECT_LINK - manifest["issue_tracker"] = ISSUE_TRACKER - - # Create a new OrderedDict with the required order - ordered_manifest = OrderedDict() - ordered_manifest["domain"] = manifest["domain"] - ordered_manifest["name"] = manifest["name"] - - # Add the rest of the items in alphabetical order - for key in sorted(manifest.keys()): - if key not in ["domain", "name"]: - ordered_manifest[key] = manifest[key] +# Constants +PROJECT_NAME = "SignalRGB Home Assistant Integration" +PROJECT_LINK = "https://github.com/hyperb1iss/signalrgb-homeassistant" +ISSUE_TRACKER = f"{PROJECT_LINK}/issues" +HASS_CONFIG_DIR = os.getenv( + "HASS_CONFIG_DIR", os.path.expanduser("~/dev/ha_core/config") +) +CUSTOM_COMPONENTS_DIR = os.path.join(HASS_CONFIG_DIR, "custom_components") + + +def print_banner(version: str) -> None: + """Print a beautiful banner for the release script.""" + banner = f""" +{Fore.MAGENTA}ā•”{'ā•' * 60}ā•— +ā•‘ {Fore.CYAN}šŸš€ {PROJECT_NAME} Release Manager {Fore.MAGENTA}ā•‘ +ā•‘ {Fore.YELLOW}Version: {version}{' ' * (49 - len(version))} {Fore.MAGENTA}ā•‘ +ā•š{'ā•' * 60}ā•{Style.RESET_ALL} +""" + print(banner) + + +def print_message(message: str, color: str = Fore.GREEN) -> None: + """Print a colored message.""" + print(f"{color}{message}{Style.RESET_ALL}") + + +def check_tool_installed(tool_name: str) -> None: + """Check if a tool is installed.""" + if shutil.which(tool_name) is None: + print_message( + f"āŒ {tool_name} is not installed. Please install it and try again.", + Fore.RED, + ) + sys.exit(1) + + +def copy_integration(src_path: str, dest_path: str, verbose: bool = False) -> None: + """Copy the integration files to the destination.""" + try: + if os.path.exists(dest_path): + shutil.rmtree(dest_path) + if verbose: + print_message( + f"šŸ—‘ļø Removed existing directory at {dest_path}", Fore.BLUE + ) + shutil.copytree(src_path, dest_path) + print_message(f"āœ… Copied integration from {src_path} to {dest_path}") + except OSError as e: + print_message(f"āŒ Error copying integration: {e}", Fore.RED) + sys.exit(1) + + +def update_manifest( + manifest_path: str, new_version: str, verbose: bool = False +) -> None: + """Update the manifest.json file with the new version and reorder entries.""" + try: + with open(manifest_path, "r", encoding="utf-8") as f: + manifest = json.load(f) + + manifest["version"] = new_version + manifest["documentation"] = PROJECT_LINK + manifest["issue_tracker"] = ISSUE_TRACKER + + ordered_manifest = OrderedDict( + [ + ("domain", manifest["domain"]), + ("name", manifest["name"]), + ] + + sorted( + [(k, v) for k, v in manifest.items() if k not in ["domain", "name"]], + key=lambda x: x[0], + ) + ) + + with open(manifest_path, "w", encoding="utf-8") as f: + json.dump(ordered_manifest, f, indent=2) + + print_message( + f"āœ… Updated manifest version to {new_version} and reordered entries" + ) + if verbose: + print_message( + f"šŸ“„ New manifest: {json.dumps(ordered_manifest, indent=2)}", Fore.BLUE + ) + except (OSError, json.JSONDecodeError) as e: + print_message(f"āŒ Error updating manifest: {e}", Fore.RED) + sys.exit(1) + + +def git_commit_and_tag(version: str, verbose: bool = False) -> None: + """Commit changes and create a new tag.""" + try: + commit_message = f"šŸš€ Release version {version}" + subprocess.run(["git", "add", "custom_components"], check=True) + subprocess.run(["git", "commit", "-m", commit_message], check=True) + subprocess.run( + ["git", "tag", "-a", f"v{version}", "-m", f"Version {version}"], check=True + ) + print_message(f"āœ… Changes committed and tagged as v{version}") + if verbose: + print_message(f"šŸ”– Created tag: v{version}", Fore.BLUE) + except subprocess.CalledProcessError as e: + print_message(f"āŒ Error committing/tagging: {e}", Fore.RED) + sys.exit(1) + + +def update_hass(src_path: str, verbose: bool = False) -> None: + """Update Home Assistant integration.""" + src_path = os.path.join(os.getcwd(), "custom_components", "signalrgb") + dest_path = os.path.join(CUSTOM_COMPONENTS_DIR, "signalrgb") + + print_message( + f"šŸ”„ Updating Home Assistant integration from {src_path} to {dest_path}", + Fore.BLUE, + ) + copy_integration(src_path, dest_path, verbose) + print_message( + "āš ļø Remember to reload Home Assistant to apply the changes.", Fore.YELLOW + ) - with open(manifest_path, "w") as f: - json.dump(ordered_manifest, f, indent=2) - print(f"Updated manifest version to {new_version} and reordered entries") +def do_release(src_path: str, version: str, verbose: bool = False) -> None: + """Perform the release process.""" + print_banner(version) + manifest_path = os.path.join(src_path, "manifest.json") + update_manifest(manifest_path, version, verbose) + git_commit_and_tag(version, verbose) -def git_commit_and_tag(version): - commit_message = f"Release version {version}" - subprocess.run(["git", "add", "custom_components"], check=True) - subprocess.run(["git", "commit", "-m", commit_message], check=True) - subprocess.run( - ["git", "tag", "-a", f"v{version}", "-m", f"Version {version}"], check=True + print_message(f"\nšŸŽ‰ Release [{version}] process completed successfully!", Fore.CYAN) + print_message( + "āš ļø Don't forget to push the changes and the new tag to GitHub.", Fore.YELLOW ) - print(f"Changes committed and tagged as v{version}") -def main(): +def main() -> None: + """Main function to handle argument parsing and command execution.""" parser = argparse.ArgumentParser( - description="Release management for SignalRGB Home Assistant integration" + description=f"Release management for {PROJECT_NAME}" ) - parser.add_argument("version", help="New version number for the release") - args = parser.parse_args() - - # Paths - ha_src_path = f"{BASE_PATH}/ha_core/homeassistant/components/signalrgb" - custom_components_path = ( - f"{BASE_PATH}/signalrgb-homeassistant/custom_components/signalrgb" + parser.add_argument( + "command", choices=["update-hass", "release"], help="Command to run" + ) + parser.add_argument( + "version", + nargs="?", + help="Version number for the release (required for release command)", + ) + parser.add_argument( + "-v", "--verbose", action="store_true", help="Enable verbose output" ) - manifest_path = os.path.join(custom_components_path, "manifest.json") - - # Copy integration files - copy_integration(ha_src_path, custom_components_path) - - # Update manifest version - update_manifest_version(manifest_path, args.version) - # Commit changes and create tag - git_commit_and_tag(args.version) + args = parser.parse_args() - print("Release process completed successfully!") + # Paths + src_path = os.path.join(os.getcwd(), "custom_components", "signalrgb") + + # Check for necessary tools + check_tool_installed("git") + + if args.command == "update-hass": + update_hass(src_path, args.verbose) + elif args.command == "release": + if not args.version: + print_message("āŒ Version number is required for release command.", Fore.RED) + sys.exit(1) + do_release(src_path, args.version, args.verbose) if __name__ == "__main__":