fix: remove plugins from recorded install roots

This commit is contained in:
Shakker
2026-04-26 08:08:30 +01:00
parent d419fb561d
commit 6428440086
2 changed files with 71 additions and 0 deletions

View File

@@ -801,6 +801,26 @@ describe("uninstallPlugin", () => {
});
await expect(fs.access(managedDir)).rejects.toThrow();
});
it("deletes tracked installs from a recorded managed extensions root", async () => {
const currentExtensionsDir = path.join(tempDir, "current", "extensions");
const recordedExtensionsDir = path.join(tempDir, "recorded", "extensions");
const installPath = resolvePluginInstallDir("my-plugin", recordedExtensionsDir);
await fs.mkdir(installPath, { recursive: true });
await fs.writeFile(path.join(installPath, "index.js"), "// plugin");
const result = await uninstallPlugin({
config: createSingleNpmInstallConfig(installPath),
pluginId: "my-plugin",
deleteFiles: true,
extensionsDir: currentExtensionsDir,
});
expectSuccessfulUninstallActions(result, {
directory: true,
});
await expect(fs.access(installPath)).rejects.toThrow();
});
});
describe("resolveUninstallDirectoryTarget", () => {
@@ -851,4 +871,27 @@ describe("resolveUninstallDirectoryTarget", () => {
}),
).toBe(installPath);
});
it("uses configured installPath when it is under the recorded managed extensions root", () => {
const currentExtensionsDir = path.join(os.tmpdir(), "openclaw-uninstall-current", "extensions");
const recordedExtensionsDir = path.join(
os.tmpdir(),
"openclaw-uninstall-recorded",
"extensions",
);
const installPath = resolvePluginInstallDir("my-plugin", recordedExtensionsDir);
expect(
resolveUninstallDirectoryTarget({
pluginId: "my-plugin",
hasInstall: true,
installRecord: {
source: "npm",
spec: "my-plugin@1.0.0",
installPath,
},
extensionsDir: currentExtensionsDir,
}),
).toBe(installPath);
});
});

View File

@@ -61,11 +61,39 @@ export function resolveUninstallDirectoryTarget(params: {
return configuredPath;
}
const recordedManagedPath = resolveRecordedManagedInstallPath({
pluginId: params.pluginId,
installPath: configuredPath,
});
if (recordedManagedPath) {
return recordedManagedPath;
}
// Never trust configured installPath blindly for recursive deletes outside
// the managed extensions directory.
return defaultPath;
}
function resolveRecordedManagedInstallPath(params: {
pluginId: string;
installPath: string;
}): string | null {
const resolvedInstallPath = path.resolve(params.installPath);
const recordedExtensionsDir = path.dirname(resolvedInstallPath);
if (path.basename(recordedExtensionsDir) !== "extensions") {
return null;
}
try {
const canonicalInstallPath = path.resolve(
resolvePluginInstallDir(params.pluginId, recordedExtensionsDir),
);
return canonicalInstallPath === resolvedInstallPath ? params.installPath : null;
} catch {
return null;
}
}
const SHARED_CHANNEL_CONFIG_KEYS = new Set(["defaults", "modelByChannel"]);
/**