Files
leo-claude-mktplace/scripts/validate-marketplace.sh
lmiranda e824b18e44 fix(schema): move domain field to metadata.json for Claude Code compatibility
BREAKING FIX: Claude Code's marketplace schema validator rejects
unrecognized keys. The 'domain' field added in v8.0.0 caused
'Failed to load marketplace' errors on all 20 plugin entries.

- Removed 'domain' from marketplace.json (all 3 profiles)
- Removed 'domain' from all 20 plugin.json files
- Added 'domain' to metadata.json for all 20 plugins
- Updated validate-marketplace.sh to read from metadata.json
- Updated docs (CANONICAL-PATHS.md, CLAUDE.md) to reference metadata.json
- 12 new metadata.json files created (plugins without mcp_servers)
- 8 existing metadata.json files updated with domain field

Version: 9.1.2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-07 17:00:58 -05:00

360 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
# v8.0.0: Valid domain values
VALID_DOMAINS="core data saas ops debug"
echo "=== Validating Marketplace ==="
# Check marketplace.json exists and is valid JSON
MARKETPLACE_JSON="$ROOT_DIR/.claude-plugin/marketplace.json"
if [[ ! -f "$MARKETPLACE_JSON" ]]; then
echo "ERROR: Missing $MARKETPLACE_JSON"
exit 1
fi
if ! jq empty "$MARKETPLACE_JSON" 2>/dev/null; then
echo "ERROR: Invalid JSON in marketplace.json"
exit 1
fi
echo "✓ marketplace.json is valid JSON"
# Check required fields
if ! jq -e '.name' "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing 'name' field in marketplace.json"
exit 1
fi
if ! jq -e '.owner.name' "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing 'owner.name' field in marketplace.json"
exit 1
fi
if ! jq -e '.owner.email' "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing 'owner.email' field in marketplace.json"
exit 1
fi
echo "✓ Required marketplace fields present"
# Check plugins array exists
if ! jq -e '.plugins | type == "array"' "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing or invalid 'plugins' array in marketplace.json"
exit 1
fi
# Check each plugin entry in marketplace.json
PLUGIN_COUNT=$(jq '.plugins | length' "$MARKETPLACE_JSON")
echo "Found $PLUGIN_COUNT plugins in marketplace.json"
for i in $(seq 0 $((PLUGIN_COUNT - 1))); do
PLUGIN_NAME=$(jq -r ".plugins[$i].name" "$MARKETPLACE_JSON")
echo "--- Checking marketplace entry: $PLUGIN_NAME ---"
# Check required fields in marketplace entry
for field in name source description version; do
if ! jq -e ".plugins[$i].$field" "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing '$field' in marketplace entry for $PLUGIN_NAME"
exit 1
fi
done
# Check author field
if ! jq -e ".plugins[$i].author.name" "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing 'author.name' in marketplace entry for $PLUGIN_NAME"
exit 1
fi
# Check homepage and repository
if ! jq -e ".plugins[$i].homepage" "$MARKETPLACE_JSON" >/dev/null; then
echo "WARNING: Missing 'homepage' in marketplace entry for $PLUGIN_NAME"
fi
if ! jq -e ".plugins[$i].repository" "$MARKETPLACE_JSON" >/dev/null; then
echo "WARNING: Missing 'repository' in marketplace entry for $PLUGIN_NAME"
fi
# v3.0.0: Check category, tags, license fields
if ! jq -e ".plugins[$i].category" "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing 'category' in marketplace entry for $PLUGIN_NAME (required v3.0.0+)"
exit 1
fi
if ! jq -e ".plugins[$i].tags | type == \"array\"" "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing or invalid 'tags' array in marketplace entry for $PLUGIN_NAME (required v3.0.0+)"
exit 1
fi
if ! jq -e ".plugins[$i].license" "$MARKETPLACE_JSON" >/dev/null; then
echo "ERROR: Missing 'license' in marketplace entry for $PLUGIN_NAME (required v3.0.0+)"
exit 1
fi
# v8.0.0/v9.1.2: Check domain field in metadata.json (moved from marketplace.json for schema compliance)
PLUGIN_SOURCE=$(jq -r ".plugins[$i].source" "$MARKETPLACE_JSON")
METADATA_JSON="$ROOT_DIR/$PLUGIN_SOURCE/.claude-plugin/metadata.json"
if [[ -f "$METADATA_JSON" ]]; then
PLUGIN_DOMAIN=$(jq -r '.domain // empty' "$METADATA_JSON")
else
PLUGIN_DOMAIN=""
fi
if [[ -z "$PLUGIN_DOMAIN" ]]; then
echo "ERROR: Missing 'domain' in $PLUGIN_NAME/.claude-plugin/metadata.json (required v8.0.0+)"
exit 1
fi
if ! echo "$VALID_DOMAINS" | grep -qw "$PLUGIN_DOMAIN"; then
echo "ERROR: Invalid domain '$PLUGIN_DOMAIN' in $PLUGIN_NAME/metadata.json (allowed: $VALID_DOMAINS)"
exit 1
fi
echo " ✓ domain: $PLUGIN_DOMAIN (from metadata.json)"
echo "✓ Marketplace entry $PLUGIN_NAME valid"
done
# Validate each plugin directory
PLUGINS_DIR="$ROOT_DIR/plugins"
echo ""
echo "=== Validating Plugin Directories ==="
for plugin_dir in "$PLUGINS_DIR"/*/; do
plugin_name=$(basename "$plugin_dir")
echo "--- Checking plugin directory: $plugin_name ---"
# Check plugin.json exists
plugin_json="$plugin_dir.claude-plugin/plugin.json"
if [[ ! -f "$plugin_json" ]]; then
echo "WARNING: Missing plugin.json in $plugin_name/.claude-plugin/"
continue
fi
# Validate JSON syntax
if ! jq empty "$plugin_json" 2>/dev/null; then
echo "ERROR: Invalid JSON in $plugin_name/plugin.json"
exit 1
fi
# Check required plugin fields
for field in name description version; do
if ! jq -e ".$field" "$plugin_json" >/dev/null; then
echo "ERROR: Missing '$field' in $plugin_name/plugin.json"
exit 1
fi
done
# Check recommended fields
if ! jq -e '.author.name' "$plugin_json" >/dev/null; then
echo "WARNING: Missing 'author.name' in $plugin_name/plugin.json"
fi
if ! jq -e '.homepage' "$plugin_json" >/dev/null; then
echo "WARNING: Missing 'homepage' in $plugin_name/plugin.json"
fi
if ! jq -e '.repository' "$plugin_json" >/dev/null; then
echo "WARNING: Missing 'repository' in $plugin_name/plugin.json"
fi
if ! jq -e '.license' "$plugin_json" >/dev/null; then
echo "WARNING: Missing 'license' in $plugin_name/plugin.json"
fi
if ! jq -e '.keywords | type == "array"' "$plugin_json" >/dev/null; then
echo "WARNING: Missing 'keywords' array in $plugin_name/plugin.json"
fi
# v8.0.0/v9.1.2: Check domain field in metadata.json (moved from plugin.json for schema compliance)
metadata_json="$plugin_dir.claude-plugin/metadata.json"
if [[ -f "$metadata_json" ]]; then
PLUGIN_DOMAIN_MD=$(jq -r '.domain // empty' "$metadata_json")
else
PLUGIN_DOMAIN_MD=""
fi
if [[ -z "$PLUGIN_DOMAIN_MD" ]]; then
echo "ERROR: Missing 'domain' in $plugin_name/.claude-plugin/metadata.json (required v8.0.0+)"
exit 1
fi
if ! echo "$VALID_DOMAINS" | grep -qw "$PLUGIN_DOMAIN_MD"; then
echo "ERROR: Invalid domain '$PLUGIN_DOMAIN_MD' in $plugin_name/metadata.json (allowed: $VALID_DOMAINS)"
exit 1
fi
echo " ✓ domain: $PLUGIN_DOMAIN_MD (from metadata.json)"
# Check README exists
if [[ ! -f "$plugin_dir/README.md" ]]; then
echo "WARNING: Missing README.md in $plugin_name/"
fi
# CRITICAL: Validate file references exist (mcpServers, hooks, commands)
# This prevents broken references that silently break plugin loading
# Check mcpServers references
mcp_servers=$(jq -r '.mcpServers // [] | .[]' "$plugin_json" 2>/dev/null)
for mcp_ref in $mcp_servers; do
mcp_path="$plugin_dir/$mcp_ref"
if [[ ! -f "$mcp_path" ]]; then
echo "ERROR: BROKEN REFERENCE in $plugin_name/plugin.json"
echo " mcpServers references '$mcp_ref' but file does not exist at:"
echo " $mcp_path"
echo ""
echo " FIX: Either create the file or remove the mcpServers entry"
exit 1
fi
echo " ✓ mcpServers reference: $mcp_ref exists"
done
# Check hooks references (can be array of file paths OR object with handlers)
hooks_type=$(jq -r '.hooks | type' "$plugin_json" 2>/dev/null)
if [[ "$hooks_type" == "array" ]]; then
# Array format: ["./hooks/hooks.json"]
hooks=$(jq -r '.hooks[]' "$plugin_json" 2>/dev/null)
for hook_ref in $hooks; do
hook_path="$plugin_dir/$hook_ref"
if [[ ! -f "$hook_path" ]]; then
echo "ERROR: BROKEN REFERENCE in $plugin_name/plugin.json"
echo " hooks references '$hook_ref' but file does not exist at:"
echo " $hook_path"
echo ""
echo " FIX: Either create the file or remove the hooks entry"
exit 1
fi
echo " ✓ hooks reference: $hook_ref exists"
done
elif [[ "$hooks_type" == "object" ]]; then
# Object format: { "PostToolUse": [...] } - inline hooks, no file reference to validate
echo " ✓ hooks: inline object format (no file references)"
fi
# Check commands directory references
commands=$(jq -r '.commands // [] | .[]' "$plugin_json" 2>/dev/null)
for cmd_ref in $commands; do
cmd_path="$plugin_dir/$cmd_ref"
if [[ ! -d "$cmd_path" ]] && [[ ! -f "$cmd_path" ]]; then
echo "ERROR: BROKEN REFERENCE in $plugin_name/plugin.json"
echo " commands references '$cmd_ref' but path does not exist at:"
echo " $cmd_path"
echo ""
echo " FIX: Either create the path or remove the commands entry"
exit 1
fi
echo " ✓ commands reference: $cmd_ref exists"
done
echo "$plugin_name valid"
done
echo ""
echo "=== Validating Plugin Metadata (MCP Mappings) ==="
for plugin_dir in "$PLUGINS_DIR"/*/; do
plugin_name=$(basename "$plugin_dir")
metadata_json="$plugin_dir.claude-plugin/metadata.json"
if jq -e '.mcp_servers' "$plugin_dir.claude-plugin/plugin.json" >/dev/null 2>&1; then
echo "ERROR: $plugin_name/plugin.json contains 'mcp_servers' — move to metadata.json"
exit 1
fi
if [[ -f "$metadata_json" ]]; then
if ! jq empty "$metadata_json" 2>/dev/null; then
echo "ERROR: Invalid JSON in $plugin_name/.claude-plugin/metadata.json"
exit 1
fi
mcp_servers=$(jq -r '.mcp_servers // [] | .[]' "$metadata_json" 2>/dev/null)
for server in $mcp_servers; do
if [[ ! -d "$ROOT_DIR/mcp-servers/$server" ]]; then
echo "ERROR: $plugin_name metadata.json references '$server' but mcp-servers/$server/ missing"
exit 1
fi
echo "$plugin_name$server"
done
fi
done
echo "✓ Plugin metadata validation passed"
# CRITICAL: Validate marketplace.json file references
echo ""
echo "=== Validating Marketplace File References (CRITICAL) ==="
for i in $(seq 0 $((PLUGIN_COUNT - 1))); do
PLUGIN_NAME=$(jq -r ".plugins[$i].name" "$MARKETPLACE_JSON")
PLUGIN_SOURCE=$(jq -r ".plugins[$i].source" "$MARKETPLACE_JSON")
PLUGIN_DIR="$ROOT_DIR/$PLUGIN_SOURCE"
# Check mcpServers in marketplace.json
mcp_servers=$(jq -r ".plugins[$i].mcpServers // [] | .[]" "$MARKETPLACE_JSON" 2>/dev/null)
for mcp_ref in $mcp_servers; do
mcp_path="$PLUGIN_DIR/$mcp_ref"
if [[ ! -f "$mcp_path" ]]; then
echo "ERROR: BROKEN REFERENCE in marketplace.json for $PLUGIN_NAME"
echo " mcpServers references '$mcp_ref' but file does not exist at:"
echo " $mcp_path"
echo ""
echo " FIX: Either create the file or remove the mcpServers entry from marketplace.json"
exit 1
fi
echo "$PLUGIN_NAME: mcpServers reference $mcp_ref exists"
done
# Check hooks in marketplace.json
hooks=$(jq -r ".plugins[$i].hooks // [] | .[]" "$MARKETPLACE_JSON" 2>/dev/null)
for hook_ref in $hooks; do
hook_path="$PLUGIN_DIR/$hook_ref"
if [[ ! -f "$hook_path" ]]; then
echo "ERROR: BROKEN REFERENCE in marketplace.json for $PLUGIN_NAME"
echo " hooks references '$hook_ref' but file does not exist at:"
echo " $hook_path"
echo ""
echo " FIX: Either create the file or remove the hooks entry from marketplace.json"
exit 1
fi
echo "$PLUGIN_NAME: hooks reference $hook_ref exists"
done
done
echo "✓ All file references validated"
# Validate MCP servers and configuration
echo ""
echo "=== Validating MCP Servers ==="
# Check shared MCP servers exist
for server in gitea netbox data-platform viz-platform contract-validator; do
if [[ ! -d "$ROOT_DIR/mcp-servers/$server" ]]; then
echo "ERROR: Shared $server MCP server not found at mcp-servers/$server/"
exit 1
fi
echo "✓ Shared $server MCP server exists"
done
# Check .mcp.json exists at root
if [[ ! -f "$ROOT_DIR/.mcp.json" ]]; then
echo "ERROR: .mcp.json not found at repository root"
exit 1
fi
echo "✓ .mcp.json configuration exists"
# v8.0.0/v9.1.2: Validate domain in metadata.json for all plugins (single source of truth)
echo ""
echo "=== Validating Domain Fields (metadata.json) ==="
for plugin_dir in "$PLUGINS_DIR"/*/; do
plugin_name=$(basename "$plugin_dir")
metadata_file="$plugin_dir.claude-plugin/metadata.json"
if [[ -f "$metadata_file" ]]; then
domain=$(jq -r '.domain // empty' "$metadata_file")
if [[ -n "$domain" ]]; then
echo "$plugin_name domain: $domain"
else
echo "ERROR: $plugin_name/metadata.json exists but missing domain field"
exit 1
fi
else
echo "ERROR: $plugin_name missing .claude-plugin/metadata.json"
exit 1
fi
done
echo ""
echo "=== All validations passed ==="