diff --git a/Cargo.lock b/Cargo.lock index d8872b4e..7092a6bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8075,6 +8075,7 @@ name = "yaak-common" version = "0.1.0" dependencies = [ "serde_json", + "tokio", ] [[package]] @@ -8121,8 +8122,10 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror 2.0.17", + "tokio", "ts-rs", "url", + "yaak-common", "yaak-models", "yaak-sync", ] @@ -8149,6 +8152,7 @@ dependencies = [ "tonic", "tonic-reflection", "uuid", + "yaak-common", "yaak-tls", ] diff --git a/crates-tauri/yaak-app/src/git_ext.rs b/crates-tauri/yaak-app/src/git_ext.rs index a5787598..2cc79088 100644 --- a/crates-tauri/yaak-app/src/git_ext.rs +++ b/crates-tauri/yaak-app/src/git_ext.rs @@ -6,11 +6,10 @@ use crate::error::Result; use std::path::{Path, PathBuf}; use tauri::command; use yaak_git::{ - GitCommit, GitRemote, GitStatusSummary, PullResult, PushResult, - git_add, git_add_credential, git_add_remote, git_checkout_branch, git_commit, - git_create_branch, git_delete_branch, git_fetch_all, git_init, git_log, - git_merge_branch, git_pull, git_push, git_remotes, git_rm_remote, git_status, - git_unstage, + GitCommit, GitRemote, GitStatusSummary, PullResult, PushResult, git_add, git_add_credential, + git_add_remote, git_checkout_branch, git_commit, git_create_branch, git_delete_branch, + git_fetch_all, git_init, git_log, git_merge_branch, git_pull, git_push, git_remotes, + git_rm_remote, git_status, git_unstage, }; // NOTE: All of these commands are async to prevent blocking work from locking up the UI @@ -52,22 +51,22 @@ pub async fn cmd_git_initialize(dir: &Path) -> Result<()> { #[command] pub async fn cmd_git_commit(dir: &Path, message: &str) -> Result<()> { - Ok(git_commit(dir, message)?) + Ok(git_commit(dir, message).await?) } #[command] pub async fn cmd_git_fetch_all(dir: &Path) -> Result<()> { - Ok(git_fetch_all(dir)?) + Ok(git_fetch_all(dir).await?) } #[command] pub async fn cmd_git_push(dir: &Path) -> Result { - Ok(git_push(dir)?) + Ok(git_push(dir).await?) } #[command] pub async fn cmd_git_pull(dir: &Path) -> Result { - Ok(git_pull(dir)?) + Ok(git_pull(dir).await?) } #[command] diff --git a/crates/yaak-common/Cargo.toml b/crates/yaak-common/Cargo.toml index bfbd3168..42bdf949 100644 --- a/crates/yaak-common/Cargo.toml +++ b/crates/yaak-common/Cargo.toml @@ -6,3 +6,4 @@ publish = false [dependencies] serde_json = { workspace = true } +tokio = { workspace = true, features = ["process"] } diff --git a/crates/yaak-common/src/command.rs b/crates/yaak-common/src/command.rs new file mode 100644 index 00000000..ee324c89 --- /dev/null +++ b/crates/yaak-common/src/command.rs @@ -0,0 +1,16 @@ +use std::ffi::OsStr; + +#[cfg(target_os = "windows")] +const CREATE_NO_WINDOW: u32 = 0x0800_0000; + +/// Creates a new `tokio::process::Command` that won't spawn a console window on Windows. +pub fn new_xplatform_command>(program: S) -> tokio::process::Command { + #[allow(unused_mut)] + let mut cmd = tokio::process::Command::new(program); + #[cfg(target_os = "windows")] + { + use std::os::windows::process::CommandExt; + cmd.creation_flags(CREATE_NO_WINDOW); + } + cmd +} diff --git a/crates/yaak-common/src/lib.rs b/crates/yaak-common/src/lib.rs index 149fb345..16a3846f 100644 --- a/crates/yaak-common/src/lib.rs +++ b/crates/yaak-common/src/lib.rs @@ -1,2 +1,3 @@ +pub mod command; pub mod platform; pub mod serde; diff --git a/crates/yaak-git/Cargo.toml b/crates/yaak-git/Cargo.toml index e924e6e1..9ae557e0 100644 --- a/crates/yaak-git/Cargo.toml +++ b/crates/yaak-git/Cargo.toml @@ -12,7 +12,9 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } serde_yaml = "0.9.34" thiserror = { workspace = true } +tokio = { workspace = true, features = ["io-util"] } ts-rs = { workspace = true, features = ["chrono-impl", "serde-json-impl"] } url = "2" +yaak-common = { workspace = true } yaak-models = { workspace = true } yaak-sync = { workspace = true } diff --git a/crates/yaak-git/src/binary.rs b/crates/yaak-git/src/binary.rs index 37d608b5..8ef0cf81 100644 --- a/crates/yaak-git/src/binary.rs +++ b/crates/yaak-git/src/binary.rs @@ -1,38 +1,24 @@ +use crate::error::Error::GitNotFound; use crate::error::Result; use std::path::Path; -use std::process::{Command, Stdio}; +use std::process::Stdio; +use tokio::process::Command; +use yaak_common::command::new_xplatform_command; -use crate::error::Error::GitNotFound; -#[cfg(target_os = "windows")] -use std::os::windows::process::CommandExt; - -#[cfg(target_os = "windows")] -const CREATE_NO_WINDOW: u32 = 0x0800_0000; - -pub(crate) fn new_binary_command(dir: &Path) -> Result { +pub(crate) async fn new_binary_command(dir: &Path) -> Result { // 1. Probe that `git` exists and is runnable - let mut probe = Command::new("git"); + let mut probe = new_xplatform_command("git"); probe.arg("--version").stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null()); - #[cfg(target_os = "windows")] - { - probe.creation_flags(CREATE_NO_WINDOW); - } - - let status = probe.status().map_err(|_| GitNotFound)?; + let status = probe.status().await.map_err(|_| GitNotFound)?; if !status.success() { return Err(GitNotFound); } // 2. Build the reusable git command - let mut cmd = Command::new("git"); + let mut cmd = new_xplatform_command("git"); cmd.arg("-C").arg(dir); - #[cfg(target_os = "windows")] - { - cmd.creation_flags(CREATE_NO_WINDOW); - } - Ok(cmd) } diff --git a/crates/yaak-git/src/commit.rs b/crates/yaak-git/src/commit.rs index 847b62a7..1058abba 100644 --- a/crates/yaak-git/src/commit.rs +++ b/crates/yaak-git/src/commit.rs @@ -3,8 +3,9 @@ use crate::error::Error::GenericError; use log::info; use std::path::Path; -pub fn git_commit(dir: &Path, message: &str) -> crate::error::Result<()> { - let out = new_binary_command(dir)?.args(["commit", "--message", message]).output()?; +pub async fn git_commit(dir: &Path, message: &str) -> crate::error::Result<()> { + let out = + new_binary_command(dir).await?.args(["commit", "--message", message]).output().await?; let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); diff --git a/crates/yaak-git/src/credential.rs b/crates/yaak-git/src/credential.rs index 8e547e4c..ab53f86c 100644 --- a/crates/yaak-git/src/credential.rs +++ b/crates/yaak-git/src/credential.rs @@ -1,9 +1,9 @@ use crate::binary::new_binary_command; use crate::error::Error::GenericError; use crate::error::Result; -use std::io::Write; use std::path::Path; use std::process::Stdio; +use tokio::io::AsyncWriteExt; use url::Url; pub async fn git_add_credential( @@ -18,7 +18,8 @@ pub async fn git_add_credential( let host = url.host_str().unwrap(); let path = Some(url.path()); - let mut child = new_binary_command(dir)? + let mut child = new_binary_command(dir) + .await? .args(["credential", "approve"]) .stdin(Stdio::piped()) .stdout(Stdio::null()) @@ -26,19 +27,21 @@ pub async fn git_add_credential( { let stdin = child.stdin.as_mut().unwrap(); - writeln!(stdin, "protocol={}", protocol)?; - writeln!(stdin, "host={}", host)?; + stdin.write_all(format!("protocol={}\n", protocol).as_bytes()).await?; + stdin.write_all(format!("host={}\n", host).as_bytes()).await?; if let Some(path) = path { if !path.is_empty() { - writeln!(stdin, "path={}", path.trim_start_matches('/'))?; + stdin + .write_all(format!("path={}\n", path.trim_start_matches('/')).as_bytes()) + .await?; } } - writeln!(stdin, "username={}", username)?; - writeln!(stdin, "password={}", password)?; - writeln!(stdin)?; // blank line terminator + stdin.write_all(format!("username={}\n", username).as_bytes()).await?; + stdin.write_all(format!("password={}\n", password).as_bytes()).await?; + stdin.write_all(b"\n").await?; // blank line terminator } - let status = child.wait()?; + let status = child.wait().await?; if !status.success() { return Err(GenericError("Failed to approve git credential".to_string())); } diff --git a/crates/yaak-git/src/fetch.rs b/crates/yaak-git/src/fetch.rs index cde03f3d..027773f2 100644 --- a/crates/yaak-git/src/fetch.rs +++ b/crates/yaak-git/src/fetch.rs @@ -3,10 +3,12 @@ use crate::error::Error::GenericError; use crate::error::Result; use std::path::Path; -pub fn git_fetch_all(dir: &Path) -> Result<()> { - let out = new_binary_command(dir)? +pub async fn git_fetch_all(dir: &Path) -> Result<()> { + let out = new_binary_command(dir) + .await? .args(["fetch", "--all", "--prune", "--tags"]) .output() + .await .map_err(|e| GenericError(format!("failed to run git pull: {e}")))?; let stdout = String::from_utf8_lossy(&out.stdout); let stderr = String::from_utf8_lossy(&out.stderr); diff --git a/crates/yaak-git/src/pull.rs b/crates/yaak-git/src/pull.rs index 4bcbd6a7..05f514ca 100644 --- a/crates/yaak-git/src/pull.rs +++ b/crates/yaak-git/src/pull.rs @@ -17,17 +17,25 @@ pub enum PullResult { NeedsCredentials { url: String, error: Option }, } -pub fn git_pull(dir: &Path) -> Result { - let repo = open_repo(dir)?; - let branch_name = get_current_branch_name(&repo)?; - let remote = get_default_remote_in_repo(&repo)?; - let remote_name = remote.name().ok_or(GenericError("Failed to get remote name".to_string()))?; - let remote_url = remote.url().ok_or(GenericError("Failed to get remote url".to_string()))?; +pub async fn git_pull(dir: &Path) -> Result { + // Extract all git2 data before any await points (git2 types are not Send) + let (branch_name, remote_name, remote_url) = { + let repo = open_repo(dir)?; + let branch_name = get_current_branch_name(&repo)?; + let remote = get_default_remote_in_repo(&repo)?; + let remote_name = + remote.name().ok_or(GenericError("Failed to get remote name".to_string()))?.to_string(); + let remote_url = + remote.url().ok_or(GenericError("Failed to get remote url".to_string()))?.to_string(); + (branch_name, remote_name, remote_url) + }; - let out = new_binary_command(dir)? + let out = new_binary_command(dir) + .await? .args(["pull", &remote_name, &branch_name]) .env("GIT_TERMINAL_PROMPT", "0") .output() + .await .map_err(|e| GenericError(format!("failed to run git pull: {e}")))?; let stdout = String::from_utf8_lossy(&out.stdout); diff --git a/crates/yaak-git/src/push.rs b/crates/yaak-git/src/push.rs index e5a20ada..d368a9f2 100644 --- a/crates/yaak-git/src/push.rs +++ b/crates/yaak-git/src/push.rs @@ -17,17 +17,25 @@ pub enum PushResult { NeedsCredentials { url: String, error: Option }, } -pub fn git_push(dir: &Path) -> Result { - let repo = open_repo(dir)?; - let branch_name = get_current_branch_name(&repo)?; - let remote = get_default_remote_for_push_in_repo(&repo)?; - let remote_name = remote.name().ok_or(GenericError("Failed to get remote name".to_string()))?; - let remote_url = remote.url().ok_or(GenericError("Failed to get remote url".to_string()))?; +pub async fn git_push(dir: &Path) -> Result { + // Extract all git2 data before any await points (git2 types are not Send) + let (branch_name, remote_name, remote_url) = { + let repo = open_repo(dir)?; + let branch_name = get_current_branch_name(&repo)?; + let remote = get_default_remote_for_push_in_repo(&repo)?; + let remote_name = + remote.name().ok_or(GenericError("Failed to get remote name".to_string()))?.to_string(); + let remote_url = + remote.url().ok_or(GenericError("Failed to get remote url".to_string()))?.to_string(); + (branch_name, remote_name, remote_url) + }; - let out = new_binary_command(dir)? + let out = new_binary_command(dir) + .await? .args(["push", &remote_name, &branch_name]) .env("GIT_TERMINAL_PROMPT", "0") .output() + .await .map_err(|e| GenericError(format!("failed to run git push: {e}")))?; let stdout = String::from_utf8_lossy(&out.stdout); diff --git a/crates/yaak-grpc/Cargo.toml b/crates/yaak-grpc/Cargo.toml index 40c74e3f..77dc0620 100644 --- a/crates/yaak-grpc/Cargo.toml +++ b/crates/yaak-grpc/Cargo.toml @@ -22,5 +22,6 @@ tokio-stream = "0.1.14" tonic = { version = "0.12.3", default-features = false, features = ["transport"] } tonic-reflection = "0.12.3" uuid = { version = "1.7.0", features = ["v4"] } +yaak-common = { workspace = true } yaak-tls = { workspace = true } thiserror = "2.0.17" diff --git a/crates/yaak-grpc/src/reflection.rs b/crates/yaak-grpc/src/reflection.rs index 4ad37160..ca41a9c1 100644 --- a/crates/yaak-grpc/src/reflection.rs +++ b/crates/yaak-grpc/src/reflection.rs @@ -16,12 +16,12 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; use tokio::fs; -use tokio::process::Command; use tokio::sync::RwLock; use tonic::codegen::http::uri::PathAndQuery; use tonic::transport::Uri; use tonic_reflection::pb::v1::server_reflection_request::MessageRequest; use tonic_reflection::pb::v1::server_reflection_response::MessageResponse; +use yaak_common::command::new_xplatform_command; use yaak_tls::ClientCertificateConfig; pub async fn fill_pool_from_files( @@ -91,11 +91,11 @@ pub async fn fill_pool_from_files( info!("Invoking protoc with {}", args.join(" ")); - let out = Command::new(&config.protoc_bin_path) - .args(&args) - .output() - .await - .map_err(|e| GenericError(format!("Failed to run protoc: {}", e)))?; + let mut cmd = new_xplatform_command(&config.protoc_bin_path); + cmd.args(&args); + + let out = + cmd.output().await.map_err(|e| GenericError(format!("Failed to run protoc: {}", e)))?; if !out.status.success() { return Err(GenericError(format!( diff --git a/crates/yaak-plugins/src/nodejs.rs b/crates/yaak-plugins/src/nodejs.rs index 58bf7ecd..d1555c08 100644 --- a/crates/yaak-plugins/src/nodejs.rs +++ b/crates/yaak-plugins/src/nodejs.rs @@ -4,8 +4,8 @@ use std::net::SocketAddr; use std::path::Path; use std::process::Stdio; use tokio::io::{AsyncBufReadExt, BufReader}; -use tokio::process::Command; use tokio::sync::watch::Receiver; +use yaak_common::command::new_xplatform_command; /// Start the Node.js plugin runtime process. /// @@ -30,13 +30,14 @@ pub async fn start_nodejs_plugin_runtime( plugin_runtime_main_str ); - let mut child = Command::new(node_bin_path) - .env("HOST", addr.ip().to_string()) + let mut cmd = new_xplatform_command(node_bin_path); + cmd.env("HOST", addr.ip().to_string()) .env("PORT", addr.port().to_string()) .arg(&plugin_runtime_main_str) .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn()?; + .stderr(Stdio::piped()); + + let mut child = cmd.spawn()?; info!("Spawned plugin runtime");