mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-24 18:31:38 +01:00
When running the CLI in WSL, dirs::data_dir() returns the Linux path instead of the Windows host path where the Yaak desktop app stores data. This detects WSL via /proc/version, resolves %APPDATA% through cmd.exe, and converts it to a WSL mount path using wslpath. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
284 lines
10 KiB
Rust
284 lines
10 KiB
Rust
mod cli;
|
|
mod commands;
|
|
mod context;
|
|
mod plugin_events;
|
|
mod ui;
|
|
mod utils;
|
|
mod version;
|
|
mod version_check;
|
|
|
|
use clap::Parser;
|
|
use cli::{Cli, Commands, PluginCommands, RequestCommands};
|
|
use context::{CliContext, CliExecutionContext};
|
|
use std::path::PathBuf;
|
|
use yaak_models::queries::any_request::AnyRequest;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let Cli { data_dir, environment, cookie_jar, verbose, log, command } = Cli::parse();
|
|
|
|
if let Some(log_level) = log {
|
|
match log_level {
|
|
Some(level) => {
|
|
env_logger::Builder::new().filter_level(level.as_filter()).init();
|
|
}
|
|
None => {
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
|
|
.init();
|
|
}
|
|
}
|
|
}
|
|
|
|
let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" };
|
|
|
|
let data_dir = data_dir.unwrap_or_else(|| resolve_data_dir(app_id));
|
|
|
|
version_check::maybe_check_for_updates().await;
|
|
|
|
let exit_code = match command {
|
|
Commands::Auth(args) => commands::auth::run(args).await,
|
|
Commands::Plugin(args) => match args.command {
|
|
PluginCommands::Build(args) => commands::plugin::run_build(args).await,
|
|
PluginCommands::Dev(args) => commands::plugin::run_dev(args).await,
|
|
PluginCommands::Generate(args) => commands::plugin::run_generate(args).await,
|
|
PluginCommands::Publish(args) => commands::plugin::run_publish(args).await,
|
|
PluginCommands::Install(install_args) => {
|
|
let mut context = CliContext::new(data_dir.clone(), app_id);
|
|
context.init_plugins(CliExecutionContext::default()).await;
|
|
let exit_code = commands::plugin::run_install(&context, install_args).await;
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
},
|
|
Commands::Build(args) => commands::plugin::run_build(args).await,
|
|
Commands::Dev(args) => commands::plugin::run_dev(args).await,
|
|
Commands::Generate(args) => commands::plugin::run_generate(args).await,
|
|
Commands::Publish(args) => commands::plugin::run_publish(args).await,
|
|
Commands::Send(args) => {
|
|
let mut context = CliContext::new(data_dir.clone(), app_id);
|
|
match resolve_send_execution_context(
|
|
&context,
|
|
&args.id,
|
|
environment.as_deref(),
|
|
cookie_jar.as_deref(),
|
|
) {
|
|
Ok(execution_context) => {
|
|
context.init_plugins(execution_context).await;
|
|
let exit_code = commands::send::run(
|
|
&context,
|
|
args,
|
|
environment.as_deref(),
|
|
cookie_jar.as_deref(),
|
|
verbose,
|
|
)
|
|
.await;
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
Err(error) => {
|
|
eprintln!("Error: {error}");
|
|
1
|
|
}
|
|
}
|
|
}
|
|
Commands::CookieJar(args) => {
|
|
let context = CliContext::new(data_dir.clone(), app_id);
|
|
let exit_code = commands::cookie_jar::run(&context, args);
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
Commands::Workspace(args) => {
|
|
let context = CliContext::new(data_dir.clone(), app_id);
|
|
let exit_code = commands::workspace::run(&context, args);
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
Commands::Request(args) => {
|
|
let mut context = CliContext::new(data_dir.clone(), app_id);
|
|
let execution_context_result = match &args.command {
|
|
RequestCommands::Send { request_id } => resolve_request_execution_context(
|
|
&context,
|
|
request_id,
|
|
environment.as_deref(),
|
|
cookie_jar.as_deref(),
|
|
),
|
|
_ => Ok(CliExecutionContext::default()),
|
|
};
|
|
match execution_context_result {
|
|
Ok(execution_context) => {
|
|
let with_plugins = matches!(
|
|
&args.command,
|
|
RequestCommands::Send { .. } | RequestCommands::Schema { .. }
|
|
);
|
|
if with_plugins {
|
|
context.init_plugins(execution_context).await;
|
|
}
|
|
let exit_code = commands::request::run(
|
|
&context,
|
|
args,
|
|
environment.as_deref(),
|
|
cookie_jar.as_deref(),
|
|
verbose,
|
|
)
|
|
.await;
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
Err(error) => {
|
|
eprintln!("Error: {error}");
|
|
1
|
|
}
|
|
}
|
|
}
|
|
Commands::Folder(args) => {
|
|
let context = CliContext::new(data_dir.clone(), app_id);
|
|
let exit_code = commands::folder::run(&context, args);
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
Commands::Environment(args) => {
|
|
let context = CliContext::new(data_dir.clone(), app_id);
|
|
let exit_code = commands::environment::run(&context, args);
|
|
context.shutdown().await;
|
|
exit_code
|
|
}
|
|
};
|
|
|
|
if exit_code != 0 {
|
|
std::process::exit(exit_code);
|
|
}
|
|
}
|
|
|
|
fn resolve_send_execution_context(
|
|
context: &CliContext,
|
|
id: &str,
|
|
environment: Option<&str>,
|
|
explicit_cookie_jar_id: Option<&str>,
|
|
) -> Result<CliExecutionContext, String> {
|
|
if let Ok(request) = context.db().get_any_request(id) {
|
|
let (request_id, workspace_id) = match request {
|
|
AnyRequest::HttpRequest(r) => (Some(r.id), r.workspace_id),
|
|
AnyRequest::GrpcRequest(r) => (Some(r.id), r.workspace_id),
|
|
AnyRequest::WebsocketRequest(r) => (Some(r.id), r.workspace_id),
|
|
};
|
|
let cookie_jar_id = resolve_cookie_jar_id(context, &workspace_id, explicit_cookie_jar_id)?;
|
|
return Ok(CliExecutionContext {
|
|
request_id,
|
|
workspace_id: Some(workspace_id),
|
|
environment_id: environment.map(str::to_string),
|
|
cookie_jar_id,
|
|
});
|
|
}
|
|
|
|
if let Ok(folder) = context.db().get_folder(id) {
|
|
let cookie_jar_id =
|
|
resolve_cookie_jar_id(context, &folder.workspace_id, explicit_cookie_jar_id)?;
|
|
return Ok(CliExecutionContext {
|
|
request_id: None,
|
|
workspace_id: Some(folder.workspace_id),
|
|
environment_id: environment.map(str::to_string),
|
|
cookie_jar_id,
|
|
});
|
|
}
|
|
|
|
if let Ok(workspace) = context.db().get_workspace(id) {
|
|
let cookie_jar_id = resolve_cookie_jar_id(context, &workspace.id, explicit_cookie_jar_id)?;
|
|
return Ok(CliExecutionContext {
|
|
request_id: None,
|
|
workspace_id: Some(workspace.id),
|
|
environment_id: environment.map(str::to_string),
|
|
cookie_jar_id,
|
|
});
|
|
}
|
|
|
|
Err(format!("Could not resolve ID '{}' as request, folder, or workspace", id))
|
|
}
|
|
|
|
fn resolve_request_execution_context(
|
|
context: &CliContext,
|
|
request_id: &str,
|
|
environment: Option<&str>,
|
|
explicit_cookie_jar_id: Option<&str>,
|
|
) -> Result<CliExecutionContext, String> {
|
|
let request = context
|
|
.db()
|
|
.get_any_request(request_id)
|
|
.map_err(|e| format!("Failed to get request: {e}"))?;
|
|
|
|
let workspace_id = match request {
|
|
AnyRequest::HttpRequest(r) => r.workspace_id,
|
|
AnyRequest::GrpcRequest(r) => r.workspace_id,
|
|
AnyRequest::WebsocketRequest(r) => r.workspace_id,
|
|
};
|
|
let cookie_jar_id = resolve_cookie_jar_id(context, &workspace_id, explicit_cookie_jar_id)?;
|
|
|
|
Ok(CliExecutionContext {
|
|
request_id: Some(request_id.to_string()),
|
|
workspace_id: Some(workspace_id),
|
|
environment_id: environment.map(str::to_string),
|
|
cookie_jar_id,
|
|
})
|
|
}
|
|
|
|
fn resolve_cookie_jar_id(
|
|
context: &CliContext,
|
|
workspace_id: &str,
|
|
explicit_cookie_jar_id: Option<&str>,
|
|
) -> Result<Option<String>, String> {
|
|
if let Some(cookie_jar_id) = explicit_cookie_jar_id {
|
|
return Ok(Some(cookie_jar_id.to_string()));
|
|
}
|
|
|
|
let default_cookie_jar = context
|
|
.db()
|
|
.list_cookie_jars(workspace_id)
|
|
.map_err(|e| format!("Failed to list cookie jars: {e}"))?
|
|
.into_iter()
|
|
.min_by_key(|jar| jar.created_at)
|
|
.map(|jar| jar.id);
|
|
Ok(default_cookie_jar)
|
|
}
|
|
|
|
fn resolve_data_dir(app_id: &str) -> PathBuf {
|
|
if let Some(dir) = wsl_data_dir(app_id) {
|
|
return dir;
|
|
}
|
|
dirs::data_dir().expect("Could not determine data directory").join(app_id)
|
|
}
|
|
|
|
/// Detect WSL and resolve the Windows AppData\Roaming path for the Yaak data directory.
|
|
fn wsl_data_dir(app_id: &str) -> Option<PathBuf> {
|
|
if !cfg!(target_os = "linux") {
|
|
return None;
|
|
}
|
|
|
|
let proc_version = std::fs::read_to_string("/proc/version").ok()?;
|
|
let is_wsl = proc_version.to_lowercase().contains("microsoft");
|
|
if !is_wsl {
|
|
return None;
|
|
}
|
|
|
|
// We're in WSL, so try to resolve the Yaak app's data directory in Windows
|
|
|
|
// Get the Windows %APPDATA% path via cmd.exe
|
|
let appdata_output =
|
|
std::process::Command::new("cmd.exe").args(["/C", "echo", "%APPDATA%"]).output().ok()?;
|
|
|
|
let win_path = String::from_utf8(appdata_output.stdout).ok()?.trim().to_string();
|
|
if win_path.is_empty() || win_path == "%APPDATA%" {
|
|
return None;
|
|
}
|
|
|
|
// Convert Windows path to WSL path using wslpath (handles custom mount points)
|
|
let wslpath_output = std::process::Command::new("wslpath").arg(&win_path).output().ok()?;
|
|
|
|
let wsl_appdata = String::from_utf8(wslpath_output.stdout).ok()?.trim().to_string();
|
|
if wsl_appdata.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let wsl_path = PathBuf::from(wsl_appdata).join(app_id);
|
|
|
|
if wsl_path.exists() { Some(wsl_path) } else { None }
|
|
}
|