test: cover staged bundled facade deps

This commit is contained in:
Peter Steinberger
2026-04-28 03:52:08 +01:00
parent 129b996a4e
commit 518d568de5
2 changed files with 92 additions and 44 deletions

View File

@@ -342,6 +342,9 @@ Facade-loaded bundled plugin public surfaces (`api.ts`, `runtime-api.ts`,
`index.ts`, `setup-entry.ts`, and similar public entry files) prefer the
active runtime config snapshot when OpenClaw is already running. If no runtime
snapshot exists yet, they fall back to the resolved config file on disk.
Packaged bundled plugin facades should be loaded through the OpenClaw SDK
facade loaders; direct imports from `dist/extensions/...` bypass staged runtime
dependency mirrors that packaged installs use for plugin-owned dependencies.
Provider plugins can expose a narrow plugin-local contract barrel when a
helper is intentionally provider-specific and does not belong in a generic SDK

View File

@@ -1,5 +1,6 @@
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 {
clearBundledRuntimeDependencyNodePaths,
@@ -8,6 +9,7 @@ import {
import { shouldExpectNativeJitiForJavaScriptTestRuntime } from "../test-utils/jiti-runtime.js";
import {
listImportedBundledPluginFacadeIds,
loadBundledPluginPublicSurfaceModule,
loadBundledPluginPublicSurfaceModuleSync,
resetFacadeLoaderStateForTest,
setFacadeLoaderJitiFactoryForTest,
@@ -24,6 +26,7 @@ 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 FACADE_LOADER_GLOBAL = "__openclawTestLoadBundledPluginPublicSurfaceModuleSync";
const STAGED_RUNTIME_DEP_NAME = "openclaw-facade-loader-runtime-dep";
type FacadeLoaderJitiFactory = NonNullable<Parameters<typeof setFacadeLoaderJitiFactoryForTest>[0]>;
function forceNodeRuntimeVersionsForTest(): () => void {
@@ -82,74 +85,84 @@ function createCircularPluginDir(prefix: string): string {
return rootDir;
}
function createPackagedBundledPluginDirWithStagedRuntimeDep(prefix: 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 createPackagedBundledPluginDirWithStagedRuntimeDep(params: {
marker: string;
prefix: string;
}): {
bundledPluginsDir: string;
env: NodeJS.ProcessEnv;
installRoot: string;
modulePath: string;
packageRoot: string;
pluginRoot: string;
stageRoot: string;
} {
const packageRoot = createTempDirSync(prefix);
const packageRoot = createTempDirSync(params.prefix);
const pluginRoot = path.join(packageRoot, "dist", "extensions", "demo");
const stageRoot = path.join(packageRoot, "stage");
const env = {
...process.env,
OPENCLAW_BUNDLED_PLUGINS_DIR: path.join(packageRoot, "dist", "extensions"),
OPENCLAW_PLUGIN_STAGE_DIR: stageRoot,
};
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",
type: "module",
dependencies: {
[STAGED_RUNTIME_DEP_NAME]: "1.0.0",
},
});
const modulePath = path.join(pluginRoot, "api.js");
fs.writeFileSync(
path.join(packageRoot, "package.json"),
JSON.stringify({ name: "openclaw", version: "0.0.0", type: "module" }, null, 2),
"utf8",
);
fs.writeFileSync(
path.join(pluginRoot, "package.json"),
JSON.stringify(
{
name: "@openclaw/plugin-demo",
version: "0.0.0",
type: "module",
dependencies: {
"facade-runtime-dep": "1.0.0",
},
},
null,
2,
),
"utf8",
);
fs.writeFileSync(
path.join(pluginRoot, "api.js"),
modulePath,
[
'import { marker as depMarker } from "facade-runtime-dep";',
`import { marker as depMarker } from ${JSON.stringify(STAGED_RUNTIME_DEP_NAME)};`,
"export const marker = `facade:${depMarker}`;",
"export const moduleUrl = import.meta.url;",
"",
].join("\n"),
"utf8",
);
const installRoot = resolveBundledRuntimeDependencyInstallRoot(pluginRoot, {
env: {
...process.env,
OPENCLAW_PLUGIN_STAGE_DIR: stageRoot,
},
env,
});
const depRoot = path.join(installRoot, "node_modules", STAGED_RUNTIME_DEP_NAME);
writeJsonFile(path.join(depRoot, "package.json"), {
name: STAGED_RUNTIME_DEP_NAME,
version: "1.0.0",
type: "module",
exports: "./index.js",
});
const depRoot = path.join(installRoot, "node_modules", "facade-runtime-dep");
fs.mkdirSync(depRoot, { recursive: true });
fs.writeFileSync(
path.join(depRoot, "package.json"),
JSON.stringify(
{ name: "facade-runtime-dep", version: "1.0.0", type: "module", exports: "./index.js" },
null,
2,
),
path.join(depRoot, "index.js"),
`export const marker = ${JSON.stringify(params.marker)};\n`,
"utf8",
);
fs.writeFileSync(path.join(depRoot, "index.js"), 'export const marker = "staged";\n', "utf8");
return {
bundledPluginsDir: path.join(packageRoot, "dist", "extensions"),
env,
installRoot,
modulePath,
packageRoot,
pluginRoot,
stageRoot,
};
}
afterEach(() => {
vi.restoreAllMocks();
resetFacadeLoaderStateForTest();
@@ -278,20 +291,52 @@ describe("plugin-sdk facade loader", () => {
}
});
it("loads built bundled public surfaces through staged runtime deps", () => {
const fixture = createPackagedBundledPluginDirWithStagedRuntimeDep(
"openclaw-facade-loader-runtime-deps-",
);
it("loads built bundled sync public surfaces through staged runtime deps", async () => {
const fixture = createPackagedBundledPluginDirWithStagedRuntimeDep({
marker: "staged",
prefix: "openclaw-facade-loader-runtime-deps-",
});
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = fixture.bundledPluginsDir;
process.env.OPENCLAW_PLUGIN_STAGE_DIR = fixture.stageRoot;
const loaded = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
await expect(import(pathToFileURL(fixture.modulePath).href)).rejects.toMatchObject({
code: "ERR_MODULE_NOT_FOUND",
});
const loaded = loadBundledPluginPublicSurfaceModuleSync<{
marker: string;
moduleUrl: string;
}>({
dirName: "demo",
artifactBasename: "api.js",
});
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")),
);
});
it("loads built bundled async public surfaces through staged runtime deps", async () => {
const fixture = createPackagedBundledPluginDirWithStagedRuntimeDep({
marker: "async-staged",
prefix: "openclaw-facade-loader-built-async-",
});
const loaded = await loadBundledPluginPublicSurfaceModule<{
marker: string;
moduleUrl: string;
}>({
dirName: "demo",
artifactBasename: "api.js",
env: fixture.env,
});
expect(loaded.marker).toBe("facade:async-staged");
expect(fs.realpathSync(fileURLToPath(loaded.moduleUrl))).toBe(
fs.realpathSync(path.join(fixture.installRoot, "dist", "extensions", "demo", "api.js")),
);
});
it("breaks circular facade re-entry during module evaluation", () => {