mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-03-16 22:23:07 +00:00
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/
This commit is contained in:
36
.github/scripts/validate-marketplace.ts
vendored
36
.github/scripts/validate-marketplace.ts
vendored
@@ -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 <path-to-marketplace.json>
|
||||
@@ -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<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) => {
|
||||
|
||||
174
.github/workflows/test-marketplace-check.js
vendored
174
.github/workflows/test-marketplace-check.js
vendored
@@ -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);
|
||||
Reference in New Issue
Block a user