Initial commit
This commit is contained in:
137
skills/build-assistant/scripts/validate-plugin.sh
Executable file
137
skills/build-assistant/scripts/validate-plugin.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
# Script: validate-plugin.sh
|
||||
# Purpose: Validate plugin directory compliance with Claude Code standards
|
||||
# Subsystem: build-system
|
||||
# Called by: /build:plugin command after generation
|
||||
# Outputs: Validation report to stdout
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
PLUGIN_DIR="${1:?Usage: $0 <plugin-directory>}"
|
||||
SETTINGS_FILE="$HOME/.claude/settings.local.json"
|
||||
|
||||
echo "[INFO] Validating plugin directory: $PLUGIN_DIR"
|
||||
|
||||
# Check directory exists
|
||||
if [[ ! -d "$PLUGIN_DIR" ]]; then
|
||||
echo "❌ ERROR: Directory not found: $PLUGIN_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check .claude-plugin directory exists
|
||||
if [[ ! -d "$PLUGIN_DIR/.claude-plugin" ]]; then
|
||||
echo "❌ ERROR: Missing .claude-plugin directory"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check plugin.json exists
|
||||
if [[ ! -f "$PLUGIN_DIR/.claude-plugin/plugin.json" ]]; then
|
||||
echo "❌ ERROR: Missing plugin.json manifest"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate JSON syntax
|
||||
if ! python3 -m json.tool "$PLUGIN_DIR/.claude-plugin/plugin.json" > /dev/null 2>&1; then
|
||||
echo "❌ ERROR: Invalid JSON in plugin.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate plugin.json schema (only allowed fields)
|
||||
ALLOWED_FIELDS=("name" "version" "description" "author" "homepage" "repository" "license" "keywords" "category" "tags" "strict" "commands" "agents" "hooks" "mcpServers")
|
||||
INVALID_FIELDS=$(python3 -c "
|
||||
import json, sys
|
||||
with open('$PLUGIN_DIR/.claude-plugin/plugin.json') as f:
|
||||
data = json.load(f)
|
||||
allowed = set($( printf "'%s', " "${ALLOWED_FIELDS[@]}" | sed 's/, $//' ))
|
||||
invalid = [k for k in data.keys() if k not in allowed]
|
||||
if invalid:
|
||||
print(' '.join(invalid))
|
||||
" 2>/dev/null)
|
||||
|
||||
if [[ -n "$INVALID_FIELDS" ]]; then
|
||||
echo "❌ ERROR: Invalid fields in plugin.json: $INVALID_FIELDS"
|
||||
echo "[INFO] Allowed fields: ${ALLOWED_FIELDS[*]}"
|
||||
echo "[INFO] Move custom metadata to keywords array for discoverability"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check required fields in plugin.json
|
||||
REQUIRED_FIELDS=("name" "version" "description")
|
||||
for field in "${REQUIRED_FIELDS[@]}"; do
|
||||
if ! grep -q "\"$field\":" "$PLUGIN_DIR/.claude-plugin/plugin.json"; then
|
||||
echo "❌ ERROR: Missing required field: $field"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Validate author field structure if present
|
||||
if grep -q "\"author\":" "$PLUGIN_DIR/.claude-plugin/plugin.json"; then
|
||||
AUTHOR_VALID=$(python3 -c "
|
||||
import json
|
||||
with open('$PLUGIN_DIR/.claude-plugin/plugin.json') as f:
|
||||
data = json.load(f)
|
||||
author = data.get('author')
|
||||
if isinstance(author, dict):
|
||||
if 'name' in author:
|
||||
print('valid')
|
||||
else:
|
||||
print('missing_name')
|
||||
elif isinstance(author, str):
|
||||
print('string')
|
||||
" 2>/dev/null)
|
||||
|
||||
if [[ "$AUTHOR_VALID" == "string" ]]; then
|
||||
echo "❌ ERROR: author field must be an object with 'name' and 'email' fields, not a string"
|
||||
exit 1
|
||||
elif [[ "$AUTHOR_VALID" == "missing_name" ]]; then
|
||||
echo "❌ ERROR: author object must include 'name' field"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check component directories are at root (not inside .claude-plugin)
|
||||
if [[ -d "$PLUGIN_DIR/.claude-plugin/commands" ]] || \
|
||||
[[ -d "$PLUGIN_DIR/.claude-plugin/skills" ]] || \
|
||||
[[ -d "$PLUGIN_DIR/.claude-plugin/hooks" ]]; then
|
||||
echo "❌ ERROR: Component directories must be at plugin root, not inside .claude-plugin/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# NEW: Check if plugin commands are registered in settings.local.json
|
||||
PLUGIN_NAME=$(basename "$PLUGIN_DIR")
|
||||
|
||||
if [[ -f "$SETTINGS_FILE" ]]; then
|
||||
if [[ -d "$PLUGIN_DIR/commands" ]]; then
|
||||
# Check for wildcard permission
|
||||
if ! grep -q "SlashCommand(/$PLUGIN_NAME:\\*)" "$SETTINGS_FILE"; then
|
||||
echo "⚠️ WARNING: Plugin commands not registered in settings.local.json"
|
||||
echo "[INFO] Run: bash plugins/domain-plugin-builder/skills/build-assistant/scripts/sync-settings-permissions.sh"
|
||||
else
|
||||
echo "✅ Plugin commands registered in settings.local.json"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "⚠️ WARNING: No settings.local.json found"
|
||||
echo "[INFO] Run: bash plugins/domain-plugin-builder/skills/build-assistant/scripts/sync-settings-permissions.sh"
|
||||
fi
|
||||
|
||||
echo "✅ Plugin validation passed"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "📦 NEXT STEP: Install Plugin to Test"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "To make the plugin available for use:"
|
||||
echo ""
|
||||
echo " 1. Update marketplace in ~/.claude:"
|
||||
echo " cp -r $PLUGIN_DIR ~/.claude/plugins/marketplaces/ai-dev-marketplace/plugins/"
|
||||
echo " cp .claude-plugin/marketplace.json ~/.claude/plugins/marketplaces/ai-dev-marketplace/.claude-plugin/"
|
||||
echo ""
|
||||
echo " 2. Install the plugin:"
|
||||
echo " /plugin install $PLUGIN_NAME@ai-dev-marketplace"
|
||||
echo ""
|
||||
echo " 3. Verify installation:"
|
||||
echo " /$PLUGIN_NAME:init (or any command from the plugin)"
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
exit 0
|
||||
Reference in New Issue
Block a user