|
1 | 1 | import fs from "node:fs"; |
2 | 2 | import os from "node:os"; |
3 | 3 | import path from "node:path"; |
| 4 | +import { pathToFileURL } from "node:url"; |
4 | 5 | import { importFreshModule } from "openclaw/plugin-sdk/test-fixtures"; |
5 | 6 | import { afterEach, describe, expect, it, vi } from "vitest"; |
6 | 7 | import { loadPluginManifestRegistry } from "../../plugins/manifest-registry.js"; |
@@ -651,6 +652,102 @@ describe("bundled channel entry shape guards", () => { |
651 | 652 | } |
652 | 653 | }); |
653 | 654 |
|
| 655 | + it("does not load bundled runtime entries through external staged runtime deps during discovery", async () => { |
| 656 | + const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-runtime-deps-")); |
| 657 | + const stageRoot = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-stage-")); |
| 658 | + const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; |
| 659 | + const previousPluginStageDir = process.env.OPENCLAW_PLUGIN_STAGE_DIR; |
| 660 | + const pluginDir = path.join(root, "dist", "extensions", "alpha"); |
| 661 | + const testGlobal = globalThis as typeof globalThis & { |
| 662 | + __bundledRuntimeDepMarker?: string; |
| 663 | + }; |
| 664 | + fs.mkdirSync(pluginDir, { recursive: true }); |
| 665 | + fs.writeFileSync( |
| 666 | + path.join(root, "package.json"), |
| 667 | + JSON.stringify({ name: "openclaw", version: "2026.4.21" }), |
| 668 | + "utf8", |
| 669 | + ); |
| 670 | + fs.writeFileSync( |
| 671 | + path.join(pluginDir, "package.json"), |
| 672 | + JSON.stringify({ |
| 673 | + name: "@openclaw/alpha", |
| 674 | + version: "2026.4.21", |
| 675 | + type: "module", |
| 676 | + dependencies: { |
| 677 | + "alpha-runtime-dep": "1.0.0", |
| 678 | + }, |
| 679 | + }), |
| 680 | + "utf8", |
| 681 | + ); |
| 682 | + fs.writeFileSync( |
| 683 | + path.join(pluginDir, "plugin.js"), |
| 684 | + [ |
| 685 | + "import { marker } from 'alpha-runtime-dep';", |
| 686 | + "globalThis.__bundledRuntimeDepMarker = marker;", |
| 687 | + "export default { id: 'alpha', meta: { label: marker }, config: {} };", |
| 688 | + "", |
| 689 | + ].join("\n"), |
| 690 | + "utf8", |
| 691 | + ); |
| 692 | + fs.writeFileSync( |
| 693 | + path.join(pluginDir, "index.js"), |
| 694 | + [ |
| 695 | + `import { defineBundledChannelEntry } from ${JSON.stringify(pathToFileURL(path.resolve("src/plugin-sdk/channel-entry-contract.ts")).href)};`, |
| 696 | + "export default defineBundledChannelEntry({", |
| 697 | + " id: 'alpha',", |
| 698 | + " name: 'Alpha',", |
| 699 | + " description: 'Alpha',", |
| 700 | + " importMetaUrl: import.meta.url,", |
| 701 | + " plugin: { specifier: './plugin.js' },", |
| 702 | + "});", |
| 703 | + "", |
| 704 | + ].join("\n"), |
| 705 | + "utf8", |
| 706 | + ); |
| 707 | + |
| 708 | + process.env.OPENCLAW_PLUGIN_STAGE_DIR = stageRoot; |
| 709 | + const { resolveBundledRuntimeDependencyInstallRoot } = |
| 710 | + await import("../../plugins/bundled-runtime-deps.js"); |
| 711 | + const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginDir); |
| 712 | + const depRoot = path.join(installRoot, "node_modules", "alpha-runtime-dep"); |
| 713 | + fs.mkdirSync(depRoot, { recursive: true }); |
| 714 | + fs.writeFileSync( |
| 715 | + path.join(depRoot, "package.json"), |
| 716 | + JSON.stringify({ |
| 717 | + name: "alpha-runtime-dep", |
| 718 | + version: "1.0.0", |
| 719 | + type: "module", |
| 720 | + main: "index.js", |
| 721 | + }), |
| 722 | + "utf8", |
| 723 | + ); |
| 724 | + fs.writeFileSync(path.join(depRoot, "index.js"), "export const marker = 'staged-alpha';\n"); |
| 725 | + |
| 726 | + mockAlphaDistExtensionRuntime(); |
| 727 | + |
| 728 | + try { |
| 729 | + process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = path.join(root, "dist", "extensions"); |
| 730 | + |
| 731 | + const bundled = await importFreshModule<typeof import("./bundled.js")>( |
| 732 | + import.meta.url, |
| 733 | + "./bundled.js?scope=bundled-runtime-deps", |
| 734 | + ); |
| 735 | + |
| 736 | + expect(bundled.getBundledChannelPlugin("alpha")).toBeUndefined(); |
| 737 | + expect(testGlobal.__bundledRuntimeDepMarker).toBeUndefined(); |
| 738 | + } finally { |
| 739 | + restoreBundledPluginsDir(previousBundledPluginsDir); |
| 740 | + if (previousPluginStageDir === undefined) { |
| 741 | + delete process.env.OPENCLAW_PLUGIN_STAGE_DIR; |
| 742 | + } else { |
| 743 | + process.env.OPENCLAW_PLUGIN_STAGE_DIR = previousPluginStageDir; |
| 744 | + } |
| 745 | + fs.rmSync(root, { recursive: true, force: true }); |
| 746 | + fs.rmSync(stageRoot, { recursive: true, force: true }); |
| 747 | + delete testGlobal.__bundledRuntimeDepMarker; |
| 748 | + } |
| 749 | + }); |
| 750 | + |
654 | 751 | it("swallows and caches bundled plugin and setup load failures", async () => { |
655 | 752 | const root = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-bundled-load-failure-")); |
656 | 753 | const previousBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; |
|
0 commit comments