Skip to content

Commit e641173

Browse files
claudeduyetbot
andcommitted
fix(clickhouse,github): fix author field format in plugin manifests
Convert author from string to object {name} in clickhouse (1.3.1), clickhouse-monitoring (1.0.1), and github (1.4.1) to fix marketplace validation errors. Add scripts/validate-plugins.sh and CI workflow to catch schema issues before they ship. Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com>
1 parent 67e38b4 commit e641173

5 files changed

Lines changed: 145 additions & 6 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Validate Plugins
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
branches: [main, master, "claude/**"]
7+
paths:
8+
- "**/.claude-plugin/plugin.json"
9+
- "scripts/validate-plugins.sh"
10+
- ".github/workflows/validate-plugins.yml"
11+
pull_request:
12+
branches: [main, master]
13+
paths:
14+
- "**/.claude-plugin/plugin.json"
15+
- "scripts/validate-plugins.sh"
16+
- ".github/workflows/validate-plugins.yml"
17+
18+
permissions:
19+
contents: read
20+
21+
jobs:
22+
validate:
23+
name: Validate plugin.json manifests
24+
runs-on: ubuntu-latest
25+
steps:
26+
- uses: actions/checkout@v5
27+
28+
- name: Validate all plugin manifests
29+
run: bash scripts/validate-plugins.sh

clickhouse-monitoring/.claude-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "clickhouse-monitoring",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Agent Skill for the ClickHouse Monitor dashboard - a real-time monitoring and observability tool for ClickHouse clusters. Covers 45 dashboard pages, query performance analysis, table management, and API integration.",
5-
"author": "duyet",
5+
"author": { "name": "duyet" },
66
"license": "MIT",
77
"claude": {
88
"minVersion": "1.0.0"

clickhouse/.claude-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"name": "clickhouse",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"description": "ClickHouse expertise with 28 atomic review rules + 14 deep reference files. Rules synced with official ClickHouse/agent-skills repository. Schema design, query optimization, insert strategy, cluster management, backups, monitoring, and security best practices.",
5-
"author": "duyetbot",
5+
"author": { "name": "duyetbot" },
66
"license": "MIT",
77
"claude": {
88
"minVersion": "1.0.0"

github/.claude-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "github",
3-
"version": "1.4.0",
3+
"version": "1.4.1",
44
"description": "GitHub operations using gh CLI - PRs, workflows, issues, repositories, batch operations, PR babysitting with AI review bot integration, and smart branch detection",
5-
"author": "duyetbot",
5+
"author": { "name": "duyetbot" },
66
"license": "MIT"
77
}

scripts/validate-plugins.sh

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env bash
2+
# Validate all plugin.json manifests in the repository.
3+
# Exits 1 if any plugin fails validation.
4+
5+
set -euo pipefail
6+
7+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8+
FAILED=0
9+
CHECKED=0
10+
11+
validate_plugin() {
12+
local manifest="$1"
13+
local plugin_dir
14+
plugin_dir="$(dirname "$(dirname "$manifest")")"
15+
local plugin_name
16+
plugin_name="$(basename "$plugin_dir")"
17+
18+
# 1. Valid JSON
19+
if ! python3 -c "import json, sys; json.load(open('$manifest'))" 2>/dev/null; then
20+
echo "$plugin_name: invalid JSON"
21+
return 1
22+
fi
23+
24+
local errors=()
25+
26+
# 2. Run all checks via Python
27+
python3 - "$manifest" "$plugin_dir" <<'PYEOF'
28+
import json, sys, os, re
29+
30+
manifest_path = sys.argv[1]
31+
plugin_dir = sys.argv[2]
32+
errors = []
33+
34+
with open(manifest_path) as f:
35+
data = json.load(f)
36+
37+
# Required string fields
38+
for field in ("name", "version", "description"):
39+
if field not in data:
40+
errors.append(f"missing required field: '{field}'")
41+
elif not isinstance(data[field], str) or not data[field].strip():
42+
errors.append(f"'{field}' must be a non-empty string")
43+
44+
# Semver: version must match X.Y.Z
45+
if "version" in data and isinstance(data["version"], str):
46+
if not re.fullmatch(r"\d+\.\d+\.\d+", data["version"]):
47+
errors.append(f"'version' must be semver (X.Y.Z), got: {data['version']!r}")
48+
49+
# Author must be object with a 'name' key
50+
author = data.get("author")
51+
if author is None:
52+
errors.append("missing required field: 'author'")
53+
elif isinstance(author, str):
54+
errors.append(f"'author' must be an object with a 'name' key, got string: {author!r}")
55+
elif not isinstance(author, dict):
56+
errors.append(f"'author' must be an object, got: {type(author).__name__}")
57+
elif "name" not in author or not isinstance(author["name"], str) or not author["name"].strip():
58+
errors.append("'author.name' must be a non-empty string")
59+
60+
# Skills array validation (optional field)
61+
skills = data.get("skills")
62+
if skills is not None:
63+
if not isinstance(skills, list):
64+
errors.append("'skills' must be an array")
65+
else:
66+
for i, skill in enumerate(skills):
67+
if not isinstance(skill, dict):
68+
errors.append(f"skills[{i}]: must be an object")
69+
continue
70+
if "name" not in skill or not isinstance(skill["name"], str) or not skill["name"].strip():
71+
errors.append(f"skills[{i}]: missing 'name'")
72+
if "description" not in skill or not isinstance(skill["description"], str) or not skill["description"].strip():
73+
errors.append(f"skills[{i}]: missing 'description'")
74+
if "path" in skill:
75+
skill_path = os.path.join(plugin_dir, skill["path"])
76+
if not os.path.isfile(skill_path):
77+
errors.append(f"skills[{i}]: path '{skill['path']}' does not exist")
78+
79+
if errors:
80+
for e in errors:
81+
print(f" - {e}", file=sys.stderr)
82+
sys.exit(1)
83+
PYEOF
84+
}
85+
86+
echo "Validating plugin manifests..."
87+
echo ""
88+
89+
while IFS= read -r -d '' manifest; do
90+
plugin_dir="$(dirname "$(dirname "$manifest")")"
91+
plugin_name="$(basename "$plugin_dir")"
92+
CHECKED=$((CHECKED + 1))
93+
94+
if validate_plugin "$manifest" 2>/tmp/plugin-validate-err; then
95+
echo "$plugin_name"
96+
else
97+
echo "$plugin_name"
98+
while IFS= read -r line; do
99+
echo " $line"
100+
done < /tmp/plugin-validate-err
101+
FAILED=$((FAILED + 1))
102+
fi
103+
done < <(find "$REPO_ROOT" -path "*/.claude-plugin/plugin.json" -not -path "*/node_modules/*" -print0 | sort -z)
104+
105+
echo ""
106+
echo "Checked $CHECKED plugin(s). $FAILED failed."
107+
108+
if [[ $FAILED -gt 0 ]]; then
109+
exit 1
110+
fi

0 commit comments

Comments
 (0)