Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
39 changes: 24 additions & 15 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python package
name: CI

on:
push:
Expand All @@ -11,31 +8,43 @@ on:
workflow_dispatch:

jobs:
build:

runs-on: ubuntu-latest
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]

Comment on lines +11 to 18
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Matrix includes Python 3.13; enable prereleases to avoid runner misses

3.13 may be RC/pre-release on some runners. Enable prereleases for setup-python.

Apply:

     - name: Set up Python ${{ matrix.python-version }}
       uses: actions/setup-python@v5
       with:
         python-version: ${{ matrix.python-version }}
+        allow-prereleases: true

Also applies to: 23-26

🤖 Prompt for AI Agents
.github/workflows/python-package.yml lines 11-18 (and likewise update lines
23-26): the workflow matrix includes Python 3.13 which may be a pre-release on
some runners; update the setup-python step to enable prerelease checking by
adding the setup-python input (check-latest: true) so the action can install
pre-release/RC versions. Modify the setup-python job step(s) to include
check-latest: true under with:, and apply the same change to the other repeated
setup-python step at lines 23-26.

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest
pip install -e .[dev]
- name: Lint with flake8

- name: Lint with ruff
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
ruff check .
ruff format --check .

- name: Type check with pyrefly
run: |
pyrefly check

- name: Test with pytest
run: |
pytest
pytest --cov=kicad_lib_manager --cov-report=xml

- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: false
Comment on lines +45 to +50
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Codecov v4 inputs/token — verify and adjust

v4 commonly requires a token for private repos and may use files: instead of file:. Please verify and update.

Apply (if needed):

-    - name: Upload coverage to Codecov
+    - name: Upload coverage to Codecov
       if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
       uses: codecov/codecov-action@v4
       with:
-        file: ./coverage.xml
+        files: ./coverage.xml
         fail_ci_if_error: false
+        token: ${{ secrets.CODECOV_TOKEN }}

Queries to confirm:


🌐 Web query:

Does codecov/codecov-action@v4 require a CODECOV_TOKEN for uploads from GitHub Actions, and is the input key `files` (plural) or `file` (singular)?

💡 Result:

Short answer:

  • Yes — codecov/codecov-action@v4 generally requires a CODECOV_TOKEN for uploads (unless you use OIDC or your repo/public-org is configured to allow tokenless uploads). [1][2]
  • The input is files (plural); file (singular) is deprecated in favor of files. [1]

Sources: [1] codecov/codecov-action README (v4) — inputs and token/OIDC notes. [2] Codecov docs on tokens / tokenless uploads.


Update Codecov v4 action inputs

  • Replace the deprecated file: key with files:
  • Add token: ${{ secrets.CODECOV_TOKEN }} (required for private repos; omit only if you’ve enabled tokenless OIDC uploads)
-    - name: Upload coverage to Codecov
+    - name: Upload coverage to Codecov
       if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
       uses: codecov/codecov-action@v4
       with:
-        file: ./coverage.xml
+        files: ./coverage.xml
+        token: ${{ secrets.CODECOV_TOKEN }}
        fail_ci_if_error: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
fail_ci_if_error: false
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
with:
files: ./coverage.xml
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
🤖 Prompt for AI Agents
.github/workflows/python-package.yml around lines 45-50: the Codecov v4 action
inputs are outdated—replace the deprecated file: key with files: (accepts a glob
or list like ./coverage.xml) and add the token input using token: ${{
secrets.CODECOV_TOKEN }} (omit only if tokenless OIDC uploads are enabled);
ensure the files value is correct and keep fail_ci_if_error: false as before.

157 changes: 157 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
name: Release

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
version:
description: 'Version to release (e.g., v0.3.1)'
required: true
type: string

permissions:
contents: write
id-token: write

jobs:
build-and-publish:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build hatch

- name: Verify version matches tag
run: |
if [[ "${{ github.event_name }}" == "push" ]]; then
TAG_VERSION="${GITHUB_REF#refs/tags/v}"
else
TAG_VERSION="${{ github.event.inputs.version }}"
TAG_VERSION="${TAG_VERSION#v}"
fi

PACKAGE_VERSION=$(python -c "import kicad_lib_manager; print(kicad_lib_manager.__version__)")

if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then
echo "Version mismatch: tag=$TAG_VERSION, package=$PACKAGE_VERSION"
exit 1
fi

echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV

Comment on lines +46 to +54
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t import package to read version during CI.
Import may fail before deps are installed; parse the version file instead.

-        PACKAGE_VERSION=$(python -c "import kicad_lib_manager; print(kicad_lib_manager.__version__)")
+        PACKAGE_VERSION=$(python - <<'PY'
+from pathlib import Path
+import re
+m = re.search(r"__version__\s*=\s*['\"]([^'\"]+)['\"]",
+              Path("kicad_lib_manager/__init__.py").read_text(encoding="utf-8"))
+print(m.group(1) if m else "")
+PY
+)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
PACKAGE_VERSION=$(python -c "import kicad_lib_manager; print(kicad_lib_manager.__version__)")
if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then
echo "Version mismatch: tag=$TAG_VERSION, package=$PACKAGE_VERSION"
exit 1
fi
echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV
PACKAGE_VERSION=$(python - <<'PY'
from pathlib import Path
import re
m = re.search(r"__version__\s*=\s*['\"]([^'\"]+)['\"]",
Path("kicad_lib_manager/__init__.py").read_text(encoding="utf-8"))
print(m.group(1) if m else "")
PY
)
if [[ "$TAG_VERSION" != "$PACKAGE_VERSION" ]]; then
echo "Version mismatch: tag=$TAG_VERSION, package=$PACKAGE_VERSION"
exit 1
fi
echo "VERSION=$TAG_VERSION" >> $GITHUB_ENV
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 47-47: trailing spaces

(trailing-spaces)


[error] 52-52: trailing spaces

(trailing-spaces)


[error] 54-54: trailing spaces

(trailing-spaces)

🤖 Prompt for AI Agents
In .github/workflows/release.yml around lines 46 to 54, the workflow imports the
package to get its version which can fail when dependencies are not yet
installed; instead read and parse the package's version source file (e.g., the
module's __init__.py or dedicated version file) directly to extract the
__version__ string without importing. Replace the python import step with a
shell-safe extraction that opens the version file, finds the line defining
__version__, parses the quoted version value, assigns it to PACKAGE_VERSION,
then perform the TAG_VERSION vs PACKAGE_VERSION check and write VERSION to
GITHUB_ENV as before.

- name: Run tests
run: |
pip install -e .[dev]
ruff check .
ruff format --check .
pyrefly
pytest

- name: Build package
run: |
python -m build

- name: Check package
run: |
pip install twine
twine check dist/*

- name: Generate release notes
id: release_notes
env:
GH_TOKEN: ${{ github.token }}
run: |
# Get the previous release tag
PREVIOUS_TAG=$(git describe --tags --abbrev=0 HEAD~1 2>/dev/null || echo "")

if [ -z "$PREVIOUS_TAG" ]; then
# First release, get all commits
COMMITS=$(git log --oneline --no-merges)
else
# Get commits since last release
COMMITS=$(git log --oneline --no-merges ${PREVIOUS_TAG}..HEAD)
fi

# Get PR information
PRS=$(gh pr list --state merged --search "merged:>${PREVIOUS_TAG:-1970-01-01}" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "")

# Generate release notes
cat > release_notes.md << EOF
## What's Changed in v${VERSION}

### Commits
$(echo "$COMMITS" | sed 's/^/- /')

EOF

if [ ! -z "$PRS" ]; then
cat >> release_notes.md << EOF

### Pull Requests
$(echo "$PRS")

Comment on lines +88 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix PR query: using tag name in merged:> filter is invalid.
gh pr list --search merged:> expects a date, not a tag.

Apply:

-        # Get PR information
-        PRS=$(gh pr list --state merged --search "merged:>${PREVIOUS_TAG:-1970-01-01}" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "")
+        # Compute a date to query PRs merged since previous tag
+        if [ -n "$PREVIOUS_TAG" ]; then
+          SINCE=$(git show -s --format=%cI "$PREVIOUS_TAG")
+        else
+          SINCE="1970-01-01"
+        fi
+        # Get PR information since that date
+        PRS=$(gh pr list --state merged --search "merged:>$SINCE" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Get PR information
PRS=$(gh pr list --state merged --search "merged:>${PREVIOUS_TAG:-1970-01-01}" --json number,title,url --jq '.[] | "- #\(.number): \(.title) (\(.url))"' 2>/dev/null || echo "")
# Generate release notes
cat > release_notes.md << EOF
## What's Changed in v${VERSION}
### Commits
$(echo "$COMMITS" | sed 's/^/- /')
EOF
if [ ! -z "$PRS" ]; then
cat >> release_notes.md << EOF
### Pull Requests
$(echo "$PRS")
# Compute a date to query PRs merged since previous tag
if [ -n "$PREVIOUS_TAG" ]; then
SINCE=$(git show -s --format=%cI "$PREVIOUS_TAG")
else
SINCE="1970-01-01"
fi
# Get PR information since that date
PRS=$(gh pr list --state merged --search "merged:>$SINCE" \
--json number,title,url \
--jq '.[] | "- #\(.number): \(.title) (\(.url))"' \
2>/dev/null || echo "")
# Generate release notes
cat > release_notes.md << EOF
## What's Changed in v${VERSION}
### Commits
$(echo "$COMMITS" | sed 's/^/- /')
EOF
if [ ! -z "$PRS" ]; then
cat >> release_notes.md << EOF
### Pull Requests
$(echo "$PRS")
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 90-90: trailing spaces

(trailing-spaces)


[error] 94-94: trailing spaces

(trailing-spaces)


[error] 97-97: trailing spaces

(trailing-spaces)


[error] 99-99: trailing spaces

(trailing-spaces)


[error] 102-102: trailing spaces

(trailing-spaces)


[error] 105-105: trailing spaces

(trailing-spaces)

🤖 Prompt for AI Agents
.github/workflows/release.yml around lines 88 to 105: the GH CLI search filter
uses merged:>${PREVIOUS_TAG} but gh expects a date, not a tag name; change the
workflow to derive the commit/tag date for PREVIOUS_TAG (e.g. via a git command
that outputs the tag/commit date) and then pass that ISO date into the gh pr
list --search "merged:>DATE" call so the search uses a valid date string; ensure
the derived date variable is set before the gh pr list invocation and used in
place of the tag.

EOF
fi

cat >> release_notes.md << 'EOF'

### Installation

**Using pipx (recommended for CLI tools):**
```bash
pipx install kilm
```

**Using pip:**
```bash
pip install kilm
```

**From release assets:**
1. Download the wheel file from this release
2. Install with pipx or pip:
```bash
pipx install kilm-${VERSION}-py3-none-any.whl
# or
pip install kilm-${VERSION}-py3-none-any.whl
```

### Auto-Update (Coming Soon)
Future versions will include `kilm update` functionality for easy updates.
EOF

# Store release notes for next step
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
cat release_notes.md >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.event_name == 'push' && github.ref_name || format('v{0}', github.event.inputs.version) }}
name: Release ${{ github.event_name == 'push' && github.ref_name || format('v{0}', github.event.inputs.version) }}
body: ${{ steps.release_notes.outputs.release_notes }}
draft: true
prerelease: false
files: |
dist/*.whl
dist/*.tar.gz

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
print-hash: true
verbose: true
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

Professional command-line tool for managing KiCad libraries across projects and workstations.

**[📚 Official Documentation](https://kilm.aristovnik.me)**
**[Official Documentation](https://kilm.aristovnik.me)**

## Features

Expand Down Expand Up @@ -41,18 +41,18 @@ kilm setup
kilm status
```

> **📚 [Complete Installation Guide](https://kilm.aristovnik.me/guides/installation/)** - Multiple installation methods, verification steps, and troubleshooting.
> **[Complete Installation Guide](https://kilm.aristovnik.me/guides/installation/)** - Multiple installation methods, verification steps, and troubleshooting.

## Documentation

**[📚 Complete Documentation](https://kilm.aristovnik.me)**
**[Complete Documentation](https://kilm.aristovnik.me)**

| Guide | Description |
|-------|-------------|
| [🚀 Getting Started](https://kilm.aristovnik.me/guides/getting-started/) | Creator and consumer workflows with Git integration |
| [⚙️ Configuration](https://kilm.aristovnik.me/guides/configuration/) | KiLM and KiCad configuration management |
| [📖 CLI Reference](https://kilm.aristovnik.me/reference/cli/) | Complete command documentation with examples |
| [🛠️ Development](https://kilm.aristovnik.me/community/development/) | Setup guide for contributors and development |
| [Getting Started](https://kilm.aristovnik.me/guides/getting-started/) | Creator and consumer workflows with Git integration |
| [Configuration](https://kilm.aristovnik.me/guides/configuration/) | KiLM and KiCad configuration management |
| [CLI Reference](https://kilm.aristovnik.me/reference/cli/) | Complete command documentation with examples |
| [Development](https://kilm.aristovnik.me/community/development/) | Setup guide for contributors and development |

## License

Expand All @@ -70,5 +70,5 @@ Contributions are welcome! See our comprehensive guides:
git clone https://github.com/barisgit/kilm.git
cd kilm
pip install -e ".[dev]"
pytest # Run tests
pytest # Run all tests
```
4 changes: 3 additions & 1 deletion kicad_lib_manager/commands/add_hook/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ def add_hook(directory, force):
else:
# Merge with existing content to preserve user logic
click.echo("Merging KiLM content with existing hook...")
new_content = merge_hook_content(existing_content, create_kilm_hook_content())
new_content = merge_hook_content(
existing_content, create_kilm_hook_content()
)

except (OSError, UnicodeDecodeError):
click.echo("Warning: Could not read existing hook content, overwriting...")
Expand Down
8 changes: 6 additions & 2 deletions kicad_lib_manager/commands/config/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,9 @@ def set_default(library_name, library_type):

# Set as current library
if library_path is None:
click.echo(f"Error: Could not find path for library '{library_name}'", err=True)
click.echo(
f"Error: Could not find path for library '{library_name}'", err=True
)
sys.exit(1)
config.set_current_library(library_path)
click.echo(f"Set {library_type} library '{library_name}' as default.")
Expand Down Expand Up @@ -350,7 +352,9 @@ def remove(library_name, library_type, force):
# Find libraries matching the name and type
matching_libraries = []
for lib in all_libraries:
if lib.get("name") == library_name and (library_type == "all" or lib.get("type") == library_type):
if lib.get("name") == library_name and (
library_type == "all" or lib.get("type") == library_type
):
matching_libraries.append(lib)

if not matching_libraries:
Expand Down
1 change: 0 additions & 1 deletion kicad_lib_manager/commands/setup/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,6 @@ def setup(

# Also list existing libraries to pin them all
try:

existing_symbols, existing_footprints = list_libraries(kicad_lib_dir)
symbol_libs = existing_symbols
footprint_libs = existing_footprints
Expand Down
15 changes: 11 additions & 4 deletions kicad_lib_manager/commands/status/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ def status():
config_data = yaml.safe_load(f)

# Show libraries
if config_data and "libraries" in config_data and config_data["libraries"]:
if (
config_data
and "libraries" in config_data
and config_data["libraries"]
):
click.echo(" Configured Libraries:")

# Group by type
Expand All @@ -48,7 +52,8 @@ def status():
path = lib.get("path", "unknown")
current = (
" (current)"
if config_data and config_data.get("current_library") == path
if config_data
and config_data.get("current_library") == path
else ""
)
click.echo(f" - {name}: {path}{current}")
Expand All @@ -67,7 +72,8 @@ def status():
path = lib.get("path", "unknown")
current = (
" (current)"
if config_data and config_data.get("current_library") == path
if config_data
and config_data.get("current_library") == path
else ""
)
click.echo(f" - {name}: {path}{current}")
Expand All @@ -83,7 +89,8 @@ def status():

# Show current library
if (
config_data and "current_library" in config_data
config_data
and "current_library" in config_data
and config_data["current_library"]
):
click.echo(
Expand Down
7 changes: 6 additions & 1 deletion kicad_lib_manager/commands/template/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -817,7 +817,12 @@ def make(
if not git_path.endswith("/"):
git_path += "/"

if gitignore_spec and gitignore_spec.match_file(git_path) or additional_spec and additional_spec.match_file(git_path):
if (
gitignore_spec
and gitignore_spec.match_file(git_path)
or additional_spec
and additional_spec.match_file(git_path)
):
dirs_to_remove.append(d)
excluded_files.append(f"{rel_path}/")

Expand Down
4 changes: 3 additions & 1 deletion kicad_lib_manager/commands/unpin/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def unpin(symbols, footprints, all, dry_run, max_backups, verbose):
"""Unpin libraries in KiCad"""
# Enforce mutual exclusivity of --all with --symbols/--footprints
if all and (symbols or footprints):
raise click.UsageError("'--all' cannot be used with '--symbols' or '--footprints'")
raise click.UsageError(
"'--all' cannot be used with '--symbols' or '--footprints'"
)

# Find KiCad configuration
try:
Expand Down
Loading