import { Command } from "commander"; import type { Mock } from "vitest"; import { vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { createCliRuntimeCapture } from "./test-runtime-capture.js"; type UnknownMock = Mock<(...args: unknown[]) => unknown>; type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise>; type LoadConfigFn = (typeof import("../config/config.js"))["loadConfig"]; type ParseClawHubPluginSpecFn = (typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]; type InstallPluginFromMarketplaceFn = (typeof import("../plugins/marketplace.js"))["installPluginFromMarketplace"]; type ListMarketplacePluginsFn = (typeof import("../plugins/marketplace.js"))["listMarketplacePlugins"]; type ResolveMarketplaceInstallShortcutFn = (typeof import("../plugins/marketplace.js"))["resolveMarketplaceInstallShortcut"]; function invokeMock(mock: unknown, ...args: TArgs): TResult { return (mock as (...args: TArgs) => TResult)(...args); } export const loadConfig: Mock = vi.fn(() => ({}) as OpenClawConfig); export const readConfigFileSnapshot: AsyncUnknownMock = vi.fn(); export const writeConfigFile: AsyncUnknownMock = vi.fn(async () => undefined); export const replaceConfigFile: AsyncUnknownMock = vi.fn( async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig), ) as AsyncUnknownMock; export const resolveStateDir: Mock<() => string> = vi.fn(() => "/tmp/openclaw-state"); export const installPluginFromMarketplace: Mock = vi.fn(); 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 clearPluginManifestRegistryCache: UnknownMock = vi.fn(); export const buildPluginSnapshotReport: UnknownMock = vi.fn(); export const buildPluginDiagnosticsReport: UnknownMock = vi.fn(); export const buildPluginCompatibilityNotices: UnknownMock = vi.fn(); export const applyExclusiveSlotSelection: UnknownMock = vi.fn(); export const uninstallPlugin: AsyncUnknownMock = vi.fn(); export const updateNpmInstalledPlugins: AsyncUnknownMock = vi.fn(); export const updateNpmInstalledHookPacks: AsyncUnknownMock = vi.fn(); export const promptYesNo: AsyncUnknownMock = vi.fn(); export const installPluginFromNpmSpec: AsyncUnknownMock = vi.fn(); export const installPluginFromPath: AsyncUnknownMock = vi.fn(); export const installPluginFromClawHub: AsyncUnknownMock = vi.fn(); export const parseClawHubPluginSpec: Mock = vi.fn(); export const installHooksFromNpmSpec: AsyncUnknownMock = vi.fn(); export const installHooksFromPath: AsyncUnknownMock = vi.fn(); export const recordHookInstall: UnknownMock = vi.fn(); const { defaultRuntime, runtimeLogs, runtimeErrors, resetRuntimeCapture } = createCliRuntimeCapture(); export { runtimeErrors, runtimeLogs }; vi.mock("../runtime.js", () => ({ defaultRuntime, })); vi.mock("../config/config.js", () => ({ loadConfig: () => loadConfig(), readConfigFileSnapshot: (( ...args: Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshot"]> ) => invokeMock< Parameters<(typeof import("../config/config.js"))["readConfigFileSnapshot"]>, ReturnType<(typeof import("../config/config.js"))["readConfigFileSnapshot"]> >( readConfigFileSnapshot, ...args, )) as (typeof import("../config/config.js"))["readConfigFileSnapshot"], writeConfigFile: ((config: OpenClawConfig) => invokeMock< [OpenClawConfig], ReturnType<(typeof import("../config/config.js"))["writeConfigFile"]> >(writeConfigFile, config)) as (typeof import("../config/config.js"))["writeConfigFile"], replaceConfigFile: (( params: Parameters<(typeof import("../config/config.js"))["replaceConfigFile"]>[0], ) => invokeMock< [Parameters<(typeof import("../config/config.js"))["replaceConfigFile"]>[0]], ReturnType<(typeof import("../config/config.js"))["replaceConfigFile"]> >(replaceConfigFile, params)) as (typeof import("../config/config.js"))["replaceConfigFile"], })); vi.mock("../config/paths.js", () => ({ resolveStateDir: () => resolveStateDir(), })); vi.mock("../plugins/marketplace.js", () => ({ installPluginFromMarketplace: ((...args: Parameters) => installPluginFromMarketplace(...args)) as InstallPluginFromMarketplaceFn, listMarketplacePlugins: ((...args: Parameters) => listMarketplacePlugins(...args)) as ListMarketplacePluginsFn, resolveMarketplaceInstallShortcut: ((...args: Parameters) => resolveMarketplaceInstallShortcut(...args)) as ResolveMarketplaceInstallShortcutFn, })); vi.mock("../plugins/enable.js", () => ({ enablePluginInConfig: ((cfg: OpenClawConfig, pluginId: string) => invokeMock<[OpenClawConfig, string], unknown>( enablePluginInConfig, cfg, pluginId, )) as (typeof import("../plugins/enable.js"))["enablePluginInConfig"], })); vi.mock("../plugins/installs.js", () => ({ recordPluginInstall: (( ...args: Parameters<(typeof import("../plugins/installs.js"))["recordPluginInstall"]> ) => invokeMock< Parameters<(typeof import("../plugins/installs.js"))["recordPluginInstall"]>, ReturnType<(typeof import("../plugins/installs.js"))["recordPluginInstall"]> >( recordPluginInstall, ...args, )) as (typeof import("../plugins/installs.js"))["recordPluginInstall"], })); vi.mock("../plugins/manifest-registry.js", () => ({ clearPluginManifestRegistryCache: () => clearPluginManifestRegistryCache(), })); vi.mock("../plugins/status.js", () => ({ buildPluginSnapshotReport: (( ...args: Parameters<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]> ) => invokeMock< Parameters<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginSnapshotReport"]> >( buildPluginSnapshotReport, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginSnapshotReport"], buildPluginDiagnosticsReport: (( ...args: Parameters<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]> ) => invokeMock< Parameters<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"]> >( buildPluginDiagnosticsReport, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginDiagnosticsReport"], buildPluginCompatibilityNotices: (( ...args: Parameters<(typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"]> ) => invokeMock< Parameters<(typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"]>, ReturnType<(typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"]> >( buildPluginCompatibilityNotices, ...args, )) as (typeof import("../plugins/status.js"))["buildPluginCompatibilityNotices"], })); vi.mock("../plugins/slots.js", () => ({ applyExclusiveSlotSelection: (( params: Parameters<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]>[0], ) => invokeMock< [Parameters<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]>[0]], ReturnType<(typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"]> >( applyExclusiveSlotSelection, params, )) as (typeof import("../plugins/slots.js"))["applyExclusiveSlotSelection"], })); vi.mock("../plugins/uninstall.js", () => ({ uninstallPlugin: (( ...args: Parameters<(typeof import("../plugins/uninstall.js"))["uninstallPlugin"]> ) => invokeMock< Parameters<(typeof import("../plugins/uninstall.js"))["uninstallPlugin"]>, ReturnType<(typeof import("../plugins/uninstall.js"))["uninstallPlugin"]> >(uninstallPlugin, ...args)) as (typeof import("../plugins/uninstall.js"))["uninstallPlugin"], resolveUninstallDirectoryTarget: ({ installRecord, }: { installRecord?: { installPath?: string; sourcePath?: string }; }) => installRecord?.installPath ?? installRecord?.sourcePath ?? null, })); vi.mock("../plugins/update.js", () => ({ updateNpmInstalledPlugins: (( ...args: Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]> ) => invokeMock< Parameters<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]>, ReturnType<(typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"]> >( updateNpmInstalledPlugins, ...args, )) as (typeof import("../plugins/update.js"))["updateNpmInstalledPlugins"], })); vi.mock("../hooks/update.js", () => ({ updateNpmInstalledHookPacks: (( ...args: Parameters<(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"]> ) => invokeMock< Parameters<(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"]>, ReturnType<(typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"]> >( updateNpmInstalledHookPacks, ...args, )) as (typeof import("../hooks/update.js"))["updateNpmInstalledHookPacks"], })); vi.mock("./prompt.js", () => ({ promptYesNo: ((...args: Parameters<(typeof import("./prompt.js"))["promptYesNo"]>) => invokeMock< Parameters<(typeof import("./prompt.js"))["promptYesNo"]>, ReturnType<(typeof import("./prompt.js"))["promptYesNo"]> >(promptYesNo, ...args)) as (typeof import("./prompt.js"))["promptYesNo"], })); vi.mock("../plugins/install.js", () => ({ PLUGIN_INSTALL_ERROR_CODE: { NPM_PACKAGE_NOT_FOUND: "npm_package_not_found", }, installPluginFromNpmSpec: (( ...args: Parameters<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]> ) => invokeMock< Parameters<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]>, ReturnType<(typeof import("../plugins/install.js"))["installPluginFromNpmSpec"]> >( installPluginFromNpmSpec, ...args, )) as (typeof import("../plugins/install.js"))["installPluginFromNpmSpec"], installPluginFromPath: (( ...args: Parameters<(typeof import("../plugins/install.js"))["installPluginFromPath"]> ) => invokeMock< Parameters<(typeof import("../plugins/install.js"))["installPluginFromPath"]>, ReturnType<(typeof import("../plugins/install.js"))["installPluginFromPath"]> >( installPluginFromPath, ...args, )) as (typeof import("../plugins/install.js"))["installPluginFromPath"], })); vi.mock("../hooks/install.js", () => ({ installHooksFromNpmSpec: (( ...args: Parameters<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]> ) => invokeMock< Parameters<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]>, ReturnType<(typeof import("../hooks/install.js"))["installHooksFromNpmSpec"]> >( installHooksFromNpmSpec, ...args, )) as (typeof import("../hooks/install.js"))["installHooksFromNpmSpec"], installHooksFromPath: (( ...args: Parameters<(typeof import("../hooks/install.js"))["installHooksFromPath"]> ) => invokeMock< Parameters<(typeof import("../hooks/install.js"))["installHooksFromPath"]>, ReturnType<(typeof import("../hooks/install.js"))["installHooksFromPath"]> >( installHooksFromPath, ...args, )) as (typeof import("../hooks/install.js"))["installHooksFromPath"], resolveHookInstallDir: (hookId: string) => `/tmp/hooks/${hookId}`, })); vi.mock("../hooks/installs.js", () => ({ recordHookInstall: (( ...args: Parameters<(typeof import("../hooks/installs.js"))["recordHookInstall"]> ) => invokeMock< Parameters<(typeof import("../hooks/installs.js"))["recordHookInstall"]>, ReturnType<(typeof import("../hooks/installs.js"))["recordHookInstall"]> >(recordHookInstall, ...args)) as (typeof import("../hooks/installs.js"))["recordHookInstall"], })); vi.mock("../plugins/clawhub.js", () => ({ CLAWHUB_INSTALL_ERROR_CODE: { PACKAGE_NOT_FOUND: "package_not_found", VERSION_NOT_FOUND: "version_not_found", }, installPluginFromClawHub: (( ...args: Parameters<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]> ) => invokeMock< Parameters<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]>, ReturnType<(typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"]> >( installPluginFromClawHub, ...args, )) as (typeof import("../plugins/clawhub.js"))["installPluginFromClawHub"], formatClawHubSpecifier: ({ name, version }: { name: string; version?: string }) => `clawhub:${name}${version ? `@${version}` : ""}`, })); vi.mock("../infra/clawhub.js", () => ({ parseClawHubPluginSpec: (( ...args: Parameters<(typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]> ) => invokeMock< Parameters<(typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]>, ReturnType<(typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"]> >( parseClawHubPluginSpec, ...args, )) as (typeof import("../infra/clawhub.js"))["parseClawHubPluginSpec"], })); const { registerPluginsCli } = await import("./plugins-cli.js"); export { registerPluginsCli }; export function runPluginsCommand(argv: string[]) { const program = new Command(); program.exitOverride(); registerPluginsCli(program); return program.parseAsync(argv, { from: "user" }); } export function resetPluginsCliTestState() { resetRuntimeCapture(); loadConfig.mockReset(); readConfigFileSnapshot.mockReset(); writeConfigFile.mockReset(); replaceConfigFile.mockReset(); resolveStateDir.mockReset(); installPluginFromMarketplace.mockReset(); listMarketplacePlugins.mockReset(); resolveMarketplaceInstallShortcut.mockReset(); enablePluginInConfig.mockReset(); recordPluginInstall.mockReset(); clearPluginManifestRegistryCache.mockReset(); buildPluginSnapshotReport.mockReset(); buildPluginDiagnosticsReport.mockReset(); buildPluginCompatibilityNotices.mockReset(); applyExclusiveSlotSelection.mockReset(); uninstallPlugin.mockReset(); updateNpmInstalledPlugins.mockReset(); updateNpmInstalledHookPacks.mockReset(); promptYesNo.mockReset(); installPluginFromNpmSpec.mockReset(); installPluginFromPath.mockReset(); installPluginFromClawHub.mockReset(); parseClawHubPluginSpec.mockReset(); installHooksFromNpmSpec.mockReset(); installHooksFromPath.mockReset(); recordHookInstall.mockReset(); loadConfig.mockReturnValue({} as OpenClawConfig); readConfigFileSnapshot.mockImplementation(async () => { const config = loadConfig(); return { path: "/tmp/openclaw-config.json5", exists: true, raw: "{}", parsed: config, resolved: config, sourceConfig: config, runtimeConfig: config, valid: true, config, hash: "mock", issues: [], warnings: [], legacyIssues: [], }; }); writeConfigFile.mockResolvedValue(undefined); replaceConfigFile.mockImplementation( (async (params: { nextConfig: OpenClawConfig }) => await writeConfigFile(params.nextConfig)) as (...args: unknown[]) => Promise, ); resolveStateDir.mockReturnValue("/tmp/openclaw-state"); resolveMarketplaceInstallShortcut.mockResolvedValue(null); installPluginFromMarketplace.mockResolvedValue({ ok: false, error: "marketplace install failed", }); enablePluginInConfig.mockImplementation(((cfg: OpenClawConfig) => ({ config: cfg })) as ( ...args: unknown[] ) => unknown); recordPluginInstall.mockImplementation( ((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown, ); const defaultPluginReport = { plugins: [], diagnostics: [], }; buildPluginSnapshotReport.mockReturnValue(defaultPluginReport); buildPluginDiagnosticsReport.mockReturnValue(defaultPluginReport); buildPluginCompatibilityNotices.mockReturnValue([]); applyExclusiveSlotSelection.mockImplementation((({ config }: { config: OpenClawConfig }) => ({ config, warnings: [], })) as (...args: unknown[]) => unknown); uninstallPlugin.mockResolvedValue({ ok: true, config: {} as OpenClawConfig, warnings: [], actions: { entry: false, install: false, allowlist: false, loadPath: false, memorySlot: false, directory: false, }, }); updateNpmInstalledPlugins.mockResolvedValue({ outcomes: [], changed: false, config: {} as OpenClawConfig, }); updateNpmInstalledHookPacks.mockResolvedValue({ outcomes: [], changed: false, config: {} as OpenClawConfig, }); promptYesNo.mockResolvedValue(true); installPluginFromPath.mockResolvedValue({ ok: false, error: "path install disabled in test" }); installPluginFromNpmSpec.mockResolvedValue({ ok: false, error: "npm install disabled in test", }); installPluginFromClawHub.mockResolvedValue({ ok: false, error: "clawhub install disabled in test", }); parseClawHubPluginSpec.mockReturnValue(null); installHooksFromPath.mockResolvedValue({ ok: false, error: "hook path install disabled in test", }); installHooksFromNpmSpec.mockResolvedValue({ ok: false, error: "hook npm install disabled in test", }); recordHookInstall.mockImplementation( ((cfg: OpenClawConfig) => cfg) as (...args: unknown[]) => unknown, ); }