import argparse import subprocess import shutil from pathlib import Path from core.logger import setup_logging def create_plugin(args: argparse.Namespace) -> None: root = Path(args.root).resolve() plugin_path = root / "plugins" / "external" / args.name plugin_path.mkdir(parents=True, exist_ok=True) (plugin_path / "__init__.py").write_text( "from .plugin import *\n", encoding="utf-8", ) (plugin_path / "plugin.py").write_text( f"from core.plugin import Plugin\n\n\nclass {args.class_name}(Plugin):\n name = \"{args.name}\"\n version = \"0.1.0\"\n author = \"{args.author}\"\n description = \"{args.description}\"\n\n async def on_load(self):\n self.log.info(\"{args.name} loaded\")\n", encoding="utf-8", ) (plugin_path / "config.yml").write_text( f"{args.name}:\n enabled: true\n settings: {{}}\n", encoding="utf-8", ) (plugin_path / "requirements.txt").write_text("", encoding="utf-8") (plugin_path / "README.md").write_text( f"# {args.name}\n\n{args.description}\n", encoding="utf-8", ) print(f"Created plugin at {plugin_path}") def validate_plugin(args: argparse.Namespace) -> None: path = Path(args.path).resolve() required = ["__init__.py", "plugin.py"] missing = [item for item in required if not (path / item).exists()] if missing: print(f"Missing files: {', '.join(missing)}") raise SystemExit(1) print("Plugin structure OK") def build_plugin(args: argparse.Namespace) -> None: path = Path(args.path).resolve() output = Path(args.output).resolve() shutil.make_archive(str(output), "zip", root_dir=path) print(f"Built {output}.zip") def publish_plugin(args: argparse.Namespace) -> None: root = Path(args.root).resolve() if not args.name or not args.version: print("Publish requires --name and --version") raise SystemExit(1) plugin_path = Path(args.path).resolve() if args.path else root / "plugins" / "external" / args.name if not plugin_path.exists(): print(f"Plugin path not found: {plugin_path}") raise SystemExit(1) tag = args.version if args.version.startswith("v") else f"v{args.version}" try: subprocess.run(["git", "status"], cwd=plugin_path, check=True, capture_output=True, text=True) except subprocess.CalledProcessError as exc: print(exc.stderr.strip() or "Git status failed") raise SystemExit(1) from exc try: subprocess.run(["git", "tag", "-a", tag, "-m", f"Release {tag}"], cwd=plugin_path, check=True) subprocess.run(["git", "push", "origin", tag], cwd=plugin_path, check=True) except subprocess.CalledProcessError as exc: print(exc.stderr.strip() or "Git tag/push failed") raise SystemExit(1) from exc try: log = subprocess.run( ["git", "log", "-5", "--pretty=format:- %s"], cwd=plugin_path, check=True, capture_output=True, text=True, ) note = log.stdout.strip() or f"Release {tag}" subprocess.run( [ "tea", "releases", "create", "--tag", tag, "--title", f"{args.name} {tag}", "--note", note, ], cwd=plugin_path, check=True, ) except subprocess.CalledProcessError as exc: print(exc.stderr.strip() or "Tea release creation failed") raise SystemExit(1) from exc print(f"Published {args.name} {tag}") def test_plugin(args: argparse.Namespace) -> None: import py_compile path = Path(args.path).resolve() errors = [] for file in path.rglob("*.py"): try: py_compile.compile(str(file), doraise=True) except Exception as exc: errors.append((file, exc)) if errors: for file, exc in errors: print(f"{file}: {exc}") raise SystemExit(1) print("Plugin tests OK") def generate_docs(args: argparse.Namespace) -> None: path = Path(args.path).resolve() (path / "README.md").write_text("# Plugin\n", encoding="utf-8") (path / "COMMANDS.md").write_text("# Commands\n", encoding="utf-8") (path / "CONFIG.md").write_text("# Config\n", encoding="utf-8") (path / "API.md").write_text("# API\n", encoding="utf-8") print("Docs generated") def run_cli() -> None: parser = argparse.ArgumentParser(prog="overub") parser.add_argument("--root", default=Path(__file__).resolve().parents[1]) sub = parser.add_subparsers(dest="command") create = sub.add_parser("create-plugin") create.add_argument("name") create.add_argument("--class-name", default="MyPlugin") create.add_argument("--author", default="overub") create.add_argument("--description", default="OverUB plugin") create.set_defaults(func=create_plugin) validate = sub.add_parser("validate-plugin") validate.add_argument("path") validate.set_defaults(func=validate_plugin) build = sub.add_parser("build-plugin") build.add_argument("path") build.add_argument("--output", default="overub-plugin") build.set_defaults(func=build_plugin) test = sub.add_parser("test-plugin") test.add_argument("path") test.set_defaults(func=test_plugin) docs = sub.add_parser("docs-plugin") docs.add_argument("path") docs.set_defaults(func=generate_docs) publish = sub.add_parser("publish-plugin") publish.add_argument("--name", default="") publish.add_argument("--version", default="") publish.add_argument("--path", default="") publish.set_defaults(func=publish_plugin) args = parser.parse_args() setup_logging("INFO") if not hasattr(args, "func"): parser.print_help() return args.func(args)