diff --git a/.github/scripts/validate-marketplace.ts b/.github/scripts/validate-marketplace.ts new file mode 100644 index 0000000..45f2600 --- /dev/null +++ b/.github/scripts/validate-marketplace.ts @@ -0,0 +1,77 @@ +#!/usr/bin/env bun +/** + * Validates marketplace.json: well-formed JSON, plugins array present, + * each entry has required fields, and no duplicate plugin names. + * + * Usage: + * bun validate-marketplace.ts + */ + +import { readFile } from "fs/promises"; + +async function main() { + const filePath = process.argv[2]; + if (!filePath) { + console.error("Usage: validate-marketplace.ts "); + process.exit(2); + } + + const content = await readFile(filePath, "utf-8"); + + let parsed: unknown; + try { + parsed = JSON.parse(content); + } catch (err) { + console.error( + `ERROR: ${filePath} is not valid JSON: ${err instanceof Error ? err.message : err}` + ); + process.exit(1); + } + + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + console.error(`ERROR: ${filePath} must be a JSON object`); + process.exit(1); + } + + const marketplace = parsed as Record; + if (!Array.isArray(marketplace.plugins)) { + console.error(`ERROR: ${filePath} missing "plugins" array`); + process.exit(1); + } + + const errors: string[] = []; + const seen = new Set(); + const required = ["name", "description", "source"] as const; + + marketplace.plugins.forEach((p, i) => { + if (!p || typeof p !== "object") { + errors.push(`plugins[${i}]: must be an object`); + return; + } + const entry = p as Record; + for (const field of required) { + if (!entry[field]) { + errors.push(`plugins[${i}] (${entry.name ?? "?"}): missing required field "${field}"`); + } + } + if (typeof entry.name === "string") { + if (seen.has(entry.name)) { + errors.push(`plugins[${i}]: duplicate plugin name "${entry.name}"`); + } + seen.add(entry.name); + } + }); + + if (errors.length) { + console.error(`ERROR: ${filePath} has ${errors.length} validation error(s):`); + for (const e of errors) console.error(` - ${e}`); + process.exit(1); + } + + console.log(`OK: ${marketplace.plugins.length} plugins, no duplicates, all required fields present`); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(2); +}); diff --git a/.github/workflows/validate-marketplace.yml b/.github/workflows/validate-marketplace.yml new file mode 100644 index 0000000..96ff599 --- /dev/null +++ b/.github/workflows/validate-marketplace.yml @@ -0,0 +1,17 @@ +name: Validate Marketplace JSON + +on: + pull_request: + paths: + - '.claude-plugin/marketplace.json' + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Validate marketplace.json + run: bun .github/scripts/validate-marketplace.ts .claude-plugin/marketplace.json