mirror of
https://github.com/anthropics/claude-plugins-official.git
synced 2026-05-13 16:02:41 +00:00
Adopt validate-plugins action suite; pin all external SHAs (#1762)
* Adopt validate-plugins action suite; pin all external SHAs Replaces the hand-rolled marketplace validator and bot-based bump workflow with the shared composite actions (pinned at f846a0b). marketplace.json: - 62 external entries that were missing a `sha` are now pinned to their current upstream HEAD (resolved via git ls-remote). Workflows: - validate-plugins.yml: invariants I1-I11 + claude plugin validate + diff-gated clone-at-SHA validation of changed external entries. SHA-pin (I5) is a hard error. I8/I11 stay warnings until the 15 known data issues (vendored dirs without manifests; one dotted name) are cleaned up. - bump-plugin-shas.yml: bot-free weekly refresh. Validates each new SHA with claude plugin validate before opening one PR; works with the default GITHUB_TOKEN (contents:write + pull-requests:write). - scan-plugins.yml: Claude policy scan of changed external entries. Non-blocking; graceful no-op if ANTHROPIC_API_KEY isn't set. Removed: - validate-marketplace.yml + the two TS helper scripts (superseded by step 11/20 of validate-plugins). validate-frontmatter.yml is kept — it's complementary (targeted checks on agent/skill/command files for in-repo plugins). * Remove 5 external entries that fail validation at HEAD Step 30 (clone at pinned SHA + claude plugin validate) fails for these at their current HEAD: aiven Unrecognized key "logo" in plugin.json atlassian-forge-skills skill YAML frontmatter parse error sagemaker-ai skill YAML frontmatter parse error speakai no plugin manifest at repo root stagehand no plugin manifest at repo root These can be re-added once the upstream repos are fixed. * Wire scan-plugins to the detailed policy prompt Adds .github/policy/prompt.md and schema.json (the full security review rubric — malicious code, privacy, deception, safety circumvention, exfiltration; plus network-call and software-install flags) and points scan-plugins at it via the policy-prompt input. With ANTHROPIC_API_KEY now configured on the repo, scan-plugins runs the actual policy review on changed external entries instead of no-op'ing. * Bump scan-plugins action pin to include L11/L12 fixes
This commit is contained in:
42
.github/scripts/check-marketplace-sorted.ts
vendored
42
.github/scripts/check-marketplace-sorted.ts
vendored
@@ -1,42 +0,0 @@
|
||||
#!/usr/bin/env bun
|
||||
/**
|
||||
* Checks that marketplace.json plugins are alphabetically sorted by name.
|
||||
*
|
||||
* Usage:
|
||||
* bun check-marketplace-sorted.ts # check, exit 1 if unsorted
|
||||
* bun check-marketplace-sorted.ts --fix # sort in place
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
|
||||
const MARKETPLACE = join(import.meta.dir, "../../.claude-plugin/marketplace.json");
|
||||
|
||||
type Plugin = { name: string; [k: string]: unknown };
|
||||
type Marketplace = { plugins: Plugin[]; [k: string]: unknown };
|
||||
|
||||
const raw = readFileSync(MARKETPLACE, "utf8");
|
||||
const mp: Marketplace = JSON.parse(raw);
|
||||
|
||||
const cmp = (a: Plugin, b: Plugin) =>
|
||||
a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
|
||||
if (process.argv.includes("--fix")) {
|
||||
mp.plugins.sort(cmp);
|
||||
writeFileSync(MARKETPLACE, JSON.stringify(mp, null, 2) + "\n");
|
||||
console.log(`sorted ${mp.plugins.length} plugins`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
for (let i = 1; i < mp.plugins.length; i++) {
|
||||
if (cmp(mp.plugins[i - 1], mp.plugins[i]) > 0) {
|
||||
console.error(
|
||||
`marketplace.json plugins are not sorted: ` +
|
||||
`'${mp.plugins[i - 1].name}' should come after '${mp.plugins[i].name}' (index ${i})`,
|
||||
);
|
||||
console.error(` run: bun .github/scripts/check-marketplace-sorted.ts --fix`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`ok: ${mp.plugins.length} plugins sorted`);
|
||||
77
.github/scripts/validate-marketplace.ts
vendored
77
.github/scripts/validate-marketplace.ts
vendored
@@ -1,77 +0,0 @@
|
||||
#!/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 <path-to-marketplace.json>
|
||||
*/
|
||||
|
||||
import { readFile } from "fs/promises";
|
||||
|
||||
async function main() {
|
||||
const filePath = process.argv[2];
|
||||
if (!filePath) {
|
||||
console.error("Usage: validate-marketplace.ts <path-to-marketplace.json>");
|
||||
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<string, unknown>;
|
||||
if (!Array.isArray(marketplace.plugins)) {
|
||||
console.error(`ERROR: ${filePath} missing "plugins" array`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
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) => {
|
||||
console.error("Fatal error:", err);
|
||||
process.exit(2);
|
||||
});
|
||||
Reference in New Issue
Block a user