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:
Generated
+5
@@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+10
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="uv (jeAddons)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
+7
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="uv (jeAddons)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="uv (jeAddons)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/jeAddons.iml" filepath="$PROJECT_DIR$/.idea/jeAddons.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -1,2 +1,39 @@
|
||||
# jeAddons
|
||||
|
||||
Clone a file or folder from Git repositories into your current project.
|
||||
|
||||
`add` is registry-based (like `shadcn add`): you manage entries in a dict.
|
||||
|
||||
## Usage with uvx
|
||||
|
||||
1. Edit `jeAddons/registry.py` and add entries to `REGISTRY`.
|
||||
2. Run:
|
||||
|
||||
```bash
|
||||
uvx jeaddons add <name> [--ref <branch-or-tag>] [--dest <target-dir>] [--force]
|
||||
```
|
||||
|
||||
3. See available names:
|
||||
|
||||
```bash
|
||||
uvx jeaddons list
|
||||
```
|
||||
|
||||
## Registry example
|
||||
|
||||
```python
|
||||
REGISTRY = {
|
||||
"button": {
|
||||
"repo": "https://github.com/user/templates.git",
|
||||
"path": "src/components/Button.tsx",
|
||||
"ref": "main",
|
||||
"dest": "src/components",
|
||||
},
|
||||
"ui": {
|
||||
"repo": "https://github.com/user/templates.git",
|
||||
"path": "src/components/ui",
|
||||
"ref": "v1.2.0",
|
||||
"dest": "src/components",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
+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]
|
||||
|
||||
+9
-1
@@ -1,12 +1,20 @@
|
||||
[project]
|
||||
name = "jeaddons"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
description = "CLI to copy files or folders from a git repo into your current project"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.14"
|
||||
dependencies = [
|
||||
"typer>=0.21.1",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["jeAddons"]
|
||||
|
||||
[project.scripts]
|
||||
jeAddons= "jeAddons.cli:app"
|
||||
jeaddons = "jeAddons.cli:app"
|
||||
|
||||
Reference in New Issue
Block a user