Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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/" },
],
Expand Down
12 changes: 6 additions & 6 deletions docs/src/content/docs/guides/automatic-updates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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.

<Aside type="tip" title="Enhanced Hook Management">
The `kilm add-hook` command now includes advanced features: - **Smart
Expand Down Expand Up @@ -51,13 +51,13 @@ If you prefer manual setup or want to customize the hook script further, you can
# KiCad Library Manager auto-update hook
# Added manually

echo "Running KiCad Library Manager update..."
kilm update
echo "Running KiCad Library Manager sync..."
kilm sync

# Uncomment to set up libraries automatically (use with caution)
# kilm setup

echo "KiCad libraries update complete."
echo "KiCad libraries sync complete."
# END KiLM-managed section
```

Expand All @@ -72,7 +72,7 @@ If you prefer manual setup or want to customize the hook script further, you can
When you run `git pull` or `git merge`:

1. Git executes the `post-merge` hook automatically
2. The hook runs `kilm update` to check for library updates
2. The hook runs `kilm sync` to check for library updates
3. If new versions are available, they are pulled from remote repositories
4. Your local KiCad libraries stay synchronized with the latest versions

Expand Down
2 changes: 1 addition & 1 deletion docs/src/content/docs/guides/troubleshooting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ Here are some common problems you might encounter when using KiLM and how to res

## Getting More Help

- Use the `--verbose` flag with commands like `kilm setup` or `kilm update` to get more detailed output.
- Use the `--verbose` flag with commands like `kilm setup` or `kilm sync` to get more detailed output.
- Use `kilm status` to check the current configuration state.
- Check the KiLM issue tracker on GitHub: [https://github.com/barisgit/kilm/issues](https://github.com/barisgit/kilm/issues) or submit a request here: [https://aristovnik.me/contact](https://aristovnik.me/contact)
5 changes: 3 additions & 2 deletions docs/src/content/docs/reference/cli/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Each command focuses on a specific task. Below is a list of available commands.
- [`template`](/reference/cli/template/): Create and manage KiCad project templates.
- [`list`](/reference/cli/list/): List symbol and footprint libraries found within a specified directory.
- [`status`](/reference/cli/status/): Check the current KiLM and KiCad configuration status.
- [`update`](/reference/cli/update/): Update Git-based libraries using `git pull`.
- [`add-hook`](/reference/cli/add-hook/): Add a Git post-merge hook to automatically run `kilm update`.
- [`sync`](/reference/cli/sync/): Update Git-based libraries using `git pull`.
- [`update`](/reference/cli/update/): Update KiLM itself to the latest version from PyPI.
- [`add-hook`](/reference/cli/add-hook/): Add a Git post-merge hook to automatically run `kilm sync`.

Use the sidebar navigation or the links above to explore each command in detail.
2 changes: 2 additions & 0 deletions kicad_lib_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""
KiCad Library Manager - Library management utilities for KiCad
"""

__version__ = "0.4.0"
236 changes: 236 additions & 0 deletions kicad_lib_manager/auto_update.py
Original file line number Diff line number Diff line change
@@ -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}"
2 changes: 2 additions & 0 deletions kicad_lib_manager/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .commands.pin import pin
from .commands.setup import setup
from .commands.status import status
from .commands.sync import sync
from .commands.template import template
from .commands.unpin import unpin
from .commands.update import update
Expand All @@ -39,6 +40,7 @@ def main():
main.add_command(init)
main.add_command(add_3d)
main.add_command(config)
main.add_command(sync)
main.add_command(update)
main.add_command(add_hook)
main.add_command(template)
Expand Down
6 changes: 3 additions & 3 deletions kicad_lib_manager/commands/add_hook/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
show_default=True,
)
def add_hook(directory, force):
"""Add a Git post-merge hook to automatically update KiCad libraries.
"""Add a Git post-merge hook to automatically sync KiCad libraries.

This command adds a Git post-merge hook to the specified repository
(or the current directory if none specified) that automatically runs
'kilm update' after a 'git pull' or 'git merge' operation.
'kilm sync' after a 'git pull' or 'git merge' operation.

This ensures your KiCad libraries are always up-to-date after pulling
changes from remote repositories.
Expand Down Expand Up @@ -98,7 +98,7 @@ def add_hook(directory, force):

click.echo(f"Successfully installed post-merge hook at {post_merge_hook}")
click.echo(
"The hook will run 'kilm update' after every 'git pull' or 'git merge' operation."
"The hook will run 'kilm sync' after every 'git pull' or 'git merge' operation."
)

if post_merge_hook.exists() and "KiLM-managed section" in new_content:
Expand Down
14 changes: 7 additions & 7 deletions kicad_lib_manager/commands/add_hook/docs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Add a Git post-merge hook to automatically update KiLM-managed libr

import { LinkCard, Card, CardGrid } from "@astrojs/starlight/components";

The `kilm add-hook` command creates or modifies a Git `post-merge` hook script in a specified repository. This hook is designed to automatically run `kilm update` after successful `git pull` or `git merge` operations, helping keep your KiLM-managed libraries synchronized.
The `kilm add-hook` command creates or modifies a Git `post-merge` hook script in a specified repository. This hook is designed to automatically run `kilm sync` after successful `git pull` or `git merge` operations, helping keep your KiLM-managed libraries synchronized.

## Usage

Expand Down Expand Up @@ -81,8 +81,8 @@ The command creates a `post-merge` hook with the following structure:
# KiCad Library Manager auto-update hook
# Added by kilm add-hook command

echo "Running KiCad Library Manager update..."
kilm update
echo "Running KiCad Library Manager sync..."
kilm sync

# Uncomment to set up libraries automatically (use with caution)
# kilm setup
Expand Down Expand Up @@ -124,8 +124,8 @@ You can manually edit the generated hook script to add more functionality. For e
# KiCad Library Manager auto-update hook
# Added by kilm add-hook command

echo "Running KiCad Library Manager update..."
kilm update
echo "Running KiCad Library Manager sync..."
kilm sync

# Uncomment to set up libraries automatically (use with caution)
kilm setup
Expand Down Expand Up @@ -158,8 +158,8 @@ kilm add-hook --directory /readonly/repo

<CardGrid>
<LinkCard
title="update"
href="/reference/cli/update/"
title="sync"
href="/reference/cli/sync/"
description="The command that gets executed by the post-merge hook"
/>
<LinkCard
Expand Down
Loading
Loading