diff --git a/src/cli/plugins-cli-test-helpers.ts b/src/cli/plugins-cli-test-helpers.ts index a5bfcf5bcfa..1979e6b1078 100644 --- a/src/cli/plugins-cli-test-helpers.ts +++ b/src/cli/plugins-cli-test-helpers.ts @@ -14,6 +14,7 @@ type ListMarketplacePluginsFn = (typeof import("../plugins/marketplace.js"))["listMarketplacePlugins"]; type ResolveMarketplaceInstallShortcutFn = (typeof import("../plugins/marketplace.js"))["resolveMarketplaceInstallShortcut"]; +type LoadPluginInstallRecordsParams = { config?: OpenClawConfig }; // oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- Test helper preserves mock call and result types. function invokeMock(mock: unknown, ...args: TArgs): TResult { @@ -32,9 +33,9 @@ export const listMarketplacePlugins: Mock = vi.fn(); export const resolveMarketplaceInstallShortcut: Mock = vi.fn(); export const enablePluginInConfig: UnknownMock = vi.fn(); export const recordPluginInstall: UnknownMock = vi.fn(); -export const loadPluginInstallRecords: AsyncUnknownMock = vi.fn(async ({ config }) => { - const cfg = config as OpenClawConfig | undefined; - return structuredClone(cfg?.plugins?.installs ?? {}); +export const loadPluginInstallRecords: AsyncUnknownMock = vi.fn(async (...args: unknown[]) => { + const params = args[0] as LoadPluginInstallRecordsParams | undefined; + return structuredClone(params?.config?.plugins?.installs ?? {}); }); export const writePersistedPluginInstallLedger: AsyncUnknownMock = vi.fn(async () => undefined); export const clearPluginManifestRegistryCache: UnknownMock = vi.fn(); @@ -518,9 +519,9 @@ export function resetPluginsCliTestState() { recordPluginInstall.mockImplementation( ((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown, ); - loadPluginInstallRecords.mockImplementation(async ({ config }) => { - const cfg = config as OpenClawConfig | undefined; - return structuredClone(cfg?.plugins?.installs ?? {}); + loadPluginInstallRecords.mockImplementation(async (...args: unknown[]) => { + const params = args[0] as LoadPluginInstallRecordsParams | undefined; + return structuredClone(params?.config?.plugins?.installs ?? {}); }); writePersistedPluginInstallLedger.mockResolvedValue(undefined); loadPluginManifestRegistry.mockReturnValue({ diff --git a/src/cli/plugins-cli.install.test.ts b/src/cli/plugins-cli.install.test.ts index d20f8202703..025f4d4ffe0 100644 --- a/src/cli/plugins-cli.install.test.ts +++ b/src/cli/plugins-cli.install.test.ts @@ -25,6 +25,7 @@ import { runtimeErrors, runtimeLogs, writeConfigFile, + writePersistedPluginInstallLedger, } from "./plugins-cli-test-helpers.js"; const CLI_STATE_ROOT = "/tmp/openclaw-state"; @@ -53,22 +54,6 @@ function createEmptyPluginConfig(): OpenClawConfig { } as OpenClawConfig; } -function createClawHubInstalledConfig(params: { - pluginId: string; - install: Record; -}): OpenClawConfig { - const enabledCfg = createEnabledPluginConfig(params.pluginId); - return { - ...enabledCfg, - plugins: { - ...enabledCfg.plugins, - installs: { - [params.pluginId]: params.install, - }, - }, - } as OpenClawConfig; -} - function createClawHubInstallResult(params: { pluginId: string; packageName: string; @@ -320,19 +305,6 @@ describe("plugins cli install", () => { }, }, } as OpenClawConfig; - const installedCfg = { - ...enabledCfg, - plugins: { - ...enabledCfg.plugins, - installs: { - alpha: { - source: "marketplace", - installPath: cliInstallPath("alpha"), - }, - }, - }, - } as OpenClawConfig; - loadConfig.mockReturnValue(cfg); installPluginFromMarketplace.mockResolvedValue({ ok: true, @@ -345,20 +317,25 @@ describe("plugins cli install", () => { marketplacePlugin: "alpha", }); enablePluginInConfig.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(installedCfg); buildPluginDiagnosticsReport.mockReturnValue({ plugins: [{ id: "alpha", kind: "provider" }], diagnostics: [], }); applyExclusiveSlotSelection.mockReturnValue({ - config: installedCfg, + config: enabledCfg, warnings: ["slot adjusted"], }); await runPluginsCommand(["plugins", "install", "alpha", "--marketplace", "local/repo"]); expect(clearPluginManifestRegistryCache).toHaveBeenCalledTimes(1); - expect(writeConfigFile).toHaveBeenCalledWith(installedCfg); + expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({ + alpha: expect.objectContaining({ + source: "marketplace", + installPath: cliInstallPath("alpha"), + }), + }); + expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg); expect(runtimeLogs.some((line) => line.includes("slot adjusted"))).toBe(true); expect(runtimeLogs.some((line) => line.includes("Installed plugin: alpha"))).toBe(true); }); @@ -384,18 +361,6 @@ describe("plugins cli install", () => { }, } as OpenClawConfig; const enabledCfg = createEnabledPluginConfig("demo"); - const installedCfg = createClawHubInstalledConfig({ - pluginId: "demo", - install: { - source: "clawhub", - spec: "clawhub:demo@1.2.3", - installPath: cliInstallPath("demo"), - clawhubPackage: "demo", - clawhubFamily: "code-plugin", - clawhubChannel: "official", - }, - }); - loadConfig.mockReturnValue(cfg); parseClawHubPluginSpec.mockReturnValue({ name: "demo" }); installPluginFromClawHub.mockResolvedValue( @@ -407,9 +372,8 @@ describe("plugins cli install", () => { }), ); enablePluginInConfig.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(installedCfg); applyExclusiveSlotSelection.mockReturnValue({ - config: installedCfg, + config: enabledCfg, warnings: [], }); @@ -420,18 +384,16 @@ describe("plugins cli install", () => { spec: "clawhub:demo", }), ); - expect(recordPluginInstall).toHaveBeenCalledWith( - enabledCfg, - expect.objectContaining({ - pluginId: "demo", + expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({ + demo: expect.objectContaining({ source: "clawhub", spec: "clawhub:demo@1.2.3", clawhubPackage: "demo", clawhubFamily: "code-plugin", clawhubChannel: "official", }), - ); - expect(writeConfigFile).toHaveBeenCalledWith(installedCfg); + }); + expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg); expect(runtimeLogs.some((line) => line.includes("Installed plugin: demo"))).toBe(true); expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); }); @@ -478,16 +440,6 @@ describe("plugins cli install", () => { }, } as OpenClawConfig; const enabledCfg = createEnabledPluginConfig("demo"); - const installedCfg = createClawHubInstalledConfig({ - pluginId: "demo", - install: { - source: "clawhub", - spec: "clawhub:demo@1.2.3", - installPath: cliInstallPath("demo"), - clawhubPackage: "demo", - }, - }); - loadConfig.mockReturnValue(cfg); installPluginFromClawHub.mockResolvedValue( createClawHubInstallResult({ @@ -498,9 +450,8 @@ describe("plugins cli install", () => { }), ); enablePluginInConfig.mockReturnValue({ config: enabledCfg }); - recordPluginInstall.mockReturnValue(installedCfg); applyExclusiveSlotSelection.mockReturnValue({ - config: installedCfg, + config: enabledCfg, warnings: [], }); @@ -512,7 +463,14 @@ describe("plugins cli install", () => { }), ); expect(installPluginFromNpmSpec).not.toHaveBeenCalled(); - expect(writeConfigFile).toHaveBeenCalledWith(installedCfg); + expect(writePersistedPluginInstallLedger).toHaveBeenCalledWith({ + demo: expect.objectContaining({ + source: "clawhub", + spec: "clawhub:demo@1.2.3", + clawhubPackage: "demo", + }), + }); + expect(writeConfigFile).toHaveBeenCalledWith(enabledCfg); }); it("falls back to npm when ClawHub does not have the package", async () => { diff --git a/src/plugins/install-ledger-store.test.ts b/src/plugins/install-ledger-store.test.ts index d0bd1928218..2453e1568ce 100644 --- a/src/plugins/install-ledger-store.test.ts +++ b/src/plugins/install-ledger-store.test.ts @@ -2,6 +2,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, it } from "vitest"; +import type { PluginInstallRecord } from "../config/types.plugins.js"; import { loadPluginInstallRecords, loadPluginInstallRecordsSync, @@ -127,7 +128,7 @@ describe("plugin install ledger store", () => { source: "npm", spec: "keep@1.0.0", }, - }; + } satisfies Record; const withInstall = recordPluginInstallInRecords(records, { pluginId: "demo", source: "npm",