Nyk
merge: resolve PR conflicts and add OpenClaw offline E2E harness
39fe4a7
import { spawn } from 'node:child_process'
import { config } from './config'
interface CommandOptions {
cwd?: string
env?: NodeJS.ProcessEnv
timeoutMs?: number
input?: string
}
interface CommandResult {
stdout: string
stderr: string
code: number | null
}
export function runCommand(
command: string,
args: string[],
options: CommandOptions = {}
): Promise<CommandResult> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
cwd: options.cwd,
env: options.env,
shell: false
})
let stdout = ''
let stderr = ''
let timeoutId: NodeJS.Timeout | undefined
if (options.timeoutMs) {
timeoutId = setTimeout(() => {
child.kill('SIGKILL')
}, options.timeoutMs)
}
child.stdout.on('data', (data) => {
stdout += data.toString()
})
child.stderr.on('data', (data) => {
stderr += data.toString()
})
child.on('error', (error) => {
if (timeoutId) clearTimeout(timeoutId)
reject(error)
})
child.on('close', (code) => {
if (timeoutId) clearTimeout(timeoutId)
if (code === 0) {
resolve({ stdout, stderr, code })
return
}
const error = new Error(
`Command failed (${command} ${args.join(' ')}): ${stderr || stdout}`
)
;(error as any).stdout = stdout
;(error as any).stderr = stderr
;(error as any).code = code
reject(error)
})
if (options.input) {
child.stdin.write(options.input)
child.stdin.end()
}
})
}
export function runOpenClaw(args: string[], options: CommandOptions = {}) {
return runCommand(config.openclawBin, args, {
...options,
cwd: options.cwd || config.openclawStateDir || process.cwd()
})
}
export function runClawdbot(args: string[], options: CommandOptions = {}) {
return runCommand(config.clawdbotBin, args, {
...options,
cwd: options.cwd || config.openclawStateDir || process.cwd()
})
}