mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-17 19:50:43 +00:00
fix(clawhub): accept live artifact resolver field aliases
Accept live ClawHub artifact resolver kind/sha256 aliases for npm-pack and legacy ZIP installs.\n\nVerified locally after rebase:\n- pnpm exec oxfmt --check --threads=1 src/plugins/clawhub.ts src/plugins/clawhub.test.ts CHANGELOG.md\n- pnpm test:serial src/plugins/clawhub.test.ts
This commit is contained in:
@@ -51,10 +51,12 @@ vi.mock("../infra/archive.js", async () => {
|
||||
});
|
||||
|
||||
const { ClawHubRequestError } = await import("../infra/clawhub.js");
|
||||
type ClawHubResolvedArtifact = import("../infra/clawhub.js").ClawHubResolvedArtifact;
|
||||
const { CLAWHUB_INSTALL_ERROR_CODE, formatClawHubSpecifier, installPluginFromClawHub } =
|
||||
await import("./clawhub.js");
|
||||
|
||||
const DEMO_ARCHIVE_INTEGRITY = "sha256-qerEjGEpvES2+Tyan0j2xwDRkbcnmh4ZFfKN9vWbsa8=";
|
||||
const DEMO_ARCHIVE_SHA256 = "a9eac48c6129bc44b6f93c9a9f48f6c700d191b7279a1e1915f28df6f59bb1af";
|
||||
const DEMO_CLAWPACK_SHA256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const DEMO_CLAWPACK_INTEGRITY = `sha256-${Buffer.from(DEMO_CLAWPACK_SHA256, "hex").toString(
|
||||
"base64",
|
||||
@@ -463,6 +465,102 @@ describe("installPluginFromClawHub", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts the live ClawHub artifact resolver shape with kind/sha256 field names", async () => {
|
||||
fetchClawHubPackageVersionMock.mockClear();
|
||||
fetchClawHubPackageArtifactMock.mockResolvedValueOnce({
|
||||
package: {
|
||||
name: "demo",
|
||||
displayName: "Demo",
|
||||
family: "code-plugin",
|
||||
},
|
||||
version: "2026.3.22",
|
||||
artifact: {
|
||||
kind: "npm-pack",
|
||||
sha256: DEMO_CLAWPACK_SHA256,
|
||||
npmIntegrity: "sha512-clawpack",
|
||||
npmShasum: "1".repeat(40),
|
||||
} as unknown as ClawHubResolvedArtifact,
|
||||
});
|
||||
downloadClawHubPackageArchiveMock.mockResolvedValueOnce({
|
||||
archivePath: "/tmp/clawhub-demo/demo-2026.3.22.tgz",
|
||||
integrity: DEMO_CLAWPACK_INTEGRITY,
|
||||
sha256Hex: DEMO_CLAWPACK_SHA256,
|
||||
artifact: "clawpack",
|
||||
clawpackHeaderSha256: DEMO_CLAWPACK_SHA256,
|
||||
npmIntegrity: "sha512-clawpack",
|
||||
npmShasum: "1".repeat(40),
|
||||
cleanup: archiveCleanupMock,
|
||||
});
|
||||
|
||||
const result = await installPluginFromClawHub({
|
||||
spec: "clawhub:demo",
|
||||
baseUrl: "https://clawhub.ai",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
clawhub: {
|
||||
artifactKind: "npm-pack",
|
||||
artifactFormat: "tgz",
|
||||
npmIntegrity: "sha512-clawpack",
|
||||
npmShasum: "1".repeat(40),
|
||||
clawpackSha256: DEMO_CLAWPACK_SHA256,
|
||||
},
|
||||
});
|
||||
expect(fetchClawHubPackageVersionMock).not.toHaveBeenCalled();
|
||||
expect(downloadClawHubPackageArchiveMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
artifact: "clawpack",
|
||||
name: "demo",
|
||||
version: "2026.3.22",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts the live ClawHub legacy zip resolver shape with kind/sha256 field names", async () => {
|
||||
fetchClawHubPackageVersionMock.mockClear();
|
||||
fetchClawHubPackageArtifactMock.mockResolvedValueOnce({
|
||||
package: {
|
||||
name: "demo",
|
||||
displayName: "Demo",
|
||||
family: "code-plugin",
|
||||
},
|
||||
version: "2026.3.22",
|
||||
artifact: {
|
||||
kind: "legacy-zip",
|
||||
sha256: DEMO_ARCHIVE_SHA256,
|
||||
} as unknown as ClawHubResolvedArtifact,
|
||||
});
|
||||
downloadClawHubPackageArchiveMock.mockResolvedValueOnce({
|
||||
archivePath: "/tmp/clawhub-demo/archive.zip",
|
||||
integrity: DEMO_ARCHIVE_INTEGRITY,
|
||||
cleanup: archiveCleanupMock,
|
||||
});
|
||||
|
||||
const result = await installPluginFromClawHub({
|
||||
spec: "clawhub:demo",
|
||||
baseUrl: "https://clawhub.ai",
|
||||
});
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
pluginId: "demo",
|
||||
clawhub: {
|
||||
artifactKind: "legacy-zip",
|
||||
artifactFormat: "zip",
|
||||
integrity: DEMO_ARCHIVE_INTEGRITY,
|
||||
},
|
||||
});
|
||||
expect(fetchClawHubPackageVersionMock).not.toHaveBeenCalled();
|
||||
expect(downloadClawHubPackageArchiveMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
artifact: "archive",
|
||||
name: "demo",
|
||||
version: "2026.3.22",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to version metadata when the ClawHub artifact resolver route is missing", async () => {
|
||||
fetchClawHubPackageArtifactMock.mockRejectedValueOnce(
|
||||
new ClawHubRequestError({
|
||||
|
||||
@@ -265,29 +265,47 @@ function normalizeArtifactResolverFiles(
|
||||
return files as NonNullable<ClawHubPackageVersion["version"]>["files"];
|
||||
}
|
||||
|
||||
type ClawHubResolvedArtifactWire = {
|
||||
artifactKind?: string | null;
|
||||
kind?: string | null;
|
||||
artifactSha256?: string | null;
|
||||
sha256?: string | null;
|
||||
npmIntegrity?: string | null;
|
||||
npmShasum?: string | null;
|
||||
downloadUrl?: string | null;
|
||||
};
|
||||
|
||||
function resolveTopLevelNpmPackArtifact(
|
||||
artifact: ClawHubResolvedArtifact | null | undefined,
|
||||
): ClawHubPackageArtifactSummary | null {
|
||||
if (artifact?.artifactKind !== "npm-pack") {
|
||||
const wire = artifact as ClawHubResolvedArtifactWire | null | undefined;
|
||||
const artifactKind = wire?.artifactKind ?? wire?.kind;
|
||||
if (artifactKind !== "npm-pack") {
|
||||
return null;
|
||||
}
|
||||
if (typeof wire?.npmIntegrity !== "string") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
kind: "npm-pack",
|
||||
format: "tgz",
|
||||
sha256: artifact.artifactSha256 ?? null,
|
||||
npmIntegrity: artifact.npmIntegrity,
|
||||
npmShasum: artifact.npmShasum ?? null,
|
||||
downloadUrl: artifact.downloadUrl ?? null,
|
||||
sha256: wire.artifactSha256 ?? wire.sha256 ?? null,
|
||||
npmIntegrity: wire.npmIntegrity,
|
||||
npmShasum: wire.npmShasum ?? null,
|
||||
downloadUrl: wire.downloadUrl ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveTopLevelLegacyArchiveVerification(
|
||||
artifact: ClawHubResolvedArtifact | null | undefined,
|
||||
): ClawHubArchiveVerification | null {
|
||||
if (artifact?.artifactKind !== "legacy-zip" || typeof artifact.artifactSha256 !== "string") {
|
||||
const wire = artifact as ClawHubResolvedArtifactWire | null | undefined;
|
||||
const artifactKind = wire?.artifactKind ?? wire?.kind;
|
||||
const artifactSha256 = wire?.artifactSha256 ?? wire?.sha256;
|
||||
if (artifactKind !== "legacy-zip" || typeof artifactSha256 !== "string") {
|
||||
return null;
|
||||
}
|
||||
const integrity = normalizeClawHubSha256Integrity(artifact.artifactSha256);
|
||||
const integrity = normalizeClawHubSha256Integrity(artifactSha256);
|
||||
return integrity ? { kind: "archive-integrity", integrity } : null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user