mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-22 15:08:11 +00:00
fix: prefer bundled channel plugins over npm duplicates (#40094)
* fix: prefer bundled channel plugins over npm duplicates * fix: tighten bundled plugin review follow-ups * fix: address check gate follow-ups * docs: add changelog for bundled plugin install fix * fix: align lifecycle test formatting with CI oxfmt
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { PLUGIN_INSTALL_ERROR_CODE } from "../plugins/install.js";
|
||||
import {
|
||||
resolveBundledInstallPlanForCatalogEntry,
|
||||
resolveBundledInstallPlanBeforeNpm,
|
||||
resolveBundledInstallPlanForNpmFailure,
|
||||
} from "./plugin-install-plan.js";
|
||||
@@ -34,6 +35,53 @@ describe("plugin install plan helpers", () => {
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("prefers bundled catalog plugin by id before npm spec", () => {
|
||||
const findBundledSource = vi
|
||||
.fn()
|
||||
.mockImplementation(({ kind, value }: { kind: "pluginId" | "npmSpec"; value: string }) => {
|
||||
if (kind === "pluginId" && value === "voice-call") {
|
||||
return {
|
||||
pluginId: "voice-call",
|
||||
localPath: "/tmp/extensions/voice-call",
|
||||
npmSpec: "@openclaw/voice-call",
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const result = resolveBundledInstallPlanForCatalogEntry({
|
||||
pluginId: "voice-call",
|
||||
npmSpec: "@openclaw/voice-call",
|
||||
findBundledSource,
|
||||
});
|
||||
|
||||
expect(findBundledSource).toHaveBeenCalledWith({ kind: "pluginId", value: "voice-call" });
|
||||
expect(result?.bundledSource.localPath).toBe("/tmp/extensions/voice-call");
|
||||
});
|
||||
|
||||
it("rejects npm-spec matches that resolve to a different plugin id", () => {
|
||||
const findBundledSource = vi
|
||||
.fn()
|
||||
.mockImplementation(({ kind }: { kind: "pluginId" | "npmSpec"; value: string }) => {
|
||||
if (kind === "npmSpec") {
|
||||
return {
|
||||
pluginId: "not-voice-call",
|
||||
localPath: "/tmp/extensions/not-voice-call",
|
||||
npmSpec: "@openclaw/voice-call",
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const result = resolveBundledInstallPlanForCatalogEntry({
|
||||
pluginId: "voice-call",
|
||||
npmSpec: "@openclaw/voice-call",
|
||||
findBundledSource,
|
||||
});
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it("uses npm-spec bundled fallback only for package-not-found", () => {
|
||||
const findBundledSource = vi.fn().mockReturnValue({
|
||||
pluginId: "voice-call",
|
||||
|
||||
@@ -12,6 +12,36 @@ function isBareNpmPackageName(spec: string): boolean {
|
||||
return /^[a-z0-9][a-z0-9-._~]*$/.test(trimmed);
|
||||
}
|
||||
|
||||
export function resolveBundledInstallPlanForCatalogEntry(params: {
|
||||
pluginId: string;
|
||||
npmSpec: string;
|
||||
findBundledSource: BundledLookup;
|
||||
}): { bundledSource: BundledPluginSource } | null {
|
||||
const pluginId = params.pluginId.trim();
|
||||
const npmSpec = params.npmSpec.trim();
|
||||
if (!pluginId || !npmSpec) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const bundledById = params.findBundledSource({
|
||||
kind: "pluginId",
|
||||
value: pluginId,
|
||||
});
|
||||
if (bundledById?.pluginId === pluginId) {
|
||||
return { bundledSource: bundledById };
|
||||
}
|
||||
|
||||
const bundledBySpec = params.findBundledSource({
|
||||
kind: "npmSpec",
|
||||
value: npmSpec,
|
||||
});
|
||||
if (bundledBySpec?.pluginId === pluginId) {
|
||||
return { bundledSource: bundledBySpec };
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function resolveBundledInstallPlanBeforeNpm(params: {
|
||||
rawSpec: string;
|
||||
findBundledSource: BundledLookup;
|
||||
|
||||
Reference in New Issue
Block a user