From 9827490f5f735a567716f128ffcdb892d9d39324 Mon Sep 17 00:00:00 2001 From: Patrick Erichsen Date: Thu, 11 Jun 2026 11:45:06 -0700 Subject: [PATCH] fix: rely on ClawHub plugin publish checks --- .github/workflows/plugin-clawhub-release.yml | 6 -- scripts/lib/plugin-clawhub-release.ts | 75 -------------------- scripts/plugin-clawhub-owner-preflight.ts | 45 ------------ test/plugin-clawhub-release.test.ts | 73 ------------------- 4 files changed, 199 deletions(-) delete mode 100644 scripts/plugin-clawhub-owner-preflight.ts diff --git a/.github/workflows/plugin-clawhub-release.yml b/.github/workflows/plugin-clawhub-release.yml index 8726dd66c3b4..77b7a833da05 100644 --- a/.github/workflows/plugin-clawhub-release.yml +++ b/.github/workflows/plugin-clawhub-release.yml @@ -197,12 +197,6 @@ jobs: exit 1 fi - - name: Verify OpenClaw ClawHub package ownership - if: steps.plan.outputs.has_candidates == 'true' - env: - CLAWHUB_REGISTRY: ${{ env.CLAWHUB_REGISTRY }} - run: node --import tsx scripts/plugin-clawhub-owner-preflight.ts .local/plugin-clawhub-release-plan.json - validate_release_publish_approval: name: Validate release publish approval needs: preview_plugins_clawhub diff --git a/scripts/lib/plugin-clawhub-release.ts b/scripts/lib/plugin-clawhub-release.ts index 3cae2408440a..b75834da3f6b 100644 --- a/scripts/lib/plugin-clawhub-release.ts +++ b/scripts/lib/plugin-clawhub-release.ts @@ -2,7 +2,6 @@ import { execFileSync } from "node:child_process"; import { resolve } from "node:path"; import { validateExternalCodePluginPackageJson } from "../../packages/plugin-package-contract/src/index.ts"; -import { readBoundedResponseText } from "./bounded-response.ts"; import { collectExtensionPackageJsonCandidates, collectChangedPathsFromGitRange, @@ -64,19 +63,12 @@ type PluginReleasePlan = { skippedPublished: PluginReleasePlanItem[]; }; -type ClawHubPackageOwnerDetail = { - owner?: { - handle?: unknown; - } | null; -}; - type ClawHubPublishablePluginPackageFilters = { extensionIds?: readonly string[]; packageNames?: readonly string[]; }; const CLAWHUB_DEFAULT_REGISTRY = "https://clawhub.ai"; -const CLAWHUB_RESPONSE_BODY_MAX_BYTES = 1024 * 1024; const SAFE_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]*$/; const CLAWHUB_SHARED_RELEASE_INPUT_PATHS = [ ".github/workflows/plugin-clawhub-release.yml", @@ -88,7 +80,6 @@ const CLAWHUB_SHARED_RELEASE_INPUT_PATHS = [ "scripts/lib/npm-publish-plan.mjs", "scripts/lib/plugin-npm-release.ts", "scripts/lib/plugin-clawhub-release.ts", - "scripts/plugin-clawhub-owner-preflight.ts", "scripts/openclaw-npm-release-check.ts", "scripts/plugin-clawhub-publish.sh", "scripts/plugin-clawhub-release-check.ts", @@ -114,19 +105,6 @@ function formatClawHubPackageArtifactName( return `clawhub-package-${safeName}-${plugin.version}`; } -async function readClawHubPackageOwnerDetail( - response: Response, - packageName: string, -): Promise { - return JSON.parse( - await readBoundedResponseText( - response, - `ClawHub package owner response for ${packageName}`, - CLAWHUB_RESPONSE_BODY_MAX_BYTES, - ), - ) as ClawHubPackageOwnerDetail; -} - export function collectClawHubPublishablePluginPackages( rootDir = resolve("."), filters: ClawHubPublishablePluginPackageFilters = {}, @@ -379,59 +357,6 @@ async function isPluginVersionPublishedOnClawHub( ); } -export async function collectClawHubOpenClawOwnerErrors(params: { - plugins: readonly Pick[]; - requiredOwnerHandle?: string; - registryBaseUrl?: string; - fetchImpl?: typeof fetch; -}): Promise { - const fetchImpl = params.fetchImpl ?? fetch; - const requiredOwnerHandle = params.requiredOwnerHandle ?? "openclaw"; - const errors: string[] = []; - - await Promise.all( - params.plugins.map(async (plugin) => { - if (!plugin.packageName.startsWith("@openclaw/")) { - return; - } - - const url = new URL( - `/api/v1/packages/${encodeURIComponent(plugin.packageName)}`, - getRegistryBaseUrl(params.registryBaseUrl), - ); - const response = await fetchImpl(url, { - method: "GET", - headers: { - Accept: "application/json", - }, - }); - - if (response.status === 404) { - errors.push( - `${plugin.packageName}: ClawHub package row must already exist under @${requiredOwnerHandle} before OpenClaw release publish.`, - ); - return; - } - if (!response.ok) { - errors.push( - `${plugin.packageName}: failed to query ClawHub owner: ${response.status} ${response.statusText}`, - ); - return; - } - - const detail = await readClawHubPackageOwnerDetail(response, plugin.packageName); - const ownerHandle = typeof detail.owner?.handle === "string" ? detail.owner.handle : null; - if (ownerHandle !== requiredOwnerHandle) { - errors.push( - `${plugin.packageName}: ClawHub package owner must be @${requiredOwnerHandle}; got ${ownerHandle ? `@${ownerHandle}` : ""}.`, - ); - } - }), - ); - - return errors.toSorted(); -} - export async function collectPluginClawHubReleasePlan(params?: { rootDir?: string; selection?: string[]; diff --git a/scripts/plugin-clawhub-owner-preflight.ts b/scripts/plugin-clawhub-owner-preflight.ts deleted file mode 100644 index dcb55d5a54aa..000000000000 --- a/scripts/plugin-clawhub-owner-preflight.ts +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env -S node --import tsx -// Plugin Clawhub Owner Preflight script supports OpenClaw repository automation. - -import { readFileSync } from "node:fs"; -import { pathToFileURL } from "node:url"; -import { collectClawHubOpenClawOwnerErrors } from "./lib/plugin-clawhub-release.ts"; - -type ReleasePlanFile = { - candidates?: Array<{ - packageName?: unknown; - }>; -}; - -export async function runClawHubOwnerPreflight(argv: string[]) { - const planPath = argv[0]; - if (!planPath) { - throw new Error("usage: plugin-clawhub-owner-preflight.ts "); - } - - const parsed = JSON.parse(readFileSync(planPath, "utf8")) as ReleasePlanFile; - const candidates = (parsed.candidates ?? []) - .filter( - (candidate): candidate is { packageName: string } => - typeof candidate.packageName === "string", - ) - .map((candidate) => ({ packageName: candidate.packageName })); - - const errors = await collectClawHubOpenClawOwnerErrors({ plugins: candidates }); - if (errors.length > 0) { - throw new Error( - `ClawHub OpenClaw package ownership preflight failed:\n${errors.map((error) => `- ${error}`).join("\n")}`, - ); - } - - console.log(`ClawHub OpenClaw owner preflight passed for ${candidates.length} candidate(s).`); -} - -if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) { - try { - await runClawHubOwnerPreflight(process.argv.slice(2)); - } catch (error) { - console.error(error instanceof Error ? error.message : String(error)); - process.exit(1); - } -} diff --git a/test/plugin-clawhub-release.test.ts b/test/plugin-clawhub-release.test.ts index a0cbfc157d68..66a31615f8db 100644 --- a/test/plugin-clawhub-release.test.ts +++ b/test/plugin-clawhub-release.test.ts @@ -12,7 +12,6 @@ import { delimiter, join } from "node:path"; import { afterEach, describe, expect, it } from "vitest"; import { collectClawHubPublishablePluginPackages, - collectClawHubOpenClawOwnerErrors, collectClawHubVersionGateErrors, collectPluginClawHubReleasePathsFromGitRange, collectPluginClawHubReleasePlan, @@ -381,78 +380,6 @@ describe("collectPluginClawHubReleasePlan", () => { }); }); -describe("collectClawHubOpenClawOwnerErrors", () => { - it("requires OpenClaw-scoped release candidates to already belong to the OpenClaw publisher", async () => { - const errors = await collectClawHubOpenClawOwnerErrors({ - plugins: [ - { packageName: "@openclaw/demo-plugin" }, - { packageName: "@openclaw/missing-plugin" }, - { packageName: "@other/safe-plugin" }, - ], - registryBaseUrl: "https://clawhub.ai", - fetchImpl: async (url) => { - const pathname = new URL(url instanceof Request ? url.url : url).pathname; - if (pathname.includes("%40openclaw%2Fmissing-plugin")) { - return new Response("not found", { status: 404 }); - } - return new Response( - JSON.stringify({ - owner: { handle: "steipete" }, - }), - { status: 200, headers: { "Content-Type": "application/json" } }, - ); - }, - }); - - expect(errors).toEqual([ - "@openclaw/demo-plugin: ClawHub package owner must be @openclaw; got @steipete.", - "@openclaw/missing-plugin: ClawHub package row must already exist under @openclaw before OpenClaw release publish.", - ]); - }); - - it("passes when OpenClaw-scoped release candidates belong to the OpenClaw publisher", async () => { - const errors = await collectClawHubOpenClawOwnerErrors({ - plugins: [{ packageName: "@openclaw/demo-plugin" }], - registryBaseUrl: "https://clawhub.ai", - fetchImpl: async () => - new Response(JSON.stringify({ owner: { handle: "openclaw" } }), { - status: 200, - headers: { "Content-Type": "application/json" }, - }), - }); - - expect(errors).toStrictEqual([]); - }); - - it("bounds ClawHub owner metadata response bodies", async () => { - await expect( - collectClawHubOpenClawOwnerErrors({ - plugins: [{ packageName: "@openclaw/demo-plugin" }], - registryBaseUrl: "https://clawhub.ai", - fetchImpl: async () => - new Response("{}", { - status: 200, - headers: { "content-length": String(1024 * 1024 + 1) }, - }), - }), - ).rejects.toThrow( - "ClawHub package owner response for @openclaw/demo-plugin response body exceeded 1048576 bytes.", - ); - }); - - it("bounds streamed ClawHub owner metadata bodies", async () => { - await expect( - collectClawHubOpenClawOwnerErrors({ - plugins: [{ packageName: "@openclaw/demo-plugin" }], - registryBaseUrl: "https://clawhub.ai", - fetchImpl: async () => new Response("x".repeat(1024 * 1024 + 1), { status: 200 }), - }), - ).rejects.toThrow( - "ClawHub package owner response for @openclaw/demo-plugin response body exceeded 1048576 bytes.", - ); - }); -}); - describe("plugin-clawhub-publish.sh", () => { it("previews the publish command through the ClawHub CLI dry-run preflight", () => { const repoDir = createTempPluginRepo();