Implements jeaddons CLI tool
Adds a CLI tool to copy files or folders from a git repository into the current project based on a registry. The tool provides commands to: - `add`: Copies files/folders based on registry entries. - `list`: Lists available registry entries. Registry entries are defined in `jeAddons/registry.py`. The tool supports overriding the registry's ref and destination.
This commit is contained in:
+57
-11
@@ -1,23 +1,69 @@
|
||||
import typer
|
||||
from pathlib import Path
|
||||
from jeAddons.registry import load_registry
|
||||
from jeAddons.copier import fetch_file
|
||||
from jeAddons.copier import fetch_path
|
||||
from jeAddons.registry import load_registry, get_registry_entry
|
||||
|
||||
app = typer.Typer()
|
||||
app = typer.Typer(no_args_is_help=True)
|
||||
|
||||
|
||||
@app.callback()
|
||||
def main():
|
||||
"""Copy files or folders from a git repository into your current project."""
|
||||
return
|
||||
|
||||
|
||||
@app.command()
|
||||
def add(name: str):
|
||||
registry = load_registry()
|
||||
|
||||
if name not in registry:
|
||||
typer.echo(f"❌ Unknown entry: {name}")
|
||||
def add(
|
||||
name: str = typer.Argument(..., help="Registry name from jeAddons/registry.py."),
|
||||
ref: str | None = typer.Option(None, "--ref", "-r", help="Override registry git ref."),
|
||||
dest: Path | None = typer.Option(None, "--dest", "-d", help="Override registry destination directory."),
|
||||
force: bool = typer.Option(False, "--force", "-f", help="Overwrite existing file/folder with same name."),
|
||||
):
|
||||
try:
|
||||
entry = get_registry_entry(name)
|
||||
except KeyError:
|
||||
typer.echo(f"Unknown registry entry: '{name}'.")
|
||||
typer.echo("Run `jeaddons list` to show available entries.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
entry = registry[name]
|
||||
fetch_file(entry["repo"], entry["file"], Path.cwd())
|
||||
repo = entry.get("repo")
|
||||
repo_path = entry.get("path")
|
||||
if not repo or not repo_path:
|
||||
typer.echo(f"Registry entry '{name}' is missing required keys: 'repo' and/or 'path'.")
|
||||
raise typer.Exit(1)
|
||||
|
||||
typer.echo(f"✅ Added {entry['file']}")
|
||||
effective_ref = ref or entry.get("ref", "HEAD")
|
||||
effective_dest = dest or Path.cwd() / entry.get("dest", ".")
|
||||
|
||||
try:
|
||||
target = fetch_path(repo, repo_path, effective_dest, ref=effective_ref, force=force)
|
||||
except FileNotFoundError:
|
||||
typer.echo(f"Path '{repo_path}' was not found in repository '{repo}'.")
|
||||
raise typer.Exit(1)
|
||||
except FileExistsError as exc:
|
||||
typer.echo(f"Target already exists: {exc}. Use --force to overwrite.")
|
||||
raise typer.Exit(1)
|
||||
except Exception as exc:
|
||||
typer.echo(f"Failed to copy from repository: {exc}")
|
||||
raise typer.Exit(1)
|
||||
|
||||
typer.echo(f"Added: {target}")
|
||||
|
||||
|
||||
@app.command("list")
|
||||
def list_entries():
|
||||
"""List available registry entries."""
|
||||
registry = load_registry()
|
||||
if not registry:
|
||||
typer.echo("Registry is empty. Add entries in jeAddons/registry.py")
|
||||
return
|
||||
|
||||
for name, entry in registry.items():
|
||||
repo = entry.get("repo", "<missing repo>")
|
||||
repo_path = entry.get("path", "<missing path>")
|
||||
ref = entry.get("ref", "HEAD")
|
||||
dest = entry.get("dest", ".")
|
||||
typer.echo(f"{name}: repo={repo} path={repo_path} ref={ref} dest={dest}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+26
-7
@@ -4,13 +4,32 @@ import shutil
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def fetch_file(repo_url: str, file_path: str, dest: Path):
|
||||
tmp = tempfile.mkdtemp()
|
||||
def fetch_path(repo_url: str, repo_path: str, dest: Path, ref: str = "HEAD", force: bool = False):
|
||||
with tempfile.TemporaryDirectory(prefix="jeaddons-") as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
subprocess.check_call(
|
||||
["git", "clone", "--filter=blob:none", "--no-checkout", "--depth", "1", repo_url, str(tmp_path)]
|
||||
)
|
||||
subprocess.check_call(["git", "-C", str(tmp_path), "sparse-checkout", "init", "--cone"])
|
||||
subprocess.check_call(["git", "-C", str(tmp_path), "sparse-checkout", "set", repo_path])
|
||||
subprocess.check_call(["git", "-C", str(tmp_path), "checkout", ref])
|
||||
|
||||
subprocess.check_call(["git", "clone", "--depth", "1", repo_url, tmp])
|
||||
src = tmp_path / repo_path
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(repo_path)
|
||||
|
||||
src = Path(tmp) / file_path
|
||||
if not src.exists():
|
||||
raise FileNotFoundError(file_path)
|
||||
target = dest / src.name
|
||||
if target.exists():
|
||||
if not force:
|
||||
raise FileExistsError(target)
|
||||
if target.is_dir():
|
||||
shutil.rmtree(target)
|
||||
else:
|
||||
target.unlink()
|
||||
|
||||
shutil.copy2(src, dest / src.name)
|
||||
if src.is_dir():
|
||||
shutil.copytree(src, target)
|
||||
return target
|
||||
|
||||
shutil.copy2(src, target)
|
||||
return target
|
||||
|
||||
+35
-7
@@ -1,9 +1,37 @@
|
||||
import json
|
||||
import urllib.request
|
||||
|
||||
REGISTRY_URL = "https://raw.githubusercontent.com/acme/registry/main/registry.json"
|
||||
from typing import TypedDict
|
||||
|
||||
|
||||
def load_registry():
|
||||
with urllib.request.urlopen(REGISTRY_URL) as r:
|
||||
return json.load(r)
|
||||
class RegistryEntry(TypedDict, total=False):
|
||||
repo: str
|
||||
path: str
|
||||
ref: str
|
||||
dest: str
|
||||
|
||||
|
||||
# Manage your repositories, files, and folders here.
|
||||
# Key = addon name you pass to `jeaddons add <name>`.
|
||||
REGISTRY: dict[str, RegistryEntry] = {
|
||||
# "button": {
|
||||
# "repo": "https://github.com/your-org/templates.git",
|
||||
# "path": "src/components/Button.tsx",
|
||||
# "ref": "main",
|
||||
# "dest": "src/components",
|
||||
# },
|
||||
# "ui": {
|
||||
# "repo": "https://github.com/your-org/templates.git",
|
||||
# "path": "src/components/ui",
|
||||
# "ref": "v1.2.0",
|
||||
# "dest": "src/components",
|
||||
# },
|
||||
}
|
||||
|
||||
|
||||
def load_registry() -> dict[str, RegistryEntry]:
|
||||
return REGISTRY
|
||||
|
||||
|
||||
def get_registry_entry(name: str) -> RegistryEntry:
|
||||
registry = load_registry()
|
||||
if name not in registry:
|
||||
raise KeyError(name)
|
||||
return registry[name]
|
||||
|
||||
Reference in New Issue
Block a user