diff --git a/scripts/validate-plugins.mjs b/scripts/validate-plugins.mjs index 6a7870854..672288211 100644 --- a/scripts/validate-plugins.mjs +++ b/scripts/validate-plugins.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node -import { readFileSync, existsSync } from "fs"; +import { readFileSync, existsSync, readdirSync, statSync } from "fs"; import { resolve, dirname } from "path"; import { fileURLToPath } from "url"; import Ajv from "ajv"; @@ -92,7 +92,35 @@ for (const entry of marketplace.plugins ?? []) { } } -// 3. Report results +// 3. Every plugin directory in the repo must be listed in marketplace.json. +// A directory with a .cursor-plugin/plugin.json that marketplace.json does not +// reference is invisible to the marketplace and to the checks above, so a +// forgotten listing (or a typo in `source`) would otherwise pass silently. +const listedDirs = new Set( + (marketplace.plugins ?? []).map((entry) => resolve(root, entry.source)) +); + +function pluginDirsUnder(dir) { + if (!existsSync(dir)) return []; + return readdirSync(dir) + .filter((name) => !name.startsWith(".")) + .map((name) => resolve(dir, name)) + .filter( + (path) => + statSync(path).isDirectory() && + existsSync(resolve(path, ".cursor-plugin/plugin.json")) + ); +} + +for (const dir of [...pluginDirsUnder(root), ...pluginDirsUnder(resolve(root, "third_party"))]) { + if (!listedDirs.has(dir)) { + fail( + `Unlisted plugin: "${dir.slice(root.length + 1)}" has a .cursor-plugin/plugin.json but no entry in marketplace.json` + ); + } +} + +// 4. Report results if (errors > 0) { console.error(`\nValidation failed with ${errors} error(s).`); process.exit(1);