master's toolcall-open extracted runNativeCommand inside apiproxy for the openPath opener while the picker seam had moved the native chooser (its other consumer) into directory-picker-native; after the merge the two packages each carried a verbatim copy. The runner now lives in packages/util/native-command (zero-dependency library, per the util-group contract) and both native integrations depend on it.
45 lines
1.4 KiB
TypeScript
45 lines
1.4 KiB
TypeScript
/**
|
|
* Shared no-shell `execFile` runner for host-native OS integrations (the
|
|
* native directory chooser, the open-with-default-application hand-off):
|
|
* utf8 stdio capture, abort propagation, Windows console hide. A library,
|
|
* not a plugin — no ctx, no state, no events.
|
|
* @module @deepseek-ai/dsh-native-command
|
|
*/
|
|
|
|
import { execFile } from 'node:child_process'
|
|
|
|
/** Testable command boundary; native implementations never invoke a shell. */
|
|
export type NativeCommandRunner = (
|
|
command: string,
|
|
args: readonly string[],
|
|
signal: AbortSignal,
|
|
) => Promise<{ stdout: string; stderr: string }>
|
|
|
|
/**
|
|
* Run a host command with utf8 stdio, abort propagation, and Windows hide.
|
|
* @param command - executable path or PATH name.
|
|
* @param args - argv (never a shell string).
|
|
* @param signal - caller/connection lifetime; abort terminates the child.
|
|
* @returns captured stdout/stderr on exit 0.
|
|
*/
|
|
export const runNativeCommand: NativeCommandRunner = (command, args, signal) =>
|
|
new Promise((resolve, reject) => {
|
|
execFile(
|
|
command,
|
|
[...args],
|
|
{ encoding: 'utf8', signal, windowsHide: true },
|
|
(error, stdout, stderr) => {
|
|
if (error !== null) {
|
|
const failure = Object.assign(new Error(error.message, { cause: error }), {
|
|
code: error.code,
|
|
stdout,
|
|
stderr,
|
|
})
|
|
reject(failure)
|
|
return
|
|
}
|
|
resolve({ stdout, stderr })
|
|
},
|
|
)
|
|
})
|