mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 00:58:32 +02:00
Gracefully handle plugin init failures (#424)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1383,13 +1383,12 @@ async fn cmd_reload_plugins<R: Runtime>(
|
|||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
plugin_manager: State<'_, PluginManager>,
|
plugin_manager: State<'_, PluginManager>,
|
||||||
) -> YaakResult<()> {
|
) -> YaakResult<Vec<(String, String)>> {
|
||||||
let plugins = app_handle.db().list_plugins()?;
|
let plugins = app_handle.db().list_plugins()?;
|
||||||
let plugin_context =
|
let plugin_context =
|
||||||
PluginContext::new(Some(window.label().to_string()), window.workspace_id());
|
PluginContext::new(Some(window.label().to_string()), window.workspace_id());
|
||||||
let _errors = plugin_manager.initialize_all_plugins(plugins, &plugin_context).await;
|
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(errors)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@@ -1731,6 +1730,7 @@ pub fn run() {
|
|||||||
git_ext::cmd_git_rm_remote,
|
git_ext::cmd_git_rm_remote,
|
||||||
//
|
//
|
||||||
// Plugin commands
|
// Plugin commands
|
||||||
|
plugins_ext::cmd_plugin_init_errors,
|
||||||
plugins_ext::cmd_plugins_install_from_directory,
|
plugins_ext::cmd_plugins_install_from_directory,
|
||||||
plugins_ext::cmd_plugins_search,
|
plugins_ext::cmd_plugins_search,
|
||||||
plugins_ext::cmd_plugins_install,
|
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?)
|
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]
|
#[command]
|
||||||
pub async fn cmd_plugins_updates<R: Runtime>(
|
pub async fn cmd_plugins_updates<R: Runtime>(
|
||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
@@ -306,7 +313,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|||||||
dev_mode,
|
dev_mode,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to initialize plugins");
|
.expect("Failed to start plugin runtime");
|
||||||
|
|
||||||
app_handle_clone.manage(manager);
|
app_handle_clone.manage(manager);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ pub struct PluginManager {
|
|||||||
vendored_plugin_dir: PathBuf,
|
vendored_plugin_dir: PathBuf,
|
||||||
pub(crate) installed_plugin_dir: PathBuf,
|
pub(crate) installed_plugin_dir: PathBuf,
|
||||||
dev_mode: bool,
|
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)
|
/// Callback for plugin initialization events (e.g., toast notifications)
|
||||||
@@ -93,6 +95,7 @@ impl PluginManager {
|
|||||||
vendored_plugin_dir,
|
vendored_plugin_dir,
|
||||||
installed_plugin_dir,
|
installed_plugin_dir,
|
||||||
dev_mode,
|
dev_mode,
|
||||||
|
init_errors: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Forward events to subscribers
|
// Forward events to subscribers
|
||||||
@@ -183,17 +186,21 @@ impl PluginManager {
|
|||||||
|
|
||||||
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
|
let init_errors = plugin_manager.initialize_all_plugins(plugins, plugin_context).await;
|
||||||
if !init_errors.is_empty() {
|
if !init_errors.is_empty() {
|
||||||
let joined = init_errors
|
for (dir, err) in &init_errors {
|
||||||
.into_iter()
|
warn!("Plugin failed to initialize: {dir}: {err}");
|
||||||
.map(|(dir, err)| format!("{dir}: {err}"))
|
}
|
||||||
.collect::<Vec<_>>()
|
*plugin_manager.init_errors.lock().await = init_errors;
|
||||||
.join("; ");
|
|
||||||
return Err(PluginErr(format!("Failed to initialize plugin(s): {joined}")));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(plugin_manager)
|
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)
|
/// Get the vendored plugin directory path (resolves dev mode path if applicable)
|
||||||
pub fn get_plugins_dir(&self) -> PathBuf {
|
pub fn get_plugins_dir(&self) -> PathBuf {
|
||||||
if self.dev_mode {
|
if self.dev_mode {
|
||||||
|
|||||||
@@ -123,6 +123,39 @@ export function initGlobalListeners() {
|
|||||||
console.log('Got plugin updates event', payload);
|
console.log('Got plugin updates event', payload);
|
||||||
showPluginUpdatesToast(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) {
|
function showUpdateInstalledToast(version: string) {
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ type TauriCmd =
|
|||||||
| 'cmd_new_child_window'
|
| 'cmd_new_child_window'
|
||||||
| 'cmd_new_main_window'
|
| 'cmd_new_main_window'
|
||||||
| 'cmd_plugin_info'
|
| 'cmd_plugin_info'
|
||||||
|
| 'cmd_plugin_init_errors'
|
||||||
| 'cmd_reload_plugins'
|
| 'cmd_reload_plugins'
|
||||||
| 'cmd_render_template'
|
| 'cmd_render_template'
|
||||||
| 'cmd_save_response'
|
| 'cmd_save_response'
|
||||||
|
|||||||
Reference in New Issue
Block a user