mirror of
https://github.com/openclaw/openclaw.git
synced 2026-03-12 07:20:45 +00:00
refactor(process): extract command env resolution helper
This commit is contained in:
@@ -2,9 +2,8 @@ import type { ChildProcess } from "node:child_process";
|
||||
import { EventEmitter } from "node:events";
|
||||
import process from "node:process";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { withEnvAsync } from "../test-utils/env.js";
|
||||
import { attachChildProcessBridge } from "./child-process-bridge.js";
|
||||
import { runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js";
|
||||
import { resolveCommandEnv, runCommandWithTimeout, shouldSpawnWithShell } from "./exec.js";
|
||||
|
||||
describe("runCommandWithTimeout", () => {
|
||||
it("never enables shell execution (Windows cmd.exe injection hardening)", () => {
|
||||
@@ -16,24 +15,31 @@ describe("runCommandWithTimeout", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("merges custom env with process.env", async () => {
|
||||
await withEnvAsync({ OPENCLAW_BASE_ENV: "base" }, async () => {
|
||||
const result = await runCommandWithTimeout(
|
||||
[
|
||||
process.execPath,
|
||||
"-e",
|
||||
'process.stdout.write((process.env.OPENCLAW_BASE_ENV ?? "") + "|" + (process.env.OPENCLAW_TEST_ENV ?? ""))',
|
||||
],
|
||||
{
|
||||
timeoutMs: 80,
|
||||
env: { OPENCLAW_TEST_ENV: "ok" },
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.stdout).toBe("base|ok");
|
||||
expect(result.termination).toBe("exit");
|
||||
it("merges custom env with base env and drops undefined values", async () => {
|
||||
const resolved = resolveCommandEnv({
|
||||
argv: ["node", "script.js"],
|
||||
baseEnv: {
|
||||
OPENCLAW_BASE_ENV: "base",
|
||||
OPENCLAW_TO_REMOVE: undefined,
|
||||
},
|
||||
env: {
|
||||
OPENCLAW_TEST_ENV: "ok",
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolved.OPENCLAW_BASE_ENV).toBe("base");
|
||||
expect(resolved.OPENCLAW_TEST_ENV).toBe("ok");
|
||||
expect(resolved.OPENCLAW_TO_REMOVE).toBeUndefined();
|
||||
});
|
||||
|
||||
it("suppresses npm fund prompts for npm argv", async () => {
|
||||
const resolved = resolveCommandEnv({
|
||||
argv: ["npm", "--version"],
|
||||
baseEnv: {},
|
||||
});
|
||||
|
||||
expect(resolved.NPM_CONFIG_FUND).toBe("false");
|
||||
expect(resolved.npm_config_fund).toBe("false");
|
||||
});
|
||||
|
||||
it("kills command when no output timeout elapses", async () => {
|
||||
|
||||
@@ -174,16 +174,13 @@ export type CommandOptions = {
|
||||
noOutputTimeoutMs?: number;
|
||||
};
|
||||
|
||||
export async function runCommandWithTimeout(
|
||||
argv: string[],
|
||||
optionsOrTimeout: number | CommandOptions,
|
||||
): Promise<SpawnResult> {
|
||||
const options: CommandOptions =
|
||||
typeof optionsOrTimeout === "number" ? { timeoutMs: optionsOrTimeout } : optionsOrTimeout;
|
||||
const { timeoutMs, cwd, input, env, noOutputTimeoutMs } = options;
|
||||
const { windowsVerbatimArguments } = options;
|
||||
const hasInput = input !== undefined;
|
||||
|
||||
export function resolveCommandEnv(params: {
|
||||
argv: string[];
|
||||
env?: NodeJS.ProcessEnv;
|
||||
baseEnv?: NodeJS.ProcessEnv;
|
||||
}): NodeJS.ProcessEnv {
|
||||
const baseEnv = params.baseEnv ?? process.env;
|
||||
const argv = params.argv;
|
||||
const shouldSuppressNpmFund = (() => {
|
||||
const cmd = path.basename(argv[0] ?? "");
|
||||
if (cmd === "npm" || cmd === "npm.cmd" || cmd === "npm.exe") {
|
||||
@@ -196,7 +193,7 @@ export async function runCommandWithTimeout(
|
||||
return false;
|
||||
})();
|
||||
|
||||
const mergedEnv = env ? { ...process.env, ...env } : { ...process.env };
|
||||
const mergedEnv = params.env ? { ...baseEnv, ...params.env } : { ...baseEnv };
|
||||
const resolvedEnv = Object.fromEntries(
|
||||
Object.entries(mergedEnv)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
@@ -210,6 +207,19 @@ export async function runCommandWithTimeout(
|
||||
resolvedEnv.npm_config_fund = "false";
|
||||
}
|
||||
}
|
||||
return resolvedEnv;
|
||||
}
|
||||
|
||||
export async function runCommandWithTimeout(
|
||||
argv: string[],
|
||||
optionsOrTimeout: number | CommandOptions,
|
||||
): Promise<SpawnResult> {
|
||||
const options: CommandOptions =
|
||||
typeof optionsOrTimeout === "number" ? { timeoutMs: optionsOrTimeout } : optionsOrTimeout;
|
||||
const { timeoutMs, cwd, input, env, noOutputTimeoutMs } = options;
|
||||
const { windowsVerbatimArguments } = options;
|
||||
const hasInput = input !== undefined;
|
||||
const resolvedEnv = resolveCommandEnv({ argv, env });
|
||||
|
||||
const stdio = resolveCommandStdio({ hasInput, preferInherit: true });
|
||||
const finalArgv = process.platform === "win32" ? (resolveNpmArgvForWindows(argv) ?? argv) : argv;
|
||||
|
||||
Reference in New Issue
Block a user