mirror of
https://github.com/lune-org/lune.git
synced 2024-12-12 13:00:37 +00:00
Refactor process spawn for more granular stdio options
This commit is contained in:
parent
1aa6aef679
commit
e16c28fd40
8 changed files with 263 additions and 105 deletions
|
@ -28,6 +28,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
part:TestMethod("Hello", "world!")
|
part:TestMethod("Hello", "world!")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Stdio options when using `process.spawn` can now be set with more granularity, allowing stderr & stdout to be disabled individually and completely to improve memory usage when they are not being used.
|
||||||
|
|
||||||
## `0.7.8` - October 5th, 2023
|
## `0.7.8` - October 5th, 2023
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
env::{self, consts},
|
env::{self, consts},
|
||||||
path,
|
path,
|
||||||
process::{ExitStatus, Stdio},
|
process::Stdio,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dunce::canonicalize;
|
use dunce::canonicalize;
|
||||||
|
@ -13,12 +13,12 @@ use crate::lune::{scheduler::Scheduler, util::TableBuilder};
|
||||||
|
|
||||||
mod tee_writer;
|
mod tee_writer;
|
||||||
|
|
||||||
mod pipe_inherit;
|
|
||||||
use pipe_inherit::pipe_and_inherit_child_process_stdio;
|
|
||||||
|
|
||||||
mod options;
|
mod options;
|
||||||
use options::ProcessSpawnOptions;
|
use options::ProcessSpawnOptions;
|
||||||
|
|
||||||
|
mod wait_for_child;
|
||||||
|
use wait_for_child::{wait_for_child, WaitForChildResult};
|
||||||
|
|
||||||
const PROCESS_EXIT_IMPL_LUA: &str = r#"
|
const PROCESS_EXIT_IMPL_LUA: &str = r#"
|
||||||
exit(...)
|
exit(...)
|
||||||
yield()
|
yield()
|
||||||
|
@ -169,21 +169,26 @@ async fn process_spawn(
|
||||||
runtime place it on a different thread if possible / necessary
|
runtime place it on a different thread if possible / necessary
|
||||||
|
|
||||||
Note that we have to use our scheduler here, we can't
|
Note that we have to use our scheduler here, we can't
|
||||||
use anything like tokio::task::spawn because our lua
|
be using tokio::task::spawn directly because our lua
|
||||||
scheduler will not drive those futures to completion
|
scheduler would not drive those futures to completion
|
||||||
*/
|
*/
|
||||||
let sched = lua
|
let sched = lua
|
||||||
.app_data_ref::<&Scheduler>()
|
.app_data_ref::<&Scheduler>()
|
||||||
.expect("Lua struct is missing scheduler");
|
.expect("Lua struct is missing scheduler");
|
||||||
|
|
||||||
let (status, stdout, stderr) = sched
|
let res = sched
|
||||||
.spawn(spawn_command(program, args, options))
|
.spawn(spawn_command(program, args, options))
|
||||||
.await
|
.await
|
||||||
.expect("Failed to receive result of spawned process")?;
|
.expect("Failed to receive result of spawned process")?;
|
||||||
|
|
||||||
// NOTE: If an exit code was not given by the child process,
|
/*
|
||||||
// we default to 1 if it yielded any error output, otherwise 0
|
NOTE: If an exit code was not given by the child process,
|
||||||
let code = status.code().unwrap_or(match stderr.is_empty() {
|
we default to 1 if it yielded any error output, otherwise 0
|
||||||
|
|
||||||
|
An exit code may be missing if the process was terminated by
|
||||||
|
some external signal, which is the only time we use this default
|
||||||
|
*/
|
||||||
|
let code = res.status.code().unwrap_or(match res.stderr.is_empty() {
|
||||||
true => 0,
|
true => 0,
|
||||||
false => 1,
|
false => 1,
|
||||||
});
|
});
|
||||||
|
@ -192,8 +197,8 @@ async fn process_spawn(
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_value("ok", code == 0)?
|
.with_value("ok", code == 0)?
|
||||||
.with_value("code", code)?
|
.with_value("code", code)?
|
||||||
.with_value("stdout", lua.create_string(&stdout)?)?
|
.with_value("stdout", lua.create_string(&res.stdout)?)?
|
||||||
.with_value("stderr", lua.create_string(&stderr)?)?
|
.with_value("stderr", lua.create_string(&res.stderr)?)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,9 +206,10 @@ async fn spawn_command(
|
||||||
program: String,
|
program: String,
|
||||||
args: Option<Vec<String>>,
|
args: Option<Vec<String>>,
|
||||||
mut options: ProcessSpawnOptions,
|
mut options: ProcessSpawnOptions,
|
||||||
) -> LuaResult<(ExitStatus, Vec<u8>, Vec<u8>)> {
|
) -> LuaResult<WaitForChildResult> {
|
||||||
let inherit_stdio = options.inherit_stdio;
|
let stdout = options.stdio.stdout;
|
||||||
let stdin = options.stdin.take();
|
let stderr = options.stdio.stderr;
|
||||||
|
let stdin = options.stdio.stdin.take();
|
||||||
|
|
||||||
let mut child = options
|
let mut child = options
|
||||||
.into_command(program, args)
|
.into_command(program, args)
|
||||||
|
@ -211,20 +217,14 @@ async fn spawn_command(
|
||||||
true => Stdio::piped(),
|
true => Stdio::piped(),
|
||||||
false => Stdio::null(),
|
false => Stdio::null(),
|
||||||
})
|
})
|
||||||
.stdout(Stdio::piped())
|
.stdout(stdout.as_stdio())
|
||||||
.stderr(Stdio::piped())
|
.stderr(stderr.as_stdio())
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
// If the stdin option was provided, we write that to the child
|
|
||||||
if let Some(stdin) = stdin {
|
if let Some(stdin) = stdin {
|
||||||
let mut child_stdin = child.stdin.take().unwrap();
|
let mut child_stdin = child.stdin.take().unwrap();
|
||||||
child_stdin.write_all(&stdin).await.into_lua_err()?;
|
child_stdin.write_all(&stdin).await.into_lua_err()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if inherit_stdio {
|
wait_for_child(child, stdout, stderr).await
|
||||||
pipe_and_inherit_child_process_stdio(child).await
|
|
||||||
} else {
|
|
||||||
let output = child.wait_with_output().await?;
|
|
||||||
Ok((output.status, output.stdout, output.stderr))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
80
src/lune/builtins/process/options/kind.rs
Normal file
80
src/lune/builtins/process/options/kind.rs
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
use std::{fmt, process::Stdio, str::FromStr};
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||||
|
pub enum ProcessSpawnOptionsStdioKind {
|
||||||
|
// TODO: We need better more obvious names
|
||||||
|
// for these, but that is a breaking change
|
||||||
|
#[default]
|
||||||
|
Default,
|
||||||
|
Forward,
|
||||||
|
Inherit,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProcessSpawnOptionsStdioKind {
|
||||||
|
pub fn all() -> &'static [Self] {
|
||||||
|
&[Self::Default, Self::Forward, Self::Inherit, Self::None]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_stdio(self) -> Stdio {
|
||||||
|
match self {
|
||||||
|
Self::None => Stdio::null(),
|
||||||
|
Self::Forward => Stdio::inherit(),
|
||||||
|
_ => Stdio::piped(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ProcessSpawnOptionsStdioKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let s = match *self {
|
||||||
|
Self::Default => "default",
|
||||||
|
Self::Forward => "forward",
|
||||||
|
Self::Inherit => "inherit",
|
||||||
|
Self::None => "none",
|
||||||
|
};
|
||||||
|
f.write_str(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ProcessSpawnOptionsStdioKind {
|
||||||
|
type Err = LuaError;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(match s.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"default" => Self::Default,
|
||||||
|
"forward" => Self::Forward,
|
||||||
|
"inherit" => Self::Inherit,
|
||||||
|
"none" => Self::None,
|
||||||
|
_ => {
|
||||||
|
return Err(LuaError::RuntimeError(format!(
|
||||||
|
"Invalid spawn options stdio kind - got '{}', expected one of {}",
|
||||||
|
s,
|
||||||
|
ProcessSpawnOptionsStdioKind::all()
|
||||||
|
.iter()
|
||||||
|
.map(|k| format!("'{k}'"))
|
||||||
|
.join(", ")
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> FromLua<'lua> for ProcessSpawnOptionsStdioKind {
|
||||||
|
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
||||||
|
match value {
|
||||||
|
LuaValue::Nil => Ok(Self::default()),
|
||||||
|
LuaValue::String(s) => s.to_str()?.parse(),
|
||||||
|
_ => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "ProcessSpawnOptionsStdioKind",
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid spawn options stdio kind - expected string, got {}",
|
||||||
|
value.type_name()
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,13 +8,18 @@ use directories::UserDirs;
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
|
|
||||||
|
mod kind;
|
||||||
|
mod stdio;
|
||||||
|
|
||||||
|
pub(super) use kind::*;
|
||||||
|
pub(super) use stdio::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ProcessSpawnOptions {
|
pub(super) struct ProcessSpawnOptions {
|
||||||
pub(crate) cwd: Option<PathBuf>,
|
pub cwd: Option<PathBuf>,
|
||||||
pub(crate) envs: HashMap<String, String>,
|
pub envs: HashMap<String, String>,
|
||||||
pub(crate) shell: Option<String>,
|
pub shell: Option<String>,
|
||||||
pub(crate) inherit_stdio: bool,
|
pub stdio: ProcessSpawnOptionsStdio,
|
||||||
pub(crate) stdin: Option<Vec<u8>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
|
impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
|
||||||
|
@ -112,34 +117,14 @@ impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
If we got options for stdio handling, make sure its one of the constant values
|
If we got options for stdio handling, parse those as well - note that
|
||||||
*/
|
we accept a separate "stdin" value here for compatibility with older
|
||||||
match value.get("stdio")? {
|
scripts, but the user should preferrably pass it in the stdio table
|
||||||
LuaValue::Nil => {}
|
|
||||||
LuaValue::String(s) => match s.to_str()? {
|
|
||||||
"inherit" => this.inherit_stdio = true,
|
|
||||||
"default" => this.inherit_stdio = false,
|
|
||||||
_ => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'",
|
|
||||||
s.to_string_lossy()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
value => {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid type for option 'stdio' - expected 'string', got '{}'",
|
|
||||||
value.type_name()
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
If we have stdin contents, we need to pass those to the child process
|
|
||||||
*/
|
*/
|
||||||
|
this.stdio = value.get("stdio")?;
|
||||||
match value.get("stdin")? {
|
match value.get("stdin")? {
|
||||||
LuaValue::Nil => {}
|
LuaValue::Nil => {}
|
||||||
LuaValue::String(s) => this.stdin = Some(s.as_bytes().to_vec()),
|
LuaValue::String(s) => this.stdio.stdin = Some(s.as_bytes().to_vec()),
|
||||||
value => {
|
value => {
|
||||||
return Err(LuaError::RuntimeError(format!(
|
return Err(LuaError::RuntimeError(format!(
|
||||||
"Invalid type for option 'stdin' - expected 'string', got '{}'",
|
"Invalid type for option 'stdin' - expected 'string', got '{}'",
|
56
src/lune/builtins/process/options/stdio.rs
Normal file
56
src/lune/builtins/process/options/stdio.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use super::kind::ProcessSpawnOptionsStdioKind;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct ProcessSpawnOptionsStdio {
|
||||||
|
pub stdout: ProcessSpawnOptionsStdioKind,
|
||||||
|
pub stderr: ProcessSpawnOptionsStdioKind,
|
||||||
|
pub stdin: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProcessSpawnOptionsStdioKind> for ProcessSpawnOptionsStdio {
|
||||||
|
fn from(value: ProcessSpawnOptionsStdioKind) -> Self {
|
||||||
|
Self {
|
||||||
|
stdout: value,
|
||||||
|
stderr: value,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> FromLua<'lua> for ProcessSpawnOptionsStdio {
|
||||||
|
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
||||||
|
match value {
|
||||||
|
LuaValue::Nil => Ok(Self::default()),
|
||||||
|
LuaValue::String(s) => {
|
||||||
|
Ok(ProcessSpawnOptionsStdioKind::from_lua(LuaValue::String(s), lua)?.into())
|
||||||
|
}
|
||||||
|
LuaValue::Table(t) => {
|
||||||
|
let mut this = Self::default();
|
||||||
|
|
||||||
|
if let Some(stdin) = t.get("stdin")? {
|
||||||
|
this.stdin = stdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stdout) = t.get("stdout")? {
|
||||||
|
this.stdout = stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(stderr) = t.get("stderr")? {
|
||||||
|
this.stderr = stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(this)
|
||||||
|
}
|
||||||
|
_ => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "ProcessSpawnOptionsStdio",
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid spawn options stdio - expected string or table, got {}",
|
||||||
|
value.type_name()
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,46 +0,0 @@
|
||||||
use std::process::ExitStatus;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use tokio::{io, process::Child, task};
|
|
||||||
|
|
||||||
use super::tee_writer::AsyncTeeWriter;
|
|
||||||
|
|
||||||
pub async fn pipe_and_inherit_child_process_stdio(
|
|
||||||
mut child: Child,
|
|
||||||
) -> LuaResult<(ExitStatus, Vec<u8>, Vec<u8>)> {
|
|
||||||
let mut child_stdout = child.stdout.take().unwrap();
|
|
||||||
let mut child_stderr = child.stderr.take().unwrap();
|
|
||||||
|
|
||||||
/*
|
|
||||||
NOTE: We do not need to register these
|
|
||||||
independent tasks spawning in the scheduler
|
|
||||||
|
|
||||||
This function is only used by `process.spawn` which in
|
|
||||||
turn registers a task with the scheduler that awaits this
|
|
||||||
*/
|
|
||||||
|
|
||||||
let stdout_thread = task::spawn(async move {
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
let mut tee = AsyncTeeWriter::new(&mut stdout);
|
|
||||||
|
|
||||||
io::copy(&mut child_stdout, &mut tee).await.into_lua_err()?;
|
|
||||||
|
|
||||||
Ok::<_, LuaError>(tee.into_vec())
|
|
||||||
});
|
|
||||||
|
|
||||||
let stderr_thread = task::spawn(async move {
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
let mut tee = AsyncTeeWriter::new(&mut stderr);
|
|
||||||
|
|
||||||
io::copy(&mut child_stderr, &mut tee).await.into_lua_err()?;
|
|
||||||
|
|
||||||
Ok::<_, LuaError>(tee.into_vec())
|
|
||||||
});
|
|
||||||
|
|
||||||
let status = child.wait().await.expect("Child process failed to start");
|
|
||||||
|
|
||||||
let stdout_buffer = stdout_thread.await.expect("Tee writer for stdout errored");
|
|
||||||
let stderr_buffer = stderr_thread.await.expect("Tee writer for stderr errored");
|
|
||||||
|
|
||||||
Ok::<_, LuaError>((status, stdout_buffer?, stderr_buffer?))
|
|
||||||
}
|
|
74
src/lune/builtins/process/wait_for_child.rs
Normal file
74
src/lune/builtins/process/wait_for_child.rs
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
use std::process::ExitStatus;
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use tokio::{
|
||||||
|
io::{self, AsyncRead, AsyncReadExt},
|
||||||
|
process::Child,
|
||||||
|
task,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{options::ProcessSpawnOptionsStdioKind, tee_writer::AsyncTeeWriter};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(super) struct WaitForChildResult {
|
||||||
|
pub status: ExitStatus,
|
||||||
|
pub stdout: Vec<u8>,
|
||||||
|
pub stderr: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_with_stdio_kind<R>(
|
||||||
|
read_from: Option<R>,
|
||||||
|
kind: ProcessSpawnOptionsStdioKind,
|
||||||
|
) -> LuaResult<Vec<u8>>
|
||||||
|
where
|
||||||
|
R: AsyncRead + Unpin,
|
||||||
|
{
|
||||||
|
Ok(match kind {
|
||||||
|
ProcessSpawnOptionsStdioKind::None => Vec::new(),
|
||||||
|
ProcessSpawnOptionsStdioKind::Forward => Vec::new(),
|
||||||
|
ProcessSpawnOptionsStdioKind::Default => {
|
||||||
|
let mut read_from =
|
||||||
|
read_from.expect("read_from must be Some when stdio kind is Default");
|
||||||
|
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
|
||||||
|
read_from.read_to_end(&mut buffer).await.into_lua_err()?;
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
ProcessSpawnOptionsStdioKind::Inherit => {
|
||||||
|
let mut read_from =
|
||||||
|
read_from.expect("read_from must be Some when stdio kind is Inherit");
|
||||||
|
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
let mut tee = AsyncTeeWriter::new(&mut stdout);
|
||||||
|
|
||||||
|
io::copy(&mut read_from, &mut tee).await.into_lua_err()?;
|
||||||
|
|
||||||
|
tee.into_vec()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn wait_for_child(
|
||||||
|
mut child: Child,
|
||||||
|
stdout_kind: ProcessSpawnOptionsStdioKind,
|
||||||
|
stderr_kind: ProcessSpawnOptionsStdioKind,
|
||||||
|
) -> LuaResult<WaitForChildResult> {
|
||||||
|
let stdout_opt = child.stdout.take();
|
||||||
|
let stderr_opt = child.stderr.take();
|
||||||
|
|
||||||
|
let stdout_task = task::spawn(read_with_stdio_kind(stdout_opt, stdout_kind));
|
||||||
|
let stderr_task = task::spawn(read_with_stdio_kind(stderr_opt, stderr_kind));
|
||||||
|
|
||||||
|
let status = child.wait().await.expect("Child process failed to start");
|
||||||
|
|
||||||
|
let stdout_buffer = stdout_task.await.into_lua_err()??;
|
||||||
|
let stderr_buffer = stderr_task.await.into_lua_err()??;
|
||||||
|
|
||||||
|
Ok(WaitForChildResult {
|
||||||
|
status,
|
||||||
|
stdout: stdout_buffer,
|
||||||
|
stderr: stderr_buffer,
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,7 +1,12 @@
|
||||||
export type OS = "linux" | "macos" | "windows"
|
export type OS = "linux" | "macos" | "windows"
|
||||||
export type Arch = "x86_64" | "aarch64"
|
export type Arch = "x86_64" | "aarch64"
|
||||||
|
|
||||||
export type SpawnOptionsStdio = "inherit" | "default"
|
export type SpawnOptionsStdioKind = "default" | "inherit" | "forward" | "none"
|
||||||
|
export type SpawnOptionsStdio = {
|
||||||
|
stdout: SpawnOptionsStdioKind?,
|
||||||
|
stderr: SpawnOptionsStdioKind?,
|
||||||
|
stdin: string?,
|
||||||
|
}
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@interface SpawnOptions
|
@interface SpawnOptions
|
||||||
|
@ -12,15 +17,15 @@ export type SpawnOptionsStdio = "inherit" | "default"
|
||||||
* `cwd` - The current working directory for the process
|
* `cwd` - The current working directory for the process
|
||||||
* `env` - Extra environment variables to give to the process
|
* `env` - Extra environment variables to give to the process
|
||||||
* `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell
|
* `shell` - Whether to run in a shell or not - set to `true` to run using the default shell, or a string to run using a specific shell
|
||||||
* `stdio` - How to treat output and error streams from the child process - set to "inherit" to pass output and error streams to the current process
|
* `stdio` - How to treat output and error streams from the child process - see `SpawnOptionsStdioKind` and `SpawnOptionsStdio` for more info
|
||||||
* `stdin` - Optional standard input to pass to spawned child process
|
* `stdin` - Optional standard input to pass to spawned child process
|
||||||
]=]
|
]=]
|
||||||
export type SpawnOptions = {
|
export type SpawnOptions = {
|
||||||
cwd: string?,
|
cwd: string?,
|
||||||
env: { [string]: string }?,
|
env: { [string]: string }?,
|
||||||
shell: (boolean | string)?,
|
shell: (boolean | string)?,
|
||||||
stdio: SpawnOptionsStdio?,
|
stdio: (SpawnOptionsStdioKind | SpawnOptionsStdio)?,
|
||||||
stdin: string?,
|
stdin: string?, -- TODO: Remove this since it is now available in stdio above, breaking change
|
||||||
}
|
}
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
|
|
Loading…
Reference in a new issue