mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix(ci): stabilize full release validation
This commit is contained in:
@@ -82,7 +82,7 @@ permissions:
|
||||
|
||||
concurrency:
|
||||
group: full-release-validation-${{ inputs.ref }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: ${{ inputs.ref == 'main' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
@@ -158,7 +158,7 @@ permissions: read-all
|
||||
|
||||
concurrency:
|
||||
group: openclaw-cross-os-release-checks-${{ inputs.ref }}-${{ inputs.provider }}-${{ inputs.mode }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: ${{ inputs.ref == 'main' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
@@ -333,6 +333,9 @@ jobs:
|
||||
cache: pnpm
|
||||
cache-dependency-path: ${{ inputs.candidate_artifact_name == '' && 'source/pnpm-lock.yaml' || 'workflow/pnpm-lock.yaml' }}
|
||||
|
||||
- name: Ensure pnpm store cache directory exists
|
||||
run: mkdir -p "$(pnpm store path --silent)"
|
||||
|
||||
- name: Build candidate artifact once
|
||||
if: inputs.candidate_artifact_name == ''
|
||||
env:
|
||||
|
||||
@@ -56,7 +56,7 @@ on:
|
||||
|
||||
concurrency:
|
||||
group: openclaw-release-checks-${{ inputs.ref }}
|
||||
cancel-in-progress: false
|
||||
cancel-in-progress: ${{ inputs.ref == 'main' }}
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
@@ -4,6 +4,8 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const runExec = vi.hoisted(() => vi.fn());
|
||||
const resolvePreferredOpenClawTmpDirMock = vi.hoisted(() => vi.fn(() => "/tmp/openclaw"));
|
||||
const OPENCLAW_TMP_ROOT = "/tmp/openclaw";
|
||||
const TRASH_SOURCE = `${OPENCLAW_TMP_ROOT}/demo`;
|
||||
|
||||
vi.mock("../process/exec.js", () => ({
|
||||
runExec,
|
||||
@@ -46,7 +48,7 @@ describe("browser trash", () => {
|
||||
const cpSync = vi.spyOn(fs, "cpSync");
|
||||
const rmSync = vi.spyOn(fs, "rmSync");
|
||||
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(runExec).not.toHaveBeenCalled();
|
||||
@@ -55,10 +57,7 @@ describe("browser trash", () => {
|
||||
mode: 0o700,
|
||||
});
|
||||
expect(mkdtempSync).toHaveBeenCalledWith("/home/test/.Trash/demo-123-");
|
||||
expect(renameSync).toHaveBeenCalledWith(
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(renameSync).toHaveBeenCalledWith(TRASH_SOURCE, "/home/test/.Trash/demo-123-secure/demo");
|
||||
expect(cpSync).not.toHaveBeenCalled();
|
||||
expect(rmSync).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -79,12 +78,12 @@ describe("browser trash", () => {
|
||||
const mkdtempSync = mockTrashContainer("secure");
|
||||
const renameSync = vi.spyOn(fs, "renameSync").mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
|
||||
"/real/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(mkdtempSync).toHaveBeenCalledWith("/real/home/test/.Trash/demo-123-");
|
||||
expect(renameSync).toHaveBeenCalledWith(
|
||||
"/tmp/openclaw/demo",
|
||||
TRASH_SOURCE,
|
||||
"/real/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
});
|
||||
@@ -106,12 +105,15 @@ describe("browser trash", () => {
|
||||
it("refuses to use a symlinked trash directory", async () => {
|
||||
const { movePathToTrash } = await import("./trash.js");
|
||||
vi.spyOn(fs, "mkdirSync").mockImplementation(() => undefined);
|
||||
vi.spyOn(fs, "lstatSync").mockReturnValue({
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => true,
|
||||
} as fs.Stats);
|
||||
vi.spyOn(fs, "lstatSync").mockImplementation(
|
||||
(candidate) =>
|
||||
({
|
||||
isDirectory: () => true,
|
||||
isSymbolicLink: () => String(candidate) === "/home/test/.Trash",
|
||||
}) as fs.Stats,
|
||||
);
|
||||
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).rejects.toThrow(
|
||||
await expect(movePathToTrash(TRASH_SOURCE)).rejects.toThrow(
|
||||
"Refusing to use non-directory/symlink trash directory",
|
||||
);
|
||||
});
|
||||
@@ -127,22 +129,15 @@ describe("browser trash", () => {
|
||||
const cpSync = vi.spyOn(fs, "cpSync").mockImplementation(() => undefined);
|
||||
const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(cpSync).toHaveBeenCalledWith(
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
{
|
||||
recursive: true,
|
||||
force: false,
|
||||
errorOnExist: true,
|
||||
},
|
||||
);
|
||||
expect(rmSync).toHaveBeenCalledWith("/tmp/openclaw/demo", {
|
||||
expect(cpSync).toHaveBeenCalledWith(TRASH_SOURCE, "/home/test/.Trash/demo-123-secure/demo", {
|
||||
recursive: true,
|
||||
force: false,
|
||||
errorOnExist: true,
|
||||
});
|
||||
expect(rmSync).toHaveBeenCalledWith(TRASH_SOURCE, { recursive: true, force: false });
|
||||
});
|
||||
|
||||
it("retries copy fallback when the copy destination is created concurrently", async () => {
|
||||
@@ -164,12 +159,12 @@ describe("browser trash", () => {
|
||||
.mockImplementation(() => undefined);
|
||||
const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
);
|
||||
expect(cpSync).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"/tmp/openclaw/demo",
|
||||
TRASH_SOURCE,
|
||||
"/home/test/.Trash/demo-123-first/demo",
|
||||
{
|
||||
recursive: true,
|
||||
@@ -179,7 +174,7 @@ describe("browser trash", () => {
|
||||
);
|
||||
expect(cpSync).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/tmp/openclaw/demo",
|
||||
TRASH_SOURCE,
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
{
|
||||
recursive: true,
|
||||
@@ -203,17 +198,17 @@ describe("browser trash", () => {
|
||||
})
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash(TRASH_SOURCE)).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
);
|
||||
expect(renameSync).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"/tmp/openclaw/demo",
|
||||
TRASH_SOURCE,
|
||||
"/home/test/.Trash/demo-123-first/demo",
|
||||
);
|
||||
expect(renameSync).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/tmp/openclaw/demo",
|
||||
TRASH_SOURCE,
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
);
|
||||
expect(Date.now).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -4,6 +4,12 @@
|
||||
"private": true,
|
||||
"description": "OpenClaw QA synthetic channel plugin",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./api.js": "./api.ts",
|
||||
"./runtime-api.js": "./runtime-api.ts",
|
||||
"./test-api.js": "./test-api.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"typebox": "1.1.33"
|
||||
},
|
||||
|
||||
@@ -51,8 +51,12 @@ describe("startQaGatewayRpcClient", () => {
|
||||
},
|
||||
{ prompt: "hi" },
|
||||
{
|
||||
clientName: "gateway-client",
|
||||
deviceIdentity: null,
|
||||
expectFinal: true,
|
||||
mode: "backend",
|
||||
progress: false,
|
||||
scopes: ["operator.admin"],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -124,8 +128,12 @@ describe("startQaGatewayRpcClient", () => {
|
||||
},
|
||||
{},
|
||||
{
|
||||
clientName: "gateway-client",
|
||||
deviceIdentity: null,
|
||||
expectFinal: undefined,
|
||||
mode: "backend",
|
||||
progress: false,
|
||||
scopes: ["operator.admin"],
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -55,8 +55,12 @@ export async function startQaGatewayRpcClient(params: {
|
||||
},
|
||||
rpcParams ?? {},
|
||||
{
|
||||
clientName: "gateway-client",
|
||||
deviceIdentity: null,
|
||||
expectFinal: opts?.expectFinal,
|
||||
mode: "backend",
|
||||
progress: false,
|
||||
scopes: ["operator.admin"],
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@@ -3,11 +3,20 @@ import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../gateway/protocol/
|
||||
import type { GatewayRpcOpts } from "./gateway-rpc.types.js";
|
||||
import { withProgress } from "./progress.js";
|
||||
|
||||
type CallGatewayFromCliRuntimeExtra = {
|
||||
clientName?: Parameters<typeof callGateway>[0]["clientName"];
|
||||
mode?: Parameters<typeof callGateway>[0]["mode"];
|
||||
deviceIdentity?: Parameters<typeof callGateway>[0]["deviceIdentity"];
|
||||
expectFinal?: boolean;
|
||||
progress?: boolean;
|
||||
scopes?: Parameters<typeof callGateway>[0]["scopes"];
|
||||
};
|
||||
|
||||
export async function callGatewayFromCliRuntime(
|
||||
method: string,
|
||||
opts: GatewayRpcOpts,
|
||||
params?: unknown,
|
||||
extra?: { expectFinal?: boolean; progress?: boolean },
|
||||
extra?: CallGatewayFromCliRuntimeExtra,
|
||||
) {
|
||||
const showProgress = extra?.progress ?? opts.json !== true;
|
||||
return await withProgress(
|
||||
@@ -22,10 +31,12 @@ export async function callGatewayFromCliRuntime(
|
||||
token: opts.token,
|
||||
method,
|
||||
params,
|
||||
deviceIdentity: extra?.deviceIdentity,
|
||||
expectFinal: extra?.expectFinal ?? Boolean(opts.expectFinal),
|
||||
scopes: extra?.scopes,
|
||||
timeoutMs: Number(opts.timeout ?? 10_000),
|
||||
clientName: GATEWAY_CLIENT_NAMES.CLI,
|
||||
mode: GATEWAY_CLIENT_MODES.CLI,
|
||||
clientName: extra?.clientName ?? GATEWAY_CLIENT_NAMES.CLI,
|
||||
mode: extra?.mode ?? GATEWAY_CLIENT_MODES.CLI,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { Command } from "commander";
|
||||
export type { GatewayRpcOpts } from "./gateway-rpc.types.js";
|
||||
import type { OperatorScope } from "../gateway/operator-scopes.js";
|
||||
import type { GatewayClientMode, GatewayClientName } from "../gateway/protocol/client-info.js";
|
||||
import type { DeviceIdentity } from "../infra/device-identity.js";
|
||||
import type { GatewayRpcOpts } from "./gateway-rpc.types.js";
|
||||
export type { GatewayRpcOpts } from "./gateway-rpc.types.js";
|
||||
|
||||
type GatewayRpcRuntimeModule = typeof import("./gateway-rpc.runtime.js");
|
||||
|
||||
@@ -23,7 +26,14 @@ export async function callGatewayFromCli(
|
||||
method: string,
|
||||
opts: GatewayRpcOpts,
|
||||
params?: unknown,
|
||||
extra?: { expectFinal?: boolean; progress?: boolean },
|
||||
extra?: {
|
||||
clientName?: GatewayClientName;
|
||||
mode?: GatewayClientMode;
|
||||
deviceIdentity?: DeviceIdentity | null;
|
||||
expectFinal?: boolean;
|
||||
progress?: boolean;
|
||||
scopes?: OperatorScope[];
|
||||
},
|
||||
) {
|
||||
const runtime = await loadGatewayRpcRuntime();
|
||||
return await runtime.callGatewayFromCliRuntime(method, opts, params, extra);
|
||||
|
||||
@@ -38,7 +38,13 @@ const EMPTY_MANIFEST_REGISTRY: PluginManifestRegistry = {
|
||||
diagnostics: [],
|
||||
};
|
||||
|
||||
afterEach(() => {
|
||||
async function setBundledPluginsDirFixture(dir: string | undefined): Promise<void> {
|
||||
const { setBundledPluginsDirOverrideForTest } = await import("../plugins/bundled-dir.js");
|
||||
setBundledPluginsDirOverrideForTest(dir);
|
||||
}
|
||||
|
||||
afterEach(async () => {
|
||||
await setBundledPluginsDirFixture(undefined);
|
||||
vi.unstubAllEnvs();
|
||||
vi.resetModules();
|
||||
cleanupTrackedTempDirs(tempDirs);
|
||||
@@ -52,10 +58,12 @@ describe("plugin auto-enable preferOver", () => {
|
||||
writeBundledChannelPackage(rootDir, channelId);
|
||||
|
||||
vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", rootDir);
|
||||
await setBundledPluginsDirFixture(rootDir);
|
||||
const { normalizeChatChannelId } = await import("../channels/ids.js");
|
||||
expect(normalizeChatChannelId(channelId)).toBe(channelId);
|
||||
|
||||
vi.stubEnv("OPENCLAW_BUNDLED_PLUGINS_DIR", path.join(rootDir, "missing"));
|
||||
await setBundledPluginsDirFixture(undefined);
|
||||
const { materializePluginAutoEnableCandidates } = await import("./plugin-auto-enable.js");
|
||||
|
||||
const result = materializePluginAutoEnableCandidates({
|
||||
|
||||
@@ -359,6 +359,15 @@ describe("handshake auth helpers", () => {
|
||||
authMethod: "token",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
locality: "shared_secret_loopback_local",
|
||||
hasBrowserOriginHeader: false,
|
||||
sharedAuthOk: true,
|
||||
authMethod: "token",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
|
||||
@@ -265,7 +265,7 @@ export function shouldSkipLocalBackendSelfPairing(params: {
|
||||
const usesSharedSecretAuth = params.authMethod === "token" || params.authMethod === "password";
|
||||
const usesDeviceTokenAuth = params.authMethod === "device-token";
|
||||
return (
|
||||
params.locality === "direct_local" &&
|
||||
(params.locality === "direct_local" || params.locality === "shared_secret_loopback_local") &&
|
||||
!params.hasBrowserOriginHeader &&
|
||||
((params.sharedAuthOk && usesSharedSecretAuth) || usesDeviceTokenAuth)
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { setBundledPluginsDirOverrideForTest } from "../plugins/bundled-dir.js";
|
||||
import {
|
||||
clearBundledRuntimeDependencyNodePaths,
|
||||
resolveBundledRuntimeDependencyInstallRoot,
|
||||
@@ -73,6 +74,11 @@ function createBundledPluginDir(prefix: string, marker: string): string {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
function useBundledPluginDirOverrideForTest(dir: string): void {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
setBundledPluginsDirOverrideForTest(dir);
|
||||
}
|
||||
|
||||
function createThrowingPluginDir(prefix: string): string {
|
||||
const rootDir = createTrustedBundledFixtureRoot(prefix);
|
||||
const pluginDir = path.join(rootDir, "bad");
|
||||
@@ -206,6 +212,7 @@ afterEach(() => {
|
||||
}
|
||||
resetFacadeLoaderStateForTest();
|
||||
setFacadeLoaderJitiFactoryForTest(undefined);
|
||||
setBundledPluginsDirOverrideForTest(undefined);
|
||||
clearBundledRuntimeDependencyNodePaths();
|
||||
delete (globalThis as typeof globalThis & Record<string, unknown>)[FACADE_LOADER_GLOBAL];
|
||||
if (originalBundledPluginsDir === undefined) {
|
||||
@@ -230,14 +237,14 @@ describe("plugin-sdk facade loader", () => {
|
||||
const overrideA = createBundledPluginDir("openclaw-facade-loader-a-", "override-a");
|
||||
const overrideB = createBundledPluginDir("openclaw-facade-loader-b-", "override-b");
|
||||
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideA;
|
||||
useBundledPluginDirOverrideForTest(overrideA);
|
||||
const fromA = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
dirName: "demo",
|
||||
artifactBasename: "api.js",
|
||||
});
|
||||
expect(fromA.marker).toBe("override-a");
|
||||
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideB;
|
||||
useBundledPluginDirOverrideForTest(overrideB);
|
||||
const fromB = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
dirName: "demo",
|
||||
artifactBasename: "api.js",
|
||||
@@ -246,7 +253,8 @@ describe("plugin-sdk facade loader", () => {
|
||||
});
|
||||
|
||||
it("falls back to package source surfaces when an override dir lacks a bundled plugin", () => {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createTempDirSync("openclaw-facade-loader-empty-");
|
||||
const overrideDir = createTrustedBundledFixtureRoot("openclaw-facade-loader-empty-");
|
||||
useBundledPluginDirOverrideForTest(overrideDir);
|
||||
|
||||
const loaded = loadBundledPluginPublicSurfaceModuleSync<{
|
||||
closeTrackedBrowserTabsForSessions: unknown;
|
||||
@@ -272,7 +280,7 @@ describe("plugin-sdk facade loader", () => {
|
||||
|
||||
it("shares loaded facade ids with facade-runtime", () => {
|
||||
const dir = createBundledPluginDir("openclaw-facade-loader-ids-", "identity-check");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
useBundledPluginDirOverrideForTest(dir);
|
||||
|
||||
const first = loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
dirName: "demo",
|
||||
@@ -301,7 +309,7 @@ describe("plugin-sdk facade loader", () => {
|
||||
'export const marker = "windows-dist-ok";\n',
|
||||
"utf8",
|
||||
);
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = bundledPluginsDir;
|
||||
useBundledPluginDirOverrideForTest(bundledPluginsDir);
|
||||
|
||||
const createJitiCalls: Parameters<FacadeLoaderJitiFactory>[] = [];
|
||||
setFacadeLoaderJitiFactoryForTest(((...args) => {
|
||||
@@ -338,7 +346,7 @@ describe("plugin-sdk facade loader", () => {
|
||||
marker: "staged",
|
||||
prefix: "openclaw-facade-loader-runtime-deps-",
|
||||
});
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = fixture.bundledPluginsDir;
|
||||
useBundledPluginDirOverrideForTest(fixture.bundledPluginsDir);
|
||||
process.env.OPENCLAW_PLUGIN_STAGE_DIR = fixture.stageRoot;
|
||||
|
||||
await expect(import(pathToFileURL(fixture.modulePath).href)).rejects.toMatchObject({
|
||||
@@ -367,6 +375,7 @@ describe("plugin-sdk facade loader", () => {
|
||||
marker: "staged",
|
||||
prefix: "openclaw-facade-loader-built-async-",
|
||||
});
|
||||
setBundledPluginsDirOverrideForTest(fixture.bundledPluginsDir);
|
||||
|
||||
const loaded = await loadBundledPluginPublicSurfaceModule<{
|
||||
marker: string;
|
||||
@@ -387,7 +396,7 @@ describe("plugin-sdk facade loader", () => {
|
||||
|
||||
it("breaks circular facade re-entry during module evaluation", () => {
|
||||
const dir = createCircularPluginDir("openclaw-facade-loader-circular-");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
useBundledPluginDirOverrideForTest(dir);
|
||||
(globalThis as typeof globalThis & Record<string, unknown>)[FACADE_LOADER_GLOBAL] =
|
||||
loadBundledPluginPublicSurfaceModuleSync;
|
||||
|
||||
@@ -401,7 +410,7 @@ describe("plugin-sdk facade loader", () => {
|
||||
|
||||
it("clears the cache on load failure so retries re-execute", () => {
|
||||
const dir = createThrowingPluginDir("openclaw-facade-loader-throw-");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
useBundledPluginDirOverrideForTest(dir);
|
||||
|
||||
expect(() =>
|
||||
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
|
||||
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js";
|
||||
import { setBundledPluginsDirOverrideForTest } from "../plugins/bundled-dir.js";
|
||||
import { createPluginActivationSource, normalizePluginsConfig } from "../plugins/config-state.js";
|
||||
import { clearPluginDiscoveryCache } from "../plugins/discovery.js";
|
||||
import { clearPluginManifestRegistryCache } from "../plugins/manifest-registry.js";
|
||||
@@ -59,6 +60,11 @@ function createBundledPluginDir(prefix: string, marker: string): string {
|
||||
return rootDir;
|
||||
}
|
||||
|
||||
function useBundledPluginDirOverrideForTest(dir: string): void {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
setBundledPluginsDirOverrideForTest(dir);
|
||||
}
|
||||
|
||||
function createThrowingPluginDir(prefix: string): string {
|
||||
const rootDir = createTrustedBundledFixtureRoot(prefix);
|
||||
const pluginDir = path.join(rootDir, "bad");
|
||||
@@ -86,6 +92,7 @@ afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
resetFacadeRuntimeStateForTest();
|
||||
resetFacadeActivationCheckRuntimeStateForTest();
|
||||
setBundledPluginsDirOverrideForTest(undefined);
|
||||
clearPluginDiscoveryCache();
|
||||
clearPluginManifestRegistryCache();
|
||||
vi.doUnmock("../plugins/manifest-registry.js");
|
||||
@@ -111,7 +118,7 @@ describe("plugin-sdk facade runtime", () => {
|
||||
const overrideA = createBundledPluginDir("openclaw-facade-runtime-a-", "override-a");
|
||||
const overrideB = createBundledPluginDir("openclaw-facade-runtime-b-", "override-b");
|
||||
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideA;
|
||||
useBundledPluginDirOverrideForTest(overrideA);
|
||||
const fromA = __testing.resolveFacadeModuleLocation({
|
||||
dirName: "demo",
|
||||
artifactBasename: "api.js",
|
||||
@@ -121,7 +128,7 @@ describe("plugin-sdk facade runtime", () => {
|
||||
boundaryRoot: overrideA,
|
||||
});
|
||||
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = overrideB;
|
||||
useBundledPluginDirOverrideForTest(overrideB);
|
||||
const fromB = __testing.resolveFacadeModuleLocation({
|
||||
dirName: "demo",
|
||||
artifactBasename: "api.js",
|
||||
@@ -133,20 +140,18 @@ describe("plugin-sdk facade runtime", () => {
|
||||
});
|
||||
|
||||
it("falls back to package source surfaces when an override dir is partial", () => {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = createBundledPluginDir(
|
||||
"openclaw-facade-runtime-partial-",
|
||||
"partial",
|
||||
);
|
||||
const overrideDir = createTrustedBundledFixtureRoot("openclaw-facade-runtime-empty-");
|
||||
useBundledPluginDirOverrideForTest(overrideDir);
|
||||
|
||||
expect(
|
||||
__testing.resolveFacadeModuleLocation({
|
||||
dirName: "browser",
|
||||
artifactBasename: "browser-maintenance.js",
|
||||
}),
|
||||
).toEqual({
|
||||
modulePath: path.resolve("extensions/browser/browser-maintenance.ts"),
|
||||
boundaryRoot: path.resolve("."),
|
||||
const resolved = __testing.resolveFacadeModuleLocation({
|
||||
dirName: "browser",
|
||||
artifactBasename: "browser-maintenance.js",
|
||||
});
|
||||
|
||||
expect(resolved?.boundaryRoot).not.toBe(overrideDir);
|
||||
expect(resolved?.modulePath).toMatch(
|
||||
/(?:^|\/)(?:extensions|dist-runtime\/extensions)\/browser\/browser-maintenance\.(?:ts|js)$/u,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not fall back to package source surfaces when bundled plugins are disabled", () => {
|
||||
@@ -163,7 +168,7 @@ describe("plugin-sdk facade runtime", () => {
|
||||
|
||||
it("returns the same object identity on repeated calls (sentinel consistency)", () => {
|
||||
const dir = createBundledPluginDir("openclaw-facade-identity-", "identity-check");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
useBundledPluginDirOverrideForTest(dir);
|
||||
const location = {
|
||||
modulePath: path.join(dir, "demo", "api.js"),
|
||||
boundaryRoot: dir,
|
||||
@@ -245,7 +250,7 @@ describe("plugin-sdk facade runtime", () => {
|
||||
});
|
||||
it("clears the cache on load failure so retries re-execute", () => {
|
||||
const dir = createThrowingPluginDir("openclaw-facade-throw-");
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
useBundledPluginDirOverrideForTest(dir);
|
||||
|
||||
expect(() =>
|
||||
loadBundledPluginPublicSurfaceModuleSync<{ marker: string }>({
|
||||
@@ -479,7 +484,7 @@ describe("plugin-sdk facade runtime", () => {
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = dir;
|
||||
useBundledPluginDirOverrideForTest(dir);
|
||||
setRuntimeConfigSnapshot(
|
||||
{
|
||||
plugins: {},
|
||||
|
||||
@@ -7,6 +7,7 @@ import { normalizeOptionalLowercaseString } from "../shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
const DISABLED_BUNDLED_PLUGINS_DIR = path.join(os.tmpdir(), "openclaw-empty-bundled-plugins");
|
||||
let bundledPluginsDirOverrideForTest: string | undefined;
|
||||
|
||||
export function areBundledPluginsDisabled(env: NodeJS.ProcessEnv = process.env): boolean {
|
||||
const raw = normalizeOptionalLowercaseString(env.OPENCLAW_DISABLE_BUNDLED_PLUGINS);
|
||||
@@ -173,6 +174,10 @@ export function resolveBundledPluginsDir(env: NodeJS.ProcessEnv = process.env):
|
||||
return resolveDisabledBundledPluginsDir();
|
||||
}
|
||||
|
||||
if (bundledPluginsDirOverrideForTest) {
|
||||
return bundledPluginsDirOverrideForTest;
|
||||
}
|
||||
|
||||
const override = env.OPENCLAW_BUNDLED_PLUGINS_DIR?.trim();
|
||||
let rejectedExistingOverride: string | null = null;
|
||||
if (override) {
|
||||
@@ -248,3 +253,10 @@ export function resolveBundledPluginsDir(env: NodeJS.ProcessEnv = process.env):
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function setBundledPluginsDirOverrideForTest(dir: string | undefined): void {
|
||||
if (process.env.VITEST !== "true" && process.env.NODE_ENV !== "test") {
|
||||
throw new Error("setBundledPluginsDirOverrideForTest is only available in tests");
|
||||
}
|
||||
bundledPluginsDirOverrideForTest = dir;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user