mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:40:43 +00:00
fix(plugins): make uninstall teardown idempotent
This commit is contained in:
@@ -271,6 +271,53 @@ describe("plugins cli uninstall", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("cleans stale policy refs even when plugin is absent from the current registry", async () => {
|
||||
const baseConfig = {
|
||||
plugins: {
|
||||
allow: ["alpha", "beta"],
|
||||
deny: ["alpha"],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
const nextConfig = {
|
||||
plugins: {
|
||||
allow: ["beta"],
|
||||
},
|
||||
} as OpenClawConfig;
|
||||
|
||||
loadConfig.mockReturnValue(baseConfig);
|
||||
buildPluginSnapshotReport.mockReturnValue({
|
||||
plugins: [],
|
||||
diagnostics: [],
|
||||
});
|
||||
planPluginUninstall.mockReturnValue({
|
||||
ok: true,
|
||||
config: nextConfig,
|
||||
actions: {
|
||||
entry: false,
|
||||
install: false,
|
||||
allowlist: true,
|
||||
denylist: true,
|
||||
loadPath: false,
|
||||
memorySlot: false,
|
||||
contextEngineSlot: false,
|
||||
channelConfig: false,
|
||||
directory: false,
|
||||
},
|
||||
directoryRemoval: null,
|
||||
});
|
||||
|
||||
await runPluginsCommand(["plugins", "uninstall", "alpha", "--force"]);
|
||||
|
||||
expect(planPluginUninstall).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pluginId: "alpha",
|
||||
deleteFiles: true,
|
||||
}),
|
||||
);
|
||||
expect(writeConfigFile).toHaveBeenCalledWith(nextConfig);
|
||||
expect(runtimeLogs.at(-2)).toContain('Uninstalled plugin "alpha"');
|
||||
});
|
||||
|
||||
it("exits when uninstall target is not managed by plugin install records", async () => {
|
||||
loadConfig.mockReturnValue({
|
||||
plugins: {
|
||||
@@ -282,12 +329,16 @@ describe("plugins cli uninstall", () => {
|
||||
plugins: [{ id: "alpha", name: "alpha" }],
|
||||
diagnostics: [],
|
||||
});
|
||||
planPluginUninstall.mockReturnValue({
|
||||
ok: false,
|
||||
error: "Plugin not found: alpha",
|
||||
});
|
||||
|
||||
await expect(runPluginsCommand(["plugins", "uninstall", "alpha", "--force"])).rejects.toThrow(
|
||||
"__exit__:1",
|
||||
);
|
||||
|
||||
expect(runtimeErrors.at(-1)).toContain("is not managed by plugins config/install records");
|
||||
expect(planPluginUninstall).not.toHaveBeenCalled();
|
||||
expect(planPluginUninstall).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -74,21 +74,6 @@ export async function runPluginUninstallCommand(
|
||||
config: cfg,
|
||||
plugins: report.plugins,
|
||||
});
|
||||
const hasEntry = pluginId in (cfg.plugins?.entries ?? {});
|
||||
const hasInstall = pluginId in (cfg.plugins?.installs ?? {});
|
||||
|
||||
if (!hasEntry && !hasInstall) {
|
||||
if (plugin) {
|
||||
runtime.error(
|
||||
`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`,
|
||||
);
|
||||
} else {
|
||||
runtime.error(`Plugin not found: ${id}`);
|
||||
}
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
const channelIds = plugin?.status === "loaded" ? plugin.channelIds : undefined;
|
||||
const plan = planPluginUninstall({
|
||||
config: cfg,
|
||||
@@ -98,10 +83,17 @@ export async function runPluginUninstallCommand(
|
||||
extensionsDir,
|
||||
});
|
||||
if (!plan.ok) {
|
||||
runtime.error(plan.error);
|
||||
if (plugin) {
|
||||
runtime.error(
|
||||
`Plugin "${pluginId}" is not managed by plugins config/install records and cannot be uninstalled.`,
|
||||
);
|
||||
} else {
|
||||
runtime.error(plan.error);
|
||||
}
|
||||
runtime.exit(1);
|
||||
return;
|
||||
}
|
||||
const hasInstall = pluginId in (cfg.plugins?.installs ?? {});
|
||||
|
||||
const preview: string[] = [];
|
||||
if (plan.actions.entry) {
|
||||
|
||||
Reference in New Issue
Block a user