mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
fix: stabilize tests and reduce plugin memory churn
This commit is contained in:
@@ -3,6 +3,7 @@ import { createRequire } from "node:module";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
import { splitCommandParts } from "./command-line.js";
|
||||
import { resolveAcpxPluginRoot } from "./config.js";
|
||||
import { OPENCLAW_ACPX_LEASE_ID_ARG, OPENCLAW_GATEWAY_INSTANCE_ID_ARG } from "./process-lease.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
@@ -24,8 +25,7 @@ const OWNED_ACP_PACKAGE_NAMES = [
|
||||
"acpx",
|
||||
];
|
||||
const ACP_PACKAGE_MARKERS = [
|
||||
"/@zed-industries/codex-acp/",
|
||||
"/@agentclientprotocol/claude-agent-acp/",
|
||||
...OWNED_ACP_PACKAGE_NAMES.map((packageName) => `/node_modules/${packageName}/`),
|
||||
"/acpx/dist/",
|
||||
];
|
||||
|
||||
@@ -65,8 +65,29 @@ function resolvePackageRoot(packageName: string): string | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
const OWNED_ACP_PACKAGE_ROOTS = OWNED_ACP_PACKAGE_NAMES.map(resolvePackageRoot).filter(
|
||||
(root): root is string => Boolean(root),
|
||||
function resolveOpenClawInstallRoot(pluginRoot: string): string {
|
||||
if (
|
||||
path.basename(pluginRoot) === "acpx" &&
|
||||
path.basename(path.dirname(pluginRoot)) === "extensions"
|
||||
) {
|
||||
const parent = path.dirname(path.dirname(pluginRoot));
|
||||
return path.basename(parent) === "dist" ? path.dirname(parent) : parent;
|
||||
}
|
||||
return path.resolve(pluginRoot, "..");
|
||||
}
|
||||
|
||||
function resolveOwnedAcpPackageRootCandidates(packageName: string): string[] {
|
||||
const pluginRoot = resolveAcpxPluginRoot(import.meta.url);
|
||||
const openClawRoot = resolveOpenClawInstallRoot(pluginRoot);
|
||||
return [
|
||||
resolvePackageRoot(packageName),
|
||||
path.join(pluginRoot, "node_modules", packageName),
|
||||
path.join(openClawRoot, "node_modules", packageName),
|
||||
].flatMap((root) => (root ? [normalizePathLike(root)] : []));
|
||||
}
|
||||
|
||||
const OWNED_ACP_PACKAGE_ROOTS = Array.from(
|
||||
new Set(OWNED_ACP_PACKAGE_NAMES.flatMap(resolveOwnedAcpPackageRootCandidates)),
|
||||
);
|
||||
|
||||
function commandBelongsToResolvedAcpPackage(command: string): boolean {
|
||||
|
||||
@@ -13,14 +13,18 @@ const { buildSessionEntryMock } = vi.hoisted(() => ({
|
||||
buildSessionEntryMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
Agent: vi.fn(),
|
||||
EnvHttpProxyAgent: vi.fn(),
|
||||
ProxyAgent: vi.fn(),
|
||||
fetch: vi.fn(),
|
||||
getGlobalDispatcher: vi.fn(),
|
||||
setGlobalDispatcher: vi.fn(),
|
||||
}));
|
||||
vi.mock("undici", async () => {
|
||||
const actual = await vi.importActual<typeof import("undici")>("undici");
|
||||
return {
|
||||
...actual,
|
||||
Agent: vi.fn(),
|
||||
EnvHttpProxyAgent: vi.fn(),
|
||||
ProxyAgent: vi.fn(),
|
||||
fetch: vi.fn(),
|
||||
getGlobalDispatcher: vi.fn(),
|
||||
setGlobalDispatcher: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/memory-core-host-engine-qmd", () => {
|
||||
const basename = (filePath: string) => filePath.split(/[\\/]/).pop() ?? filePath;
|
||||
|
||||
@@ -9,11 +9,15 @@ const closeGlobalDispatcher = vi.hoisted(() => vi.fn(async () => {}));
|
||||
vi.mock("./runners/contract/runtime.js", () => ({
|
||||
runMatrixQaLive,
|
||||
}));
|
||||
vi.mock("undici", () => ({
|
||||
getGlobalDispatcher: () => ({
|
||||
close: closeGlobalDispatcher,
|
||||
}),
|
||||
}));
|
||||
vi.mock("undici", async () => {
|
||||
const actual = await vi.importActual<typeof import("undici")>("undici");
|
||||
return {
|
||||
...actual,
|
||||
getGlobalDispatcher: () => ({
|
||||
close: closeGlobalDispatcher,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
import { runQaMatrixCommand } from "./cli.runtime.js";
|
||||
|
||||
|
||||
@@ -7,17 +7,20 @@ const ssrfMocks = {
|
||||
};
|
||||
|
||||
// Mock http and https modules before importing the client
|
||||
vi.mock("node:https", () => {
|
||||
vi.mock("node:https", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:https")>("node:https");
|
||||
const httpsRequest = vi.fn();
|
||||
const httpsGet = vi.fn();
|
||||
const httpsModule = { request: httpsRequest, get: httpsGet };
|
||||
return { default: httpsModule, request: httpsRequest, get: httpsGet };
|
||||
const httpsModule = { ...actual, request: httpsRequest, get: httpsGet };
|
||||
return { ...actual, default: httpsModule, request: httpsRequest, get: httpsGet };
|
||||
});
|
||||
|
||||
vi.mock("node:http", () => {
|
||||
vi.mock("node:http", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:http")>("node:http");
|
||||
const httpRequest = vi.fn();
|
||||
const httpGet = vi.fn();
|
||||
return { default: { request: httpRequest, get: httpGet }, request: httpRequest, get: httpGet };
|
||||
const httpModule = { ...actual, request: httpRequest, get: httpGet };
|
||||
return { ...actual, default: httpModule, request: httpRequest, get: httpGet };
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
@@ -275,6 +278,10 @@ describe("resolveLegacyWebhookNameToChatUserId", () => {
|
||||
const baseUrl2 =
|
||||
"https://nas2.example.com/webapi/entry.cgi?api=SYNO.Chat.External&method=chatbot&version=2&token=%22test-2%22";
|
||||
|
||||
beforeAll(async () => {
|
||||
({ resolveLegacyWebhookNameToChatUserId } = await import("./client.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.useFakeTimers();
|
||||
|
||||
@@ -52,11 +52,15 @@ afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
ProxyAgent: proxyMocks.ProxyAgent,
|
||||
fetch: proxyMocks.undiciFetch,
|
||||
setGlobalDispatcher: proxyMocks.setGlobalDispatcher,
|
||||
}));
|
||||
vi.mock("undici", async () => {
|
||||
const actual = await vi.importActual<typeof import("undici")>("undici");
|
||||
return {
|
||||
...actual,
|
||||
ProxyAgent: proxyMocks.ProxyAgent,
|
||||
fetch: proxyMocks.undiciFetch,
|
||||
setGlobalDispatcher: proxyMocks.setGlobalDispatcher,
|
||||
};
|
||||
});
|
||||
|
||||
describe("fetchTelegramChatId", () => {
|
||||
const cases = [
|
||||
|
||||
@@ -65,13 +65,17 @@ vi.mock("node:net", async () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
Agent: AgentCtor,
|
||||
EnvHttpProxyAgent: EnvHttpProxyAgentCtor,
|
||||
ProxyAgent: ProxyAgentCtor,
|
||||
fetch: undiciFetch,
|
||||
setGlobalDispatcher,
|
||||
}));
|
||||
vi.mock("undici", async () => {
|
||||
const actual = await vi.importActual<typeof import("undici")>("undici");
|
||||
return {
|
||||
...actual,
|
||||
Agent: AgentCtor,
|
||||
EnvHttpProxyAgent: EnvHttpProxyAgentCtor,
|
||||
ProxyAgent: ProxyAgentCtor,
|
||||
fetch: undiciFetch,
|
||||
setGlobalDispatcher,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/runtime-env", () => ({
|
||||
createSubsystemLogger: () => ({
|
||||
|
||||
@@ -139,13 +139,17 @@ vi.mock("grammy", () => ({
|
||||
InputFile: function InputFile() {},
|
||||
}));
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
Agent: undiciAgentCtor,
|
||||
EnvHttpProxyAgent: undiciEnvHttpProxyAgentCtor,
|
||||
ProxyAgent: undiciProxyAgentCtor,
|
||||
fetch: undiciFetch,
|
||||
setGlobalDispatcher: undiciSetGlobalDispatcher,
|
||||
}));
|
||||
vi.mock("undici", async () => {
|
||||
const actual = await vi.importActual<typeof import("undici")>("undici");
|
||||
return {
|
||||
...actual,
|
||||
Agent: undiciAgentCtor,
|
||||
EnvHttpProxyAgent: undiciEnvHttpProxyAgentCtor,
|
||||
ProxyAgent: undiciProxyAgentCtor,
|
||||
fetch: undiciFetch,
|
||||
setGlobalDispatcher: undiciSetGlobalDispatcher,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/plugin-config-runtime", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/plugin-config-runtime")>(
|
||||
|
||||
@@ -26,10 +26,14 @@ const { envHttpProxyAgentCtor, proxyAgentCtor } = vi.hoisted(() => ({
|
||||
|
||||
const TEST_UNDICI_RUNTIME_DEPS_KEY = "__OPENCLAW_TEST_UNDICI_RUNTIME_DEPS__";
|
||||
|
||||
vi.mock("undici", () => ({
|
||||
EnvHttpProxyAgent: envHttpProxyAgentCtor,
|
||||
ProxyAgent: proxyAgentCtor,
|
||||
}));
|
||||
vi.mock("undici", async () => {
|
||||
const actual = await vi.importActual<typeof import("undici")>("undici");
|
||||
return {
|
||||
...actual,
|
||||
EnvHttpProxyAgent: envHttpProxyAgentCtor,
|
||||
ProxyAgent: proxyAgentCtor,
|
||||
};
|
||||
});
|
||||
|
||||
const useMultiFileAuthStateMock = vi.mocked(baileys.useMultiFileAuthState);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ const hoisted = await vi.hoisted(async () => {
|
||||
mkdirMock: vi.fn(async (_filePath: string, _options?: { recursive?: boolean }) => undefined),
|
||||
accessMock: vi.fn(async (_filePath: string) => undefined),
|
||||
pathExistsMock: vi.fn(async (_filePath: string) => true),
|
||||
migrateSessionEntriesMock: vi.fn((_entries: unknown[]) => undefined),
|
||||
exportHtmlTemplateContents: new Map<string, string>(),
|
||||
sessionTranscriptContent: "",
|
||||
};
|
||||
@@ -43,6 +44,10 @@ vi.mock("../../infra/fs-safe.js", () => ({
|
||||
pathExists: hoisted.pathExistsMock,
|
||||
}));
|
||||
|
||||
vi.mock("@earendil-works/pi-coding-agent", () => ({
|
||||
migrateSessionEntries: hoisted.migrateSessionEntriesMock,
|
||||
}));
|
||||
|
||||
vi.mock("node:fs", async () => {
|
||||
const actual = await vi.importActual<typeof import("node:fs")>("node:fs");
|
||||
const mockedFs = {
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import fsp from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import {
|
||||
migrateSessionEntries,
|
||||
type FileEntry as PiSessionFileEntry,
|
||||
type SessionEntry as PiSessionEntry,
|
||||
type SessionHeader,
|
||||
import type {
|
||||
FileEntry as PiSessionFileEntry,
|
||||
SessionEntry as PiSessionEntry,
|
||||
SessionHeader,
|
||||
} from "@earendil-works/pi-coding-agent";
|
||||
import { pathExists } from "../../infra/fs-safe.js";
|
||||
import { isRecord } from "../../shared/record-coerce.js";
|
||||
@@ -44,6 +43,11 @@ async function loadTemplate(fileName: string): Promise<string> {
|
||||
return await fsp.readFile(path.join(EXPORT_HTML_DIR, fileName), "utf-8");
|
||||
}
|
||||
|
||||
async function migratePiSessionEntries(fileEntries: PiSessionFileEntry[]): Promise<void> {
|
||||
const { migrateSessionEntries } = await import("@earendil-works/pi-coding-agent");
|
||||
migrateSessionEntries(fileEntries);
|
||||
}
|
||||
|
||||
function replaceHtmlPlaceholder(template: string, name: string, value: string): string {
|
||||
let replaced = false;
|
||||
const placeholder = new RegExp(
|
||||
@@ -243,7 +247,7 @@ async function readSessionDataFromTranscript(sessionFile: string): Promise<{
|
||||
}> {
|
||||
const raw = await fsp.readFile(sessionFile, "utf-8");
|
||||
const { entries: fileEntries, warnings } = parseSessionEntriesWithWarnings(raw);
|
||||
migrateSessionEntries(fileEntries);
|
||||
await migratePiSessionEntries(fileEntries);
|
||||
const header =
|
||||
fileEntries.find((entry): entry is SessionHeader => entry.type === "session") ?? null;
|
||||
const entries = fileEntries.filter((entry): entry is PiSessionEntry => entry.type !== "session");
|
||||
|
||||
@@ -16,9 +16,13 @@ const { spawnMock } = vi.hoisted(() => ({
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
spawn: spawnMock,
|
||||
}));
|
||||
vi.mock("node:child_process", async () => {
|
||||
const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks");
|
||||
return mockNodeBuiltinModule(
|
||||
() => vi.importActual<typeof import("node:child_process")>("node:child_process"),
|
||||
{ spawn: spawnMock },
|
||||
);
|
||||
});
|
||||
|
||||
const tempDirs = new Set<string>();
|
||||
|
||||
|
||||
@@ -8,9 +8,13 @@ const mocks = vi.hoisted(() => ({
|
||||
spawn: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", () => ({
|
||||
spawn: mocks.spawn,
|
||||
}));
|
||||
vi.mock("node:child_process", async () => {
|
||||
const { mockNodeBuiltinModule } = await import("openclaw/plugin-sdk/test-node-mocks");
|
||||
return mockNodeBuiltinModule(
|
||||
() => vi.importActual<typeof import("node:child_process")>("node:child_process"),
|
||||
{ spawn: mocks.spawn },
|
||||
);
|
||||
});
|
||||
|
||||
vi.mock("../agents/skills.js", () => ({
|
||||
hasBinary: mocks.hasBinary,
|
||||
|
||||
@@ -30,13 +30,18 @@ export function registryContainsRuntimePluginIds(
|
||||
}
|
||||
const present = new Set<string>();
|
||||
const loaded = new Set<string>();
|
||||
const pluginStatusById = new Map<string, string | undefined>();
|
||||
for (const plugin of registry.plugins ?? []) {
|
||||
present.add(plugin.id);
|
||||
pluginStatusById.set(plugin.id, plugin.status);
|
||||
if (plugin.status === undefined || plugin.status === "loaded") {
|
||||
loaded.add(plugin.id);
|
||||
}
|
||||
}
|
||||
for (const value of Object.values(registry)) {
|
||||
for (const [key, value] of Object.entries(registry)) {
|
||||
if (key === "diagnostics" || key === "channelSetups") {
|
||||
continue;
|
||||
}
|
||||
if (!Array.isArray(value)) {
|
||||
continue;
|
||||
}
|
||||
@@ -45,6 +50,10 @@ export function registryContainsRuntimePluginIds(
|
||||
const pluginId = entry.pluginId;
|
||||
if (typeof pluginId === "string" && pluginId.length > 0) {
|
||||
present.add(pluginId);
|
||||
const status = pluginStatusById.get(pluginId);
|
||||
if (status === undefined || status === "loaded") {
|
||||
loaded.add(pluginId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ let resolveBundledCapabilityProviderIds: typeof import("./capability-provider-ru
|
||||
let resolveManifestCapabilityProviderIds: typeof import("./capability-provider-runtime.js").resolveManifestCapabilityProviderIds;
|
||||
let clearCurrentPluginMetadataSnapshot: typeof import("./current-plugin-metadata-snapshot.js").clearCurrentPluginMetadataSnapshot;
|
||||
let setCurrentPluginMetadataSnapshot: typeof import("./current-plugin-metadata-snapshot.js").setCurrentPluginMetadataSnapshot;
|
||||
let clearPluginMetadataLifecycleCaches: typeof import("./plugin-metadata-lifecycle.js").clearPluginMetadataLifecycleCaches;
|
||||
|
||||
function expectResolvedCapabilityProviderIds(providers: Array<{ id: string }>, expected: string[]) {
|
||||
expect(providers.map((provider) => provider.id)).toEqual(expected);
|
||||
@@ -307,10 +308,12 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
} = await import("./capability-provider-runtime.js"));
|
||||
({ clearCurrentPluginMetadataSnapshot, setCurrentPluginMetadataSnapshot } =
|
||||
await import("./current-plugin-metadata-snapshot.js"));
|
||||
({ clearPluginMetadataLifecycleCaches } = await import("./plugin-metadata-lifecycle.js"));
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
clearCurrentPluginMetadataSnapshot();
|
||||
clearPluginMetadataLifecycleCaches();
|
||||
mocks.resolveRuntimePluginRegistry.mockReset();
|
||||
mocks.resolveRuntimePluginRegistry.mockReturnValue(undefined);
|
||||
mocks.resolvePluginRegistryLoadCacheKey.mockReset();
|
||||
@@ -1167,7 +1170,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("reads manifest-derived capability plugin ids for each config snapshot", () => {
|
||||
it("reuses manifest metadata while applying compat for each config snapshot", () => {
|
||||
const { cfg, enablementCompat } = createCompatChainConfig();
|
||||
setBundledCapabilityFixture("mediaUnderstandingProviders");
|
||||
mocks.withBundledPluginEnablementCompat.mockReturnValue(enablementCompat);
|
||||
@@ -1180,7 +1183,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
resolvePluginCapabilityProviders({ key: "mediaUnderstandingProviders", cfg }),
|
||||
);
|
||||
|
||||
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledWith({
|
||||
config: cfg,
|
||||
@@ -1222,7 +1225,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
expect(snapshotLoads).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("resolves manifest-derived capability plugin ids for equivalent config snapshots independently", () => {
|
||||
it("reuses equivalent manifest metadata while applying compat per config object", () => {
|
||||
const first = createCompatChainConfig();
|
||||
const second = createCompatChainConfig();
|
||||
setBundledCapabilityFixture("mediaUnderstandingProviders");
|
||||
@@ -1242,7 +1245,7 @@ describe("resolvePluginCapabilityProviders", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.loadPluginManifestRegistry).toHaveBeenCalledTimes(1);
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenCalledTimes(2);
|
||||
expect(mocks.withBundledPluginAllowlistCompat).toHaveBeenNthCalledWith(1, {
|
||||
config: first.cfg,
|
||||
|
||||
@@ -70,9 +70,12 @@ async function withIsolatedSpeechProviderEnvAsync<T>(
|
||||
return await withEnvAsync(isolatedSpeechProviderEnv(overrides), fn);
|
||||
}
|
||||
|
||||
vi.mock("@earendil-works/pi-ai", () => {
|
||||
vi.mock("@earendil-works/pi-ai", async () => {
|
||||
const actual =
|
||||
await vi.importActual<typeof import("@earendil-works/pi-ai")>("@earendil-works/pi-ai");
|
||||
const getApiProvider = vi.fn(() => undefined);
|
||||
return {
|
||||
...actual,
|
||||
completeSimple: vi.fn(),
|
||||
createAssistantMessageEventStream: vi.fn(),
|
||||
getApiProvider,
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
clearPluginHostRuntimeState,
|
||||
dispatchPluginAgentEventSubscriptions,
|
||||
} from "./host-hook-runtime.js";
|
||||
import { clearPluginMetadataLifecycleCaches } from "./plugin-metadata-lifecycle.js";
|
||||
import { createEmptyPluginRegistry } from "./registry-empty.js";
|
||||
import { markPluginRegistryActive, markPluginRegistryRetired } from "./registry-lifecycle.js";
|
||||
import type { PluginRegistry } from "./registry-types.js";
|
||||
@@ -378,4 +379,5 @@ export function resetPluginRuntimeStateForTest(): void {
|
||||
// Otherwise per-test bleed-over of those globals can cause flaky behavior
|
||||
// since this helper is widely used across plugin/agent tests.
|
||||
clearPluginHostRuntimeState();
|
||||
clearPluginMetadataLifecycleCaches();
|
||||
}
|
||||
|
||||
@@ -250,6 +250,7 @@ describe("install-cli.sh", () => {
|
||||
"set -euo pipefail",
|
||||
`cd ${JSON.stringify(process.cwd())}`,
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`export PATH=${JSON.stringify(bin)}`,
|
||||
"os_detect() { printf 'linux\\n'; }",
|
||||
"arch_detect() { printf 'x64\\n'; }",
|
||||
"is_musl_linux() { return 0; }",
|
||||
@@ -361,6 +362,7 @@ describe("install-cli.sh", () => {
|
||||
"set -euo pipefail",
|
||||
`cd ${JSON.stringify(process.cwd())}`,
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`export PATH=${JSON.stringify(`${nodePrefixBin}:${oldBin}:${bin}`)}`,
|
||||
"os_detect() { printf 'linux\\n'; }",
|
||||
"arch_detect() { printf 'x64\\n'; }",
|
||||
"is_musl_linux() { return 0; }",
|
||||
@@ -442,6 +444,7 @@ describe("install-cli.sh", () => {
|
||||
"set -euo pipefail",
|
||||
`cd ${JSON.stringify(process.cwd())}`,
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`export PATH=${JSON.stringify(bin)}`,
|
||||
"os_detect() { printf 'linux\\n'; }",
|
||||
"arch_detect() { printf 'x64\\n'; }",
|
||||
"is_musl_linux() { return 0; }",
|
||||
@@ -514,6 +517,7 @@ describe("install-cli.sh", () => {
|
||||
"set -euo pipefail",
|
||||
`cd ${JSON.stringify(process.cwd())}`,
|
||||
`source ${JSON.stringify(SCRIPT_PATH)}`,
|
||||
`export PATH=${JSON.stringify(bin)}`,
|
||||
"os_detect() { printf 'linux\\n'; }",
|
||||
"arch_detect() { printf 'x64\\n'; }",
|
||||
"is_musl_linux() { return 0; }",
|
||||
|
||||
@@ -29,12 +29,14 @@ exit 0
|
||||
|
||||
function runEnsureNode(root: string, requested: string, extraEnv: NodeJS.ProcessEnv = {}) {
|
||||
const githubPath = join(root, "github-path");
|
||||
const pathOverride = extraEnv.PATH;
|
||||
const result = spawnSync(
|
||||
"bash",
|
||||
[
|
||||
"-c",
|
||||
[
|
||||
"set -e",
|
||||
...(pathOverride ? [`export PATH=${JSON.stringify(pathOverride)}`] : []),
|
||||
`source "${ensureNodeScript}"`,
|
||||
`openclaw_ensure_node "${requested}"`,
|
||||
"command -v node",
|
||||
|
||||
Reference in New Issue
Block a user