Selaa lähdekoodia

Add marketplace.json validation CI workflow (#347)

* Add CI workflow to validate marketplace.json on PRs

Add a GitHub Actions workflow that validates marketplace.json is
well-formed JSON with a plugins array whenever PRs modify it. Includes:
- validate-marketplace.ts: Bun script that parses and validates the JSON
- validate-marketplace.yml: GH Actions workflow triggered on PR changes
- test-marketplace-check.js: Unit tests for the validation logic

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Strengthen marketplace validator and remove orphaned test file

- validate-marketplace.ts: check duplicate names and required fields
  (name, description, source) per entry, not just valid JSON
- remove .github/workflows/test-marketplace-check.js: tested a
  checkMarketplaceViolations function that doesn't exist in the PR,
  and was in workflows/ instead of scripts/

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Tobin South <tobin.south@gmail.com>
Noah Zweben 5 kuukautta sitten
vanhempi
sitoutus
158ef95c6f

+ 77 - 0
.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 <path-to-marketplace.json>
+ */
+
+import { readFile } from "fs/promises";
+
+async function main() {
+  const filePath = process.argv[2];
+  if (!filePath) {
+    console.error("Usage: validate-marketplace.ts <path-to-marketplace.json>");
+    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<string, unknown>;
+  if (!Array.isArray(marketplace.plugins)) {
+    console.error(`ERROR: ${filePath} missing "plugins" array`);
+    process.exit(1);
+  }
+
+  const errors: string[] = [];
+  const seen = new Set<string>();
+  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<string, unknown>;
+    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);
+});

+ 17 - 0
.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