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
|
# 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
|
import typer
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from jeAddons.registry import load_registry
|
from jeAddons.copier import fetch_path
|
||||||
from jeAddons.copier import fetch_file
|
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()
|
@app.command()
|
||||||
def add(name: str):
|
def add(
|
||||||
registry = load_registry()
|
name: str = typer.Argument(..., help="Registry name from jeAddons/registry.py."),
|
||||||
|
ref: str | None = typer.Option(None, "--ref", "-r", help="Override registry git ref."),
|
||||||
if name not in registry:
|
dest: Path | None = typer.Option(None, "--dest", "-d", help="Override registry destination directory."),
|
||||||
typer.echo(f"❌ Unknown entry: {name}")
|
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)
|
raise typer.Exit(1)
|
||||||
|
|
||||||
entry = registry[name]
|
repo = entry.get("repo")
|
||||||
fetch_file(entry["repo"], entry["file"], Path.cwd())
|
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__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+26
-7
@@ -4,13 +4,32 @@ import shutil
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
def fetch_file(repo_url: str, file_path: str, dest: Path):
|
def fetch_path(repo_url: str, repo_path: str, dest: Path, ref: str = "HEAD", force: bool = False):
|
||||||
tmp = tempfile.mkdtemp()
|
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
|
target = dest / src.name
|
||||||
if not src.exists():
|
if target.exists():
|
||||||
raise FileNotFoundError(file_path)
|
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
|
from typing import TypedDict
|
||||||
import urllib.request
|
|
||||||
|
|
||||||
REGISTRY_URL = "https://raw.githubusercontent.com/acme/registry/main/registry.json"
|
|
||||||
|
|
||||||
|
|
||||||
def load_registry():
|
class RegistryEntry(TypedDict, total=False):
|
||||||
with urllib.request.urlopen(REGISTRY_URL) as r:
|
repo: str
|
||||||
return json.load(r)
|
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]
|
||||||
|
|||||||
+10
-2
@@ -1,12 +1,20 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "jeaddons"
|
name = "jeaddons"
|
||||||
version = "0.1.0"
|
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"
|
readme = "README.md"
|
||||||
requires-python = ">=3.14"
|
requires-python = ">=3.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"typer>=0.21.1",
|
"typer>=0.21.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["hatchling"]
|
||||||
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
|
[tool.hatch.build.targets.wheel]
|
||||||
|
packages = ["jeAddons"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
jeAddons= "jeAddons.cli:app"
|
jeAddons= "jeAddons.cli:app"
|
||||||
|
jeaddons = "jeAddons.cli:app"
|
||||||
|
|||||||
Reference in New Issue
Block a user