























@@ -46,6 +46,26 @@ describe("stageBundledPluginRuntimeDeps", () => {
4646return path.join(repoRoot, ".artifacts", "bundled-runtime-deps-stamps", `${pluginId}.json`);
4747}
484849+function legacyRuntimeDepsNodeModulesPath(
50+repoRoot: string,
51+stageKey = "fixture-plugin-1234567890abcdef",
52+) {
53+return path.join(repoRoot, ".local", "bundled-plugin-runtime-deps", stageKey, "node_modules");
54+}
55+56+function writeLegacyRuntimeDepsNodeModulesSymlink(params: {
57+pluginDir: string;
58+repoRoot: string;
59+stageKey?: string;
60+}) {
61+const legacyNodeModulesDir = legacyRuntimeDepsNodeModulesPath(params.repoRoot, params.stageKey);
62+const nodeModulesDir = path.join(params.pluginDir, "node_modules");
63+fs.mkdirSync(legacyNodeModulesDir, { recursive: true });
64+fs.writeFileSync(path.join(legacyNodeModulesDir, "legacy.js"), "module.exports = 0;\n", "utf8");
65+fs.symlinkSync(legacyNodeModulesDir, nodeModulesDir);
66+return { legacyNodeModulesDir, nodeModulesDir };
67+}
68+4969it("pins fallback install specs to exact installed versions", () => {
5070const { repoRoot } = createBundledPluginFixture({
5171packageJson: {
@@ -718,32 +738,81 @@ describe("stageBundledPluginRuntimeDeps", () => {
718738},
719739});
720740const directDir = path.join(repoRoot, "node_modules", "direct");
721-const legacyNodeModulesDir = path.join(
741+fs.mkdirSync(directDir, { recursive: true });
742+fs.writeFileSync(
743+path.join(directDir, "package.json"),
744+'{ "name": "direct", "version": "1.0.0" }\n',
745+"utf8",
746+);
747+fs.writeFileSync(path.join(directDir, "index.js"), "module.exports = 'direct';\n", "utf8");
748+const { legacyNodeModulesDir, nodeModulesDir } = writeLegacyRuntimeDepsNodeModulesSymlink({
749+ pluginDir,
750+ repoRoot,
751+});
752+753+stageBundledPluginRuntimeDeps({ cwd: repoRoot });
754+755+expect(fs.lstatSync(nodeModulesDir).isSymbolicLink()).toBe(false);
756+expect(fs.readFileSync(path.join(nodeModulesDir, "direct", "index.js"), "utf8")).toBe(
757+"module.exports = 'direct';\n",
758+);
759+expect(fs.existsSync(path.join(legacyNodeModulesDir, "legacy.js"))).toBe(true);
760+});
761+762+it("removes legacy OpenClaw-owned symlinked plugin node_modules when deps converge to empty", () => {
763+const { pluginDir, repoRoot } = createBundledPluginFixture({
764+packageJson: {
765+name: "@openclaw/fixture-plugin",
766+version: "1.0.0",
767+optionalDependencies: { optional: "1.0.0" },
768+openclaw: { bundle: { stageRuntimeDependencies: true } },
769+},
770+});
771+const rootNodeModulesDir = path.join(repoRoot, "node_modules");
772+fs.mkdirSync(rootNodeModulesDir, { recursive: true });
773+const { legacyNodeModulesDir, nodeModulesDir } = writeLegacyRuntimeDepsNodeModulesSymlink({
774+ pluginDir,
775+ repoRoot,
776+});
777+778+stageBundledPluginRuntimeDeps({ cwd: repoRoot });
779+780+expect(fs.existsSync(nodeModulesDir)).toBe(false);
781+expect(fs.existsSync(path.join(legacyNodeModulesDir, "legacy.js"))).toBe(true);
782+});
783+784+it("refuses nested symlink targets under the legacy runtime deps root", () => {
785+const { pluginDir, repoRoot } = createBundledPluginFixture({
786+packageJson: {
787+name: "@openclaw/fixture-plugin",
788+version: "1.0.0",
789+dependencies: { direct: "1.0.0" },
790+openclaw: { bundle: { stageRuntimeDependencies: true } },
791+},
792+});
793+const directDir = path.join(repoRoot, "node_modules", "direct");
794+const nestedLegacyNodeModulesDir = path.join(
722795repoRoot,
723796".local",
724797"bundled-plugin-runtime-deps",
725798"fixture-plugin-1234567890abcdef",
799+"nested",
726800"node_modules",
727801);
728802const nodeModulesDir = path.join(pluginDir, "node_modules");
729803fs.mkdirSync(directDir, { recursive: true });
730-fs.mkdirSync(legacyNodeModulesDir, { recursive: true });
804+fs.mkdirSync(nestedLegacyNodeModulesDir, { recursive: true });
731805fs.writeFileSync(
732806path.join(directDir, "package.json"),
733807'{ "name": "direct", "version": "1.0.0" }\n',
734808"utf8",
735809);
736810fs.writeFileSync(path.join(directDir, "index.js"), "module.exports = 'direct';\n", "utf8");
737-fs.writeFileSync(path.join(legacyNodeModulesDir, "legacy.js"), "module.exports = 0;\n", "utf8");
738-fs.symlinkSync(legacyNodeModulesDir, nodeModulesDir);
811+fs.symlinkSync(nestedLegacyNodeModulesDir, nodeModulesDir);
739812740-stageBundledPluginRuntimeDeps({ cwd: repoRoot });
741-742-expect(fs.lstatSync(nodeModulesDir).isSymbolicLink()).toBe(false);
743-expect(fs.readFileSync(path.join(nodeModulesDir, "direct", "index.js"), "utf8")).toBe(
744-"module.exports = 'direct';\n",
813+expect(() => stageBundledPluginRuntimeDeps({ cwd: repoRoot })).toThrow(
814+/refusing to replace runtime deps via symlinked path/u,
745815);
746-expect(fs.existsSync(path.join(legacyNodeModulesDir, "legacy.js"))).toBe(true);
747816});
748817749818it("refuses to write a runtime deps stamp through a symlink", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。