mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:30:42 +00:00
fix: restore ci gates on main
This commit is contained in:
@@ -3,11 +3,16 @@ import os from "node:os";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const runExec = vi.hoisted(() => vi.fn());
|
||||
const resolvePreferredOpenClawTmpDirMock = vi.hoisted(() => vi.fn(() => "/tmp/openclaw"));
|
||||
|
||||
vi.mock("../process/exec.js", () => ({
|
||||
runExec,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/temp-path", () => ({
|
||||
resolvePreferredOpenClawTmpDir: resolvePreferredOpenClawTmpDirMock,
|
||||
}));
|
||||
|
||||
function mockTrashContainer(...suffixes: string[]) {
|
||||
let call = 0;
|
||||
return vi.spyOn(fs, "mkdtempSync").mockImplementation((prefix) => {
|
||||
@@ -21,6 +26,8 @@ describe("browser trash", () => {
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
runExec.mockReset();
|
||||
resolvePreferredOpenClawTmpDirMock.mockReset();
|
||||
resolvePreferredOpenClawTmpDirMock.mockReturnValue("/tmp/openclaw");
|
||||
vi.spyOn(Date, "now").mockReturnValue(123);
|
||||
vi.spyOn(os, "homedir").mockReturnValue("/home/test");
|
||||
vi.spyOn(os, "tmpdir").mockReturnValue("/tmp");
|
||||
@@ -39,7 +46,7 @@ describe("browser trash", () => {
|
||||
const cpSync = vi.spyOn(fs, "cpSync");
|
||||
const rmSync = vi.spyOn(fs, "rmSync");
|
||||
|
||||
await expect(movePathToTrash("/tmp/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(runExec).not.toHaveBeenCalled();
|
||||
@@ -48,7 +55,10 @@ describe("browser trash", () => {
|
||||
mode: 0o700,
|
||||
});
|
||||
expect(mkdtempSync).toHaveBeenCalledWith("/home/test/.Trash/demo-123-");
|
||||
expect(renameSync).toHaveBeenCalledWith("/tmp/demo", "/home/test/.Trash/demo-123-secure/demo");
|
||||
expect(renameSync).toHaveBeenCalledWith(
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(cpSync).not.toHaveBeenCalled();
|
||||
expect(rmSync).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -69,12 +79,12 @@ describe("browser trash", () => {
|
||||
const mkdtempSync = mockTrashContainer("secure");
|
||||
const renameSync = vi.spyOn(fs, "renameSync").mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
"/real/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(mkdtempSync).toHaveBeenCalledWith("/real/home/test/.Trash/demo-123-");
|
||||
expect(renameSync).toHaveBeenCalledWith(
|
||||
"/tmp/demo",
|
||||
"/tmp/openclaw/demo",
|
||||
"/real/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
});
|
||||
@@ -101,7 +111,7 @@ describe("browser trash", () => {
|
||||
isSymbolicLink: () => true,
|
||||
} as fs.Stats);
|
||||
|
||||
await expect(movePathToTrash("/tmp/demo")).rejects.toThrow(
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).rejects.toThrow(
|
||||
"Refusing to use non-directory/symlink trash directory",
|
||||
);
|
||||
});
|
||||
@@ -117,15 +127,22 @@ describe("browser trash", () => {
|
||||
const cpSync = vi.spyOn(fs, "cpSync").mockImplementation(() => undefined);
|
||||
const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-secure/demo",
|
||||
);
|
||||
expect(cpSync).toHaveBeenCalledWith("/tmp/demo", "/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", {
|
||||
recursive: true,
|
||||
force: false,
|
||||
errorOnExist: true,
|
||||
});
|
||||
expect(rmSync).toHaveBeenCalledWith("/tmp/demo", { recursive: true, force: false });
|
||||
});
|
||||
|
||||
it("retries copy fallback when the copy destination is created concurrently", async () => {
|
||||
@@ -147,12 +164,12 @@ describe("browser trash", () => {
|
||||
.mockImplementation(() => undefined);
|
||||
const rmSync = vi.spyOn(fs, "rmSync").mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
);
|
||||
expect(cpSync).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"/tmp/demo",
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-first/demo",
|
||||
{
|
||||
recursive: true,
|
||||
@@ -162,7 +179,7 @@ describe("browser trash", () => {
|
||||
);
|
||||
expect(cpSync).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/tmp/demo",
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
{
|
||||
recursive: true,
|
||||
@@ -186,17 +203,17 @@ describe("browser trash", () => {
|
||||
})
|
||||
.mockImplementation(() => undefined);
|
||||
|
||||
await expect(movePathToTrash("/tmp/demo")).resolves.toBe(
|
||||
await expect(movePathToTrash("/tmp/openclaw/demo")).resolves.toBe(
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
);
|
||||
expect(renameSync).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
"/tmp/demo",
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-first/demo",
|
||||
);
|
||||
expect(renameSync).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
"/tmp/demo",
|
||||
"/tmp/openclaw/demo",
|
||||
"/home/test/.Trash/demo-123-second/demo",
|
||||
);
|
||||
expect(Date.now).toHaveBeenCalledTimes(1);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } from "../config/config.js";
|
||||
import {
|
||||
@@ -15,20 +16,32 @@ import {
|
||||
} from "./test-helpers/temp-plugin-extension-fixtures.js";
|
||||
|
||||
const originalBundledPluginsDir = process.env.OPENCLAW_BUNDLED_PLUGINS_DIR;
|
||||
const originalDisableBundledPlugins = process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
|
||||
const tempDirs: string[] = [];
|
||||
|
||||
function createTempDir(): string {
|
||||
return createTempPluginDir(tempDirs, "openclaw-codex-ext-");
|
||||
}
|
||||
|
||||
function createBundledTempDir(): string {
|
||||
delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
|
||||
return createTempPluginDir(tempDirs, "openclaw-codex-ext-", {
|
||||
parentDir: path.join(process.cwd(), "dist-runtime", "extensions"),
|
||||
});
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
cleanupTempPluginTestEnvironment(tempDirs, originalBundledPluginsDir);
|
||||
cleanupTempPluginTestEnvironment(
|
||||
tempDirs,
|
||||
originalBundledPluginsDir,
|
||||
originalDisableBundledPlugins,
|
||||
);
|
||||
});
|
||||
|
||||
describe("agent tool result middleware", () => {
|
||||
it("includes plugin-registered middleware and restores it from cache", async () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
@@ -81,7 +94,7 @@ describe("agent tool result middleware", () => {
|
||||
});
|
||||
|
||||
it("rejects middleware when the manifest omits the runtime contract", () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
@@ -158,7 +171,7 @@ describe("agent tool result middleware", () => {
|
||||
});
|
||||
|
||||
it("merges runtimes when a plugin registers the same middleware function twice", () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
@@ -194,7 +207,7 @@ export default { id: "tool-result-middleware", register(api) {
|
||||
});
|
||||
|
||||
it("lazily loads bundled middleware owners from manifest contracts", async () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
@@ -246,7 +259,7 @@ export default { id: "tool-result-middleware", register(api) {
|
||||
|
||||
describe("Codex app-server extension factories", () => {
|
||||
it("includes plugin-registered Codex app-server extension factories and restores them from cache", async () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
@@ -337,7 +350,7 @@ describe("Codex app-server extension factories", () => {
|
||||
});
|
||||
|
||||
it("rejects bundled plugins that omit the Codex app-server extension contract", () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
@@ -373,7 +386,7 @@ describe("Codex app-server extension factories", () => {
|
||||
});
|
||||
|
||||
it("rejects non-function Codex app-server extension factories from bundled plugins", () => {
|
||||
const tmp = createTempDir();
|
||||
const tmp = createBundledTempDir();
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tmp;
|
||||
|
||||
writeTempPlugin({
|
||||
|
||||
@@ -7,8 +7,14 @@ import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
|
||||
const EMPTY_PLUGIN_SCHEMA = { type: "object", additionalProperties: false, properties: {} };
|
||||
|
||||
export function createTempPluginDir(tempDirs: string[], prefix: string): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
export function createTempPluginDir(
|
||||
tempDirs: string[],
|
||||
prefix: string,
|
||||
options?: { parentDir?: string },
|
||||
): string {
|
||||
const parentDir = options?.parentDir ?? os.tmpdir();
|
||||
fs.mkdirSync(parentDir, { recursive: true });
|
||||
const dir = fs.mkdtempSync(path.join(parentDir, prefix));
|
||||
tempDirs.push(dir);
|
||||
return dir;
|
||||
}
|
||||
@@ -43,6 +49,7 @@ export function writeTempPlugin(params: {
|
||||
export function cleanupTempPluginTestEnvironment(
|
||||
tempDirs: string[],
|
||||
originalBundledPluginsDir: string | undefined,
|
||||
originalDisableBundledPlugins?: string,
|
||||
) {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
@@ -54,6 +61,11 @@ export function cleanupTempPluginTestEnvironment(
|
||||
} else {
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = originalBundledPluginsDir;
|
||||
}
|
||||
if (originalDisableBundledPlugins === undefined) {
|
||||
delete process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS;
|
||||
} else {
|
||||
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = originalDisableBundledPlugins;
|
||||
}
|
||||
}
|
||||
|
||||
export function resetActivePluginRegistryForTest() {
|
||||
|
||||
@@ -15,6 +15,20 @@ function makeTempDir() {
|
||||
return makeTrackedTempDir("openclaw-doctor-plugin-manifests", tempDirs);
|
||||
}
|
||||
|
||||
function makePluginWorkspace() {
|
||||
const workspaceDir = makeTempDir();
|
||||
return {
|
||||
workspaceDir,
|
||||
pluginsRoot: path.join(workspaceDir, ".openclaw", "extensions"),
|
||||
env: {
|
||||
...process.env,
|
||||
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
|
||||
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1",
|
||||
OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE: "1",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function writeManifest(dir: string, manifest: Record<string, unknown>) {
|
||||
fs.writeFileSync(
|
||||
path.join(dir, "openclaw.plugin.json"),
|
||||
@@ -76,7 +90,7 @@ describe("doctor plugin manifest legacy contract repair", () => {
|
||||
});
|
||||
|
||||
it("collects legacy top-level capability keys for migration", () => {
|
||||
const pluginsRoot = makeTempDir();
|
||||
const { pluginsRoot } = makePluginWorkspace();
|
||||
const root = path.join(pluginsRoot, "openai");
|
||||
fs.mkdirSync(root, { recursive: true });
|
||||
writePackageJson(root);
|
||||
@@ -101,7 +115,7 @@ describe("doctor plugin manifest legacy contract repair", () => {
|
||||
});
|
||||
|
||||
it("rewrites legacy top-level capability keys into contracts", async () => {
|
||||
const pluginsRoot = makeTempDir();
|
||||
const { pluginsRoot } = makePluginWorkspace();
|
||||
const root = path.join(pluginsRoot, "openai");
|
||||
fs.mkdirSync(root, { recursive: true });
|
||||
writePackageJson(root);
|
||||
@@ -141,7 +155,7 @@ describe("doctor plugin manifest legacy contract repair", () => {
|
||||
});
|
||||
|
||||
it("ignores non-object contracts payloads when collecting migrations", () => {
|
||||
const pluginsRoot = makeTempDir();
|
||||
const { pluginsRoot } = makePluginWorkspace();
|
||||
const root = path.join(pluginsRoot, "openai");
|
||||
fs.mkdirSync(root, { recursive: true });
|
||||
writePackageJson(root);
|
||||
|
||||
@@ -83,6 +83,7 @@ function buildLegacyManifestContractMigration(params: {
|
||||
export function collectLegacyPluginManifestContractMigrations(params?: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
manifestRoots?: string[];
|
||||
workspaceDir?: string;
|
||||
}): LegacyManifestContractMigration[] {
|
||||
const seen = new Set<string>();
|
||||
const migrations: LegacyManifestContractMigration[] = [];
|
||||
@@ -114,6 +115,7 @@ export function collectLegacyPluginManifestContractMigrations(params?: {
|
||||
for (const plugin of loadPluginManifestRegistry({
|
||||
cache: false,
|
||||
...(params?.env ? { env: params.env } : {}),
|
||||
...(params?.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
}).plugins) {
|
||||
if (seen.has(plugin.manifestPath)) {
|
||||
continue;
|
||||
@@ -138,6 +140,7 @@ export function collectLegacyPluginManifestContractMigrations(params?: {
|
||||
export async function maybeRepairLegacyPluginManifestContracts(params: {
|
||||
env?: NodeJS.ProcessEnv;
|
||||
manifestRoots?: string[];
|
||||
workspaceDir?: string;
|
||||
runtime: RuntimeEnv;
|
||||
prompter: DoctorPrompter;
|
||||
note?: typeof note;
|
||||
@@ -145,6 +148,7 @@ export async function maybeRepairLegacyPluginManifestContracts(params: {
|
||||
const migrations = collectLegacyPluginManifestContractMigrations({
|
||||
...(params.env ? { env: params.env } : {}),
|
||||
...(params.manifestRoots ? { manifestRoots: params.manifestRoots } : {}),
|
||||
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
|
||||
});
|
||||
if (migrations.length === 0) {
|
||||
return;
|
||||
|
||||
@@ -35,6 +35,7 @@ const GATEWAY_TEST_ENV_KEYS = [
|
||||
"OPENCLAW_SKIP_BROWSER_CONTROL_SERVER",
|
||||
"OPENCLAW_SKIP_PROVIDERS",
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
|
||||
] as const;
|
||||
|
||||
function nextGatewayId(prefix: string): string {
|
||||
@@ -110,6 +111,7 @@ async function setupGatewayTempHome(params: { prefix: string; minimalGateway?: b
|
||||
const workspaceDir = path.join(tempHome, "openclaw");
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = await createEmptyBundledPluginsDir(tempHome);
|
||||
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1";
|
||||
return { envSnapshot, tempHome, workspaceDir };
|
||||
}
|
||||
|
||||
@@ -318,6 +320,7 @@ module.exports = {
|
||||
"OPENCLAW_SKIP_BROWSER_CONTROL_SERVER",
|
||||
"OPENCLAW_SKIP_PROVIDERS",
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
|
||||
"OPENCLAW_TEST_MINIMAL_GATEWAY",
|
||||
]);
|
||||
|
||||
@@ -333,6 +336,7 @@ module.exports = {
|
||||
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-wizard-home-"));
|
||||
process.env.HOME = tempHome;
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = await createEmptyBundledPluginsDir(tempHome);
|
||||
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1";
|
||||
delete process.env.OPENCLAW_STATE_DIR;
|
||||
delete process.env.OPENCLAW_CONFIG_PATH;
|
||||
|
||||
|
||||
@@ -815,11 +815,18 @@ describe("gateway server misc", () => {
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
await withEnvAsync({ OPENCLAW_TEST_MINIMAL_GATEWAY: undefined }, async () => {
|
||||
const autoPort = await getFreePort();
|
||||
const autoServer = await startGatewayServer(autoPort);
|
||||
await autoServer.close();
|
||||
});
|
||||
await withEnvAsync(
|
||||
{
|
||||
OPENCLAW_TEST_MINIMAL_GATEWAY: undefined,
|
||||
OPENCLAW_DISABLE_BUNDLED_PLUGINS: undefined,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: path.resolve("extensions"),
|
||||
},
|
||||
async () => {
|
||||
const autoPort = await getFreePort();
|
||||
const autoServer = await startGatewayServer(autoPort);
|
||||
await autoServer.close();
|
||||
},
|
||||
);
|
||||
|
||||
const updated = JSON.parse(await fs.readFile(configPath, "utf-8")) as Record<string, unknown>;
|
||||
const channels = updated.channels as Record<string, unknown> | undefined;
|
||||
|
||||
@@ -70,6 +70,7 @@ const GATEWAY_TEST_ENV_KEYS = [
|
||||
"OPENCLAW_SKIP_GMAIL_WATCHER",
|
||||
"OPENCLAW_SKIP_CANVAS_HOST",
|
||||
"OPENCLAW_BUNDLED_PLUGINS_DIR",
|
||||
"OPENCLAW_DISABLE_BUNDLED_PLUGINS",
|
||||
"OPENCLAW_SKIP_CHANNELS",
|
||||
"OPENCLAW_SKIP_PROVIDERS",
|
||||
"OPENCLAW_SKIP_CRON",
|
||||
@@ -235,6 +236,7 @@ function applyGatewaySkipEnv() {
|
||||
process.env.OPENCLAW_SKIP_PROVIDERS = "1";
|
||||
process.env.OPENCLAW_SKIP_CRON = "1";
|
||||
process.env.OPENCLAW_TEST_MINIMAL_GATEWAY = "1";
|
||||
process.env.OPENCLAW_DISABLE_BUNDLED_PLUGINS = "1";
|
||||
process.env.OPENCLAW_BUNDLED_PLUGINS_DIR = tempHome
|
||||
? path.join(tempHome, "openclaw-test-no-bundled-extensions")
|
||||
: "openclaw-test-no-bundled-extensions";
|
||||
|
||||
@@ -203,6 +203,23 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the Node-adjacent POSIX npm shim when npm-cli.js is unavailable", () => {
|
||||
const execPath = "/opt/node/bin/node";
|
||||
const npmPath = "/opt/node/bin/npm";
|
||||
const runner = resolveBundledRuntimeDepsNpmRunner({
|
||||
env: {},
|
||||
execPath,
|
||||
existsSync: (candidate) => candidate === npmPath,
|
||||
npmArgs: ["install", "acpx@0.5.3"],
|
||||
platform: "linux",
|
||||
});
|
||||
|
||||
expect(runner).toEqual({
|
||||
command: npmPath,
|
||||
args: ["install", "acpx@0.5.3"],
|
||||
});
|
||||
});
|
||||
|
||||
it("refuses Windows shell fallback when no safe npm executable is available", () => {
|
||||
expect(() =>
|
||||
resolveBundledRuntimeDepsNpmRunner({
|
||||
@@ -222,7 +239,7 @@ describe("resolveBundledRuntimeDepsNpmRunner", () => {
|
||||
PATH: "/repo/evil/bin:/usr/bin:/bin",
|
||||
},
|
||||
execPath: "/opt/node/bin/node",
|
||||
existsSync: (candidate) => candidate === "/opt/node/bin/npm",
|
||||
existsSync: (candidate) => candidate === "/usr/bin/npm",
|
||||
npmArgs: ["install"],
|
||||
platform: "linux",
|
||||
}),
|
||||
|
||||
@@ -1325,6 +1325,14 @@ export function resolveBundledRuntimeDepsNpmRunner(params: {
|
||||
throw new Error("Unable to resolve a safe npm executable on Windows");
|
||||
}
|
||||
|
||||
const npmExePath = pathImpl.resolve(nodeDir, "npm");
|
||||
if (existsSync(npmExePath)) {
|
||||
return {
|
||||
command: npmExePath,
|
||||
args: params.npmArgs,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Unable to resolve a safe npm executable");
|
||||
}
|
||||
type BundledPluginRuntimeDepsManifest = {
|
||||
|
||||
@@ -45,6 +45,7 @@ function collectBundledChannelOwnerPluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
channelIds: readonly string[];
|
||||
env: NodeJS.ProcessEnv;
|
||||
bundledPluginsDir?: string;
|
||||
}): string[] {
|
||||
const plugins = normalizePluginsConfig(params.config.plugins);
|
||||
const channelIds = new Set(
|
||||
@@ -55,7 +56,7 @@ function collectBundledChannelOwnerPluginIds(params: {
|
||||
if (channelIds.size === 0) {
|
||||
return [];
|
||||
}
|
||||
const bundledDir = resolveBundledPluginsDir(params.env);
|
||||
const bundledDir = params.bundledPluginsDir ?? resolveBundledPluginsDir(params.env);
|
||||
if (!bundledDir) {
|
||||
return [];
|
||||
}
|
||||
@@ -126,6 +127,7 @@ export function resolveEffectivePluginIds(params: {
|
||||
config: OpenClawConfig;
|
||||
env: NodeJS.ProcessEnv;
|
||||
workspaceDir?: string;
|
||||
bundledPluginsDir?: string;
|
||||
}): string[] {
|
||||
const autoEnabled = applyPluginAutoEnable({
|
||||
config: params.config,
|
||||
@@ -150,6 +152,7 @@ export function resolveEffectivePluginIds(params: {
|
||||
config: effectiveConfig,
|
||||
channelIds: configuredChannelIds,
|
||||
env: params.env,
|
||||
...(params.bundledPluginsDir ? { bundledPluginsDir: params.bundledPluginsDir } : {}),
|
||||
})) {
|
||||
ids.add(pluginId);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user