mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-28 21:45:00 +03:00
Co-authored-by: Amr Bashir <48618675+amrbashir@users.noreply.github.com>
This commit is contained in:
parent
f867e1396d
commit
721e98f175
6
.changes/command-options.md
Normal file
6
.changes/command-options.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
"api": patch
|
||||
"tauri": patch
|
||||
---
|
||||
|
||||
Adds `options` argument to the shell command API (`env` and `cwd` configuration).
|
File diff suppressed because one or more lines are too long
@ -3,7 +3,9 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{BufRead, BufReader, Write},
|
||||
path::PathBuf,
|
||||
process::{Command as StdCommand, Stdio},
|
||||
sync::Arc,
|
||||
};
|
||||
@ -52,6 +54,13 @@ macro_rules! get_std_command {
|
||||
command.stdout(Stdio::piped());
|
||||
command.stdin(Stdio::piped());
|
||||
command.stderr(Stdio::piped());
|
||||
if $self.env_clear {
|
||||
command.env_clear();
|
||||
}
|
||||
command.envs($self.env);
|
||||
if let Some(current_dir) = $self.current_dir {
|
||||
command.current_dir(current_dir);
|
||||
}
|
||||
#[cfg(windows)]
|
||||
command.creation_flags(CREATE_NO_WINDOW);
|
||||
command
|
||||
@ -62,6 +71,9 @@ macro_rules! get_std_command {
|
||||
pub struct Command {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
env_clear: bool,
|
||||
env: HashMap<String, String>,
|
||||
current_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Child spawned.
|
||||
@ -76,6 +88,7 @@ impl CommandChild {
|
||||
self.stdin_writer.write_all(buf)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a kill signal to the child.
|
||||
pub fn kill(self) -> crate::api::Result<()> {
|
||||
self.inner.kill()?;
|
||||
@ -118,6 +131,9 @@ impl Command {
|
||||
Self {
|
||||
program: program.into(),
|
||||
args: Default::default(),
|
||||
env_clear: false,
|
||||
env: Default::default(),
|
||||
current_dir: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,6 +159,24 @@ impl Command {
|
||||
self
|
||||
}
|
||||
|
||||
/// Clears the entire environment map for the child process.
|
||||
pub fn env_clear(mut self) -> Self {
|
||||
self.env_clear = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds or updates multiple environment variable mappings.
|
||||
pub fn envs(mut self, env: HashMap<String, String>) -> Self {
|
||||
self.env = env;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the working directory for the child process.
|
||||
pub fn current_dir(mut self, current_dir: PathBuf) -> Self {
|
||||
self.current_dir.replace(current_dir);
|
||||
self
|
||||
}
|
||||
|
||||
/// Spawns the command.
|
||||
pub fn spawn(self) -> crate::api::Result<(Receiver<CommandEvent>, CommandChild)> {
|
||||
let mut command = get_std_command!(self);
|
||||
|
@ -6,10 +6,8 @@ use crate::{endpoints::InvokeResponse, Params, Window};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[cfg(shell_execute)]
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
type ChildId = u32;
|
||||
#[cfg(shell_execute)]
|
||||
@ -29,6 +27,23 @@ pub enum Buffer {
|
||||
Raw(Vec<u8>),
|
||||
}
|
||||
|
||||
fn default_env() -> Option<HashMap<String, String>> {
|
||||
Some(Default::default())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CommandOptions {
|
||||
#[serde(default)]
|
||||
sidecar: bool,
|
||||
cwd: Option<PathBuf>,
|
||||
// by default we don't add any env variables to the spawned process
|
||||
// but the env is an `Option` so when it's `None` we clear the env.
|
||||
#[serde(default = "default_env")]
|
||||
env: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
/// The API descriptor.
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "cmd", rename_all = "camelCase")]
|
||||
@ -40,7 +55,7 @@ pub enum Cmd {
|
||||
args: Vec<String>,
|
||||
on_event_fn: String,
|
||||
#[serde(default)]
|
||||
sidecar: bool,
|
||||
options: CommandOptions,
|
||||
},
|
||||
StdinWrite {
|
||||
pid: ChildId,
|
||||
@ -63,16 +78,24 @@ impl Cmd {
|
||||
program,
|
||||
args,
|
||||
on_event_fn,
|
||||
sidecar,
|
||||
options,
|
||||
} => {
|
||||
#[cfg(shell_execute)]
|
||||
{
|
||||
let mut command = if sidecar {
|
||||
let mut command = if options.sidecar {
|
||||
crate::api::command::Command::new_sidecar(program)?
|
||||
} else {
|
||||
crate::api::command::Command::new(program)
|
||||
};
|
||||
command = command.args(args);
|
||||
if let Some(cwd) = options.cwd {
|
||||
command = command.current_dir(cwd);
|
||||
}
|
||||
if let Some(env) = options.env {
|
||||
command = command.envs(env);
|
||||
} else {
|
||||
command = command.env_clear();
|
||||
}
|
||||
let (mut rx, child) = command.spawn()?;
|
||||
|
||||
let pid = child.pid();
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,12 +7,24 @@
|
||||
export let onMessage;
|
||||
|
||||
let script = 'echo "hello world"'
|
||||
let cwd = null
|
||||
let env = 'SOMETHING=value ANOTHER=2'
|
||||
let stdin = ''
|
||||
let child
|
||||
|
||||
function _getEnv() {
|
||||
return env.split(' ').reduce((env, clause) => {
|
||||
let [key, value] = clause.split('=')
|
||||
return {
|
||||
...env,
|
||||
[key]: value
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
function spawn() {
|
||||
child = null
|
||||
const command = new Command(cmd, [...args, script])
|
||||
const command = new Command(cmd, [...args, script], { cwd: cwd || null, env: _getEnv() })
|
||||
|
||||
command.on('close', data => {
|
||||
onMessage(`command finished with code ${data.code} and signal ${data.signal}`)
|
||||
@ -49,4 +61,8 @@
|
||||
<button class="button" on:click={writeToStdin}>Write</button>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
<input bind:value={cwd} placeholder="Working directory">
|
||||
<input bind:value={env} placeholder="Environment variables" style="width: 300px">
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,6 +5,24 @@
|
||||
import { invokeTauriCommand } from './helpers/tauri'
|
||||
import { transformCallback } from './tauri'
|
||||
|
||||
interface SpawnOptions {
|
||||
/** Current working directory. */
|
||||
cwd?: string
|
||||
/** Environment variables. set to `null` to clear the process env. */
|
||||
env?: { [name: string]: string }
|
||||
}
|
||||
|
||||
interface InternalSpawnOptions extends SpawnOptions {
|
||||
sidecar?: boolean
|
||||
}
|
||||
|
||||
interface ChildProcess {
|
||||
code: number | null
|
||||
signal: number | null
|
||||
stdout: string
|
||||
stderr: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns a process.
|
||||
*
|
||||
@ -15,10 +33,10 @@ import { transformCallback } from './tauri'
|
||||
* @returns A promise resolving to the process id.
|
||||
*/
|
||||
async function execute(
|
||||
program: string,
|
||||
sidecar: boolean,
|
||||
onEvent: (event: CommandEvent) => void,
|
||||
args?: string | string[]
|
||||
program: string,
|
||||
args?: string | string[],
|
||||
options?: InternalSpawnOptions
|
||||
): Promise<number> {
|
||||
if (typeof args === 'object') {
|
||||
Object.freeze(args)
|
||||
@ -29,20 +47,13 @@ async function execute(
|
||||
message: {
|
||||
cmd: 'execute',
|
||||
program,
|
||||
sidecar,
|
||||
onEventFn: transformCallback(onEvent),
|
||||
args: typeof args === 'string' ? [args] : args
|
||||
args: typeof args === 'string' ? [args] : args,
|
||||
options,
|
||||
onEventFn: transformCallback(onEvent)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
interface ChildProcess {
|
||||
code: number | null
|
||||
signal: number | null
|
||||
stdout: string
|
||||
stderr: string
|
||||
}
|
||||
|
||||
class EventEmitter<E> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
eventListeners: { [key: string]: Array<(arg: any) => void> } = Object.create(
|
||||
@ -107,15 +118,20 @@ class Child {
|
||||
class Command extends EventEmitter<'close' | 'error'> {
|
||||
program: string
|
||||
args: string[]
|
||||
sidecar = false
|
||||
options: InternalSpawnOptions
|
||||
stdout = new EventEmitter<'data'>()
|
||||
stderr = new EventEmitter<'data'>()
|
||||
pid: number | null = null
|
||||
|
||||
constructor(program: string, args: string | string[] = []) {
|
||||
constructor(
|
||||
program: string,
|
||||
args: string | string[] = [],
|
||||
options?: SpawnOptions
|
||||
) {
|
||||
super()
|
||||
this.program = program
|
||||
this.args = typeof args === 'string' ? [args] : args
|
||||
this.options = options ?? {}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -126,14 +142,12 @@ class Command extends EventEmitter<'close' | 'error'> {
|
||||
*/
|
||||
static sidecar(program: string, args: string | string[] = []): Command {
|
||||
const instance = new Command(program, args)
|
||||
instance.sidecar = true
|
||||
instance.options.sidecar = true
|
||||
return instance
|
||||
}
|
||||
|
||||
async spawn(): Promise<Child> {
|
||||
return execute(
|
||||
this.program,
|
||||
this.sidecar,
|
||||
(event) => {
|
||||
switch (event.event) {
|
||||
case 'Error':
|
||||
@ -150,7 +164,9 @@ class Command extends EventEmitter<'close' | 'error'> {
|
||||
break
|
||||
}
|
||||
},
|
||||
this.args
|
||||
this.program,
|
||||
this.args,
|
||||
this.options
|
||||
).then((pid) => new Child(pid))
|
||||
}
|
||||
|
||||
@ -214,3 +230,4 @@ async function open(path: string, openWith?: string): Promise<void> {
|
||||
}
|
||||
|
||||
export { Command, Child, open }
|
||||
export type { ChildProcess, SpawnOptions }
|
||||
|
Loading…
Reference in New Issue
Block a user