#!/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 */ import { readFile } from "fs/promises"; async function main() { const filePath = process.argv[2]; if (!filePath) { console.error("Usage: validate-marketplace.ts "); 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; if (!Array.isArray(marketplace.plugins)) { console.error(`ERROR: ${filePath} missing "plugins" array`); process.exit(1); } 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) => { console.error("Fatal error:", err); process.exit(2); });