SuperClaude/scripts/build_superclaude_plugin.py
BlackBear 18c0e4e127
Add missing install.sh script (#483)
* feat: add missing install.sh script referenced in README\n\n- Create comprehensive installation script with POSIX compatibility\n- Add interactive and non-interactive installation modes\n- Include prerequisites checking and MCP server setup guidance\n- Replace echo -e with printf for better POSIX compliance

* fix: resolve linting errors in install_mcp.py and clean_command_names.py

Fix multiple ruff linting errors to ensure CI/CD pipeline passes:

- install_mcp.py: Remove unused pathlib.Path import, replace bare except
  with specific exception types (ValueError, IndexError), remove
  extraneous f-string prefixes on lines without placeholders
- clean_command_names.py: Remove unused os import, convert f-strings
  without placeholders to regular strings
- pyproject.toml: Exclude docs/ directory from ruff checks to avoid
  N999 module naming violations in documentation templates

All linting checks now pass successfully.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* style: apply ruff format to Python source files

Apply ruff formatting rules to CLI and scripts modules to ensure
consistent code style across the codebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ci): remove incompatible pip cache from quick-check workflow

## Problem
GitHub Actions was failing with error:
"Cache folder path is retrieved for pip but doesn't exist on disk:
/home/runner/.cache/pip. This likely indicates that there are no
dependencies to cache."

## Root Cause
The quick-check.yml workflow specified `cache: 'pip'` in the Python
setup step, but the workflow uses UV (not pip) for package management
via `uv pip install --system -e ".[dev]"`.

UV uses its own cache directory (~/.cache/uv), so the pip cache path
was never created, causing the error.

This was a migration oversight:
- When UV was adopted as the project standard (commit 00706f0), the
  CLAUDE.md established "CRITICAL: Never use pip directly" rule
- The test.yml workflow was created correctly without pip cache
- The quick-check.yml workflow incorrectly included pip cache from
  initial creation (commit 8c0559c) and was not updated during migration

## Solution
Remove `cache: 'pip'` line to align with:
- Project's UV-first architecture (CLAUDE.md)
- test.yml workflow (which runs successfully without pip cache)
- readme-quality-check.yml workflow (no cache needed)

Note: publish-pypi.yml intentionally uses pip cache as it directly
runs `python -m pip install` commands, which is correct for that workflow.

## Impact
-  Eliminates GitHub Actions cache warning
-  Aligns all UV-based workflows consistently
-  Follows project standards documented in CLAUDE.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-14 08:03:04 +05:30

98 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""
Build SuperClaude plugin distribution artefacts from unified sources.
Usage:
python scripts/build_superclaude_plugin.py
"""
from __future__ import annotations
import json
import shutil
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
PLUGIN_SRC = ROOT / "plugins" / "superclaude"
DIST_ROOT = ROOT / "dist" / "plugins" / "superclaude"
MANIFEST_DIR = PLUGIN_SRC / "manifest"
def load_metadata() -> dict:
with (MANIFEST_DIR / "metadata.json").open() as f:
metadata = json.load(f)
version_file = ROOT / "VERSION"
if version_file.exists():
metadata["plugin_version"] = version_file.read_text().strip()
else:
# Fall back to metadata override or default version
metadata["plugin_version"] = metadata.get("plugin_version", "0.0.0")
metadata.setdefault("keywords", [])
return metadata
def render_template(template_path: Path, placeholders: dict[str, str]) -> str:
content = template_path.read_text()
for key, value in placeholders.items():
token = f"{{{{{key}}}}}"
content = content.replace(token, value)
return content
def copy_tree(src: Path, dest: Path) -> None:
if not src.exists():
return
shutil.copytree(src, dest, dirs_exist_ok=True)
def main() -> None:
if not PLUGIN_SRC.exists():
raise SystemExit(f"Missing plugin sources: {PLUGIN_SRC}")
metadata = load_metadata()
placeholders = {
"plugin_name": metadata["plugin_name"],
"plugin_version": metadata["plugin_version"],
"plugin_description": metadata["plugin_description"],
"author_name": metadata["author_name"],
"homepage_url": metadata["homepage_url"],
"repository_url": metadata["repository_url"],
"license": metadata["license"],
"keywords_json": json.dumps(metadata["keywords"]),
"marketplace_name": metadata["marketplace_name"],
"marketplace_description": metadata["marketplace_description"],
}
# Clean dist directory
if DIST_ROOT.exists():
shutil.rmtree(DIST_ROOT)
DIST_ROOT.mkdir(parents=True, exist_ok=True)
# Copy top-level asset directories
for folder in ["agents", "commands", "hooks", "scripts", "skills"]:
copy_tree(PLUGIN_SRC / folder, DIST_ROOT / folder)
# Render manifests
claude_dir = DIST_ROOT / ".claude-plugin"
claude_dir.mkdir(parents=True, exist_ok=True)
plugin_manifest = render_template(MANIFEST_DIR / "plugin.template.json", placeholders)
(claude_dir / "plugin.json").write_text(plugin_manifest + "\n")
marketplace_manifest = render_template(MANIFEST_DIR / "marketplace.template.json", placeholders)
(claude_dir / "marketplace.json").write_text(marketplace_manifest + "\n")
# Copy tests into manifest directory
tests_src = PLUGIN_SRC / "tests"
if tests_src.exists():
copy_tree(tests_src, claude_dir / "tests")
print(f"✅ Built plugin artefacts at {DIST_ROOT}")
if __name__ == "__main__":
main()