Skip to content
31 changes: 26 additions & 5 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 bundled_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 bundled_plugin_dir == embedded_vendored_plugin_dir {
prepare_embedded_vendored_plugins(&embedded_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 @@ -52,13 +56,13 @@ impl CliContext {
});

match PluginManager::new(
vendored_plugin_dir,
bundled_plugin_dir,
embedded_vendored_plugin_dir,
installed_plugin_dir,
node_bin_path,
plugin_runtime_main,
&query_manager,
&PluginContext::new_empty(),
false,
)
.await
{
Expand Down Expand Up @@ -131,3 +135,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)
}
17 changes: 15 additions & 2 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 @@ -243,6 +244,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.path()
.resolve("vendored/plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory resource");
let bundled_plugin_dir = if is_dev() {
resolve_workspace_plugins_dir().unwrap_or_else(|| vendored_plugin_dir.clone())
} else {
vendored_plugin_dir.clone()
};

let installed_plugin_dir = app_handle
.path()
Expand All @@ -266,21 +272,20 @@ 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();

// Create plugin manager asynchronously
let app_handle_clone = app_handle.clone();
tauri::async_runtime::block_on(async move {
let manager = PluginManager::new(
bundled_plugin_dir,
vendored_plugin_dir,
installed_plugin_dir,
node_bin_path,
plugin_runtime_main,
&query_manager,
&PluginContext::new_empty(),
dev_mode,
)
.await
.expect("Failed to initialize plugins");
Expand Down Expand Up @@ -322,3 +327,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()
}
27 changes: 6 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 @@ -46,9 +45,9 @@ pub struct PluginManager {
kill_tx: tokio::sync::watch::Sender<bool>,
killed_rx: Arc<Mutex<Option<oneshot::Receiver<()>>>>,
ws_service: Arc<PluginRuntimeServerWebsocket>,
bundled_plugin_dir: PathBuf,
vendored_plugin_dir: PathBuf,
pub(crate) installed_plugin_dir: PathBuf,
dev_mode: bool,
}

/// Callback for plugin initialization events (e.g., toast notifications)
Expand All @@ -58,21 +57,21 @@ impl PluginManager {
/// Create a new PluginManager with the given paths.
///
/// # Arguments
/// * `bundled_plugin_dir` - Directory to scan for bundled plugins
/// * `vendored_plugin_dir` - Path to vendored plugins directory
/// * `installed_plugin_dir` - Path to installed plugins directory
/// * `node_bin_path` - Path to the yaaknode binary
/// * `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(
bundled_plugin_dir: PathBuf,
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 @@ -89,9 +88,9 @@ impl PluginManager {
ws_service: Arc::new(ws_service.clone()),
kill_tx: kill_server_tx,
killed_rx: Arc::new(Mutex::new(Some(killed_rx))),
bundled_plugin_dir,
vendored_plugin_dir,
installed_plugin_dir,
dev_mode,
};

// Forward events to subscribers
Expand Down Expand Up @@ -192,25 +191,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.bundled_plugin_dir);
read_plugins_dir(&self.bundled_plugin_dir).await
}

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