validate-marketplace.ts 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. #!/usr/bin/env bun
  2. /**
  3. * Validates marketplace.json: well-formed JSON, plugins array present,
  4. * each entry has required fields, and no duplicate plugin names.
  5. *
  6. * Usage:
  7. * bun validate-marketplace.ts <path-to-marketplace.json>
  8. */
  9. import { readFile } from "fs/promises";
  10. async function main() {
  11. const filePath = process.argv[2];
  12. if (!filePath) {
  13. console.error("Usage: validate-marketplace.ts <path-to-marketplace.json>");
  14. process.exit(2);
  15. }
  16. const content = await readFile(filePath, "utf-8");
  17. let parsed: unknown;
  18. try {
  19. parsed = JSON.parse(content);
  20. } catch (err) {
  21. console.error(
  22. `ERROR: ${filePath} is not valid JSON: ${err instanceof Error ? err.message : err}`
  23. );
  24. process.exit(1);
  25. }
  26. if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
  27. console.error(`ERROR: ${filePath} must be a JSON object`);
  28. process.exit(1);
  29. }
  30. const marketplace = parsed as Record<string, unknown>;
  31. if (!Array.isArray(marketplace.plugins)) {
  32. console.error(`ERROR: ${filePath} missing "plugins" array`);
  33. process.exit(1);
  34. }
  35. const errors: string[] = [];
  36. const seen = new Set<string>();
  37. const required = ["name", "description", "source"] as const;
  38. marketplace.plugins.forEach((p, i) => {
  39. if (!p || typeof p !== "object") {
  40. errors.push(`plugins[${i}]: must be an object`);
  41. return;
  42. }
  43. const entry = p as Record<string, unknown>;
  44. for (const field of required) {
  45. if (!entry[field]) {
  46. errors.push(`plugins[${i}] (${entry.name ?? "?"}): missing required field "${field}"`);
  47. }
  48. }
  49. if (typeof entry.name === "string") {
  50. if (seen.has(entry.name)) {
  51. errors.push(`plugins[${i}]: duplicate plugin name "${entry.name}"`);
  52. }
  53. seen.add(entry.name);
  54. }
  55. });
  56. if (errors.length) {
  57. console.error(`ERROR: ${filePath} has ${errors.length} validation error(s):`);
  58. for (const e of errors) console.error(` - ${e}`);
  59. process.exit(1);
  60. }
  61. console.log(`OK: ${marketplace.plugins.length} plugins, no duplicates, all required fields present`);
  62. }
  63. main().catch((err) => {
  64. console.error("Fatal error:", err);
  65. process.exit(2);
  66. });