
























@@ -299,6 +299,40 @@ describe("installPluginFromNpmSpec", () => {
299299).toBe(false);
300300});
301301302+it("allows duplicate npm installs in update mode", async () => {
303+const stateDir = suiteTempRootTracker.makeTempDir();
304+const npmRoot = path.join(stateDir, "npm");
305+const installRoot = path.join(npmRoot, "node_modules", "@openclaw", "voice-call");
306+fs.mkdirSync(installRoot, { recursive: true });
307+fs.writeFileSync(path.join(installRoot, "old.txt"), "old", "utf-8");
308+mockNpmViewAndInstall({
309+spec: "@openclaw/voice-call@0.0.2",
310+packageName: "@openclaw/voice-call",
311+version: "0.0.2",
312+pluginId: "voice-call",
313+ npmRoot,
314+});
315+316+const result = await installPluginFromNpmSpec({
317+spec: "@openclaw/voice-call@0.0.2",
318+npmDir: npmRoot,
319+mode: "update",
320+logger: { info: () => {}, warn: () => {} },
321+});
322+323+expect(result.ok).toBe(true);
324+if (!result.ok) {
325+throw new Error(result.error);
326+}
327+expect(result.targetDir).toBe(installRoot);
328+expect(result.npmResolution?.version).toBe("0.0.2");
329+expectNpmInstallIntoRoot({
330+calls: runCommandWithTimeoutMock.mock.calls,
331+ npmRoot,
332+spec: "@openclaw/voice-call@0.0.2",
333+});
334+});
335+302336it("aborts when integrity drift callback rejects the fetched artifact", async () => {
303337mockNpmViewMetadataResult(runCommandWithTimeoutMock, {
304338name: "@openclaw/voice-call",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。