workflow_node/
child_process.rs

1use crate::require;
2use js_sys::{Array, Object};
3use lazy_static::lazy_static;
4use node_sys::*;
5use wasm_bindgen::prelude::*;
6use workflow_log::log_info;
7
8lazy_static! {
9    static ref CP: Cp = require("child_process").unchecked_into();
10}
11
12#[wasm_bindgen]
13extern "C" {
14
15    #[wasm_bindgen(extends = Object)]
16    #[derive(Clone)]
17    pub type Cp;
18
19    #[wasm_bindgen(js_name = spawn, method)]
20    pub fn cp_spawn(this: &Cp, cmd: &str) -> ChildProcess;
21
22    #[wasm_bindgen(js_name = spawn, method)]
23    pub fn cp_spawn_with_args(this: &Cp, cmd: &str, args: &SpawnArgs) -> ChildProcess;
24
25    #[wasm_bindgen(js_name = spawn, method)]
26    pub fn cp_spawn_with_args_and_options(
27        this: &Cp,
28        cmd: &str,
29        args: &SpawnArgs,
30        options: &SpawnOptions,
31    ) -> ChildProcess;
32
33    #[wasm_bindgen(extends = Array, js_namespace = child_process)]
34    #[derive(Debug, Clone, PartialEq, Eq)]
35    pub type SpawnArgs;
36
37    #[wasm_bindgen(extends = Object, js_namespace = child_process)]
38    #[derive(Debug, Clone, PartialEq, Eq)]
39    pub type SpawnOptions;
40
41    #[wasm_bindgen(extends = EventEmitter, js_namespace = child_process)]
42    #[derive(Clone, Debug)]
43    pub type ChildProcess;
44
45    #[wasm_bindgen(method, getter)]
46    pub fn exit_code(this: &ChildProcess) -> u64;
47
48    #[wasm_bindgen(method, getter)]
49    pub fn pid(this: &ChildProcess) -> u64;
50
51    #[wasm_bindgen(method, getter)]
52    pub fn stdout(this: &ChildProcess) -> ReadableStream;
53
54    #[wasm_bindgen(method, getter)]
55    pub fn stderr(this: &ChildProcess) -> ReadableStream;
56
57    #[wasm_bindgen(method, getter)]
58    pub fn stdin(this: &ChildProcess) -> WritableStream;
59
60    #[wasm_bindgen(method)]
61    pub fn kill(this: &ChildProcess) -> bool;
62
63    #[wasm_bindgen(method, js_name=kill)]
64    fn kill_with_signal_impl(this: &ChildProcess, signal: JsValue) -> bool;
65}
66
67unsafe impl Send for Cp {}
68unsafe impl Sync for Cp {}
69
70unsafe impl Send for ChildProcess {}
71unsafe impl Sync for ChildProcess {}
72
73unsafe impl Send for SpawnOptions {}
74unsafe impl Sync for SpawnOptions {}
75
76unsafe impl Send for SpawnArgs {}
77unsafe impl Sync for SpawnArgs {}
78
79#[inline(always)]
80pub fn spawn(cmd: &str) -> ChildProcess {
81    CP.cp_spawn(cmd)
82}
83
84#[inline(always)]
85pub fn spawn_with_args(cmd: &str, args: &SpawnArgs) -> ChildProcess {
86    CP.cp_spawn_with_args(cmd, args)
87}
88
89#[inline(always)]
90pub fn spawn_with_args_and_options(
91    cmd: &str,
92    args: &SpawnArgs,
93    options: &SpawnOptions,
94) -> ChildProcess {
95    CP.cp_spawn_with_args_and_options(cmd, args, options)
96}
97
98#[derive(Debug)]
99pub enum KillSignal<'s> {
100    None,
101    SIGKILL,
102    SIGTERM,
103    Message(&'s str),
104    Code(u32),
105}
106
107impl ChildProcess {
108    pub fn kill_with_signal(self: &ChildProcess, signal: KillSignal) -> bool {
109        log_info!("kill_with_signal {:?}", signal);
110        match signal {
111            KillSignal::None => self.kill(),
112            KillSignal::SIGKILL => self.kill_with_signal_impl(JsValue::from("SIGKILL")),
113            KillSignal::SIGTERM => self.kill_with_signal_impl(JsValue::from("SIGTERM")),
114            KillSignal::Message(str) => self.kill_with_signal_impl(JsValue::from(str)),
115            KillSignal::Code(code) => self.kill_with_signal_impl(JsValue::from(code)),
116        }
117    }
118}
119
120impl From<Vec<&str>> for SpawnArgs {
121    fn from(list: Vec<&str>) -> Self {
122        let array = Array::new();
123        for (index, value) in list.iter().enumerate() {
124            array.set(index as u32, JsValue::from(*value));
125        }
126
127        #[allow(unused_mut)]
128        let mut args: Self = ::wasm_bindgen::JsCast::unchecked_into(array);
129        args
130    }
131}
132
133impl From<&[&str]> for SpawnArgs {
134    fn from(list: &[&str]) -> Self {
135        let array = Array::new();
136        for (index, value) in list.iter().enumerate() {
137            array.set(index as u32, JsValue::from(*value));
138        }
139
140        #[allow(unused_mut)]
141        let mut args: Self = ::wasm_bindgen::JsCast::unchecked_into(array);
142        args
143    }
144}
145
146impl From<&[String]> for SpawnArgs {
147    fn from(list: &[String]) -> Self {
148        let array = Array::new();
149        for (index, value) in list.iter().enumerate() {
150            array.set(index as u32, JsValue::from(value));
151        }
152
153        #[allow(unused_mut)]
154        let mut args: Self = ::wasm_bindgen::JsCast::unchecked_into(array);
155        args
156    }
157}
158
159impl Default for SpawnOptions {
160    fn default() -> Self {
161        Self::new()
162    }
163}
164
165impl SpawnOptions {
166    /// "Construct a new `SpawnOptions`.
167    ///
168    /// [NODEJS Documentation](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options)
169    pub fn new() -> Self {
170        #[allow(unused_mut)]
171        let mut ret: Self = ::wasm_bindgen::JsCast::unchecked_into(Object::new());
172        ret
173    }
174
175    pub fn set(&self, key: &str, value: JsValue) -> &Self {
176        let r = ::js_sys::Reflect::set(self.as_ref(), &JsValue::from(key), &value);
177        debug_assert!(
178            r.is_ok(),
179            "setting properties should never fail on our dictionary objects"
180        );
181        let _ = r;
182        self
183    }
184
185    pub fn cwd(&self, cwd: &str) -> &Self {
186        self.set("cwd", JsValue::from(cwd))
187    }
188
189    pub fn env(&self, env: ProcessEnv) -> &Self {
190        self.set("env", JsValue::from(env))
191    }
192
193    pub fn argv0(&self, argv0: &str) -> &Self {
194        self.set("argv0", JsValue::from(argv0))
195    }
196
197    pub fn detached(&self, detached: bool) -> &Self {
198        self.set("detached", JsValue::from(detached))
199    }
200
201    pub fn uid(&self, uid: &str) -> &Self {
202        self.set("uid", JsValue::from(uid))
203    }
204
205    pub fn gid(&self, gid: &str) -> &Self {
206        self.set("gid", JsValue::from(gid))
207    }
208
209    pub fn serialization(&self, serialization: &str) -> &Self {
210        self.set("serialization", JsValue::from(serialization))
211    }
212
213    pub fn shell(&self, shell: bool) -> &Self {
214        self.set("shell", JsValue::from(shell))
215    }
216
217    pub fn shell_str(&self, shell: &str) -> &Self {
218        self.set("shell", JsValue::from(shell))
219    }
220
221    pub fn windows_verbatim_arguments(&self, args: bool) -> &Self {
222        self.set("windowsVerbatimArguments", JsValue::from(args))
223    }
224
225    pub fn windows_hide(&self, windows_hide: bool) -> &Self {
226        self.set("windowsHide", JsValue::from(windows_hide))
227    }
228
229    pub fn timeout(&self, timeout: u32) -> &Self {
230        self.set("timeout", JsValue::from(timeout))
231    }
232
233    // TODO: AbortSignal
234
235    pub fn kill_signal(&self, signal: u32) -> &Self {
236        self.set("killSignal", JsValue::from(signal))
237    }
238
239    pub fn kill_signal_str(&self, signal: &str) -> &Self {
240        self.set("killSignal", JsValue::from(signal))
241    }
242
243    pub fn stdio(&self, stdio: &str) -> &Self {
244        self.set("stdio", JsValue::from(stdio))
245    }
246
247    pub fn stdio_with_array(&self, array: js_sys::Array) -> &Self {
248        self.set("stdio", array.into())
249    }
250}