build: prepare external plugin beta publishing

This commit is contained in:
Peter Steinberger
2026-05-02 02:58:17 +01:00
parent 4c9390a36e
commit 0a6c9ca9ee
10 changed files with 188 additions and 14 deletions

View File

@@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai
### Changes
- Plugins/source checkout: load bundled plugins from the `extensions/*` pnpm workspace tree in source checkouts, so plugin-local dependencies and edits are used directly while packaged installs keep using the built runtime tree. Thanks @vincentkoc.
- Plugins/beta: prepare Brave, Codex, Feishu, Synology Chat, Tlon, and Twitch for `2026.5.1-beta.1` npm and ClawHub publishing. Thanks @vincentkoc.
- Providers/xAI: add Grok 4.3 to the bundled catalog and make it the default xAI chat model.
- Plugins/ClawHub: prefer versioned ClawPack artifacts when ClawHub publishes digest metadata, verifying the ClawPack response header and downloaded bytes before installing. Thanks @vincentkoc.
- Plugins/ClawHub: persist ClawPack digest metadata on ClawHub plugin install and update records so registry refreshes and download verification can reuse stored artifact facts. Thanks @vincentkoc.

View File

@@ -1,8 +1,11 @@
{
"name": "@openclaw/brave-plugin",
"version": "2026.4.25",
"private": true,
"version": "2026.5.1-beta.1",
"description": "OpenClaw Brave plugin",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"
@@ -10,6 +13,21 @@
"openclaw": {
"extensions": [
"./index.ts"
]
],
"install": {
"npmSpec": "@openclaw/brave-plugin",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.4.10"
},
"build": {
"openclawVersion": "2026.5.1-beta.1"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View File

@@ -1,7 +1,11 @@
{
"name": "@openclaw/codex",
"version": "2026.4.25",
"version": "2026.5.1-beta.1",
"description": "OpenClaw Codex harness and model provider plugin",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"dependencies": {
"@mariozechner/pi-coding-agent": "0.71.1",
@@ -16,6 +20,21 @@
"openclaw": {
"extensions": [
"./index.ts"
]
],
"install": {
"npmSpec": "@openclaw/codex",
"defaultChoice": "npm",
"minHostVersion": ">=2026.5.1-beta.1"
},
"compat": {
"pluginApi": ">=2026.5.1-beta.1"
},
"build": {
"openclawVersion": "2026.5.1-beta.1"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View File

@@ -1,7 +1,11 @@
{
"name": "@openclaw/feishu",
"version": "2026.4.25",
"version": "2026.5.1-beta.1",
"description": "OpenClaw Feishu/Lark channel plugin (community maintained by @m1heng)",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"dependencies": {
"@larksuiteoapi/node-sdk": "^1.62.1",
@@ -46,7 +50,7 @@
"pluginApi": ">=2026.4.25"
},
"build": {
"openclawVersion": "2026.4.25"
"openclawVersion": "2026.5.1-beta.1"
},
"release": {
"publishToClawHub": true,

View File

@@ -1,7 +1,11 @@
{
"name": "@openclaw/synology-chat",
"version": "2026.4.25",
"version": "2026.5.1-beta.1",
"description": "Synology Chat channel plugin for OpenClaw",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"dependencies": {
"zod": "^4.4.1"
@@ -27,6 +31,16 @@
"npmSpec": "@openclaw/synology-chat",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.4.10"
},
"build": {
"openclawVersion": "2026.5.1-beta.1"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View File

@@ -1,7 +1,11 @@
{
"name": "@openclaw/tlon",
"version": "2026.4.25",
"version": "2026.5.1-beta.1",
"description": "OpenClaw Tlon/Urbit channel plugin",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"dependencies": {
"@aws-sdk/client-s3": "3.1041.0",
@@ -66,6 +70,16 @@
"npmSpec": "@openclaw/tlon",
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.4.10"
},
"build": {
"openclawVersion": "2026.5.1-beta.1"
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View File

@@ -25,6 +25,12 @@
"defaultChoice": "npm",
"minHostVersion": ">=2026.4.10"
},
"compat": {
"pluginApi": ">=2026.4.10"
},
"build": {
"openclawVersion": "2026.5.1-beta.1"
},
"channel": {
"id": "twitch",
"label": "Twitch",
@@ -36,6 +42,7 @@
]
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}

View File

@@ -73,6 +73,11 @@ export type PluginReleasePlan = {
skippedPublished: PluginReleasePlanItem[];
};
export type ClawHubPublishablePluginPackageFilters = {
extensionIds?: readonly string[];
packageNames?: readonly string[];
};
const CLAWHUB_DEFAULT_REGISTRY = "https://clawhub.ai";
const SAFE_EXTENSION_ID_RE = /^[a-z0-9][a-z0-9._-]*$/;
const CLAWHUB_SHARED_RELEASE_INPUT_PATHS = [
@@ -101,12 +106,24 @@ function getRegistryBaseUrl(explicit?: string) {
export function collectClawHubPublishablePluginPackages(
rootDir = resolve("."),
filters: ClawHubPublishablePluginPackageFilters = {},
): PublishablePluginPackage[] {
const publishable: PublishablePluginPackage[] = [];
const validationErrors: string[] = [];
const selectedExtensionIds = new Set(filters.extensionIds ?? []);
const selectedPackageNames = new Set(filters.packageNames ?? []);
const hasSelectedExtensionIds = Array.isArray(filters.extensionIds);
const hasSelectedPackageNames = Array.isArray(filters.packageNames);
for (const candidate of collectExtensionPackageJsonCandidates(rootDir)) {
const { extensionId, packageDir, packageJson } = candidate;
if (hasSelectedExtensionIds && !selectedExtensionIds.has(extensionId)) {
continue;
}
const packageName = packageJson.name?.trim() ?? "";
if (hasSelectedPackageNames && !selectedPackageNames.has(packageName)) {
continue;
}
if (packageJson.openclaw?.release?.publishToClawHub !== true) {
continue;
}
@@ -147,7 +164,7 @@ export function collectClawHubPublishablePluginPackages(
publishable.push({
extensionId,
packageDir,
packageName: packageJson.name!.trim(),
packageName,
version,
channel: parsedVersion.channel,
publishTag: parsedVersion.channel === "beta" ? "beta" : "latest",
@@ -342,13 +359,29 @@ export async function collectPluginClawHubReleasePlan(params?: {
registryBaseUrl?: string;
fetchImpl?: typeof fetch;
}): Promise<PluginReleasePlan> {
const allPublishable = collectClawHubPublishablePluginPackages(params?.rootDir);
const rootDir = params?.rootDir;
const selection = params?.selection ?? [];
const changedPaths = params?.gitRange
? collectPluginClawHubRelevantPathsFromGitRange({
rootDir,
gitRange: params.gitRange,
})
: [];
const sharedInputChanged = hasSharedClawHubReleaseInputChanges(changedPaths);
const extensionIds =
params?.selectionMode === "all-publishable" || !params?.gitRange || sharedInputChanged
? undefined
: collectChangedExtensionIdsFromPaths(changedPaths);
const allPublishable = collectClawHubPublishablePluginPackages(rootDir, {
extensionIds,
packageNames: selection.length > 0 ? selection : undefined,
});
const selectedPublishable = resolveSelectedClawHubPublishablePluginPackages({
plugins: allPublishable,
selection: params?.selection,
selection,
selectionMode: params?.selectionMode,
gitRange: params?.gitRange,
rootDir: params?.rootDir,
rootDir,
});
const all = await Promise.all(

View File

@@ -10,7 +10,10 @@ import {
export async function runPluginClawHubReleaseCheck(argv: string[]) {
const { selection, selectionMode, baseRef, headRef } = parsePluginReleaseArgs(argv);
const publishable = collectClawHubPublishablePluginPackages();
const publishable = collectClawHubPublishablePluginPackages(".", {
packageNames:
selectionMode === "all-publishable" || selection.length === 0 ? undefined : selection,
});
const gitRange = baseRef && headRef ? { baseRef, headRef } : undefined;
const selected = resolveSelectedClawHubPublishablePluginPackages({
plugins: publishable,

View File

@@ -69,6 +69,35 @@ describe("collectClawHubPublishablePluginPackages", () => {
"Demo Plugin: extension directory name must match",
);
});
it("validates only selected package names when filters are provided", () => {
const repoDir = createTempPluginRepo({
extraExtensionIds: ["broken-plugin"],
});
writeFileSync(
join(repoDir, "extensions", "broken-plugin", "package.json"),
JSON.stringify(
{
name: "@openclaw/broken-plugin",
version: "2026.4.1",
openclaw: {
extensions: ["./index.ts"],
release: {
publishToClawHub: true,
},
},
},
null,
2,
),
);
expect(
collectClawHubPublishablePluginPackages(repoDir, {
packageNames: ["@openclaw/demo-plugin"],
}).map((plugin) => plugin.packageName),
).toEqual(["@openclaw/demo-plugin"]);
});
});
describe("collectClawHubVersionGateErrors", () => {
@@ -238,6 +267,38 @@ describe("collectPluginClawHubReleasePlan", () => {
version: "2026.4.1",
});
});
it("plans selected packages without validating unrelated publishable packages", async () => {
const repoDir = createTempPluginRepo({
extraExtensionIds: ["broken-plugin"],
});
writeFileSync(
join(repoDir, "extensions", "broken-plugin", "package.json"),
JSON.stringify(
{
name: "@openclaw/broken-plugin",
version: "2026.4.1",
openclaw: {
extensions: ["./index.ts"],
release: {
publishToClawHub: true,
},
},
},
null,
2,
),
);
const plan = await collectPluginClawHubReleasePlan({
rootDir: repoDir,
selection: ["@openclaw/demo-plugin"],
fetchImpl: async () => new Response("{}", { status: 404 }),
registryBaseUrl: "https://clawhub.ai",
});
expect(plan.candidates.map((plugin) => plugin.packageName)).toEqual(["@openclaw/demo-plugin"]);
});
});
describe("collectPluginClawHubReleasePathsFromGitRange", () => {