From 4a91a6e2665f559f61877f03e36b54886eef359e Mon Sep 17 00:00:00 2001 From: ericzakariasson Date: Wed, 11 Feb 2026 12:57:31 -0800 Subject: [PATCH] Add plugin validation workflow Adds a GitHub Actions workflow that validates marketplace.json and each plugin's plugin.json against their JSON schemas on PRs. Includes a Node.js validation script and adds missing category/tags fields to the plugin schema. Co-authored-by: Cursor --- .github/workflows/validate-plugins.yml | 24 ++++++ schemas/plugin.schema.json | 9 +++ scripts/validate-plugins.mjs | 102 +++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 .github/workflows/validate-plugins.yml create mode 100644 scripts/validate-plugins.mjs diff --git a/.github/workflows/validate-plugins.yml b/.github/workflows/validate-plugins.yml new file mode 100644 index 0000000..03e8e19 --- /dev/null +++ b/.github/workflows/validate-plugins.yml @@ -0,0 +1,24 @@ +name: Validate plugins + +on: + pull_request: + paths: + - ".cursor-plugin/marketplace.json" + - "**/plugin.json" + - "schemas/**" + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: Install dependencies + run: npm install --no-save ajv ajv-formats + + - name: Validate plugin definitions + run: node scripts/validate-plugins.mjs diff --git a/schemas/plugin.schema.json b/schemas/plugin.schema.json index 19c642e..d4c539e 100644 --- a/schemas/plugin.schema.json +++ b/schemas/plugin.schema.json @@ -57,6 +57,15 @@ "items": { "type": "string" }, "description": "Keywords for discovery and search." }, + "category": { + "type": "string", + "description": "Plugin category for marketplace classification." + }, + "tags": { + "type": "array", + "items": { "type": "string" }, + "description": "Tags for filtering and discovery." + }, "commands": { "$ref": "#/$defs/stringOrStringArray", "description": "Glob pattern(s) or path(s) to command files." diff --git a/scripts/validate-plugins.mjs b/scripts/validate-plugins.mjs new file mode 100644 index 0000000..6a78708 --- /dev/null +++ b/scripts/validate-plugins.mjs @@ -0,0 +1,102 @@ +#!/usr/bin/env node + +import { readFileSync, existsSync } from "fs"; +import { resolve, dirname } from "path"; +import { fileURLToPath } from "url"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const root = resolve(__dirname, ".."); + +function loadJSON(path) { + return JSON.parse(readFileSync(path, "utf-8")); +} + +const marketplaceSchema = loadJSON( + resolve(root, "schemas/marketplace.schema.json") +); +const pluginSchema = loadJSON(resolve(root, "schemas/plugin.schema.json")); + +const ajv = new Ajv({ allErrors: true }); +addFormats(ajv); + +const validateMarketplace = ajv.compile(marketplaceSchema); +const validatePlugin = ajv.compile(pluginSchema); + +let errors = 0; + +function fail(message) { + console.error(`ERROR: ${message}`); + errors++; +} + +// 1. Validate marketplace.json +const marketplacePath = resolve(root, ".cursor-plugin/marketplace.json"); + +if (!existsSync(marketplacePath)) { + fail(".cursor-plugin/marketplace.json not found"); + process.exit(1); +} + +const marketplace = loadJSON(marketplacePath); + +if (!validateMarketplace(marketplace)) { + fail("marketplace.json schema validation failed:"); + for (const err of validateMarketplace.errors) { + console.error(` ${err.instancePath || "/"}: ${err.message}`); + } +} + +// 2. Validate each plugin +for (const entry of marketplace.plugins ?? []) { + const pluginDir = resolve(root, entry.source); + const pluginJsonPath = resolve(pluginDir, ".cursor-plugin/plugin.json"); + + // Check source directory exists + if (!existsSync(pluginDir)) { + fail( + `Plugin "${entry.name}": source directory "${entry.source}" does not exist` + ); + continue; + } + + // Check plugin.json exists + if (!existsSync(pluginJsonPath)) { + fail( + `Plugin "${entry.name}": missing .cursor-plugin/plugin.json in "${entry.source}"` + ); + continue; + } + + const pluginJson = loadJSON(pluginJsonPath); + + if (!validatePlugin(pluginJson)) { + fail( + `Plugin "${entry.name}": plugin.json schema validation failed (${entry.source}/.cursor-plugin/plugin.json):` + ); + for (const err of validatePlugin.errors) { + const detail = + err.keyword === "additionalProperties" + ? `${err.message}: "${err.params.additionalProperty}"` + : err.message; + console.error(` ${err.instancePath || "/"}: ${detail}`); + } + } + + // Check that marketplace name matches plugin name + if (pluginJson.name && pluginJson.name !== entry.name) { + fail( + `Plugin "${entry.name}": marketplace name does not match plugin.json name "${pluginJson.name}"` + ); + } +} + +// 3. Report results +if (errors > 0) { + console.error(`\nValidation failed with ${errors} error(s).`); + process.exit(1); +} else { + console.log("All plugins validated successfully."); + process.exit(0); +}