#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" SCRIPT="$REPO_ROOT/scripts/check-manifests.sh" PASS=0 FAIL=0 pass() { echo " PASS: $1"; PASS=$((PASS + 1)); } fail() { echo " FAIL: $1"; FAIL=$((FAIL + 1)); } # Helper: make a minimal valid repo fixture with marketplace + plugin structure. # Per ADR-0015/ADR-0017, the manifest check-manifests.sh validates is # .claude-plugin/plugin.json (compiled output) -- not the root-level plugin.json, # which was deleted repo-wide, and not the skills/hooks/mcpServers/agents pointer # fields apm's compiler deliberately never populates (see scripts/check-manifests.sh's # own header comment). Content-presence drift is scripts/sync-plugin-content.sh's job. make_valid_fixture() { local dir dir="$(mktemp -d)" mkdir -p "$dir/.claude-plugin" mkdir -p "$dir/plugins/myplugin/.claude-plugin" cat > "$dir/.claude-plugin/marketplace.json" <<'JSON' { "name": "test-marketplace", "plugins": [ { "name": "myplugin", "source": "./plugins/myplugin" } ] } JSON cat > "$dir/plugins/myplugin/.claude-plugin/plugin.json" <<'JSON' { "name": "myplugin" } JSON echo "$dir" } # --- 1. Exits 0 against valid repo structure --- echo "" echo "--- exits 0 when all references are valid ---" FIXTURE="$(make_valid_fixture)" trap 'rm -rf "$FIXTURE"' EXIT if bash "$SCRIPT" "$FIXTURE" > /dev/null 2>&1; then pass "exits 0 when all manifest references resolve" else fail "exited non-zero against a valid fixture" fi # --- 2. Exits 1 when plugin source dir is missing --- echo "" echo "--- exits 1 when plugin source directory missing ---" FIXTURE2="$(mktemp -d)" trap 'rm -rf "$FIXTURE2"' EXIT mkdir -p "$FIXTURE2/.claude-plugin" cat > "$FIXTURE2/.claude-plugin/marketplace.json" <<'JSON' { "name": "test-marketplace", "plugins": [ { "name": "ghost", "source": "./plugins/ghost" } ] } JSON if bash "$SCRIPT" "$FIXTURE2" > /dev/null 2>&1; then fail "exited 0 when plugin source dir is missing — expected exit 1" else pass "exits non-zero when plugin source directory does not exist" fi # --- 3. Exits 1 when .claude-plugin/plugin.json is missing from plugin dir --- echo "" echo "--- exits 1 when .claude-plugin/plugin.json missing from plugin directory ---" FIXTURE3="$(mktemp -d)" trap 'rm -rf "$FIXTURE3"' EXIT mkdir -p "$FIXTURE3/.claude-plugin" mkdir -p "$FIXTURE3/plugins/nomanifest" cat > "$FIXTURE3/.claude-plugin/marketplace.json" <<'JSON' { "name": "test-marketplace", "plugins": [ { "name": "nomanifest", "source": "./plugins/nomanifest" } ] } JSON if bash "$SCRIPT" "$FIXTURE3" > /dev/null 2>&1; then fail "exited 0 when .claude-plugin/plugin.json is missing — expected exit 1" else pass "exits non-zero when .claude-plugin/plugin.json is missing from plugin directory" fi # --- 4. Exits 1 when a remote-source plugin entry's local plugin still lacks a manifest --- # Remote sources (object-typed `source:`) are skipped entirely; only string (local path) # sources are checked. This guards that a mixed marketplace.json still catches a broken # local entry alongside a legitimately-skipped remote one. echo "" echo "--- exits 1 for a broken local entry even when a remote entry is present ---" FIXTURE4="$(mktemp -d)" trap 'rm -rf "$FIXTURE4"' EXIT mkdir -p "$FIXTURE4/.claude-plugin" mkdir -p "$FIXTURE4/plugins/broken" cat > "$FIXTURE4/.claude-plugin/marketplace.json" <<'JSON' { "name": "test-marketplace", "plugins": [ { "name": "remote-thing", "source": { "repo": "someorg/somerepo", "source": "github" } }, { "name": "broken", "source": "./plugins/broken" } ] } JSON if bash "$SCRIPT" "$FIXTURE4" > /dev/null 2>&1; then fail "exited 0 with a broken local entry present — expected exit 1" else pass "exits non-zero for a broken local entry even alongside a skipped remote entry" fi echo "" echo "Results: $PASS passed, $FAIL failed" [[ $FAIL -eq 0 ]]