diff --git a/src/plugin-sdk/facade-loader.test.ts b/src/plugin-sdk/facade-loader.test.ts index b0767367c44..e5a0217a1eb 100644 --- a/src/plugin-sdk/facade-loader.test.ts +++ b/src/plugin-sdk/facade-loader.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { clearBundledRuntimeDependencyNodePaths, resolveBundledRuntimeDependencyInstallRoot, @@ -15,19 +15,17 @@ import { setFacadeLoaderJitiFactoryForTest, } from "./facade-loader.js"; import { listImportedBundledPluginFacadeIds as listImportedFacadeRuntimeIds } from "./facade-runtime.js"; -import { - createBundledPluginPublicSurfaceFixture, - createPluginSdkTestHarness, - createThrowingBundledPluginPublicSurfaceFixture, -} from "./test-helpers.js"; +import { createPluginSdkTestHarness } from "./test-helpers.js"; const { createTempDirSync } = createPluginSdkTestHarness(); const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; const originalPluginStageDir = process.env.OPENCLAW_PLUGIN_STAGE_DIR; +const trustedBundledFixturesRoot = path.resolve("dist-runtime", "extensions"); const FACADE_LOADER_GLOBAL = "__openclawTestLoadBundledPluginPublicSurfaceModuleSync"; const STAGED_RUNTIME_DEP_NAME = "openclaw-facade-loader-runtime-dep"; type FacadeLoaderJitiFactory = NonNullable[0]>; +const trustedBundledFixtureDirs: string[] = []; function forceNodeRuntimeVersionsForTest(): () => void { const originalVersions = process.versions; @@ -47,17 +45,52 @@ function forceNodeRuntimeVersionsForTest(): () => void { }; } +function createTrustedBundledFixtureRoot(prefix: string): string { + fs.mkdirSync(trustedBundledFixturesRoot, { recursive: true }); + const rootDir = fs.mkdtempSync(path.join(trustedBundledFixturesRoot, `.${prefix}`)); + trustedBundledFixtureDirs.push(rootDir); + return rootDir; +} + +function writePluginPackageJson(pluginDir: string, name = "demo"): void { + writeJsonFile(path.join(pluginDir, "package.json"), { + name: `@openclaw/plugin-${name}`, + version: "0.0.0", + type: "module", + }); +} + function createBundledPluginDir(prefix: string, marker: string): string { - return createBundledPluginPublicSurfaceFixture({ createTempDirSync, marker, prefix }); + const rootDir = createTrustedBundledFixtureRoot(prefix); + const pluginDir = path.join(rootDir, "demo"); + fs.mkdirSync(pluginDir, { recursive: true }); + writePluginPackageJson(pluginDir); + fs.writeFileSync( + path.join(pluginDir, "api.js"), + `export const marker = ${JSON.stringify(marker)};\n`, + "utf8", + ); + return rootDir; } function createThrowingPluginDir(prefix: string): string { - return createThrowingBundledPluginPublicSurfaceFixture({ createTempDirSync, prefix }); + const rootDir = createTrustedBundledFixtureRoot(prefix); + const pluginDir = path.join(rootDir, "bad"); + fs.mkdirSync(pluginDir, { recursive: true }); + writePluginPackageJson(pluginDir, "bad"); + fs.writeFileSync( + path.join(pluginDir, "api.js"), + `throw new Error("plugin load failure");\n`, + "utf8", + ); + return rootDir; } function createCircularPluginDir(prefix: string): string { - const rootDir = createTempDirSync(prefix); - fs.mkdirSync(path.join(rootDir, "demo"), { recursive: true }); + const rootDir = createTrustedBundledFixtureRoot(prefix); + const pluginDir = path.join(rootDir, "demo"); + fs.mkdirSync(pluginDir, { recursive: true }); + writePluginPackageJson(pluginDir); fs.writeFileSync( path.join(rootDir, "facade.mjs"), [ @@ -71,14 +104,14 @@ function createCircularPluginDir(prefix: string): string { "utf8", ); fs.writeFileSync( - path.join(rootDir, "demo", "helper.js"), + path.join(pluginDir, "helper.js"), ['import { marker } from "../facade.mjs";', "export const circularMarker = marker;", ""].join( "\n", ), "utf8", ); fs.writeFileSync( - path.join(rootDir, "demo", "api.js"), + path.join(pluginDir, "api.js"), ['import "./helper.js";', 'export const marker = "circular-ok";', ""].join("\n"), "utf8", ); @@ -102,21 +135,17 @@ function createPackagedBundledPluginDirWithStagedRuntimeDep(params: { pluginRoot: string; stageRoot: string; } { - const packageRoot = createTempDirSync(params.prefix); - const pluginRoot = path.join(packageRoot, "dist", "extensions", "demo"); - const stageRoot = path.join(packageRoot, "stage"); + const packageRoot = path.resolve("."); + const pluginRoot = path.join(trustedBundledFixturesRoot, "demo"); + const stageRoot = createTempDirSync(`${params.prefix}stage-`); const env = { ...process.env, - OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(packageRoot, "dist", "extensions"), + OPENCLAW_BUNDLED_PLUGINS_DIR: trustedBundledFixturesRoot, OPENCLAW_PLUGIN_STAGE_DIR: stageRoot, }; + trustedBundledFixtureDirs.push(pluginRoot); fs.mkdirSync(pluginRoot, { recursive: true }); - writeJsonFile(path.join(packageRoot, "package.json"), { - name: "openclaw", - version: "0.0.0", - type: "module", - }); writeJsonFile(path.join(pluginRoot, "package.json"), { name: "@openclaw/plugin-demo", version: "0.0.0", @@ -154,7 +183,7 @@ function createPackagedBundledPluginDirWithStagedRuntimeDep(params: { ); return { - bundledPluginsDir: path.join(packageRoot, "dist", "extensions"), + bundledPluginsDir: trustedBundledFixturesRoot, env, installRoot, modulePath, @@ -163,8 +192,18 @@ function createPackagedBundledPluginDirWithStagedRuntimeDep(params: { stageRoot, }; } + +beforeEach(() => { + delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; + delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; + delete process.env.OPENCLAW_PLUGIN_STAGE_DIR; +}); + afterEach(() => { vi.restoreAllMocks(); + for (const dir of trustedBundledFixtureDirs.splice(0)) { + fs.rmSync(dir, { recursive: true, force: true }); + } resetFacadeLoaderStateForTest(); setFacadeLoaderJitiFactoryForTest(undefined); clearBundledRuntimeDependencyNodePaths(); @@ -187,7 +226,7 @@ afterEach(() => { }); describe("plugin-sdk facade loader", () => { - it("honors bundled plugin dir overrides outside the package root", () => { + it("honors trusted bundled plugin dir overrides", () => { const overrideA = createBundledPluginDir("openclaw-facade-loader-a-", "override-a"); const overrideB = createBundledPluginDir("openclaw-facade-loader-b-", "override-b"); @@ -251,11 +290,14 @@ describe("plugin-sdk facade loader", () => { }); it("uses the runtime-supported Jiti boundary for Windows dist facade loads", () => { - const dir = createTempDirSync("openclaw-facade-loader-windows-dist-"); - const bundledPluginsDir = path.join(dir, "dist"); - fs.mkdirSync(path.join(bundledPluginsDir, "demo"), { recursive: true }); + const bundledPluginsDir = createTrustedBundledFixtureRoot( + "openclaw-facade-loader-windows-dist-", + ); + const pluginDir = path.join(bundledPluginsDir, "demo"); + fs.mkdirSync(pluginDir, { recursive: true }); + writePluginPackageJson(pluginDir); fs.writeFileSync( - path.join(bundledPluginsDir, "demo", "api.js"), + path.join(pluginDir, "api.js"), 'export const marker = "windows-dist-ok";\n', "utf8", ); @@ -314,13 +356,15 @@ describe("plugin-sdk facade loader", () => { expect(loaded.marker).toBe("facade:staged"); expect(fs.existsSync(path.join(fixture.pluginRoot, "node_modules"))).toBe(false); expect(fs.realpathSync(fileURLToPath(loaded.moduleUrl))).toBe( - fs.realpathSync(path.join(fixture.installRoot, "dist", "extensions", "demo", "api.js")), + fs.realpathSync( + path.join(fixture.installRoot, "dist-runtime", "extensions", "demo", "api.js"), + ), ); }); it("loads built bundled async public surfaces through staged runtime deps", async () => { const fixture = createPackagedBundledPluginDirWithStagedRuntimeDep({ - marker: "async-staged", + marker: "staged", prefix: "openclaw-facade-loader-built-async-", }); @@ -333,9 +377,11 @@ describe("plugin-sdk facade loader", () => { env: fixture.env, }); - expect(loaded.marker).toBe("facade:async-staged"); + expect(loaded.marker).toBe("facade:staged"); expect(fs.realpathSync(fileURLToPath(loaded.moduleUrl))).toBe( - fs.realpathSync(path.join(fixture.installRoot, "dist", "extensions", "demo", "api.js")), + fs.realpathSync( + path.join(fixture.installRoot, "dist-runtime", "extensions", "demo", "api.js"), + ), ); }); diff --git a/src/plugin-sdk/facade-runtime.test.ts b/src/plugin-sdk/facade-runtime.test.ts index b50fd09e504..13a62d83de7 100644 --- a/src/plugin-sdk/facade-runtime.test.ts +++ b/src/plugin-sdk/facade-runtime.test.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import path from "node:path"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js"; import { createPluginActivationSource, normalizePluginsConfig } from "../plugins/config-state.js"; import { clearPluginDiscoveryCache } from "../plugins/discovery.js"; @@ -17,27 +17,72 @@ import { loadBundledPluginPublicSurfaceModuleSync, resetFacadeRuntimeStateForTest, } from "./facade-runtime.js"; -import { - createBundledPluginPublicSurfaceFixture, - createPluginSdkTestHarness, - createThrowingBundledPluginPublicSurfaceFixture, -} from "./test-helpers.js"; +import { createPluginSdkTestHarness } from "./test-helpers.js"; const { createTempDirSync } = createPluginSdkTestHarness(); const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; const originalStateDir = process.env.OPENCLAW_STATE_DIR; +const trustedBundledFixturesRoot = path.resolve("dist-runtime", "extensions"); +const trustedBundledFixtureDirs: string[] = []; + +function writeJsonFile(filePath: string, value: unknown): void { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8"); +} + +function createTrustedBundledFixtureRoot(prefix: string): string { + fs.mkdirSync(trustedBundledFixturesRoot, { recursive: true }); + const rootDir = fs.mkdtempSync(path.join(trustedBundledFixturesRoot, `.${prefix}`)); + trustedBundledFixtureDirs.push(rootDir); + return rootDir; +} + +function writePluginPackageJson(pluginDir: string, name = "demo"): void { + writeJsonFile(path.join(pluginDir, "package.json"), { + name: `@openclaw/plugin-${name}`, + version: "0.0.0", + type: "module", + }); +} function createBundledPluginDir(prefix: string, marker: string): string { - return createBundledPluginPublicSurfaceFixture({ createTempDirSync, marker, prefix }); + const rootDir = createTrustedBundledFixtureRoot(prefix); + const pluginDir = path.join(rootDir, "demo"); + fs.mkdirSync(pluginDir, { recursive: true }); + writePluginPackageJson(pluginDir); + fs.writeFileSync( + path.join(pluginDir, "api.js"), + `export const marker = ${JSON.stringify(marker)};\n`, + "utf8", + ); + return rootDir; } function createThrowingPluginDir(prefix: string): string { - return createThrowingBundledPluginPublicSurfaceFixture({ createTempDirSync, prefix }); + const rootDir = createTrustedBundledFixtureRoot(prefix); + const pluginDir = path.join(rootDir, "bad"); + fs.mkdirSync(pluginDir, { recursive: true }); + writePluginPackageJson(pluginDir, "bad"); + fs.writeFileSync( + path.join(pluginDir, "api.js"), + `throw new Error("plugin load failure");\n`, + "utf8", + ); + return rootDir; } +beforeEach(() => { + delete process.env.OPENCLAW_BUNDLED_PLUGINS_DIR; + delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS; + delete process.env.OPENCLAW_STATE_DIR; +}); + afterEach(() => { vi.restoreAllMocks(); + for (const dir of trustedBundledFixtureDirs.splice(0)) { + fs.rmSync(dir, { recursive: true, force: true }); + } clearRuntimeConfigSnapshot(); resetFacadeRuntimeStateForTest(); resetFacadeActivationCheckRuntimeStateForTest(); @@ -62,7 +107,7 @@ afterEach(() => { }); describe("plugin-sdk facade runtime", () => { - it("honors bundled plugin dir overrides outside the package root", () => { + it("honors trusted bundled plugin dir overrides", () => { const overrideA = createBundledPluginDir("openclaw-facade-runtime-a-", "override-a"); const overrideB = createBundledPluginDir("openclaw-facade-runtime-b-", "override-b"); @@ -88,7 +133,10 @@ describe("plugin-sdk facade runtime", () => { }); it("falls back to package source surfaces when an override dir is partial", () => { - process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createTempDirSync("openclaw-facade-runtime-empty-"); + process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createBundledPluginDir( + "openclaw-facade-runtime-partial-", + "partial", + ); expect( __testing.resolveFacadeModuleLocation({