Files
openclaw/extensions/qa-lab/src/node-exec.ts
Peter Steinberger db02a96c4c refactor(process): route bounded commands through Execa (#106495)
* refactor(process): centralize bounded command execution

* refactor(process): migrate core one-shot commands

* refactor(plugins): migrate one-shot commands

* fix(process): await Windows tree termination

* chore(plugin-sdk): refresh process runtime surface

* refactor(process): migrate remaining bounded commands

* refactor(process): normalize command result handling

* refactor(process): split execution responsibilities

* chore(plugin-sdk): refresh API baseline

* chore(process): remove release-owned changelog entry

* fix(process): narrow binary command input checks

* fix(process): cap sandbox command output

* fix(qa-lab): preserve exact node probe env

* chore(ci): refresh dead export baseline

* fix(process): preserve force-kill command deadlines

* fix(process): avoid post-exit timeout reclassification

* test(process): update scp staging wrapper mock

* test(process): update remaining wrapper mocks

* refactor(qa-lab): preserve Execa tar execution
2026-07-13 11:07:35 -07:00

69 lines
2.1 KiB
TypeScript

// Qa Lab plugin module implements node exec behavior.
import path from "node:path";
import { runExec } from "openclaw/plugin-sdk/process-runtime";
import { resolveQaWindowsSystem32ExePath } from "./windows-system-tools.js";
type ExecFileAsync = (
file: string,
args: readonly string[],
options: {
encoding: "utf8";
env?: NodeJS.ProcessEnv;
},
) => Promise<{ stdout: string; stderr: string }>;
const execFileAsync: ExecFileAsync = async (file, args, options) =>
await runExec(file, [...args], { baseEnv: options.env, logOutput: false });
function isNodeExecPath(execPath: string, platform: NodeJS.Platform): boolean {
const pathModule = platform === "win32" ? path.win32 : path.posix;
const basename = pathModule.basename(execPath).toLowerCase();
return (
basename === "node" ||
basename === "node.exe" ||
basename === "nodejs" ||
basename === "nodejs.exe"
);
}
export async function resolveQaNodeExecPath(params?: {
execPath?: string;
platform?: NodeJS.Platform;
versions?: NodeJS.ProcessVersions;
env?: NodeJS.ProcessEnv;
execFileImpl?: ExecFileAsync;
}): Promise<string> {
const execPath = params?.execPath ?? process.execPath;
const platform = params?.platform ?? process.platform;
const versions = params?.versions ?? process.versions;
if (typeof versions.bun !== "string" && isNodeExecPath(execPath, platform)) {
return execPath;
}
const locator =
platform === "win32" ? resolveQaWindowsSystem32ExePath("where.exe", params?.env) : "which";
const execFileImpl = params?.execFileImpl ?? execFileAsync;
let stdout;
try {
({ stdout } = await execFileImpl(locator, ["node"], {
encoding: "utf8",
env: params?.env,
}));
} catch {
throw new Error(
"Node not found in PATH. QA live lanes require Node for child gateway and CLI processes.",
);
}
const resolved = stdout
.split(/\r?\n/)
.map((entry) => entry.trim())
.find((entry) => entry.length > 0);
if (!resolved) {
throw new Error(
"Node not found in PATH. QA live lanes require Node for child gateway and CLI processes.",
);
}
return resolved;
}