mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-12 05:21:54 +01:00
Compare commits
2 Commits
v2026.3.0-
...
v2026.3.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8ba35e268 | ||
|
|
8a330ad1ec |
@@ -10,6 +10,7 @@ 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]
|
||||
@@ -30,9 +31,7 @@ async fn main() {
|
||||
|
||||
let app_id = if cfg!(debug_assertions) { "app.yaak.desktop.dev" } else { "app.yaak.desktop" };
|
||||
|
||||
let data_dir = data_dir.unwrap_or_else(|| {
|
||||
dirs::data_dir().expect("Could not determine data directory").join(app_id)
|
||||
});
|
||||
let data_dir = data_dir.unwrap_or_else(|| resolve_data_dir(app_id));
|
||||
|
||||
version_check::maybe_check_for_updates().await;
|
||||
|
||||
@@ -239,3 +238,46 @@ fn resolve_cookie_jar_id(
|
||||
.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 }
|
||||
}
|
||||
|
||||
@@ -1383,13 +1383,12 @@ async fn cmd_reload_plugins<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
window: WebviewWindow<R>,
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> YaakResult<()> {
|
||||
) -> YaakResult<Vec<(String, String)>> {
|
||||
let plugins = app_handle.db().list_plugins()?;
|
||||
let plugin_context =
|
||||
PluginContext::new(Some(window.label().to_string()), window.workspace_id());
|
||||
let _errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
|
||||
// Note: errors are returned but we don't show toasts here since this is a manual reload
|
||||
Ok(())
|
||||
let errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
|
||||
Ok(errors)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -1731,6 +1730,7 @@ pub fn run() {
|
||||
git_ext::cmd_git_rm_remote,
|
||||
//
|
||||
// Plugin commands
|
||||
plugins_ext::cmd_plugin_init_errors,
|
||||
plugins_ext::cmd_plugins_install_from_directory,
|
||||
plugins_ext::cmd_plugins_search,
|
||||
plugins_ext::cmd_plugins_install,
|
||||
|
||||
@@ -198,6 +198,13 @@ pub async fn cmd_plugins_uninstall<R: Runtime>(
|
||||
Ok(delete_and_uninstall(plugin_manager, &query_manager, &plugin_context, plugin_id).await?)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn cmd_plugin_init_errors(
|
||||
plugin_manager: State<'_, PluginManager>,
|
||||
) -> Result<Vec<(String, String)>> {
|
||||
Ok(plugin_manager.take_init_errors().await)
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub async fn cmd_plugins_updates<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
@@ -306,7 +313,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
dev_mode,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to initialize plugins");
|
||||
.expect("Failed to start plugin runtime");
|
||||
|
||||
app_handle_clone.manage(manager);
|
||||
});
|
||||
|
||||
@@ -50,6 +50,8 @@ pub struct PluginManager {
|
||||
vendored_plugin_dir: PathBuf,
|
||||
pub(crate) installed_plugin_dir: PathBuf,
|
||||
dev_mode: bool,
|
||||
/// Errors from plugin initialization, retrievable once via `take_init_errors`.
|
||||
init_errors: Arc<Mutex<Vec<(String, String)>>>,
|
||||
}
|
||||
|
||||
/// Callback for plugin initialization events (e.g., toast notifications)
|
||||
@@ -93,6 +95,7 @@ impl PluginManager {
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
dev_mode,
|
||||
init_errors: Default::default(),
|
||||
};
|
||||
|
||||
// Forward events to subscribers
|
||||
@@ -183,17 +186,21 @@ impl PluginManager {
|
||||
|
||||
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
|
||||
if !init_errors.is_empty() {
|
||||
let joined = init_errors
|
||||
.into_iter()
|
||||
.map(|(dir, err)| format!("{dir}: {err}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("; ");
|
||||
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
|
||||
for (dir, err) in &init_errors {
|
||||
warn!("Plugin failed to initialize: {dir}: {err}");
|
||||
}
|
||||
*plugin_manager.init_errors.lock().await = init_errors;
|
||||
}
|
||||
|
||||
Ok(plugin_manager)
|
||||
}
|
||||
|
||||
/// Take any initialization errors, clearing them from the manager.
|
||||
/// Returns a list of `(plugin_directory, error_message)` pairs.
|
||||
pub async fn take_init_errors(&self) -> Vec<(String, String)> {
|
||||
std::mem::take(&mut *self.init_errors.lock().await)
|
||||
}
|
||||
|
||||
/// Get the vendored plugin directory path (resolves dev mode path if applicable)
|
||||
pub fn get_plugins_dir(&self) -> PathBuf {
|
||||
if self.dev_mode {
|
||||
|
||||
@@ -123,6 +123,39 @@ export function initGlobalListeners() {
|
||||
console.log('Got plugin updates event', payload);
|
||||
showPluginUpdatesToast(payload);
|
||||
});
|
||||
|
||||
// Check for plugin initialization errors
|
||||
invokeCmd<[string, string][]>('cmd_plugin_init_errors').then((errors) => {
|
||||
for (const [dir, message] of errors) {
|
||||
const dirBasename = dir.split('/').pop() ?? dir;
|
||||
showToast({
|
||||
id: `plugin-init-error-${dirBasename}`,
|
||||
color: 'warning',
|
||||
timeout: null,
|
||||
message: (
|
||||
<VStack>
|
||||
<h2 className="font-semibold">Plugin failed to load</h2>
|
||||
<p className="text-text-subtle text-sm">
|
||||
{dirBasename}: {message}
|
||||
</p>
|
||||
</VStack>
|
||||
),
|
||||
action: ({ hide }) => (
|
||||
<Button
|
||||
size="xs"
|
||||
color="warning"
|
||||
variant="border"
|
||||
onClick={() => {
|
||||
hide();
|
||||
openSettings.mutate('plugins:installed');
|
||||
}}
|
||||
>
|
||||
View Plugins
|
||||
</Button>
|
||||
),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showUpdateInstalledToast(version: string) {
|
||||
|
||||
@@ -42,6 +42,7 @@ type TauriCmd =
|
||||
| 'cmd_new_child_window'
|
||||
| 'cmd_new_main_window'
|
||||
| 'cmd_plugin_info'
|
||||
| 'cmd_plugin_init_errors'
|
||||
| 'cmd_reload_plugins'
|
||||
| 'cmd_render_template'
|
||||
| 'cmd_save_response'
|
||||
|
||||
Reference in New Issue
Block a user