mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-24 17:58:27 +02:00
app: detect CLI availability and add command palette copy action
This commit is contained in:
@@ -31,6 +31,7 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
use yaak_common::command::new_checked_command;
|
||||||
use yaak_crypto::manager::EncryptionManager;
|
use yaak_crypto::manager::EncryptionManager;
|
||||||
use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
|
use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
|
||||||
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
|
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
|
||||||
@@ -97,6 +98,7 @@ impl<R: Runtime> PluginContextExt<R> for WebviewWindow<R> {
|
|||||||
struct AppMetaData {
|
struct AppMetaData {
|
||||||
is_dev: bool,
|
is_dev: bool,
|
||||||
version: String,
|
version: String,
|
||||||
|
cli_version: Option<String>,
|
||||||
name: String,
|
name: String,
|
||||||
app_data_dir: String,
|
app_data_dir: String,
|
||||||
app_log_dir: String,
|
app_log_dir: String,
|
||||||
@@ -113,9 +115,11 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
|||||||
let vendored_plugin_dir =
|
let vendored_plugin_dir =
|
||||||
app_handle.path().resolve("vendored/plugins", BaseDirectory::Resource)?;
|
app_handle.path().resolve("vendored/plugins", BaseDirectory::Resource)?;
|
||||||
let default_project_dir = app_handle.path().home_dir()?.join("YaakProjects");
|
let default_project_dir = app_handle.path().home_dir()?.join("YaakProjects");
|
||||||
|
let cli_version = detect_cli_version().await;
|
||||||
Ok(AppMetaData {
|
Ok(AppMetaData {
|
||||||
is_dev: is_dev(),
|
is_dev: is_dev(),
|
||||||
version: app_handle.package_info().version.to_string(),
|
version: app_handle.package_info().version.to_string(),
|
||||||
|
cli_version,
|
||||||
name: app_handle.package_info().name.to_string(),
|
name: app_handle.package_info().name.to_string(),
|
||||||
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
||||||
app_log_dir: app_log_dir.to_string_lossy().to_string(),
|
app_log_dir: app_log_dir.to_string_lossy().to_string(),
|
||||||
@@ -126,6 +130,28 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn detect_cli_version() -> Option<String> {
|
||||||
|
// Prefer `yaak`, but support the legacy `yaakcli` alias if present.
|
||||||
|
if let Some(version) = detect_cli_version_for_binary("yaak").await {
|
||||||
|
return Some(version);
|
||||||
|
}
|
||||||
|
detect_cli_version_for_binary("yaakcli").await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn detect_cli_version_for_binary(program: &str) -> Option<String> {
|
||||||
|
let mut cmd = new_checked_command(program, "--version").await.ok()?;
|
||||||
|
let out = cmd.arg("--version").output().await.ok()?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = String::from_utf8(out.stdout).ok()?;
|
||||||
|
let line = line.lines().find(|l| !l.trim().is_empty())?.trim();
|
||||||
|
let mut parts = line.split_whitespace();
|
||||||
|
let _name = parts.next();
|
||||||
|
Some(parts.next().unwrap_or(line).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_template_tokens_to_string<R: Runtime>(
|
async fn cmd_template_tokens_to_string<R: Runtime>(
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::io::{self, ErrorKind};
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||||
@@ -14,3 +16,27 @@ pub fn new_xplatform_command<S: AsRef<OsStr>>(program: S) -> tokio::process::Com
|
|||||||
}
|
}
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a command only if the binary exists and can be invoked with the given probe argument.
|
||||||
|
pub async fn new_checked_command<S: AsRef<OsStr>>(
|
||||||
|
program: S,
|
||||||
|
probe_arg: &str,
|
||||||
|
) -> io::Result<tokio::process::Command> {
|
||||||
|
let program: OsString = program.as_ref().to_os_string();
|
||||||
|
|
||||||
|
let mut probe = new_xplatform_command(&program);
|
||||||
|
probe.arg(probe_arg).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||||
|
|
||||||
|
let status = probe.status().await?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
format!(
|
||||||
|
"'{}' is not available on PATH or failed to execute",
|
||||||
|
program.to_string_lossy()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_xplatform_command(&program))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::error::Error::GitNotFound;
|
use crate::error::Error::GitNotFound;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Stdio;
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use yaak_common::command::new_xplatform_command;
|
use yaak_common::command::new_checked_command;
|
||||||
|
|
||||||
/// Create a git command that runs in the specified directory
|
/// Create a git command that runs in the specified directory
|
||||||
pub(crate) async fn new_binary_command(dir: &Path) -> Result<Command> {
|
pub(crate) async fn new_binary_command(dir: &Path) -> Result<Command> {
|
||||||
@@ -14,17 +13,5 @@ pub(crate) async fn new_binary_command(dir: &Path) -> Result<Command> {
|
|||||||
|
|
||||||
/// Create a git command without a specific directory (for global operations)
|
/// Create a git command without a specific directory (for global operations)
|
||||||
pub(crate) async fn new_binary_command_global() -> Result<Command> {
|
pub(crate) async fn new_binary_command_global() -> Result<Command> {
|
||||||
// 1. Probe that `git` exists and is runnable
|
new_checked_command("git", "--version").await.map_err(|_| GitNotFound)
|
||||||
let mut probe = new_xplatform_command("git");
|
|
||||||
probe.arg("--version").stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
|
||||||
|
|
||||||
let status = probe.status().await.map_err(|_| GitNotFound)?;
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
return Err(GitNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Build the reusable git command
|
|
||||||
let cmd = new_xplatform_command("git");
|
|
||||||
Ok(cmd)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
|||||||
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
||||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
|
import { appInfo } from '../lib/appInfo';
|
||||||
|
import { copyToClipboard } from '../lib/copy';
|
||||||
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
||||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
@@ -162,6 +164,14 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
|||||||
label: 'Send Request',
|
label: 'Send Request',
|
||||||
onSelect: () => sendRequest(activeRequest.id),
|
onSelect: () => sendRequest(activeRequest.id),
|
||||||
});
|
});
|
||||||
|
if (appInfo.cliVersion != null) {
|
||||||
|
commands.push({
|
||||||
|
key: 'request.copy_cli_send',
|
||||||
|
searchText: `copy cli send yaak request send ${activeRequest.id}`,
|
||||||
|
label: 'Copy CLI Send Command',
|
||||||
|
onSelect: () => copyToClipboard(`yaak request send ${activeRequest.id}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
httpRequestActions.forEach((a, i) => {
|
httpRequestActions.forEach((a, i) => {
|
||||||
commands.push({
|
commands.push({
|
||||||
key: `http_request_action.${i}`,
|
key: `http_request_action.${i}`,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { invokeCmd } from './tauri';
|
|||||||
export interface AppInfo {
|
export interface AppInfo {
|
||||||
isDev: boolean;
|
isDev: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
|
cliVersion: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
appDataDir: string;
|
appDataDir: string;
|
||||||
appLogDir: string;
|
appLogDir: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user