























@@ -444,7 +444,7 @@ describe("installPluginFromNpmSpec e2e", () => {
444444).resolves.toBeTruthy();
445445});
446446447-it("does not attribute repaired pre-existing peer dependencies to later installs", async () => {
447+it("scans repaired pre-existing peer dependencies during later installs", async () => {
448448const rootDir = await makeTempDir("npm-plugin-repaired-peer-scan-e2e");
449449const npmRoot = path.join(rootDir, "managed-npm");
450450const pluginWithRuntimePeer = `existing-peer-plugin-${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
@@ -531,13 +531,27 @@ describe("installPluginFromNpmSpec e2e", () => {
531531logger: { info: () => {}, warn: () => {} },
532532timeoutMs: 120_000,
533533});
534-if (!later.ok) {
535-throw new Error(later.error);
534+expect(later.ok).toBe(false);
535+if (later.ok) {
536+throw new Error("expected repaired peer dependency scan to block installation");
536537}
538+expect(later.error).toContain("Dynamic code execution detected");
537539540+await expect(
541+fs.lstat(path.join(npmRoot, "node_modules", laterPlugin, "package.json")),
542+).rejects.toHaveProperty("code", "ENOENT");
538543await expect(
539544fs.lstat(path.join(npmRoot, "node_modules", runtimePeer, "package.json")),
540-).resolves.toBeTruthy();
545+).rejects.toHaveProperty("code", "ENOENT");
546+const rootManifest = JSON.parse(
547+await fs.readFile(path.join(npmRoot, "package.json"), "utf8"),
548+) as {
549+dependencies?: Record<string, string>;
550+openclaw?: { managedPeerDependencies?: string[] };
551+};
552+expect(rootManifest.dependencies?.[laterPlugin]).toBeUndefined();
553+expect(rootManifest.dependencies?.[runtimePeer]).toBeUndefined();
554+expect(rootManifest.openclaw?.managedPeerDependencies ?? []).not.toContain(runtimePeer);
541555});
542556543557it("bounds peer dependency discovery across repeated nested package realpaths", async () => {
@@ -680,6 +694,119 @@ describe("installPluginFromNpmSpec e2e", () => {
680694).rejects.toHaveProperty("code", "ENOENT");
681695});
682696697+it("does not take ownership of an existing root dependency observed as a peer", async () => {
698+const rootDir = await makeTempDir("npm-plugin-peer-existing-root-e2e");
699+const npmRoot = path.join(rootDir, "managed-npm");
700+const existingRootDependency = `existing-root-${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
701+const blockedPlugin = `blocked-plugin-${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
702+const runtimePeer = `runtime-peer-${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
703+const registry = await startStaticRegistry([
704+{
705+packageName: existingRootDependency,
706+latest: "1.0.0",
707+versions: [
708+await packPlugin({
709+packageName: existingRootDependency,
710+pluginId: existingRootDependency,
711+version: "1.0.0",
712+ rootDir,
713+}),
714+],
715+},
716+{
717+packageName: blockedPlugin,
718+latest: "1.0.0",
719+versions: [
720+await packPlugin({
721+packageName: blockedPlugin,
722+peerDependencies: {
723+[existingRootDependency]: "^1.0.0",
724+[runtimePeer]: "^1.0.0",
725+},
726+peerDependenciesMeta: {},
727+pluginId: blockedPlugin,
728+version: "1.0.0",
729+ rootDir,
730+}),
731+],
732+},
733+{
734+packageName: runtimePeer,
735+latest: "1.0.0",
736+versions: [
737+await packPlugin({
738+indexJs: "eval('1');\n",
739+packageName: runtimePeer,
740+pluginId: runtimePeer,
741+version: "1.0.0",
742+ rootDir,
743+}),
744+],
745+},
746+]);
747+process.env.NPM_CONFIG_REGISTRY = registry;
748+process.env.npm_config_registry = registry;
749+750+await fs.mkdir(npmRoot, { recursive: true });
751+await fs.writeFile(
752+path.join(npmRoot, "package.json"),
753+`${JSON.stringify(
754+ {
755+ private: true,
756+ dependencies: { [existingRootDependency]: "1.0.0" },
757+ },
758+ null,
759+ 2,
760+ )}\n`,
761+"utf8",
762+);
763+await execFileAsync(
764+"npm",
765+[
766+"install",
767+"--omit=dev",
768+"--omit=peer",
769+"--legacy-peer-deps",
770+"--loglevel=error",
771+"--ignore-scripts",
772+"--no-audit",
773+"--no-fund",
774+],
775+{ cwd: npmRoot },
776+);
777+778+const result = await installPluginFromNpmSpec({
779+spec: `${blockedPlugin}@1.0.0`,
780+npmDir: npmRoot,
781+logger: { info: () => {}, warn: () => {} },
782+timeoutMs: 120_000,
783+});
784+785+expect(result.ok).toBe(false);
786+const rootManifest = JSON.parse(
787+await fs.readFile(path.join(npmRoot, "package.json"), "utf8"),
788+) as {
789+dependencies?: Record<string, string>;
790+openclaw?: { managedPeerDependencies?: string[] };
791+};
792+expect(rootManifest.dependencies?.[existingRootDependency]).toBe("1.0.0");
793+expect(rootManifest.dependencies?.[blockedPlugin]).toBeUndefined();
794+expect(rootManifest.dependencies?.[runtimePeer]).toBeUndefined();
795+expect(rootManifest.openclaw?.managedPeerDependencies ?? []).not.toContain(
796+existingRootDependency,
797+);
798+expect(rootManifest.openclaw?.managedPeerDependencies ?? []).not.toContain(runtimePeer);
799+await expect(
800+fs.lstat(path.join(npmRoot, "node_modules", existingRootDependency, "package.json")),
801+).resolves.toBeTruthy();
802+await expect(
803+fs.lstat(path.join(npmRoot, "node_modules", blockedPlugin, "package.json")),
804+).rejects.toHaveProperty("code", "ENOENT");
805+await expect(
806+fs.lstat(path.join(npmRoot, "node_modules", runtimePeer, "package.json")),
807+).rejects.toHaveProperty("code", "ENOENT");
808+});
809+683810it("scrubs host peers when installing beside an existing host-peer plugin", async () => {
684811const rootDir = await makeTempDir("npm-plugin-sibling-peer-e2e");
685812const npmRoot = path.join(rootDir, "managed-npm");
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。