Skip to content
28 changes: 24 additions & 4 deletions crates-cli/yaak-cli/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,16 @@ impl CliContext {
let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id));

let plugin_manager = if with_plugins {
let vendored_plugin_dir = data_dir.join("vendored-plugins");
let embedded_vendored_plugin_dir = data_dir.join("vendored-plugins");
let vendored_plugin_dir =
resolve_bundled_plugin_dir_for_cli(&embedded_vendored_plugin_dir);
let installed_plugin_dir = data_dir.join("installed-plugins");
let node_bin_path = PathBuf::from("node");

prepare_embedded_vendored_plugins(&vendored_plugin_dir)
.expect("Failed to prepare bundled plugins");
if vendored_plugin_dir == embedded_vendored_plugin_dir {
prepare_embedded_vendored_plugins(&vendored_plugin_dir)
.expect("Failed to prepare bundled plugins");
}

let plugin_runtime_main =
std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| {
Expand All @@ -58,7 +62,6 @@ impl CliContext {
plugin_runtime_main,
&query_manager,
&PluginContext::new_empty(),
false,
)
.await
{
Expand Down Expand Up @@ -131,3 +134,20 @@ fn prepare_embedded_vendored_plugins(vendored_plugin_dir: &Path) -> std::io::Res
EMBEDDED_VENDORED_PLUGINS.extract(vendored_plugin_dir)?;
Ok(())
}

fn resolve_bundled_plugin_dir_for_cli(embedded_vendored_plugin_dir: &Path) -> PathBuf {
if !cfg!(debug_assertions) {
return embedded_vendored_plugin_dir.to_path_buf();
}

let plugins_dir = match std::env::current_dir() {
Ok(cwd) => cwd.join("plugins"),
Comment on lines +144 to +145

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Anchor debug bundled-plugin lookup to workspace root

In debug builds this now resolves bundled plugins from current_dir()/plugins, so any plugin-enabled CLI command run inside an unrelated repo that happens to contain a plugins folder will load those directories as bundled plugins; PluginManager::new then auto-registers bundled dirs as enabled: true and boots them. This makes plugin execution depend on shell CWD instead of the Yaak workspace and can execute unintended code when developers run the debug CLI outside this repo.

Useful? React with 👍 / 👎.

Err(_) => return embedded_vendored_plugin_dir.to_path_buf(),
};

if !plugins_dir.is_dir() {
return embedded_vendored_plugin_dir.to_path_buf();
}

plugins_dir.canonicalize().unwrap_or(plugins_dir)
}
18 changes: 15 additions & 3 deletions crates-tauri/yaak-app/src/plugins_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use crate::error::Result;
use crate::models_ext::QueryManagerExt;
use log::{error, info, warn};
use serde::Serialize;
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant};
Expand Down Expand Up @@ -239,10 +240,15 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("yaak-plugins")
.setup(|app_handle, _| {
// Resolve paths for plugin manager
let vendored_plugin_dir = app_handle
let bundled_plugin_dir = app_handle
.path()
.resolve("vendored/plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource");
let vendored_plugin_dir = if is_dev() {
resolve_workspace_plugins_dir().unwrap_or(bundled_plugin_dir)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep dev workspace plugins in watch mode

By assigning vendored_plugin_dir to the workspace plugins path in dev mode, bundled plugin directories now satisfy the vendored check in PluginManager::add_plugin and boot with watch: false; this removes hot-reload behavior for local plugin development and forces app restarts after edits. The bundled-source override should not also change vendored classification, or dev mode should explicitly preserve watch for workspace plugins.

Useful? React with 👍 / 👎.

} else {
bundled_plugin_dir
};

let installed_plugin_dir = app_handle
.path()
Expand All @@ -266,7 +272,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.expect("failed to resolve plugin runtime")
.join("index.cjs");

let dev_mode = is_dev();
let query_manager =
app_handle.state::<yaak_models::query_manager::QueryManager>().inner().clone();

Expand All @@ -280,7 +285,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
plugin_runtime_main,
&query_manager,
&PluginContext::new_empty(),
dev_mode,
)
.await
.expect("Failed to initialize plugins");
Expand Down Expand Up @@ -322,3 +326,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
})
.build()
}

fn resolve_workspace_plugins_dir() -> Option<PathBuf> {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("plugins")
.canonicalize()
.ok()
}
23 changes: 2 additions & 21 deletions crates/yaak-plugins/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ use crate::plugin_handle::PluginHandle;
use crate::server_ws::PluginRuntimeServerWebsocket;
use log::{error, info, warn};
use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
Expand All @@ -48,7 +47,6 @@ pub struct PluginManager {
ws_service: Arc<PluginRuntimeServerWebsocket>,
vendored_plugin_dir: PathBuf,
pub(crate) installed_plugin_dir: PathBuf,
dev_mode: bool,
}

/// Callback for plugin initialization events (e.g., toast notifications)
Expand All @@ -64,15 +62,13 @@ impl PluginManager {
/// * `plugin_runtime_main` - Path to the plugin runtime index.cjs
/// * `query_manager` - Query manager for bundled plugin registration and loading
/// * `plugin_context` - Context to use while initializing plugins
/// * `dev_mode` - Whether the app is in dev mode (affects plugin loading)
pub async fn new(
vendored_plugin_dir: PathBuf,
installed_plugin_dir: PathBuf,
node_bin_path: PathBuf,
plugin_runtime_main: PathBuf,
query_manager: &QueryManager,
plugin_context: &PluginContext,
dev_mode: bool,
) -> Result<PluginManager> {
let (events_tx, mut events_rx) = mpsc::channel(2048);
let (kill_server_tx, kill_server_rx) = tokio::sync::watch::channel(false);
Expand All @@ -91,7 +87,6 @@ impl PluginManager {
killed_rx: Arc::new(Mutex::new(Some(killed_rx))),
vendored_plugin_dir,
installed_plugin_dir,
dev_mode,
};

// Forward events to subscribers
Expand Down Expand Up @@ -192,25 +187,11 @@ impl PluginManager {
Ok(plugin_manager)
}

/// Get the vendored plugin directory path (resolves dev mode path if applicable)
pub fn get_plugins_dir(&self) -> PathBuf {
if self.dev_mode {
// Use plugins directly for easy development
// Tauri runs from crates-tauri/yaak-app/, so go up two levels to reach project root
env::current_dir()
.map(|cwd| cwd.join("../../plugins").canonicalize().unwrap())
.unwrap_or_else(|_| self.vendored_plugin_dir.clone())
} else {
self.vendored_plugin_dir.clone()
}
}

/// Read plugin directories from disk and return their paths.
/// This is useful for discovering bundled plugins.
pub async fn list_bundled_plugin_dirs(&self) -> Result<Vec<String>> {
let plugins_dir = self.get_plugins_dir();
info!("Loading bundled plugins from {plugins_dir:?}");
read_plugins_dir(&plugins_dir).await
info!("Loading bundled plugins from {:?}", self.vendored_plugin_dir);
read_plugins_dir(&self.vendored_plugin_dir).await
}

pub async fn uninstall(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {
Expand Down