293 lines
7.8 KiB
Python
293 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
shadcn/ui Component Installer
|
|
|
|
Add shadcn/ui components to project with automatic dependency handling.
|
|
Wraps shadcn CLI for programmatic component installation.
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import List, Optional
|
|
|
|
|
|
class ShadcnInstaller:
|
|
"""Handle shadcn/ui component installation."""
|
|
|
|
def __init__(self, project_root: Optional[Path] = None, dry_run: bool = False):
|
|
"""
|
|
Initialize installer.
|
|
|
|
Args:
|
|
project_root: Project root directory (default: current directory)
|
|
dry_run: If True, show actions without executing
|
|
"""
|
|
self.project_root = project_root or Path.cwd()
|
|
self.dry_run = dry_run
|
|
self.components_json = self.project_root / "components.json"
|
|
|
|
def check_shadcn_config(self) -> bool:
|
|
"""
|
|
Check if shadcn is initialized in project.
|
|
|
|
Returns:
|
|
True if components.json exists
|
|
"""
|
|
return self.components_json.exists()
|
|
|
|
def get_installed_components(self) -> List[str]:
|
|
"""
|
|
Get list of already installed components.
|
|
|
|
Returns:
|
|
List of installed component names
|
|
"""
|
|
if not self.check_shadcn_config():
|
|
return []
|
|
|
|
try:
|
|
with open(self.components_json) as f:
|
|
config = json.load(f)
|
|
|
|
components_dir = self.project_root / config.get("aliases", {}).get(
|
|
"components", "components"
|
|
).replace("@/", "")
|
|
ui_dir = components_dir / "ui"
|
|
|
|
if not ui_dir.exists():
|
|
return []
|
|
|
|
return [f.stem for f in ui_dir.glob("*.tsx") if f.is_file()]
|
|
except (json.JSONDecodeError, KeyError, OSError):
|
|
return []
|
|
|
|
def add_components(
|
|
self, components: List[str], overwrite: bool = False
|
|
) -> tuple[bool, str]:
|
|
"""
|
|
Add shadcn/ui components.
|
|
|
|
Args:
|
|
components: List of component names to add
|
|
overwrite: If True, overwrite existing components
|
|
|
|
Returns:
|
|
Tuple of (success, message)
|
|
"""
|
|
if not components:
|
|
return False, "No components specified"
|
|
|
|
if not self.check_shadcn_config():
|
|
return (
|
|
False,
|
|
"shadcn not initialized. Run 'npx shadcn@latest init' first",
|
|
)
|
|
|
|
# Check which components already exist
|
|
installed = self.get_installed_components()
|
|
already_installed = [c for c in components if c in installed]
|
|
|
|
if already_installed and not overwrite:
|
|
return (
|
|
False,
|
|
f"Components already installed: {', '.join(already_installed)}. "
|
|
"Use --overwrite to reinstall",
|
|
)
|
|
|
|
# Build command
|
|
cmd = ["npx", "shadcn@latest", "add"] + components
|
|
|
|
if overwrite:
|
|
cmd.append("--overwrite")
|
|
|
|
if self.dry_run:
|
|
return True, f"Would run: {' '.join(cmd)}"
|
|
|
|
# Execute command
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
success_msg = f"Successfully added components: {', '.join(components)}"
|
|
if result.stdout:
|
|
success_msg += f"\n\nOutput:\n{result.stdout}"
|
|
|
|
return True, success_msg
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
error_msg = f"Failed to add components: {e.stderr or e.stdout or str(e)}"
|
|
return False, error_msg
|
|
except FileNotFoundError:
|
|
return False, "npx not found. Ensure Node.js is installed"
|
|
|
|
def add_all_components(self, overwrite: bool = False) -> tuple[bool, str]:
|
|
"""
|
|
Add all available shadcn/ui components.
|
|
|
|
Args:
|
|
overwrite: If True, overwrite existing components
|
|
|
|
Returns:
|
|
Tuple of (success, message)
|
|
"""
|
|
if not self.check_shadcn_config():
|
|
return (
|
|
False,
|
|
"shadcn not initialized. Run 'npx shadcn@latest init' first",
|
|
)
|
|
|
|
cmd = ["npx", "shadcn@latest", "add", "--all"]
|
|
|
|
if overwrite:
|
|
cmd.append("--overwrite")
|
|
|
|
if self.dry_run:
|
|
return True, f"Would run: {' '.join(cmd)}"
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
cmd,
|
|
cwd=self.project_root,
|
|
capture_output=True,
|
|
text=True,
|
|
check=True,
|
|
)
|
|
|
|
success_msg = "Successfully added all components"
|
|
if result.stdout:
|
|
success_msg += f"\n\nOutput:\n{result.stdout}"
|
|
|
|
return True, success_msg
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
error_msg = f"Failed to add all components: {e.stderr or e.stdout or str(e)}"
|
|
return False, error_msg
|
|
except FileNotFoundError:
|
|
return False, "npx not found. Ensure Node.js is installed"
|
|
|
|
def list_installed(self) -> tuple[bool, str]:
|
|
"""
|
|
List installed components.
|
|
|
|
Returns:
|
|
Tuple of (success, message with component list)
|
|
"""
|
|
if not self.check_shadcn_config():
|
|
return False, "shadcn not initialized"
|
|
|
|
installed = self.get_installed_components()
|
|
|
|
if not installed:
|
|
return True, "No components installed"
|
|
|
|
return True, f"Installed components:\n" + "\n".join(f" - {c}" for c in sorted(installed))
|
|
|
|
|
|
def main():
|
|
"""CLI entry point."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Add shadcn/ui components to your project",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Add single component
|
|
python shadcn_add.py button
|
|
|
|
# Add multiple components
|
|
python shadcn_add.py button card dialog
|
|
|
|
# Add all components
|
|
python shadcn_add.py --all
|
|
|
|
# Overwrite existing components
|
|
python shadcn_add.py button --overwrite
|
|
|
|
# Dry run (show what would be done)
|
|
python shadcn_add.py button card --dry-run
|
|
|
|
# List installed components
|
|
python shadcn_add.py --list
|
|
""",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"components",
|
|
nargs="*",
|
|
help="Component names to add (e.g., button, card, dialog)",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--all",
|
|
action="store_true",
|
|
help="Add all available components",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--overwrite",
|
|
action="store_true",
|
|
help="Overwrite existing components",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--dry-run",
|
|
action="store_true",
|
|
help="Show what would be done without executing",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--list",
|
|
action="store_true",
|
|
help="List installed components",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--project-root",
|
|
type=Path,
|
|
help="Project root directory (default: current directory)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Initialize installer
|
|
installer = ShadcnInstaller(
|
|
project_root=args.project_root,
|
|
dry_run=args.dry_run,
|
|
)
|
|
|
|
# Handle list command
|
|
if args.list:
|
|
success, message = installer.list_installed()
|
|
print(message)
|
|
sys.exit(0 if success else 1)
|
|
|
|
# Handle add all command
|
|
if args.all:
|
|
success, message = installer.add_all_components(overwrite=args.overwrite)
|
|
print(message)
|
|
sys.exit(0 if success else 1)
|
|
|
|
# Handle add specific components
|
|
if not args.components:
|
|
parser.print_help()
|
|
sys.exit(1)
|
|
|
|
success, message = installer.add_components(
|
|
args.components,
|
|
overwrite=args.overwrite,
|
|
)
|
|
|
|
print(message)
|
|
sys.exit(0 if success else 1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|