

























@@ -1085,6 +1085,107 @@ describe("uninstallPlugin", () => {
10851085await expect(fs.lstat(peerLink).then((stat) => stat.isSymbolicLink())).resolves.toBe(true);
10861086});
108710871088+it("prunes managed peer dependencies after their owning npm plugin is uninstalled", async () => {
1089+const stateDir = path.join(tempDir, "state");
1090+const npmRoot = path.join(stateDir, "npm");
1091+const removedPluginDir = path.join(npmRoot, "node_modules", "removed-plugin");
1092+const runtimePeerDir = path.join(npmRoot, "node_modules", "runtime-peer");
1093+await fs.mkdir(removedPluginDir, { recursive: true });
1094+await fs.mkdir(runtimePeerDir, { recursive: true });
1095+await fs.writeFile(
1096+path.join(npmRoot, "package.json"),
1097+`${JSON.stringify(
1098+ {
1099+ private: true,
1100+ dependencies: {
1101+ "removed-plugin": "1.0.0",
1102+ "runtime-peer": "1.0.0",
1103+ },
1104+ openclaw: {
1105+ managedPeerDependencies: ["runtime-peer"],
1106+ },
1107+ },
1108+ null,
1109+ 2,
1110+ )}\n`,
1111+);
1112+await fs.writeFile(
1113+path.join(removedPluginDir, "package.json"),
1114+`${JSON.stringify(
1115+ {
1116+ name: "removed-plugin",
1117+ version: "1.0.0",
1118+ peerDependencies: { "runtime-peer": "^1.0.0" },
1119+ },
1120+ null,
1121+ 2,
1122+ )}\n`,
1123+);
1124+await fs.writeFile(
1125+path.join(runtimePeerDir, "package.json"),
1126+`${JSON.stringify({ name: "runtime-peer", version: "1.0.0" }, null, 2)}\n`,
1127+);
1128+runCommandWithTimeoutMock.mockImplementation(async (argv: string[]) => {
1129+if (argv[1] === "uninstall") {
1130+expect(argv).toContain("--legacy-peer-deps");
1131+await fs.rm(removedPluginDir, { recursive: true, force: true });
1132+const rootManifest = JSON.parse(
1133+await fs.readFile(path.join(npmRoot, "package.json"), "utf8"),
1134+) as { dependencies?: Record<string, string> };
1135+delete rootManifest.dependencies?.["removed-plugin"];
1136+await fs.writeFile(
1137+path.join(npmRoot, "package.json"),
1138+`${JSON.stringify(rootManifest, null, 2)}\n`,
1139+);
1140+return {
1141+code: 0,
1142+stdout: "",
1143+stderr: "",
1144+signal: null,
1145+killed: false,
1146+termination: "exit",
1147+};
1148+}
1149+if (argv[1] === "install") {
1150+expect(argv).toContain("--legacy-peer-deps");
1151+expect(argv).toContain("--omit=peer");
1152+await fs.rm(runtimePeerDir, { recursive: true, force: true });
1153+return {
1154+code: 0,
1155+stdout: "",
1156+stderr: "",
1157+signal: null,
1158+killed: false,
1159+termination: "exit",
1160+};
1161+}
1162+throw new Error(`unexpected command: ${argv.join(" ")}`);
1163+});
1164+1165+const applied = await applyPluginUninstallDirectoryRemoval({
1166+target: removedPluginDir,
1167+cleanup: {
1168+kind: "npm",
1169+ npmRoot,
1170+packageName: "removed-plugin",
1171+},
1172+});
1173+1174+expect(applied).toEqual({ directoryRemoved: true, warnings: [] });
1175+await expectPathAccessState(removedPluginDir, "missing");
1176+await expectPathAccessState(runtimePeerDir, "missing");
1177+const rootManifest = JSON.parse(
1178+await fs.readFile(path.join(npmRoot, "package.json"), "utf8"),
1179+) as {
1180+dependencies?: Record<string, string>;
1181+openclaw?: { managedPeerDependencies?: string[] };
1182+};
1183+expect(rootManifest.dependencies?.["removed-plugin"]).toBeUndefined();
1184+expect(rootManifest.dependencies?.["runtime-peer"]).toBeUndefined();
1185+expect(rootManifest.openclaw?.managedPeerDependencies ?? []).not.toContain("runtime-peer");
1186+expect(runCommandWithTimeoutMock).toHaveBeenCalledTimes(2);
1187+});
1188+10881189it("runs npm cleanup when the managed package directory is already absent", async () => {
10891190const stateDir = path.join(tempDir, "state");
10901191const npmRoot = path.join(stateDir, "npm");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。