feat: add marketplace.json sync validation and fix Copilot schema
Applies the same shared-field sync logic to the two marketplace.json files that we already applied to plugin.json: name, owner.name, description, version, and the full plugin catalog (name, source, description per entry) must match across .claude-plugin/ and .github/plugin/ manifests. Fixes .github/plugin/marketplace.json to use the canonical Copilot schema (description and version nested under metadata) rather than a copy of Claude's top-level format. Adds owner field to both files. Validate.sh handles the structural divergence by extracting values from either location before comparing. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -224,12 +224,14 @@ ROOT="$(cd "$ROOT" && pwd)"
|
||||
|
||||
# ── validate marketplace.json ─────────────────────────────────────────────────
|
||||
|
||||
CLAUDE_MARKETPLACE="$ROOT/.claude-plugin/marketplace.json"
|
||||
COPILOT_MARKETPLACE="$ROOT/.github/plugin/marketplace.json"
|
||||
MARKETPLACE_JSON=""
|
||||
for mp in "$ROOT/.claude-plugin/marketplace.json" "$ROOT/.github/plugin/marketplace.json"; do
|
||||
|
||||
for mp in "$CLAUDE_MARKETPLACE" "$COPILOT_MARKETPLACE"; do
|
||||
if [[ -f "$mp" ]]; then
|
||||
MARKETPLACE_JSON="$mp"
|
||||
[[ -z "$MARKETPLACE_JSON" ]] && MARKETPLACE_JSON="$mp"
|
||||
validate_marketplace_json "$mp"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -237,6 +239,57 @@ if [[ -z "$MARKETPLACE_JSON" ]]; then
|
||||
warn "No marketplace.json found. Expected at .claude-plugin/marketplace.json"
|
||||
fi
|
||||
|
||||
# Marketplace sync check — shared identity fields must match; description/version
|
||||
# legitimately differ in structure (Claude: top-level; Copilot: under metadata)
|
||||
if [[ -f "$CLAUDE_MARKETPLACE" && -f "$COPILOT_MARKETPLACE" ]]; then
|
||||
cm_val=$(jq -r '.name // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.name // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: 'name' differs — .claude-plugin ('$cm_val') vs .github/plugin ('$cp_val')"
|
||||
fi
|
||||
|
||||
cm_val=$(jq -r '.owner.name // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.owner.name // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: 'owner.name' differs — '$cm_val' vs '$cp_val'"
|
||||
fi
|
||||
|
||||
# description: Claude top-level, Copilot under metadata — compare values regardless of path
|
||||
cm_val=$(jq -r '.description // .metadata.description // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.metadata.description // .description // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: description differs between .claude-plugin/marketplace.json and .github/plugin/marketplace.json"
|
||||
fi
|
||||
|
||||
# version: same structural divergence as description
|
||||
cm_val=$(jq -r '.version // .metadata.version // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r '.metadata.version // .version // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace: version differs — '$cm_val' vs '$cp_val'"
|
||||
fi
|
||||
|
||||
# Plugin catalog must be identical across both files
|
||||
cm_plugins=$(jq -r '.plugins[].name' "$CLAUDE_MARKETPLACE" 2>/dev/null | sort)
|
||||
cp_plugins=$(jq -r '.plugins[].name' "$COPILOT_MARKETPLACE" 2>/dev/null | sort)
|
||||
if [[ "$cm_plugins" != "$cp_plugins" ]]; then
|
||||
error "marketplace: plugin lists differ between .claude-plugin/marketplace.json and .github/plugin/marketplace.json"
|
||||
else
|
||||
while IFS= read -r pname; do
|
||||
[[ -z "$pname" ]] && continue
|
||||
cm_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .source // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace plugin '$pname': source differs — '$cm_val' vs '$cp_val'"
|
||||
fi
|
||||
cm_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .description // empty' "$CLAUDE_MARKETPLACE")
|
||||
cp_val=$(jq -r --arg n "$pname" '.plugins[] | select(.name==$n) | .description // empty' "$COPILOT_MARKETPLACE")
|
||||
if [[ ( -n "$cm_val" || -n "$cp_val" ) && "$cm_val" != "$cp_val" ]]; then
|
||||
error "marketplace plugin '$pname': description differs between the two marketplace.json files"
|
||||
fi
|
||||
done <<< "$cm_plugins"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── validate plugins ──────────────────────────────────────────────────────────
|
||||
|
||||
if [[ -n "$PLUGIN_ONLY" ]]; then
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "holocron",
|
||||
"owner": { "name": "Your Name", "email": "you@example.com" },
|
||||
"description": "AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.",
|
||||
"version": "0.1.0",
|
||||
"plugins": [
|
||||
|
||||
7
.github/plugin/marketplace.json
vendored
7
.github/plugin/marketplace.json
vendored
@@ -1,7 +1,10 @@
|
||||
{
|
||||
"name": "holocron",
|
||||
"description": "AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.",
|
||||
"version": "0.1.0",
|
||||
"owner": { "name": "Your Name", "email": "you@example.com" },
|
||||
"metadata": {
|
||||
"description": "AI development skills for Claude Code and GitHub Copilot CLI — factory, design, implement, review, and cross-cutting workflows.",
|
||||
"version": "0.1.0"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "hello-world",
|
||||
|
||||
Reference in New Issue
Block a user