#!/usr/bin/env bash set -euo pipefail # Validates that all paths referenced in marketplace.json and plugin.json manifests # resolve to existing files or directories. Run from repo root or pass REPO_ROOT as arg. REPO_ROOT="${1:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}" FAIL=0 err() { echo " FAIL: $1" >&2; FAIL=$((FAIL + 1)); } if ! command -v jq &>/dev/null; then echo "Error: jq is required but not installed" >&2 exit 1 fi MARKETPLACE="$REPO_ROOT/.claude-plugin/marketplace.json" if [[ ! -f "$MARKETPLACE" ]]; then exit 0 fi plugin_count=$(jq '.plugins | length' "$MARKETPLACE") for ((i = 0; i < plugin_count; i++)); do name=$(jq -r ".plugins[$i].name" "$MARKETPLACE") source_type=$(jq -r ".plugins[$i].source | type" "$MARKETPLACE") # Remote sources (github, git, npm objects) have no local directory to check if [[ "$source_type" != "string" ]]; then continue fi source=$(jq -r ".plugins[$i].source" "$MARKETPLACE") source="${source#./}" plugin_dir="$REPO_ROOT/$source" if [[ ! -d "$plugin_dir" ]]; then err "plugin '$name': source directory not found: $source" continue fi manifest="$plugin_dir/plugin.json" if [[ ! -f "$manifest" ]]; then err "plugin '$name': plugin.json not found in $source" continue fi # Check skills directories skill_count=$(jq '.skills | if . then length else 0 end' "$manifest") for ((s = 0; s < skill_count; s++)); do skill_path=$(jq -r ".skills[$s]" "$manifest") full_path="$plugin_dir/$skill_path" full_path="${full_path%/}" if [[ ! -d "$full_path" ]]; then err "plugin '$name': skills path not found: $skill_path" fi done # Check file references (hooks, mcpServers, agents) for field in hooks mcpServers agents; do ref=$(jq -r ".${field} // empty" "$manifest") [[ -z "$ref" ]] && continue full_path="$plugin_dir/$ref" full_path="${full_path%/}" if [[ ! -e "$full_path" ]]; then err "plugin '$name': $field path not found: $ref" fi done done if [[ $FAIL -gt 0 ]]; then echo "Manifest check failed: $FAIL error(s)" >&2 exit 1 fi