























@@ -360,6 +360,109 @@ describe("installPluginFromNpmSpec e2e", () => {
360360).resolves.toBe(true);
361361});
362362363+it("omits peers when installing beside an existing host-peer plugin", async () => {
364+const rootDir = await makeTempDir("npm-plugin-sibling-peer-e2e");
365+const npmRoot = path.join(rootDir, "managed-npm");
366+const codexName = `codex-peer-plugin-${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
367+const opikName = `opik-peer-plugin-${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
368+const registry = await startStaticRegistry([
369+{
370+packageName: codexName,
371+latest: "1.0.0",
372+versions: [
373+await packPlugin({
374+packageName: codexName,
375+peerDependencies: { openclaw: ">=2026.5.5-beta.2" },
376+peerDependenciesMeta: { openclaw: { optional: true } },
377+pluginId: codexName,
378+version: "1.0.0",
379+ rootDir,
380+}),
381+],
382+},
383+{
384+packageName: opikName,
385+latest: "1.0.0",
386+versions: [
387+await packPlugin({
388+packageName: opikName,
389+peerDependencies: { openclaw: ">=2026.3.2" },
390+peerDependenciesMeta: {},
391+pluginId: opikName,
392+version: "1.0.0",
393+ rootDir,
394+}),
395+],
396+},
397+{
398+packageName: "openclaw",
399+latest: "2026.5.4",
400+versions: [
401+await packPlugin({
402+packageName: "openclaw",
403+pluginId: "registry-openclaw-copy",
404+version: "2026.5.4",
405+ rootDir,
406+}),
407+],
408+},
409+]);
410+process.env.NPM_CONFIG_REGISTRY = registry;
411+process.env.npm_config_registry = registry;
412+413+await fs.mkdir(npmRoot, { recursive: true });
414+await fs.writeFile(
415+path.join(npmRoot, "package.json"),
416+`${JSON.stringify({ private: true, dependencies: { [codexName]: "1.0.0" } }, null, 2)}\n`,
417+"utf8",
418+);
419+await execFileAsync(
420+"npm",
421+["install", "--omit=peer", "--ignore-scripts", "--no-audit", "--no-fund", "--loglevel=error"],
422+{
423+cwd: npmRoot,
424+env: {
425+ ...process.env,
426+NPM_CONFIG_REGISTRY: registry,
427+NPM_CONFIG_LEGACY_PEER_DEPS: "false",
428+NPM_CONFIG_STRICT_PEER_DEPS: "false",
429+npm_config_registry: registry,
430+npm_config_legacy_peer_deps: "false",
431+npm_config_strict_peer_deps: "false",
432+},
433+timeout: 120_000,
434+},
435+);
436+437+const result = await installPluginFromNpmSpec({
438+spec: `${opikName}@1.0.0`,
439+npmDir: npmRoot,
440+logger: { info: () => {}, warn: () => {} },
441+timeoutMs: 120_000,
442+});
443+if (!result.ok) {
444+throw new Error(result.error);
445+}
446+447+const lock = JSON.parse(await fs.readFile(path.join(npmRoot, "package-lock.json"), "utf8")) as {
448+packages?: Record<string, unknown>;
449+};
450+expect(lock.packages?.["node_modules/openclaw"]).toBeUndefined();
451+await expect(fs.lstat(path.join(npmRoot, "node_modules", "openclaw"))).rejects.toMatchObject({
452+code: "ENOENT",
453+});
454+await expect(
455+fs
456+.lstat(path.join(npmRoot, "node_modules", codexName, "node_modules", "openclaw"))
457+.then((stat) => stat.isSymbolicLink()),
458+).resolves.toBe(true);
459+await expect(
460+fs
461+.lstat(path.join(npmRoot, "node_modules", opikName, "node_modules", "openclaw"))
462+.then((stat) => stat.isSymbolicLink()),
463+).resolves.toBe(true);
464+});
465+363466it("relinks managed npm sibling openclaw peers after later plugin installs", async () => {
364467const rootDir = await makeTempDir("npm-plugin-peer-e2e");
365468const npmRoot = path.join(rootDir, "managed-npm");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。