From be7a7e614bb8168e3252ae29947dfca8f0a8834b Mon Sep 17 00:00:00 2001
From: ruben-iteng <94007802+ruben-iteng@users.noreply.github.com>
Date: Thu, 23 Apr 2026 10:02:45 -0700
Subject: [PATCH] Replace outdated scripts with `ato package` cli
---
README.md | 16 +-
scripts/build-all.sh | 59 ------
scripts/build_all.py | 131 --------------
scripts/bump_versions.py | 111 ------------
scripts/check_status.py | 260 ---------------------------
scripts/discover-changed-packages.sh | 83 ---------
scripts/package_info.py | 232 ------------------------
scripts/review_modified_pcbs.py | 204 ---------------------
8 files changed, 9 insertions(+), 1087 deletions(-)
delete mode 100755 scripts/build-all.sh
delete mode 100644 scripts/build_all.py
delete mode 100644 scripts/bump_versions.py
delete mode 100644 scripts/check_status.py
delete mode 100755 scripts/discover-changed-packages.sh
delete mode 100644 scripts/package_info.py
delete mode 100755 scripts/review_modified_pcbs.py
diff --git a/README.md b/README.md
index 741521772..07d4ccbfd 100644
--- a/README.md
+++ b/README.md
@@ -62,14 +62,16 @@ Please ensure your PR:
### Building Packages
-To build all packages in the repository, use the `build-all.sh` script:
+Batch operations are provided by the `ato` CLI itself ā point it at the
+`packages/` directory:
```bash
-# Build all packages
-./build-all.sh
+# Build every package
+ato build packages/
-# Build all packages with specific flags (e.g., --frozen)
-./build-all.sh --frozen
-```
+# List packages with identifier/version
+ato package list packages/
-The script will attempt to build each package and provide a summary report of successes and failures.
+# Show latest build status per target
+ato package status packages/
+```
diff --git a/scripts/build-all.sh b/scripts/build-all.sh
deleted file mode 100755
index 33d0a3ac2..000000000
--- a/scripts/build-all.sh
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/bin/bash
-
-# Store the original directory
-ORIGINAL_DIR=$(pwd)
-
-# Arrays to store results
-declare -a successful_builds=()
-declare -a failed_builds=()
-
-# Function to handle errors
-handle_error() {
- echo "ā Error occurred in directory: $1"
- failed_builds+=("$1")
- cd "$ORIGINAL_DIR/packages"
- return 1
-}
-
-# Change to packages directory
-cd packages
-
-# Loop through all directories
-for dir in */; do
- if [ -d "$dir" ]; then
- echo "šØ Building package in $dir with arguments: $@"
- if (cd "$dir" && ato build "$@"); then
- echo "ā
Successfully built $dir"
- successful_builds+=("$dir")
- else
- handle_error "$dir"
- # Continue to next directory even if this one failed
- continue
- fi
- fi
-done
-
-# Return to original directory
-cd "$ORIGINAL_DIR"
-
-# Print summary report
-echo -e "\nš Build Summary Report"
-echo "====================="
-echo -e "\nā
Successful builds (${#successful_builds[@]}):"
-for build in "${successful_builds[@]}"; do
- echo " - $build"
-done
-
-echo -e "\nā Failed builds (${#failed_builds[@]}):"
-for build in "${failed_builds[@]}"; do
- echo " - $build"
-done
-
-echo -e "\nš Total packages: $((${#successful_builds[@]} + ${#failed_builds[@]}))"
-echo "ā
Successful: ${#successful_builds[@]}"
-echo "ā Failed: ${#failed_builds[@]}"
-
-# Exit with error if any builds failed
-if [ ${#failed_builds[@]} -gt 0 ]; then
- exit 1
-fi
diff --git a/scripts/build_all.py b/scripts/build_all.py
deleted file mode 100644
index 054bd5c43..000000000
--- a/scripts/build_all.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#! uv run
-# /// script
-# dependencies = [
-# "typer>=0.12",
-# "typing_extensions>=4.10.0",
-# "rich>=13.0.0",
-# ]
-# ///
-
-import re
-import typer
-import subprocess
-from concurrent.futures import ProcessPoolExecutor, as_completed
-from pathlib import Path
-from rich.console import Console
-from rich.table import Table
-from rich.progress import (
- Progress,
- SpinnerColumn,
- BarColumn,
- TextColumn,
- TimeElapsedColumn,
-)
-
-app = typer.Typer()
-
-console = Console()
-
-
-def build_package(package_dir: Path, args: tuple) -> tuple[str, bool]:
- """Builds a single package using 'ato build' and captures output."""
- package_name = package_dir.name
- args_str = " with args:" + (" ".join(args)) if args else ""
- console.print(f"[yellow]šØ Building package in {package_name}{args_str}[/yellow]")
- try:
- # Convert args tuple to a list for subprocess
- process = subprocess.run(
- ["ato", "build"] + list(args),
- cwd=package_dir,
- capture_output=True,
- text=True,
- check=True,
- )
- console.print(f"[green]ā
Successfully built {package_name}[/green]")
- # console.print(f"Output:\n{process.stdout}") # Optional: print stdout
- return package_name, True
- except subprocess.CalledProcessError as e:
- console.print(f"[red]ā Error occurred in directory: {package_name}[/red]")
- # console.print(f"Stderr:\n{e.stderr}")
- return package_name, False
-
-
-@app.command()
-def main(
- args: list[str] = typer.Argument(None, help="Arguments to pass to ato build"),
- package_regex: str = typer.Option(None, help="Regex to filter packages to build"),
-):
- """Builds all packages in the 'packages' directory in parallel."""
- original_dir = Path.cwd()
- packages_dir = Path("packages")
-
- if not packages_dir.is_dir():
- console.print(
- f"[red]ā Error: 'packages' directory not found in {original_dir}[/red]"
- )
- return
-
- package_subdirs = [d for d in packages_dir.iterdir() if d.is_dir()]
-
- if not package_subdirs:
- console.print(f"[yellow]No packages found in {packages_dir}[/yellow]")
- return
-
- if package_regex:
- package_subdirs = [
- d for d in package_subdirs if re.match(package_regex, d.name)
- ]
-
- build_args = tuple(args) if args else ()
-
- successful_builds = []
- failed_builds = []
-
- with Progress(
- SpinnerColumn(),
- TextColumn("[progress.description]{task.description}"),
- BarColumn(),
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
- TimeElapsedColumn(),
- console=console,
- ) as progress:
- task = progress.add_task(
- "[cyan]Building packages...[/cyan]", total=len(package_subdirs)
- )
- with ProcessPoolExecutor() as executor:
- futures = {
- executor.submit(build_package, subdir, build_args): subdir
- for subdir in package_subdirs
- }
- for future in as_completed(futures):
- package_name, success = future.result()
- if success:
- successful_builds.append(package_name)
- else:
- failed_builds.append(package_name)
- progress.update(task, advance=1)
-
- # Print summary report
- console.print("\n[bold blue]š Build Summary Report[/bold blue]")
- table = Table(show_header=True, header_style="bold magenta")
- table.add_column("Status", style="dim", width=12)
- table.add_column("Package Name")
-
- console.print(f"\n[green]ā
Successful builds ({len(successful_builds)}):[/green]")
- for build in sorted(successful_builds):
- console.print(f" - {build}")
-
- console.print(f"\n[red]ā Failed builds ({len(failed_builds)}):[/red]")
- for build in sorted(failed_builds):
- console.print(f" - {build}")
-
- console.print(f"\n[bold]š Total packages: {len(package_subdirs)}[/bold]")
- console.print(f"[green]ā
Successful: {len(successful_builds)}[/green]")
- console.print(f"[red]ā Failed: {len(failed_builds)}[/red]")
-
- if failed_builds:
- raise typer.Exit(code=1)
-
-
-if __name__ == "__main__":
- app()
diff --git a/scripts/bump_versions.py b/scripts/bump_versions.py
deleted file mode 100644
index 8377a8bcd..000000000
--- a/scripts/bump_versions.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# /// script
-# dependencies = [
-# "typer>=0.12",
-# "pyyaml>=6.0.2",
-# "typing_extensions>=4.10.0",
-# ]
-# ///
-
-
-# INSTRUCTIONS:
-# - this shall be run with uv with embedded depencies so we need no venv etc
-# - use typer for the cli
-# - argumment: major, minor, patch (which one to bump)
-# - optional regex for the package name
-# - look for all ato.yaml files
-# - use yaml to read the version, and bump the appropriate field
-# - save yaml again (without changing formatting or anything else in the file)
-# - (there is a library for that kind of yaml operation: ruamel.yaml)
-
-import typer
-from typing_extensions import Annotated
-from pathlib import Path
-import re
-import yaml
-
-app = typer.Typer()
-
-
-@app.command()
-def main(
- version_part: Annotated[
- str,
- typer.Argument(
- help="Which part of the version to bump: major, minor, or patch."
- ),
- ],
- package_regex: Annotated[
- str, typer.Option(help="Optional regex to filter package names.")
- ] = ".*",
- dry_run: Annotated[
- bool, typer.Option(help="Dry run, don't actually change anything.")
- ] = False,
-):
- """
- Bumps the version in ato.yaml files.
- """
- if version_part not in ["major", "minor", "patch"]:
- print(
- f"Error: version_part must be one of 'major', 'minor', or 'patch'. Got '{version_part}'."
- )
- raise typer.Exit(code=1)
-
- print(
- f"Bumping {version_part} version for packages matching regex: '{package_regex}'"
- )
-
- current_path = Path(".")
- for ato_file in current_path.rglob("**/ato.yaml"):
- try:
- ato_yaml = ato_file.read_text()
- data = yaml.load(ato_yaml, Loader=yaml.FullLoader)
-
- package = data.get("package")
- if not package:
- print(f"Skipping {ato_file}, no package found.")
- continue
-
- package_name = package.get("identifier", "").split("/")[-1]
- if not package_name:
- print(f"Skipping {ato_file}, no package name found.")
- continue
-
- if not re.match(package_regex, package_name):
- print(
- f"Skipping {ato_file} (name: {package_name}) due to regex mismatch or missing name."
- )
- continue
-
- version_str = package.get("version")
- if not version_str:
- print(f"Skipping {ato_file}, no version found.")
- continue
-
- major, minor, patch_val = map(int, version_str.split("."))
-
- if version_part == "major":
- major += 1
- minor = 0
- patch_val = 0
- elif version_part == "minor":
- minor += 1
- patch_val = 0
- elif version_part == "patch":
- patch_val += 1
-
- new_version_str = f"{major}.{minor}.{patch_val}"
- package["version"] = new_version_str
-
- # A bit ghetto, but doesn't fuck with the formatting
- ato_yaml = ato_yaml.replace(f'"{version_str}"', f'"{new_version_str}"')
- if not dry_run:
- ato_file.write_text(ato_yaml)
-
- print(f"ā
{version_str} -> {new_version_str} | {package_name} ")
-
- except Exception as e:
- print(f"Error processing {ato_file}: {e}")
-
-
-if __name__ == "__main__":
- app()
diff --git a/scripts/check_status.py b/scripts/check_status.py
deleted file mode 100644
index e6ad71298..000000000
--- a/scripts/check_status.py
+++ /dev/null
@@ -1,260 +0,0 @@
-#! uv run
-# /// script
-# dependencies = [
-# "typer>=0.12",
-# "typing_extensions>=4.10.0",
-# "rich>=13.0.0",
-# "pandas>=2.0.0",
-# ]
-# ///
-
-import re
-import time
-import subprocess
-from concurrent.futures import ProcessPoolExecutor, as_completed
-from pathlib import Path
-import typer
-from rich.console import Console, Group
-from rich.table import Table
-from rich.live import Live
-
-app = typer.Typer()
-
-console = Console()
-
-
-def build_and_verify(
- package_dir: Path, args: tuple
-) -> tuple[
- str,
- bool,
- bool,
- float,
- int,
- str,
- str,
- int | None,
- str | None,
- str | None,
-]:
- """
- Runs 'ato build --keep-picked-parts' then 'ato package verify' for a package.
- Returns: (package_name, build_success, verify_success, build_seconds)
- """
- package_name = package_dir.name
-
- # Build
- build_start = time.perf_counter()
- build_proc = subprocess.run(
- ["ato", "build", "--keep-picked-parts"] + list(args),
- cwd=package_dir,
- capture_output=True,
- text=True,
- check=False,
- )
- build_success = build_proc.returncode == 0
- build_seconds = max(0.0, time.perf_counter() - build_start)
-
- # Verify (only if build ok)
- verify_success = False
- verify_rc: int | None = None
- verify_stdout: str | None = None
- verify_stderr: str | None = None
- if build_success:
- verify_proc = subprocess.run(
- ["ato", "package", "verify"],
- cwd=package_dir,
- capture_output=True,
- text=True,
- check=False,
- )
- verify_rc = verify_proc.returncode
- verify_stdout = verify_proc.stdout
- verify_stderr = verify_proc.stderr
- verify_success = verify_rc == 0
-
- return (
- package_name,
- build_success,
- verify_success,
- build_seconds,
- build_proc.returncode,
- build_proc.stdout,
- build_proc.stderr,
- verify_rc,
- verify_stdout,
- verify_stderr,
- )
-
-
-@app.command()
-def main(
- args: list[str] = typer.Argument(None, help="Arguments to pass to ato build"),
- package_regex: str = typer.Option(None, help="Regex to filter packages to build"),
-):
- """Builds and verifies all packages in the 'packages' directory in parallel."""
- original_dir = Path.cwd()
- packages_dir = Path("packages")
-
- if not packages_dir.is_dir():
- console.print(
- f"[red]ā Error: 'packages' directory not found in {original_dir}[/red]"
- )
- return
-
- package_subdirs = [d for d in packages_dir.iterdir() if d.is_dir()]
-
- if not package_subdirs:
- console.print(f"[yellow]No packages found in {packages_dir}[/yellow]")
- return
-
- if package_regex:
- package_subdirs = [
- d for d in package_subdirs if re.match(package_regex, d.name)
- ]
-
- build_args = tuple(args) if args else ()
-
- # Accumulator for results and DataFrame
- results_rows: list[dict] = []
-
- def make_summary_tables() -> Group:
- # Totals
- build_fail = sum(1 for r in results_rows if r["build_success"] is False)
- verify_fail = sum(
- 1 for r in results_rows if r["build_success"] and not r["verify_success"]
- )
- pass_both = sum(
- 1 for r in results_rows if r["build_success"] and r["verify_success"]
- )
-
- # Top summary table with counts in headers
- summary = Table(show_header=True, header_style="bold magenta")
- summary.add_column(f"Fails Build ({build_fail})", justify="center")
- summary.add_column(f"Fails Verify ({verify_fail})", justify="center")
- summary.add_column(f"Passes Both ({pass_both})", justify="center")
- summary.add_row(
- f"[red]{build_fail}[/red]",
- f"[yellow]{verify_fail}[/yellow]",
- f"[green]{pass_both}[/green]",
- )
-
- # Detailed per-package table
- detail = Table(show_header=True, header_style="bold cyan")
- detail.add_column("Package", overflow="fold")
- detail.add_column("Build", justify="center")
- detail.add_column("Verify", justify="center")
- detail.add_column("Build Time (s)", justify="right")
-
- # Sort by build time (descending: slowest first)
- for r in sorted(results_rows, key=lambda x: x["build_seconds"], reverse=True):
- build_cell = (
- "[green]PASS[/green]" if r["build_success"] else "[red]FAIL[/red]"
- )
- if not r["build_success"]:
- verify_cell = "[dim]-[/dim]"
- else:
- verify_cell = (
- "[green]PASS[/green]" if r["verify_success"] else "[red]FAIL[/red]"
- )
- detail.add_row(
- r["package_name"],
- build_cell,
- verify_cell,
- f"{r['build_seconds']:.1f}",
- )
-
- return Group(summary, detail)
-
- with Live(console=console, refresh_per_second=8) as live:
- with ProcessPoolExecutor() as executor:
- futures = {
- executor.submit(build_and_verify, subdir, build_args): subdir
- for subdir in package_subdirs
- }
- for future in as_completed(futures):
- (
- package_name,
- build_ok,
- verify_ok,
- build_secs,
- build_rc,
- build_out,
- build_err,
- verify_rc,
- verify_out,
- verify_err,
- ) = future.result()
- results_rows.append(
- {
- "package_name": package_name,
- "build_success": build_ok,
- "verify_success": verify_ok,
- "build_seconds": build_secs,
- "build_rc": build_rc,
- "build_stdout": build_out,
- "build_stderr": build_err,
- "verify_rc": verify_rc,
- "verify_stdout": verify_out,
- "verify_stderr": verify_err,
- }
- )
- # Update live tables
- live.update(make_summary_tables())
-
- # Construct DataFrame (optional) and save to CSV if pandas is available
- try:
- pd = __import__("pandas")
- df = pd.DataFrame(
- results_rows,
- columns=[
- "package_name",
- "build_success",
- "verify_success",
- "build_seconds",
- ],
- )
- out_csv = Path("build_status.csv")
- df.to_csv(out_csv, index=False)
- console.print(f"[dim]Saved build status to {out_csv.resolve()}[/dim]")
- except Exception:
- console.print(
- "[yellow]pandas not available; skipping DataFrame export[/yellow]"
- )
-
- # Exit with non-zero if any failed build or failed verify; print detailed logs
- any_failed = any(
- (not r["build_success"]) or (r["build_success"] and not r["verify_success"])
- for r in results_rows
- )
- if any_failed:
- console.rule("[bold red]Failure Details")
- for r in sorted(results_rows, key=lambda x: x["package_name"].lower()):
- if not r["build_success"]:
- console.print(
- f"[red]ā {r['package_name']} ā Build failed (rc={r.get('build_rc')})[/red]"
- )
- if r.get("build_stderr"):
- console.print("[bold]stderr:[/bold]")
- console.print(r["build_stderr"])
- if r.get("build_stdout"):
- console.print("[bold]stdout:[/bold]")
- console.print(r["build_stdout"])
- console.print()
- continue
- if r["build_success"] and not r["verify_success"]:
- console.print(
- f"[yellow]ā ļø {r['package_name']} ā Verify failed (rc={r.get('verify_rc')})[/yellow]"
- )
- if r.get("verify_stderr"):
- console.print("[bold]stderr:[/bold]")
- console.print(r["verify_stderr"])
- if r.get("verify_stdout"):
- console.print("[bold]stdout:[/bold]")
- console.print(r["verify_stdout"])
- console.print()
- raise typer.Exit(code=1)
-
-
-if __name__ == "__main__":
- app()
diff --git a/scripts/discover-changed-packages.sh b/scripts/discover-changed-packages.sh
deleted file mode 100755
index bea0ec767..000000000
--- a/scripts/discover-changed-packages.sh
+++ /dev/null
@@ -1,83 +0,0 @@
-#!/usr/bin/env bash
-
-set -euo pipefail
-
-find_all_packages() {
- find packages -mindepth 2 -maxdepth 2 -name "ato.yaml" -type f | \
- sed 's|/ato.yaml||' | \
- sort
-}
-
-find_changed_packages() {
- local base_ref="$1"
- local changed_files=$(
- git diff \
- --name-only \
- "$base_ref"...HEAD \
- -- \
- ':/packages/**' \
- ':(exclude)packages/archive/**' \
- 2>/dev/null || echo ""
- )
-
- if [[ -z "$changed_files" ]]; then
- return
- fi
-
- echo "$changed_files" | \
- grep -E '^packages/[^/]+/' | \
- cut -d'/' -f1,2 | \
- sort -u | \
- while read -r package_dir; do
- if [[ -d "$package_dir" ]]; then
- echo "$package_dir"
- fi
- done
-}
-
-to_json_array() {
- local input="$1"
-
- if [[ -z "$input" ]]; then
- echo "[]"
- else
- jq --raw-input --slurp --compact-output 'split("\n") | map(select(length > 0))' <<< "$input"
- fi
-}
-
-main() {
- local event_name="${1:-}"
- local base_ref="${2:-}"
- local packages_json=""
-
- case "$event_name" in
- "pull_request"|"push")
- if [[ -z "$base_ref" ]]; then
- echo "Error: base_ref is required for $event_name events" >&2
- exit 1
- fi
-
- if [[ "$base_ref" =~ ^0+$ ]]; then
- echo "Initial commit detected, building all packages" >&2
- packages=$(find_all_packages)
- packages_json=$(to_json_array "$packages")
- else
- changed_packages=$(find_changed_packages "$base_ref")
-
- packages_json=$(to_json_array "$changed_packages")
- fi
- ;;
-
- "workflow_dispatch"|*)
- echo "Building all packages (event: ${event_name:-unknown})" >&2
- packages=$(find_all_packages)
- packages_json=$(to_json_array "$packages")
- ;;
- esac
-
- echo "$packages_json"
-
- echo "Discovered packages: $packages_json" >&2
-}
-
-main "$@"
diff --git a/scripts/package_info.py b/scripts/package_info.py
deleted file mode 100644
index fa03fb0d2..000000000
--- a/scripts/package_info.py
+++ /dev/null
@@ -1,232 +0,0 @@
-#! uv run
-# /// script
-# dependencies = [
-# "typer>=0.12",
-# "typing_extensions>=4.10.0",
-# "rich>=13.0.0",
-# "pyyaml>=6.0.2",
-# ]
-# ///
-
-from enum import StrEnum
-
-import typer
-from typing_extensions import Annotated
-from typing import Optional
-from pathlib import Path
-import re
-import yaml
-import subprocess
-from datetime import datetime
-from rich.console import Console
-from rich.table import Table
-
-app = typer.Typer()
-console = Console()
-
-
-class SortBy(StrEnum):
- package = "package"
- version = "version"
- requires_atopile = "requires-atopile"
- last_commit = "last-commit"
- dependencies = "dependencies"
-
-
-def get_last_commit_date(package_path: Path) -> str:
- """Get the last commit date for a package directory."""
- try:
- result = subprocess.run(
- [
- "git",
- "log",
- "--pretty=format:%ad",
- "--date=iso",
- "--",
- str(package_path),
- ],
- capture_output=True,
- text=True,
- cwd=package_path.parent.parent, # Go up to packages root
- check=True,
- )
- if result.stdout.strip():
- # Parse the ISO date and format it nicely
- date_str = result.stdout.strip().split("\n")[0]
- dt = datetime.fromisoformat(date_str.replace(" +", "+").replace(" -", "-"))
- return dt.strftime("%Y-%m-%d %H:%M:%S")
- return "Unknown"
- except subprocess.CalledProcessError:
- return "Unknown"
-
-
-def format_dependencies_markdown(deps: str) -> str:
- """Format dependencies for markdown (replace newlines with
)"""
- if deps == "":
- return ""
- return deps.replace("\n", "
")
-
-
-def extract_dependencies(package_dir: Path, package_name: str) -> str:
- """Extract dependencies from .ato files in the package."""
- dependencies = set()
-
- # Look for .ato files in the package directory
- for ato_file in package_dir.glob("*.ato"):
- try:
- with open(ato_file, "r", encoding="utf-8") as f:
- content = f.read()
-
- # Find "from" imports that reference atopile packages
- from_imports = re.findall(r'from\s+"(atopile/[^"]+)"', content)
- for imp in from_imports:
- # Extract package name from path like "atopile/package-name/file.ato"
- dep_package_name = imp.split("/")[1]
- # Exclude the package itself from dependencies
- if dep_package_name != package_name:
- dependencies.add(dep_package_name)
-
- except Exception:
- continue
-
- if dependencies:
- return "\n".join(sorted(dependencies))
- return ""
-
-
-@app.command()
-def main(
- package_regex: Annotated[
- str,
- typer.Option("--package-regex", "-p", help="Regex to filter packages list"),
- ] = ".*",
- markdown_output: Annotated[
- Optional[Path],
- typer.Option("--markdown-output", "-m", help="Path to export markdown file"),
- ] = None,
- sort_by: Annotated[
- SortBy,
- typer.Option("--sort-by", "-s", help="Column to sort by"),
- ] = SortBy.package,
- reverse: Annotated[
- bool,
- typer.Option("--reverse", "-r", help="Reverse sort order"),
- ] = False,
-):
- """
- Prints a formatted table of packages with:
- - package name
- - version
- - requires-atopile version
- - date and time of last commit to the package
- - list of dependencies (other packages)
- """
- packages_root = Path(__file__).parent.parent / "packages"
-
- if not packages_root.exists():
- typer.echo("Error: packages directory not found", err=True)
- raise typer.Exit(1)
-
- # Collect package information
- packages_info = []
-
- for package_dir in sorted(packages_root.iterdir()):
- if not package_dir.is_dir() or package_dir.name.startswith("."):
- continue
-
- # Check if package name matches regex
- if not re.search(package_regex, package_dir.name):
- continue
-
- ato_yaml_path = package_dir / "ato.yaml"
- if not ato_yaml_path.exists():
- continue
-
- try:
- with open(ato_yaml_path, "r", encoding="utf-8") as f:
- config = yaml.safe_load(f)
-
- package_info = config.get("package", {})
- package_name = package_info.get("identifier", package_dir.name)
- if package_name.startswith("atopile/"):
- package_name = package_name[8:] # Remove 'atopile/' prefix
-
- version = package_info.get("version", "Unknown")
- requires_atopile = config.get("requires-atopile", "Unknown")
- last_commit = get_last_commit_date(package_dir)
- dependencies = extract_dependencies(package_dir, package_name)
-
- packages_info.append(
- {
- "name": package_name,
- "version": version,
- "requires_atopile": requires_atopile,
- "last_commit": last_commit,
- "dependencies": dependencies,
- }
- )
-
- except Exception as e:
- typer.echo(f"Error processing {package_dir.name}: {e}", err=True)
- continue
-
- if not packages_info:
- typer.echo("No packages found matching the criteria.")
- return
-
- # Sort packages
- sort_keys = {
- SortBy.package: lambda p: p["name"].lower(),
- SortBy.version: lambda p: (p["version"], p["name"].lower()),
- SortBy.requires_atopile: lambda p: (p["requires_atopile"], p["name"].lower()),
- SortBy.last_commit: lambda p: (p["last_commit"], p["name"].lower()),
- SortBy.dependencies: lambda p: (
- len(p["dependencies"].split("\n")) if p["dependencies"] else 0,
- p["name"].lower(),
- ),
- }
- packages_info.sort(key=sort_keys[sort_by], reverse=reverse)
-
- # Create Rich table
- table = Table(title=f"Package Information ({len(packages_info)} packages)")
-
- table.add_column("Package", style="cyan", no_wrap=True)
- table.add_column("Version", style="magenta")
- table.add_column("Requires Atopile", style="green")
- table.add_column("Last Commit", style="yellow")
- table.add_column("Dependencies", style="blue", no_wrap=True)
-
- for pkg in packages_info:
- table.add_row(
- pkg["name"],
- pkg["version"],
- pkg["requires_atopile"],
- pkg["last_commit"],
- pkg["dependencies"],
- )
-
- # Export to markdown if requested
- if markdown_output:
- with open(markdown_output, "w", encoding="utf-8") as f:
- f.write(f"# Package Information ({len(packages_info)} packages)\n\n")
- f.write(
- "| Package | Version | Requires Atopile | Last Commit | Dependencies |\n"
- )
- f.write(
- "|---------|---------|------------------|-------------|--------------|\n"
- )
-
- for pkg in packages_info:
- deps_md = format_dependencies_markdown(pkg["dependencies"])
- f.write(
- f"| {pkg['name']} | {pkg['version']} | {pkg['requires_atopile']} | {pkg['last_commit']} | {deps_md} |\n"
- )
-
- typer.echo(f"Exported package information to {markdown_output}")
-
- # Always print to console
- console.print(table)
-
-
-if __name__ == "__main__":
- app()
diff --git a/scripts/review_modified_pcbs.py b/scripts/review_modified_pcbs.py
deleted file mode 100755
index 972af61de..000000000
--- a/scripts/review_modified_pcbs.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#! uv run
-# /// script
-# dependencies = [
-# "typer>=0.12",
-# "rich>=13.0.0",
-# ]
-# ///
-
-import subprocess
-import sys
-import os
-from pathlib import Path
-import typer
-from rich.console import Console
-from rich.prompt import Confirm
-from rich.table import Table
-from rich.panel import Panel
-
-app = typer.Typer()
-console = Console()
-
-
-def get_modified_pcb_files(base_branch: str = "origin/main") -> list[Path]:
- """Get all modified .kicad_pcb files compared to the base branch."""
- try:
- # Get all modified files
- result = subprocess.run(
- ["git", "diff", "--name-only", f"{base_branch}...HEAD"],
- capture_output=True,
- text=True,
- check=True,
- )
-
- # Filter for .kicad_pcb files
- all_files = result.stdout.strip().split("\n")
- pcb_files = [
- Path(f) for f in all_files if f.endswith(".kicad_pcb") and f.strip()
- ]
-
- # Filter to only existing files (in case some were deleted)
- existing_pcb_files = [f for f in pcb_files if f.exists()]
-
- return existing_pcb_files
-
- except subprocess.CalledProcessError as e:
- console.print(f"[red]Error getting git diff: {e}[/red]")
- return []
-
-
-def open_pcb_file(file_path: Path) -> bool:
- """Open a PCB file with the default application."""
- try:
- if sys.platform == "darwin": # macOS
- subprocess.run(["open", str(file_path)], check=True)
- elif sys.platform == "linux":
- subprocess.run(["xdg-open", str(file_path)], check=True)
- elif sys.platform == "win32":
- os.startfile(str(file_path))
- else:
- console.print(f"[yellow]Unsupported platform: {sys.platform}[/yellow]")
- console.print(f"Please manually open: {file_path}")
- return False
- return True
- except subprocess.CalledProcessError as e:
- console.print(f"[red]Error opening file {file_path}: {e}[/red]")
- return False
- except Exception as e:
- console.print(f"[red]Unexpected error opening file {file_path}: {e}[/red]")
- return False
-
-
-@app.command()
-def main(
- base_branch: str = typer.Option(
- "origin/main", "--base", "-b", help="Base branch to compare against"
- ),
- auto_open: bool = typer.Option(
- False,
- "--auto-open",
- "-a",
- help="Automatically open all files without prompting",
- ),
-):
- """
- Find modified .kicad_pcb files in the current branch and interactively open them for review.
-
- This script will:
- 1. Find all .kicad_pcb files modified compared to the base branch
- 2. Display them in a table
- 3. Allow you to open each one by pressing Enter (or skip with any other key)
- """
-
- # Check if we're in a git repository
- try:
- subprocess.run(
- ["git", "rev-parse", "--git-dir"], capture_output=True, check=True
- )
- except subprocess.CalledProcessError:
- console.print("[red]ā Error: Not in a git repository[/red]")
- raise typer.Exit(1)
-
- console.print(
- f"[bold blue]š Finding modified .kicad_pcb files compared to {base_branch}...[/bold blue]"
- )
-
- pcb_files = get_modified_pcb_files(base_branch)
-
- if not pcb_files:
- console.print("[yellow]No modified .kicad_pcb files found.[/yellow]")
- return
-
- # Display summary table
- table = Table(
- title="Modified PCB Files", show_header=True, header_style="bold magenta"
- )
- table.add_column("File", style="cyan", overflow="fold")
- table.add_column("Package", style="green")
- table.add_column("Size", justify="right", style="dim")
-
- for pcb_file in pcb_files:
- # Extract package name (assuming structure: packages/package-name/...)
- parts = pcb_file.parts
- package_name = "unknown"
- if len(parts) >= 2 and parts[0] == "packages":
- package_name = parts[1]
-
- # Get file size
- try:
- size = pcb_file.stat().st_size
- size_str = f"{size:,} bytes"
- except:
- size_str = "unknown"
-
- table.add_row(str(pcb_file), package_name, size_str)
-
- console.print(table)
- console.print()
-
- if auto_open:
- console.print("[bold yellow]Auto-opening all files...[/bold yellow]")
- for pcb_file in pcb_files:
- console.print(f"Opening: [cyan]{pcb_file}[/cyan]")
- open_pcb_file(pcb_file)
- return
-
- # Interactive review
- console.print(
- Panel.fit(
- "[bold]Interactive Review Mode[/bold]\n\n"
- "For each file:\n"
- "⢠Press [green]Enter[/green] to open the file\n"
- "⢠Press [red]any other key[/red] to skip\n"
- "⢠Press [yellow]Ctrl+C[/yellow] to exit",
- border_style="blue",
- )
- )
- console.print()
-
- opened_count = 0
- skipped_count = 0
-
- try:
- for i, pcb_file in enumerate(pcb_files, 1):
- console.print(
- f"[bold]File {i}/{len(pcb_files)}:[/bold] [cyan]{pcb_file}[/cyan]"
- )
-
- try:
- # Wait for user input
- user_input = input("Press Enter to open (any other key to skip): ")
-
- if user_input == "": # Enter was pressed
- console.print(f"[green]Opening {pcb_file}...[/green]")
- if open_pcb_file(pcb_file):
- opened_count += 1
- else:
- console.print("[red]Failed to open file[/red]")
- else:
- console.print("[yellow]Skipped[/yellow]")
- skipped_count += 1
-
- except KeyboardInterrupt:
- console.print("\n[yellow]Interrupted by user[/yellow]")
- break
-
- console.print()
-
- except KeyboardInterrupt:
- console.print("\n[yellow]Interrupted by user[/yellow]")
-
- # Summary
- console.print(
- Panel.fit(
- f"[bold]Review Summary[/bold]\n\n"
- f"Files found: {len(pcb_files)}\n"
- f"Files opened: [green]{opened_count}[/green]\n"
- f"Files skipped: [yellow]{skipped_count}[/yellow]",
- border_style="green",
- )
- )
-
-
-if __name__ == "__main__":
- app()