diff --git a/.github/scripts/validate-marketplace.ts b/.github/scripts/validate-marketplace.ts index 69692df..45f2600 100644 --- a/.github/scripts/validate-marketplace.ts +++ b/.github/scripts/validate-marketplace.ts @@ -1,6 +1,7 @@ #!/usr/bin/env bun /** - * Validates that marketplace.json is well-formed JSON with a plugins array. + * Validates marketplace.json: well-formed JSON, plugins array present, + * each entry has required fields, and no duplicate plugin names. * * Usage: * bun validate-marketplace.ts @@ -38,9 +39,36 @@ async function main() { process.exit(1); } - console.log( - `marketplace.json is valid (${marketplace.plugins.length} plugins)` - ); + 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) => { diff --git a/.github/workflows/test-marketplace-check.js b/.github/workflows/test-marketplace-check.js deleted file mode 100644 index bccbb77..0000000 --- a/.github/workflows/test-marketplace-check.js +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env node - -/** - * Test script for marketplace.json PR validation logic. - * Run with: node .github/workflows/test-marketplace-check.js - */ - -function checkMarketplaceViolations(mainPlugins, prPlugins) { - const mainSourceByName = new Map( - mainPlugins.map(p => [p.name, JSON.stringify(p.source)]) - ); - - const violations = []; - for (const plugin of prPlugins) { - if (!mainSourceByName.has(plugin.name)) { - violations.push(`- Adding new plugin: \`${plugin.name}\``); - } else if (mainSourceByName.get(plugin.name) !== JSON.stringify(plugin.source)) { - violations.push(`- Changing source for plugin: \`${plugin.name}\``); - } - } - - return violations; -} - -// Test cases -const tests = [ - { - name: "No changes - should allow", - main: [ - { name: "foo", source: "./plugins/foo", description: "Foo plugin" } - ], - pr: [ - { name: "foo", source: "./plugins/foo", description: "Foo plugin" } - ], - expectBlocked: false - }, - { - name: "Description change only - should allow", - main: [ - { name: "foo", source: "./plugins/foo", description: "Old description" } - ], - pr: [ - { name: "foo", source: "./plugins/foo", description: "New description" } - ], - expectBlocked: false - }, - { - name: "Version/category change - should allow", - main: [ - { name: "foo", source: "./plugins/foo", version: "1.0.0", category: "dev" } - ], - pr: [ - { name: "foo", source: "./plugins/foo", version: "2.0.0", category: "productivity" } - ], - expectBlocked: false - }, - { - name: "New plugin added - should block", - main: [ - { name: "foo", source: "./plugins/foo" } - ], - pr: [ - { name: "foo", source: "./plugins/foo" }, - { name: "bar", source: "./plugins/bar" } - ], - expectBlocked: true, - expectedViolation: "Adding new plugin: `bar`" - }, - { - name: "Source changed (string) - should block", - main: [ - { name: "foo", source: "./plugins/foo" } - ], - pr: [ - { name: "foo", source: "./plugins/evil" } - ], - expectBlocked: true, - expectedViolation: "Changing source for plugin: `foo`" - }, - { - name: "Source changed (string to object) - should block", - main: [ - { name: "foo", source: "./plugins/foo" } - ], - pr: [ - { name: "foo", source: { source: "url", url: "https://evil.com/repo.git" } } - ], - expectBlocked: true, - expectedViolation: "Changing source for plugin: `foo`" - }, - { - name: "Source changed (object URL) - should block", - main: [ - { name: "foo", source: { source: "url", url: "https://github.com/good/repo.git" } } - ], - pr: [ - { name: "foo", source: { source: "url", url: "https://github.com/evil/repo.git" } } - ], - expectBlocked: true, - expectedViolation: "Changing source for plugin: `foo`" - }, - { - name: "Plugin removed - should allow", - main: [ - { name: "foo", source: "./plugins/foo" }, - { name: "bar", source: "./plugins/bar" } - ], - pr: [ - { name: "foo", source: "./plugins/foo" } - ], - expectBlocked: false - }, - { - name: "Multiple violations - should block with all listed", - main: [ - { name: "foo", source: "./plugins/foo" } - ], - pr: [ - { name: "foo", source: "./plugins/evil" }, - { name: "bar", source: "./plugins/bar" } - ], - expectBlocked: true, - expectedViolationCount: 2 - }, - { - name: "Object source unchanged - should allow", - main: [ - { name: "foo", source: { source: "url", url: "https://github.com/org/repo.git" } } - ], - pr: [ - { name: "foo", source: { source: "url", url: "https://github.com/org/repo.git" }, description: "Updated" } - ], - expectBlocked: false - } -]; - -// Run tests -console.log("Running marketplace.json validation tests\n"); -console.log("=".repeat(50)); - -let passed = 0; -let failed = 0; - -for (const test of tests) { - const violations = checkMarketplaceViolations(test.main, test.pr); - const blocked = violations.length > 0; - - let success = blocked === test.expectBlocked; - - if (success && test.expectedViolation) { - success = violations.some(v => v.includes(test.expectedViolation)); - } - - if (success && test.expectedViolationCount) { - success = violations.length === test.expectedViolationCount; - } - - if (success) { - console.log(`✓ ${test.name}`); - passed++; - } else { - console.log(`✗ ${test.name}`); - console.log(` Expected blocked: ${test.expectBlocked}, got: ${blocked}`); - if (violations.length > 0) { - console.log(` Violations: ${violations.join(", ")}`); - } - failed++; - } -} - -console.log("=".repeat(50)); -console.log(`\nResults: ${passed} passed, ${failed} failed`); - -process.exit(failed > 0 ? 1 : 0);