From 65a5a72f9b37007c919f5eb21651c446b023d74d Mon Sep 17 00:00:00 2001 From: barisgit Date: Sat, 6 Sep 2025 00:34:02 +0200 Subject: [PATCH 1/7] Add sync command as replacement for current update command --- kicad_lib_manager/commands/sync/__init__.py | 7 + kicad_lib_manager/commands/sync/command.py | 238 ++++++++++++++++++++ kicad_lib_manager/commands/sync/docs.mdx | 92 ++++++++ 3 files changed, 337 insertions(+) create mode 100644 kicad_lib_manager/commands/sync/__init__.py create mode 100644 kicad_lib_manager/commands/sync/command.py create mode 100644 kicad_lib_manager/commands/sync/docs.mdx diff --git a/kicad_lib_manager/commands/sync/__init__.py b/kicad_lib_manager/commands/sync/__init__.py new file mode 100644 index 0000000..26f8bd8 --- /dev/null +++ b/kicad_lib_manager/commands/sync/__init__.py @@ -0,0 +1,7 @@ +""" +Sync command - Update/sync library content from git repositories +""" + +from .command import sync + +__all__ = ["sync"] diff --git a/kicad_lib_manager/commands/sync/command.py b/kicad_lib_manager/commands/sync/command.py new file mode 100644 index 0000000..add4f31 --- /dev/null +++ b/kicad_lib_manager/commands/sync/command.py @@ -0,0 +1,238 @@ +""" +Sync command implementation for KiCad Library Manager. +Performs 'git pull' on all configured GitHub libraries (symbols/footprints). +""" + +import re +import subprocess +from pathlib import Path + +import click + +from ...config import Config + + +@click.command() +@click.option( + "--dry-run", + is_flag=True, + default=False, + help="Show what would be synced without making changes", + show_default=True, +) +@click.option( + "--verbose", + is_flag=True, + default=False, + help="Show detailed output", + show_default=True, +) +@click.option( + "--auto-setup", + is_flag=True, + default=False, + help="Run 'kilm setup' automatically if new libraries are detected", + show_default=True, +) +def sync(dry_run, verbose, auto_setup): + """Sync all configured GitHub libraries with git pull. + + This command syncs all configured GitHub libraries (symbols/footprints) + by performing a 'git pull' operation in each library directory. + It will only attempt to sync directories that are valid git repositories. + + After syncing, the command will check if new libraries have been added + and recommend running 'kilm setup' if needed. Use --auto-setup to run + setup automatically when new libraries are detected. + """ + config = Config() + + # Get GitHub libraries from config (symbols/footprints) + libraries = config.get_libraries(library_type="github") + + if not libraries: + click.echo("No GitHub libraries configured. Use 'kilm init' to add a library.") + return + + click.echo(f"Syncing {len(libraries)} KiCad GitHub libraries...") + + updated_count = 0 # Actually changed + up_to_date_count = 0 # Already at latest version + skipped_count = 0 # Could not sync (not git, etc.) + failed_count = 0 # Git pull failed + + # Track libraries that have changes that might require setup + libraries_with_changes = [] + + for lib in libraries: + lib_name = lib.get("name", "unnamed") + lib_path = lib.get("path") + + if not lib_path: + click.echo(f" Skipping {lib_name}: No path defined") + skipped_count += 1 + continue + + lib_path = Path(lib_path) + if not lib_path.exists(): + click.echo(f" Skipping {lib_name}: Path does not exist: {lib_path}") + skipped_count += 1 + continue + + git_dir = lib_path / ".git" + if not git_dir.exists() or not git_dir.is_dir(): + click.echo(f" Skipping {lib_name}: Not a git repository: {lib_path}") + skipped_count += 1 + continue + + # Prepare to run git pull + click.echo(f" Syncing {lib_name} at {lib_path}...") + + if dry_run: + click.echo(f" Dry run: would execute 'git pull' in {lib_path}") + updated_count += 1 + continue + + try: + # Run git pull + result = subprocess.run( + ["git", "pull"], + cwd=lib_path, + capture_output=True, + text=True, + check=False, + ) + + if result.returncode == 0: + output = result.stdout.strip() or "Already up to date." + if verbose: + click.echo(f" Success: {output}") + else: + is_updated = "Already up to date" not in output + if is_updated: + click.echo(" Updated") + updated_count += 1 + else: + click.echo(" Up to date") + up_to_date_count += 1 + + # Check if there are new library files that would require setup + if "Already up to date" not in output: + changes_require_setup = check_for_library_changes( + result.stdout, lib_path + ) + if changes_require_setup: + libraries_with_changes.append( + (lib_name, lib_path, changes_require_setup) + ) + if verbose: + click.echo( + f" Detected new library content: {', '.join(changes_require_setup)}" + ) + else: + click.echo(f" Failed: {result.stderr.strip()}") + failed_count += 1 + + except Exception as e: + click.echo(f" Error: {str(e)}") + failed_count += 1 + + # Summary + click.echo("\nSync Summary:") + click.echo(f" {updated_count} libraries synced") + click.echo(f" {up_to_date_count} libraries up to date") + click.echo(f" {skipped_count} libraries skipped") + click.echo(f" {failed_count} libraries failed") + + # If libraries with changes were detected, suggest running setup + if libraries_with_changes: + click.echo("\nNew library content detected in:") + for lib_name, _lib_path, changes in libraries_with_changes: + click.echo(f" - {lib_name}: {', '.join(changes)}") + + if auto_setup: + click.echo("\nRunning 'kilm setup' to configure new libraries...") + # Import at runtime to avoid circular imports + try: + from ...commands.setup import setup as setup_cmd + + ctx = click.Context(setup_cmd) + setup_cmd.invoke(ctx) + except ImportError: + click.echo( + "Error: Could not import setup command. Please run 'kilm setup' manually." + ) + else: + click.echo( + "\nYou should run 'kilm setup' to configure the new libraries in KiCad." + ) + click.echo( + "Run 'kilm sync --auto-setup' next time to automatically run setup after sync." + ) + else: + click.echo( + "\nNo new libraries detected that would require running 'kilm setup'." + ) + click.echo("Use 'kilm status' to check your current configuration.") + + +# TODO: Should be in services or utils +def check_for_library_changes(git_output, lib_path): + """ + Check if git pull output and filesystem changes indicate new libraries that would require setup. + + Args: + git_output: Output from git pull command + lib_path: Path to the library directory + + Returns: + List of change types found ('symbols', 'footprints', 'templates') or empty list if none + """ + changes = [] + + # Check git output for new files in key directories + pattern = r"[^\s]+\s+\|\s+\d+ [+]+(?:-+)?\s+(?:symbols|footprints|templates|3d)" + if re.search(pattern, git_output, re.IGNORECASE): + # This is a basic heuristic that indicates changes in library directories + # For more accuracy, we'll check the actual directories + pass + + # Check for specific library file types + symbols_path = lib_path / "symbols" + footprints_path = lib_path / "footprints" + templates_path = lib_path / "templates" + + # Look for symbol libraries (.kicad_sym files) + if ( + symbols_path.exists() + and symbols_path.is_dir() + and any( + f.name.endswith(".kicad_sym") for f in symbols_path.glob("**/*.kicad_sym") + ) + ): + changes.append("symbols") + + # Look for footprint libraries (.pretty directories) + if ( + footprints_path.exists() + and footprints_path.is_dir() + and any( + f.is_dir() and f.name.endswith(".pretty") + for f in footprints_path.glob("**/*.pretty") + ) + ): + changes.append("footprints") + + # Look for project templates (directories with metadata.yaml) + if ( + templates_path.exists() + and templates_path.is_dir() + and any( + (f / "metadata.yaml").exists() + for f in templates_path.glob("*") + if f.is_dir() + ) + ): + changes.append("templates") + + return changes diff --git a/kicad_lib_manager/commands/sync/docs.mdx b/kicad_lib_manager/commands/sync/docs.mdx new file mode 100644 index 0000000..33359dd --- /dev/null +++ b/kicad_lib_manager/commands/sync/docs.mdx @@ -0,0 +1,92 @@ +--- +title: sync +description: Update configured Git-based libraries using git pull. +--- + +import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components"; + +The `kilm sync` command attempts to update all configured symbol, footprint, and template libraries (type `github`) that are identified as Git repositories by running `git pull` within their directories. + +This command helps keep your local copies of shared libraries synchronized with their remote Git repositories. + +:::note[Command History] +In KiLM v0.4.0, library synchronization was moved from `kilm update` to `kilm sync` to make room for self-update functionality. +::: + +## Usage + +```bash +kilm sync [OPTIONS] +``` + +## Options + +- `--dry-run`: + Show which libraries are detected as Git repositories and would be updated, but do not actually run `git pull`. + _Example:_ `kilm sync --dry-run` + +- `--verbose`: + Show detailed output during the update process, including the full output from the `git pull` commands. + _Example:_ `kilm sync --verbose` + +- `--auto-setup`: + If the `git pull` operation results in changes that likely require updating KiCad's configuration (e.g., new symbol or footprint libraries detected), automatically run `kilm setup` after the updates are complete. Default: `False`. + _Example:_ `kilm sync --auto-setup` + +- `--help`: + Show the help message and exit. + +## Behavior + +1. **Reads KiLM Config:** Loads library information from `config.yaml`. +2. **Identifies `github` Libraries:** Filters for libraries with type `github`. +3. **Checks for `.git`:** For each library path, checks if it exists and contains a `.git` directory. +4. **Runs `git pull`:** If it's a valid Git repository, navigates into the directory and executes `git pull` (unless `--dry-run`). +5. **Checks for Changes:** After a successful pull, analyzes the output and file system to detect if new symbol libraries (`.kicad_sym`), footprint libraries (`.pretty`), or templates (`templates/*/metadata.yaml`) were added. +6. **Reports & Optional Setup:** Summarizes the update results. If changes requiring configuration updates were detected, it recommends running `kilm setup` or runs it automatically if `--auto-setup` was specified. + +## Examples + +**Sync all Git-based libraries:** + +```bash +kilm sync +``` + +**Preview which libraries would be synced:** + +```bash +kilm sync --dry-run +``` + +**Sync libraries with detailed output:** + +```bash +kilm sync --verbose +``` + +**Sync libraries and automatically run setup if needed:** + +```bash +kilm sync --auto-setup +``` + +## Troubleshooting + +**Git pull fails with merge conflicts:** +Navigate to the library directory and resolve conflicts manually using standard Git commands: + +```bash +cd /path/to/library +git status +git merge --continue # after resolving conflicts +``` + +**Library not found or not a Git repository:** +- Ensure the library path is correct in your KiLM configuration +- Verify the directory contains a `.git` folder +- Use `kilm list` to check available libraries + +**Permission denied errors:** +- Check file permissions in the library directory +- Ensure you have write access to the library path \ No newline at end of file From bd261a9c99f3bafd873da5d9cb7122b1a7d4730f Mon Sep 17 00:00:00 2001 From: barisgit Date: Sat, 6 Sep 2025 00:34:21 +0200 Subject: [PATCH 2/7] Implement auto update and modify update command to use that --- kicad_lib_manager/auto_update.py | 236 ++++++++++++++++ kicad_lib_manager/commands/update/__init__.py | 4 +- kicad_lib_manager/commands/update/command.py | 264 ++++-------------- kicad_lib_manager/commands/update/docs.mdx | 84 ++++-- 4 files changed, 354 insertions(+), 234 deletions(-) create mode 100644 kicad_lib_manager/auto_update.py diff --git a/kicad_lib_manager/auto_update.py b/kicad_lib_manager/auto_update.py new file mode 100644 index 0000000..f6f74e2 --- /dev/null +++ b/kicad_lib_manager/auto_update.py @@ -0,0 +1,236 @@ +""" +Auto-update functionality for KiLM. +Handles installation method detection, PyPI integration, and update execution. +""" + +import json +import os +import subprocess +import sys +import time +from pathlib import Path +from typing import Dict, Optional + +import requests + + +def detect_installation_method() -> str: + """ + Detect how KiLM was installed to determine appropriate update strategy. + Returns: 'pipx' | 'pip' | 'conda' | 'uv' | 'homebrew' | 'unknown' + """ + executable_path = Path(sys.executable) + + # Check for pipx installation + if any( + part in str(executable_path) for part in [".local/share/pipx", "pipx/venvs"] + ): + return "pipx" + + if os.environ.get("PIPX_HOME") and "pipx" in str(executable_path): + return "pipx" + + # Check for conda installation + if os.environ.get("CONDA_DEFAULT_ENV") or "conda" in str(executable_path): + return "conda" + + # Check for homebrew installation + if any( + path in str(executable_path) for path in ["/opt/homebrew", "/usr/local/Cellar"] + ): + return "homebrew" + + # Check for uv installation + if os.environ.get("UV_PROJECT_ENVIRONMENT") or "uv" in str(executable_path): + return "uv" + + # Check for virtual environment (pip in venv) + if os.environ.get("VIRTUAL_ENV"): + return "pip-venv" + + # Default to system pip + return "pip" + + +class PyPIVersionChecker: + """Responsible PyPI API client with caching and proper headers.""" + + def __init__(self, package_name: str): + self.package_name = package_name + self.base_url = f"https://pypi.org/pypi/{package_name}/json" + self.cache_file = Path.home() / ".cache" / "kilm" / "version_check.json" + self.user_agent = "KiLM/0.4.0 (+https://github.com/barisgit/KiLM)" + + def check_latest_version(self) -> Optional[str]: + """ + Check latest version from PyPI with caching and rate limiting. + Returns None if check fails or is rate limited. + """ + try: + headers = {"User-Agent": self.user_agent} + + # Use cached ETag if available + cached_data = self._load_cache() + if cached_data is not None and "etag" in cached_data: + headers["If-None-Match"] = cached_data["etag"] + + response = requests.get(self.base_url, headers=headers, timeout=10) + + if response.status_code == 304: # Not Modified + return cached_data.get("version") if cached_data else None + + if response.status_code == 200: + data = response.json() + latest_version = data["info"]["version"] + + # Cache response with ETag + self._save_cache( + { + "version": latest_version, + "etag": response.headers.get("ETag"), + "timestamp": time.time(), + } + ) + + return latest_version + + except (requests.RequestException, KeyError, json.JSONDecodeError): + # Fail silently - don't block CLI functionality + pass + + return None + + def _load_cache(self) -> Optional[Dict]: + """Load cached version data.""" + if self.cache_file.exists(): + try: + with Path(self.cache_file).open() as f: + data = json.load(f) + # Cache valid for 24 hours + if time.time() - data.get("timestamp", 0) < 86400: + return data + except (json.JSONDecodeError, KeyError): + pass + return None + + def _save_cache(self, data: Dict): + """Save version data to cache.""" + self.cache_file.parent.mkdir(parents=True, exist_ok=True) + with Path(self.cache_file).open("w") as f: + json.dump(data, f) + + +def update_via_pipx() -> bool: + """Update KiLM via pipx. Most reliable method for CLI tools.""" + try: + result = subprocess.run( + ["pipx", "upgrade", "kilm"], capture_output=True, text=True, timeout=300 + ) + return result.returncode == 0 + except (subprocess.TimeoutExpired, FileNotFoundError): + return False + + +def update_via_pip() -> bool: + """Update KiLM via pip.""" + try: + # Use same Python interpreter that's running KiLM + result = subprocess.run( + [sys.executable, "-m", "pip", "install", "--upgrade", "kilm"], + capture_output=True, + text=True, + timeout=300, + ) + return result.returncode == 0 + except (subprocess.TimeoutExpired, FileNotFoundError): + return False + + +def update_via_uv() -> bool: + """Update KiLM via uv.""" + try: + result = subprocess.run( + ["uv", "tool", "upgrade", "kilm"], + capture_output=True, + text=True, + timeout=300, + ) + return result.returncode == 0 + except (subprocess.TimeoutExpired, FileNotFoundError): + return False + + +class UpdateManager: + """Manages update checking and execution for KiLM.""" + + def __init__(self, current_version: str): + self.version_checker = PyPIVersionChecker("kilm") + self.current_version = current_version + self.installation_method = detect_installation_method() + + def check_latest_version(self) -> Optional[str]: + """Check for latest version available on PyPI.""" + return self.version_checker.check_latest_version() + + def is_newer_version_available(self, latest_version: str) -> bool: + """Compare versions to determine if update is available.""" + try: + # Simple version comparison for now + current_parts = [int(x) for x in self.current_version.split(".")] + latest_parts = [int(x) for x in latest_version.split(".")] + + # Pad shorter version with zeros + max_len = max(len(current_parts), len(latest_parts)) + current_parts.extend([0] * (max_len - len(current_parts))) + latest_parts.extend([0] * (max_len - len(latest_parts))) + + return latest_parts > current_parts + except (ValueError, AttributeError): + return False + + def get_update_instruction(self) -> str: + """Get update instruction for the detected installation method.""" + instructions = { + "pipx": "pipx upgrade kilm", + "pip": "pip install --upgrade kilm", + "pip-venv": "pip install --upgrade kilm", + "uv": "uv tool upgrade kilm", + "conda": "conda update kilm", + "homebrew": "brew upgrade kilm", + } + return instructions.get(self.installation_method, "Check your package manager") + + def can_auto_update(self) -> bool: + """Check if automatic update is possible for this installation method.""" + return self.installation_method in ["pipx", "pip", "pip-venv", "uv"] + + def perform_update(self) -> tuple[bool, str]: + """ + Execute update using detected installation method. + Returns: (success: bool, message: str) + """ + if not self.can_auto_update(): + instruction = self.get_update_instruction() + return False, f"Manual update required. Run: {instruction}" + + update_functions = { + "pipx": update_via_pipx, + "pip": update_via_pip, + "pip-venv": update_via_pip, + "uv": update_via_uv, + } + + update_func = update_functions.get(self.installation_method) + if update_func: + try: + success = update_func() + if success: + return True, "KiLM updated successfully!" + else: + instruction = self.get_update_instruction() + return False, f"Auto-update failed. Try manually: {instruction}" + except Exception as e: + return False, f"Update error: {str(e)}" + else: + instruction = self.get_update_instruction() + return False, f"Unsupported installation method. Run: {instruction}" diff --git a/kicad_lib_manager/commands/update/__init__.py b/kicad_lib_manager/commands/update/__init__.py index 8a0a7f6..1ed481f 100644 --- a/kicad_lib_manager/commands/update/__init__.py +++ b/kicad_lib_manager/commands/update/__init__.py @@ -1,3 +1,3 @@ -from .command import check_for_library_changes, update +from .command import update -__all__ = ["check_for_library_changes", "update"] +__all__ = ["update"] diff --git a/kicad_lib_manager/commands/update/command.py b/kicad_lib_manager/commands/update/command.py index 2a6a47f..4a4a89e 100644 --- a/kicad_lib_manager/commands/update/command.py +++ b/kicad_lib_manager/commands/update/command.py @@ -1,238 +1,94 @@ """ Update command implementation for KiCad Library Manager. -Performs 'git pull' on all configured GitHub libraries (symbols/footprints). +Updates KiLM itself to the latest version. """ -import re -import subprocess -from pathlib import Path - import click -from ...config import Config +from ... import __version__ +from ...auto_update import UpdateManager @click.command() @click.option( - "--dry-run", - is_flag=True, - default=False, - help="Show what would be updated without making changes", - show_default=True, -) -@click.option( - "--verbose", + "--check", is_flag=True, default=False, - help="Show detailed output", + help="Check for updates without installing", show_default=True, ) @click.option( - "--auto-setup", + "--force", is_flag=True, default=False, - help="Run 'kilm setup' automatically if new libraries are detected", + help="Force update even if already up to date", show_default=True, ) -def update(dry_run, verbose, auto_setup): - """Update all configured GitHub libraries with git pull. - - This command updates all configured GitHub libraries (symbols/footprints) - by performing a 'git pull' operation in each library directory. - It will only attempt to update directories that are valid git repositories. - - After updating, the command will check if new libraries have been added - and recommend running 'kilm setup' if needed. Use --auto-setup to run - setup automatically when new libraries are detected. - """ - config = Config() - - # Get GitHub libraries from config (symbols/footprints) - libraries = config.get_libraries(library_type="github") +def update(check, force): + """Update KiLM to the latest version. - if not libraries: - click.echo("No GitHub libraries configured. Use 'kilm init' to add a library.") - return - - click.echo(f"Updating {len(libraries)} KiCad GitHub libraries...") - - updated_count = 0 # Actually changed - up_to_date_count = 0 # Already at latest version - skipped_count = 0 # Could not update (not git, etc.) - failed_count = 0 # Git pull failed - - # Track libraries that have changes that might require setup - libraries_with_changes = [] - - for lib in libraries: - lib_name = lib.get("name", "unnamed") - lib_path = lib.get("path") + This command updates KiLM itself by downloading and installing the latest + version from PyPI. The update method depends on how KiLM was installed + (pip, pipx, conda, etc.). - if not lib_path: - click.echo(f" Skipping {lib_name}: No path defined") - skipped_count += 1 - continue + ⚠️ DEPRECATION NOTICE: + In KiLM 0.4.0, the 'update' command now updates KiLM itself. + To update library content, use 'kilm sync' instead. + This banner will be removed in a future version. - lib_path = Path(lib_path) - if not lib_path.exists(): - click.echo(f" Skipping {lib_name}: Path does not exist: {lib_path}") - skipped_count += 1 - continue - - git_dir = lib_path / ".git" - if not git_dir.exists() or not git_dir.is_dir(): - click.echo(f" Skipping {lib_name}: Not a git repository: {lib_path}") - skipped_count += 1 - continue - - # Prepare to run git pull - click.echo(f" Updating {lib_name} at {lib_path}...") - - if dry_run: - click.echo(f" Dry run: would execute 'git pull' in {lib_path}") - updated_count += 1 - continue + Use --check to see if updates are available without installing. + """ + # Display deprecation notice prominently + click.echo("\n" + "=" * 70) + click.echo("⚠️ BREAKING CHANGE NOTICE (KiLM 0.4.0)") + click.echo("=" * 70) + click.echo("The 'kilm update' command now updates KiLM itself.") + click.echo("To update library content, use 'kilm sync' instead.") + click.echo("This notice will be removed in a future version.") + click.echo("=" * 70 + "\n") - try: - # Run git pull - result = subprocess.run( - ["git", "pull"], - cwd=lib_path, - capture_output=True, - text=True, - check=False, - ) + update_manager = UpdateManager(__version__) - if result.returncode == 0: - output = result.stdout.strip() or "Already up to date." - if verbose: - click.echo(f" Success: {output}") - else: - is_updated = "Already up to date" not in output - if is_updated: - click.echo(" Updated") - updated_count += 1 - else: - click.echo(" Up to date") - up_to_date_count += 1 + click.echo(f"Current KiLM version: {__version__}") + click.echo(f"Installation method: {update_manager.installation_method}") + click.echo("\nChecking for updates...") - # Check if there are new library files that would require setup - if "Already up to date" not in output: - changes_require_setup = check_for_library_changes( - result.stdout, lib_path - ) - if changes_require_setup: - libraries_with_changes.append( - (lib_name, lib_path, changes_require_setup) - ) - if verbose: - click.echo( - f" Detected new library content: {', '.join(changes_require_setup)}" - ) - else: - click.echo(f" Failed: {result.stderr.strip()}") - failed_count += 1 + latest_version = update_manager.check_latest_version() - except Exception as e: - click.echo(f" Error: {str(e)}") - failed_count += 1 + if latest_version is None: + click.echo("Could not check for updates. Please try again later.") + return - # Summary - click.echo("\nUpdate Summary:") - click.echo(f" {updated_count} libraries updated") - click.echo(f" {up_to_date_count} libraries up to date") - click.echo(f" {skipped_count} libraries skipped") - click.echo(f" {failed_count} libraries failed") + if not update_manager.is_newer_version_available(latest_version): + if not force: + click.echo(f"KiLM is up to date (v{__version__})") + return + else: + click.echo(f"Forcing update to v{latest_version} (current: v{__version__})") + else: + click.echo(f"New version available: {latest_version}") - # If libraries with changes were detected, suggest running setup - if libraries_with_changes: - click.echo("\nNew library content detected in:") - for lib_name, _lib_path, changes in libraries_with_changes: - click.echo(f" - {lib_name}: {', '.join(changes)}") + if check: + if update_manager.is_newer_version_available(latest_version): + click.echo(f"\nUpdate available: {latest_version}") + click.echo(f"To update, run: {update_manager.get_update_instruction()}") + else: + click.echo("No updates available.") + return - if auto_setup: - click.echo("\nRunning 'kilm setup' to configure new libraries...") - # Import at runtime to avoid circular imports - try: - from ...commands.setup import setup as setup_cmd + # Perform the update + if update_manager.can_auto_update(): + click.echo(f"\nUpdating KiLM to version {latest_version}...") + success, message = update_manager.perform_update() - ctx = click.Context(setup_cmd) - setup_cmd.invoke(ctx) - except ImportError: - click.echo( - "Error: Could not import setup command. Please run 'kilm setup' manually." - ) + if success: + click.echo(f"✅ {message}") + click.echo(f"KiLM has been updated to version {latest_version}") else: - click.echo( - "\nYou should run 'kilm setup' to configure the new libraries in KiCad." - ) - click.echo( - "Run 'kilm update --auto-setup' next time to automatically run setup after updates." - ) + click.echo(f"❌ {message}") else: + instruction = update_manager.get_update_instruction() click.echo( - "\nNo new libraries detected that would require running 'kilm setup'." + f"\nManual update required for {update_manager.installation_method} installation." ) - click.echo("Use 'kilm status' to check your current configuration.") - - -# TODO: Should be in services or utils -def check_for_library_changes(git_output, lib_path): - """ - Check if git pull output and filesystem changes indicate new libraries that would require setup. - - Args: - git_output: Output from git pull command - lib_path: Path to the library directory - - Returns: - List of change types found ('symbols', 'footprints', 'templates') or empty list if none - """ - changes = [] - - # Check git output for new files in key directories - pattern = r"[^\s]+\s+\|\s+\d+ [+]+(?:-+)?\s+(?:symbols|footprints|templates|3d)" - if re.search(pattern, git_output, re.IGNORECASE): - # This is a basic heuristic that indicates changes in library directories - # For more accuracy, we'll check the actual directories - pass - - # Check for specific library file types - symbols_path = lib_path / "symbols" - footprints_path = lib_path / "footprints" - templates_path = lib_path / "templates" - - # Look for symbol libraries (.kicad_sym files) - if ( - symbols_path.exists() - and symbols_path.is_dir() - and any( - f.name.endswith(".kicad_sym") for f in symbols_path.glob("**/*.kicad_sym") - ) - ): - changes.append("symbols") - - # Look for footprint libraries (.pretty directories) - if ( - footprints_path.exists() - and footprints_path.is_dir() - and any( - f.is_dir() and f.name.endswith(".pretty") - for f in footprints_path.glob("**/*.pretty") - ) - ): - changes.append("footprints") - - # Look for project templates (directories with metadata.yaml) - if ( - templates_path.exists() - and templates_path.is_dir() - and any( - (f / "metadata.yaml").exists() - for f in templates_path.glob("*") - if f.is_dir() - ) - ): - changes.append("templates") - - return changes + click.echo(f"Please run: {instruction}") diff --git a/kicad_lib_manager/commands/update/docs.mdx b/kicad_lib_manager/commands/update/docs.mdx index 3e772bd..9500f33 100644 --- a/kicad_lib_manager/commands/update/docs.mdx +++ b/kicad_lib_manager/commands/update/docs.mdx @@ -1,13 +1,19 @@ --- title: update -description: Update configured Git-based libraries using git pull. +description: Update KiLM to the latest version from PyPI. --- import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components"; -The `kilm update` command attempts to update all configured symbol, footprint, and template libraries (type `github`) that are identified as Git repositories by running `git pull` within their directories. +The `kilm update` command updates KiLM itself to the latest version available on PyPI. It automatically detects your installation method and uses the appropriate update mechanism. -This command helps keep your local copies of shared libraries synchronized with their remote Git repositories. +:::caution[Breaking Change in v0.4.0] +The `kilm update` command behavior changed in KiLM v0.4.0: +- **Now:** Updates KiLM itself (self-update) +- **Previously:** Updated library content (now use `kilm sync`) + +A deprecation banner will guide users during the transition period. +::: ## Usage @@ -17,54 +23,76 @@ kilm update [OPTIONS] ## Options -- `--dry-run`: - Show which libraries are detected as Git repositories and would be updated, but do not actually run `git pull`. - _Example:_ `kilm update --dry-run` - -- `--verbose`: - Show detailed output during the update process, including the full output from the `git pull` commands. - _Example:_ `kilm update --verbose` +- `--check`: + Check for available updates without installing them. Shows current version, latest version, and update instructions. + _Example:_ `kilm update --check` -- `--auto-setup`: - If the `git pull` operation results in changes that likely require updating KiCad's configuration (e.g., new symbol or footprint libraries detected), automatically run `kilm setup` after the updates are complete. Default: `False`. - _Example:_ `kilm update --auto-setup` +- `--force`: + Force update even if already up to date. Useful for reinstalling the current version. + _Example:_ `kilm update --force` - `--help`: Show the help message and exit. ## Behavior -1. **Reads KiLM Config:** Loads library information from `config.yaml`. -2. **Identifies `github` Libraries:** Filters for libraries with type `github`. -3. **Checks for `.git`:** For each library path, checks if it exists and contains a `.git` directory. -4. **Runs `git pull`:** If it's a valid Git repository, navigates into the directory and executes `git pull` (unless `--dry-run`). -5. **Checks for Changes:** After a successful pull, analyzes the output and file system to detect if new symbol libraries (`.kicad_sym`), footprint libraries (`.pretty`), or templates (`templates/*/metadata.yaml`) were added. -6. **Reports & Optional Setup:** Summarizes the update results. If changes requiring configuration updates were detected, it recommends running `kilm setup` or runs it automatically if `--auto-setup` was specified. +1. **Installation Detection:** Automatically detects how KiLM was installed (pipx, pip, conda, uv, homebrew). +2. **Version Check:** Queries PyPI for the latest available version using cached requests. +3. **Update Execution:** For supported methods (pipx, pip, uv), automatically executes the update command. +4. **Manual Instructions:** For unsupported methods (conda, homebrew), provides specific update instructions. + +## Supported Installation Methods + +| Method | Auto-Update | Update Command | +|--------|-------------|----------------| +| **pipx** ✅ | Yes | `pipx upgrade kilm` | +| **pip** ✅ | Yes | `pip install --upgrade kilm` | +| **pip (venv)** ✅ | Yes | `pip install --upgrade kilm` | +| **uv** ✅ | Yes | `uv tool upgrade kilm` | +| **conda** | Manual | `conda update kilm` | +| **homebrew** | Manual | `brew upgrade kilm` | ## Examples -**Update all Git-based libraries:** +**Check for updates:** ```bash -kilm update +kilm update --check ``` -**Preview which libraries would be updated:** +**Update KiLM to latest version:** ```bash -kilm update --dry-run +kilm update ``` -**Update libraries with detailed output:** +**Force reinstall current version:** ```bash -kilm update --verbose +kilm update --force ``` -**Update libraries and automatically run setup if needed:** +## Configuration + +Update checking behavior can be configured using `kilm config`: ```bash -kilm update --auto-setup +# Disable update checking +kilm config set update_check false + +# Set check frequency +kilm config set update_check_frequency weekly # daily, weekly, never ``` -**Note:** If `git pull` fails (e.g., due to local changes or merge conflicts), you will need to resolve the issues manually within the affected repository directory using standard Git commands before `kilm update` can succeed for that library. +## Troubleshooting + +**Update fails with permission errors:** +- For system pip: Try `pip install --user --upgrade kilm` +- For pipx: Reinstall with `pipx uninstall kilm && pipx install kilm` + +**Cannot detect installation method:** +- Install via pipx for best CLI tool experience: `pipx install kilm` + +**Network/PyPI connection issues:** +- Check internet connection +- Try again later (PyPI may be temporarily unavailable) \ No newline at end of file From cdd1699c6a0f87c4077cba5ead0b0bf3123ee970 Mon Sep 17 00:00:00 2001 From: barisgit Date: Sat, 6 Sep 2025 00:34:38 +0200 Subject: [PATCH 3/7] Fix other references and docs regarding previous changes --- docs/astro.config.mjs | 1 + .../content/docs/guides/automatic-updates.mdx | 12 +-- .../content/docs/guides/troubleshooting.mdx | 2 +- docs/src/content/docs/reference/cli/index.mdx | 5 +- kicad_lib_manager/__init__.py | 2 + kicad_lib_manager/cli.py | 2 + .../commands/add_hook/command.py | 6 +- kicad_lib_manager/commands/add_hook/docs.mdx | 14 ++-- kicad_lib_manager/config.py | 81 +++++++++++++++++++ kicad_lib_manager/utils/git_utils.py | 8 +- pyproject.toml | 2 +- tests/test_git_utils.py | 4 +- ...update_command.py => test_sync_command.py} | 44 +++++----- 13 files changed, 135 insertions(+), 48 deletions(-) rename tests/{test_update_command.py => test_sync_command.py} (81%) diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 7ce8c94..95fc346 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -60,6 +60,7 @@ export default defineConfig({ { label: "template", link: "/reference/cli/template/" }, { label: "list", link: "/reference/cli/list/" }, { label: "status", link: "/reference/cli/status/" }, + { label: "sync", link: "/reference/cli/sync/" }, { label: "update", link: "/reference/cli/update/" }, { label: "add-hook", link: "/reference/cli/add-hook/" }, ], diff --git a/docs/src/content/docs/guides/automatic-updates.mdx b/docs/src/content/docs/guides/automatic-updates.mdx index 73a7dd1..bff74f6 100644 --- a/docs/src/content/docs/guides/automatic-updates.mdx +++ b/docs/src/content/docs/guides/automatic-updates.mdx @@ -7,7 +7,7 @@ import { Aside } from "@astrojs/starlight/components"; > **Note:** This guide primarily describes features beneficial for **Consumers** – those using libraries managed by a Creator. The setup of the hook itself might be done by a Creator or an advanced Consumer. -KiLM provides a helper command [`kilm add-hook`](/reference/cli/add-hook/) to easily create a basic `post-merge` Git hook that runs `kilm update`. +KiLM provides a helper command [`kilm add-hook`](/reference/cli/add-hook/) to easily create a basic `post-merge` Git hook that runs `kilm sync`. ## Using `kilm add-hook` (Recommended) @@ -17,7 +17,7 @@ Navigate to the root of your Git repository containing your KiCad libraries and kilm add-hook ``` -This creates a `.git/hooks/post-merge` script that automatically executes `kilm update` every time you successfully run `git pull` or `git merge` in that repository. +This creates a `.git/hooks/post-merge` script that automatically executes `kilm sync` every time you successfully run `git pull` or `git merge` in that repository.