Compare commits

..

14 Commits

Author SHA1 Message Date
Gregory Schier
b9b90188a0 Revert "plugin-events: move model/find handlers to shared path"
This reverts commit 072b486857.
2026-02-26 10:13:36 -08:00
Gregory Schier
e64404d7a5 plugins: keep dev workspace plugins in watch mode 2026-02-26 09:53:58 -08:00
Gregory Schier
072b486857 plugin-events: move model/find handlers to shared path 2026-02-26 09:51:17 -08:00
Gregory Schier
23e9cbb376 cli: inline cwd plugins resolver 2026-02-26 09:22:54 -08:00
Gregory Schier
9c09e32a56 cli: only use cwd plugins in debug builds 2026-02-26 09:19:59 -08:00
Gregory Schier
be26cc4db4 cli: prefer cwd plugins dir when present 2026-02-26 09:19:18 -08:00
Gregory Schier
a2f12aef35 cli: use explicit flag for workspace plugin dir 2026-02-26 09:17:11 -08:00
Gregory Schier
e34301ccab plugins: have callers provide bundled plugin dir 2026-02-26 08:36:42 -08:00
Gregory Schier
8f0062f917 cli: avoid tauri dev path logic in plugin manager 2026-02-26 08:30:35 -08:00
Gregory Schier
68d68035a1 cli: use workspace plugins dir in dev mode 2026-02-26 08:22:44 -08:00
Gregory Schier
ffc80d234c Remove noisy logs 2026-02-26 08:13:18 -08:00
Gregory Schier
644c683714 Update CLI package 2026-02-26 08:13:07 -08:00
Gregory Schier
d2c1bd79ac Fix chaining multiple requests together.
Fixes
https://yaak.app/feedback/posts/request-chaining-issue-on-cold-start
2026-02-26 08:12:54 -08:00
Gregory Schier
020589f2e6 fix: keep Send All response updates window-scoped (#405) 2026-02-25 06:54:59 -08:00
11 changed files with 122 additions and 75 deletions

View File

@@ -38,12 +38,16 @@ impl CliContext {
let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id)); let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id));
let plugin_manager = if with_plugins { 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 installed_plugin_dir = data_dir.join("installed-plugins");
let node_bin_path = PathBuf::from("node"); let node_bin_path = PathBuf::from("node");
prepare_embedded_vendored_plugins(&vendored_plugin_dir) if bundled_plugin_dir == embedded_vendored_plugin_dir {
.expect("Failed to prepare bundled plugins"); prepare_embedded_vendored_plugins(&embedded_vendored_plugin_dir)
.expect("Failed to prepare bundled plugins");
}
let plugin_runtime_main = let plugin_runtime_main =
std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| { std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| {
@@ -52,13 +56,13 @@ impl CliContext {
}); });
match PluginManager::new( match PluginManager::new(
vendored_plugin_dir, bundled_plugin_dir,
embedded_vendored_plugin_dir,
installed_plugin_dir, installed_plugin_dir,
node_bin_path, node_bin_path,
plugin_runtime_main, plugin_runtime_main,
&query_manager, &query_manager,
&PluginContext::new_empty(), &PluginContext::new_empty(),
false,
) )
.await .await
{ {
@@ -131,3 +135,20 @@ fn prepare_embedded_vendored_plugins(vendored_plugin_dir: &Path) -> std::io::Res
EMBEDDED_VENDORED_PLUGINS.extract(vendored_plugin_dir)?; EMBEDDED_VENDORED_PLUGINS.extract(vendored_plugin_dir)?;
Ok(()) 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"),
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)
}

View File

@@ -362,7 +362,7 @@ async fn handle_host_plugin_request<R: Runtime>(
workspace_id: http_request.workspace_id.clone(), workspace_id: http_request.workspace_id.clone(),
..Default::default() ..Default::default()
}, },
&UpdateSource::Plugin, &UpdateSource::from_window_label(window.label()),
&blobs, &blobs,
)? )?
}; };

View File

@@ -10,6 +10,7 @@ use crate::error::Result;
use crate::models_ext::QueryManagerExt; use crate::models_ext::QueryManagerExt;
use log::{error, info, warn}; use log::{error, info, warn};
use serde::Serialize; use serde::Serialize;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -243,6 +244,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.path() .path()
.resolve("vendored/plugins", BaseDirectory::Resource) .resolve("vendored/plugins", BaseDirectory::Resource)
.expect("failed to resolve plugin directory 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 let installed_plugin_dir = app_handle
.path() .path()
@@ -266,7 +272,6 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
.expect("failed to resolve plugin runtime") .expect("failed to resolve plugin runtime")
.join("index.cjs"); .join("index.cjs");
let dev_mode = is_dev();
let query_manager = let query_manager =
app_handle.state::<yaak_models::query_manager::QueryManager>().inner().clone(); app_handle.state::<yaak_models::query_manager::QueryManager>().inner().clone();
@@ -274,13 +279,13 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
let app_handle_clone = app_handle.clone(); let app_handle_clone = app_handle.clone();
tauri::async_runtime::block_on(async move { tauri::async_runtime::block_on(async move {
let manager = PluginManager::new( let manager = PluginManager::new(
bundled_plugin_dir,
vendored_plugin_dir, vendored_plugin_dir,
installed_plugin_dir, installed_plugin_dir,
node_bin_path, node_bin_path,
plugin_runtime_main, plugin_runtime_main,
&query_manager, &query_manager,
&PluginContext::new_empty(), &PluginContext::new_empty(),
dev_mode,
) )
.await .await
.expect("Failed to initialize plugins"); .expect("Failed to initialize plugins");
@@ -322,3 +327,11 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
}) })
.build() .build()
} }
fn resolve_workspace_plugins_dir() -> Option<PathBuf> {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../..")
.join("plugins")
.canonicalize()
.ok()
}

View File

@@ -36,7 +36,6 @@ impl HttpConnectionManager {
connections.retain(|_, (_, last_used)| last_used.elapsed() <= self.ttl); connections.retain(|_, (_, last_used)| last_used.elapsed() <= self.ttl);
if let Some((cached, last_used)) = connections.get_mut(&id) { if let Some((cached, last_used)) = connections.get_mut(&id) {
info!("Re-using HTTP client {id}");
*last_used = Instant::now(); *last_used = Instant::now();
return Ok(CachedClient { return Ok(CachedClient {
client: cached.client.clone(), client: cached.client.clone(),

View File

@@ -24,7 +24,6 @@ use crate::plugin_handle::PluginHandle;
use crate::server_ws::PluginRuntimeServerWebsocket; use crate::server_ws::PluginRuntimeServerWebsocket;
use log::{error, info, warn}; use log::{error, info, warn};
use std::collections::HashMap; use std::collections::HashMap;
use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
@@ -46,9 +45,9 @@ pub struct PluginManager {
kill_tx: tokio::sync::watch::Sender<bool>, kill_tx: tokio::sync::watch::Sender<bool>,
killed_rx: Arc<Mutex<Option<oneshot::Receiver<()>>>>, killed_rx: Arc<Mutex<Option<oneshot::Receiver<()>>>>,
ws_service: Arc<PluginRuntimeServerWebsocket>, ws_service: Arc<PluginRuntimeServerWebsocket>,
bundled_plugin_dir: PathBuf,
vendored_plugin_dir: PathBuf, vendored_plugin_dir: PathBuf,
pub(crate) installed_plugin_dir: PathBuf, pub(crate) installed_plugin_dir: PathBuf,
dev_mode: bool,
} }
/// Callback for plugin initialization events (e.g., toast notifications) /// Callback for plugin initialization events (e.g., toast notifications)
@@ -58,21 +57,21 @@ impl PluginManager {
/// Create a new PluginManager with the given paths. /// Create a new PluginManager with the given paths.
/// ///
/// # Arguments /// # Arguments
/// * `bundled_plugin_dir` - Directory to scan for bundled plugins
/// * `vendored_plugin_dir` - Path to vendored plugins directory /// * `vendored_plugin_dir` - Path to vendored plugins directory
/// * `installed_plugin_dir` - Path to installed plugins directory /// * `installed_plugin_dir` - Path to installed plugins directory
/// * `node_bin_path` - Path to the yaaknode binary /// * `node_bin_path` - Path to the yaaknode binary
/// * `plugin_runtime_main` - Path to the plugin runtime index.cjs /// * `plugin_runtime_main` - Path to the plugin runtime index.cjs
/// * `query_manager` - Query manager for bundled plugin registration and loading /// * `query_manager` - Query manager for bundled plugin registration and loading
/// * `plugin_context` - Context to use while initializing plugins /// * `plugin_context` - Context to use while initializing plugins
/// * `dev_mode` - Whether the app is in dev mode (affects plugin loading)
pub async fn new( pub async fn new(
bundled_plugin_dir: PathBuf,
vendored_plugin_dir: PathBuf, vendored_plugin_dir: PathBuf,
installed_plugin_dir: PathBuf, installed_plugin_dir: PathBuf,
node_bin_path: PathBuf, node_bin_path: PathBuf,
plugin_runtime_main: PathBuf, plugin_runtime_main: PathBuf,
query_manager: &QueryManager, query_manager: &QueryManager,
plugin_context: &PluginContext, plugin_context: &PluginContext,
dev_mode: bool,
) -> Result<PluginManager> { ) -> Result<PluginManager> {
let (events_tx, mut events_rx) = mpsc::channel(2048); let (events_tx, mut events_rx) = mpsc::channel(2048);
let (kill_server_tx, kill_server_rx) = tokio::sync::watch::channel(false); let (kill_server_tx, kill_server_rx) = tokio::sync::watch::channel(false);
@@ -89,9 +88,9 @@ impl PluginManager {
ws_service: Arc::new(ws_service.clone()), ws_service: Arc::new(ws_service.clone()),
kill_tx: kill_server_tx, kill_tx: kill_server_tx,
killed_rx: Arc::new(Mutex::new(Some(killed_rx))), killed_rx: Arc::new(Mutex::new(Some(killed_rx))),
bundled_plugin_dir,
vendored_plugin_dir, vendored_plugin_dir,
installed_plugin_dir, installed_plugin_dir,
dev_mode,
}; };
// Forward events to subscribers // Forward events to subscribers
@@ -192,25 +191,11 @@ impl PluginManager {
Ok(plugin_manager) 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. /// Read plugin directories from disk and return their paths.
/// This is useful for discovering bundled plugins. /// This is useful for discovering bundled plugins.
pub async fn list_bundled_plugin_dirs(&self) -> Result<Vec<String>> { pub async fn list_bundled_plugin_dirs(&self) -> Result<Vec<String>> {
let plugins_dir = self.get_plugins_dir(); info!("Loading bundled plugins from {:?}", self.bundled_plugin_dir);
info!("Loading bundled plugins from {plugins_dir:?}"); read_plugins_dir(&self.bundled_plugin_dir).await
read_plugins_dir(&plugins_dir).await
} }
pub async fn uninstall(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> { pub async fn uninstall(&self, plugin_context: &PluginContext, dir: &str) -> Result<()> {

View File

@@ -273,6 +273,5 @@ pub fn find_client_certificate(
}); });
} }
debug!("No matching client certificate found for {}", url_string);
None None
} }

56
package-lock.json generated
View File

@@ -73,7 +73,7 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.13", "@biomejs/biome": "^2.3.13",
"@tauri-apps/cli": "^2.9.6", "@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.4.0-beta.2", "@yaakapp/cli": "^0.4.0",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"nodejs-file-downloader": "^4.13.0", "nodejs-file-downloader": "^4.13.0",
@@ -4326,9 +4326,9 @@
"link": true "link": true
}, },
"node_modules/@yaakapp/cli": { "node_modules/@yaakapp/cli": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0.tgz",
"integrity": "sha512-UXPxTS9oWVCIr4rShC7HjcAX+gSmw/BQ5F1Xp3Rub3vY/G7+513JJsc1HhLGVZqFfOVRSMEKRxtF9/9okSyiHg==", "integrity": "sha512-8xnu2oFWlgV+xeIAHMuEgsqX6Sxq4UYrSH2WbafwDLbSep6fxpO74tiBH7xp4wakt/7Bcy9a2Q5R9nkAc1ZUdA==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"bin": { "bin": {
@@ -4336,18 +4336,18 @@
"yaakcli": "bin/cli.js" "yaakcli": "bin/cli.js"
}, },
"optionalDependencies": { "optionalDependencies": {
"@yaakapp/cli-darwin-arm64": "0.4.0-beta.2", "@yaakapp/cli-darwin-arm64": "0.4.0",
"@yaakapp/cli-darwin-x64": "0.4.0-beta.2", "@yaakapp/cli-darwin-x64": "0.4.0",
"@yaakapp/cli-linux-arm64": "0.4.0-beta.2", "@yaakapp/cli-linux-arm64": "0.4.0",
"@yaakapp/cli-linux-x64": "0.4.0-beta.2", "@yaakapp/cli-linux-x64": "0.4.0",
"@yaakapp/cli-win32-arm64": "0.4.0-beta.2", "@yaakapp/cli-win32-arm64": "0.4.0",
"@yaakapp/cli-win32-x64": "0.4.0-beta.2" "@yaakapp/cli-win32-x64": "0.4.0"
} }
}, },
"node_modules/@yaakapp/cli-darwin-arm64": { "node_modules/@yaakapp/cli-darwin-arm64": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0.tgz",
"integrity": "sha512-mqkyH5tIPRLs9JumP9ZmzjB5gIwmOL1yCDoJ1qVU8DIJ7mwlcQaPGYTK98pVdBcKOjofVakBTcpol9P8rBv4qw==", "integrity": "sha512-bl8+VQNPMabXNGQCa7u6w0JGe3CmzYZPsGE8Q+5wGSxa3trGf1bmq/fMW5JXrMi1P7Laepnyad0TGGP/2C8uwQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4358,9 +4358,9 @@
] ]
}, },
"node_modules/@yaakapp/cli-darwin-x64": { "node_modules/@yaakapp/cli-darwin-x64": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0.tgz",
"integrity": "sha512-QI/H2yUF8CkJq+cnRthoUWWTEJPH4QPA78FYcGjFRhvBaj1m2G/GlCA5NkTXm/fvIjNkQEODSihXrhU+zoSSCw==", "integrity": "sha512-R+ETXNBWvmA3W88ZoTk/JtG/PZaUb85y3SwBgMbwcgdhBVwNS/g+DbCspcTFI5zs8Txsf5VuiFU+dW9M9olZ6A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4371,9 +4371,9 @@
] ]
}, },
"node_modules/@yaakapp/cli-linux-arm64": { "node_modules/@yaakapp/cli-linux-arm64": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0.tgz",
"integrity": "sha512-nvAp97LkgRpqVHyMwDdpkzlKOWG2kJXezCLRZaRWaEpbnNuviSF+0yzCuFGZRHEEspj7B0TiM+sKGkpvjNlweA==", "integrity": "sha512-Pf7VyQf4r85FsI0qYnnst7URQF8/RxSZZj79cXLai0FnN3fDiypX4CmHx765bJxgfQZlBvqVmvPAaMW/TeiJEQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4384,9 +4384,9 @@
] ]
}, },
"node_modules/@yaakapp/cli-linux-x64": { "node_modules/@yaakapp/cli-linux-x64": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0.tgz",
"integrity": "sha512-9/qAMNrtE9glxih3XWGfFssIJpQ4mHNUTuWYKroc0aZZUrunnCw3tX1tQtFDxy0QRIZcGlBeBRtgxuuBd2fYbg==", "integrity": "sha512-bYWWfHAIW81A+ydJChjH1Qo3+aihz9gFLh7/9MOa6CJgnC6H3V5cnapmh50Hddt9l5ic02aA1FB8ORQOXxb01A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@@ -4397,9 +4397,9 @@
] ]
}, },
"node_modules/@yaakapp/cli-win32-arm64": { "node_modules/@yaakapp/cli-win32-arm64": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0.tgz",
"integrity": "sha512-eM1zL+hl0y3NBLxWO90y9VyaFsAf0HAsECBWvhKhvEdd6KG4K1XzpXrC30cHQBGePIrCa/az8eSuvTde0Z2C/g==", "integrity": "sha512-8X12xkyidyYZ5vtarZGFSYR6HJbUMFUsNxYPNQccnYJIY+soNkjJHOWDjaRvBzCbR8MLT9N04Y5PE/Jv20gXpA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@@ -4410,9 +4410,9 @@
] ]
}, },
"node_modules/@yaakapp/cli-win32-x64": { "node_modules/@yaakapp/cli-win32-x64": {
"version": "0.4.0-beta.2", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0-beta.2.tgz", "resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0.tgz",
"integrity": "sha512-ySdiK0h216EqURkM5KZoqbPTgbIX4eNK/IgrKwSazxRb369HOZYQ8X68as+VRxEL4NCMmWlQNdbBDuf+apg/mg==", "integrity": "sha512-wansfrCCycFcFclowQQxfsNLIAyATyqnnbITED5gUfUrBf8NFHrG0sWVCWlXUhHU7YvpmqL7CsdtlMkIGiZCPQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],

View File

@@ -97,7 +97,7 @@
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.3.13", "@biomejs/biome": "^2.3.13",
"@tauri-apps/cli": "^2.9.6", "@tauri-apps/cli": "^2.9.6",
"@yaakapp/cli": "^0.4.0-beta.2", "@yaakapp/cli": "^0.4.0",
"dotenv-cli": "^11.0.0", "dotenv-cli": "^11.0.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"nodejs-file-downloader": "^4.13.0", "nodejs-file-downloader": "^4.13.0",

View File

@@ -1,7 +1,7 @@
{ {
"name": "@yaak/action-send-folder", "name": "@yaak/action-send-folder",
"displayName": "Send All", "displayName": "Send All",
"description": "Send all HTTP requests in a folder sequentially", "description": "Send all HTTP requests in a folder sequentially in tree order",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/mountain-loop/yaak.git", "url": "https://github.com/mountain-loop/yaak.git",

View File

@@ -14,22 +14,44 @@ export const plugin: PluginDefinition = {
ctx.httpRequest.list(), ctx.httpRequest.list(),
]); ]);
// Build a set of all folder IDs that are descendants of the target folder // Build the send order to match tree ordering:
const folderIds = new Set<string>([targetFolder.id]); // sort siblings by sortPriority then updatedAt, and traverse folders depth-first.
const addDescendants = (parentId: string) => { const compareByOrder = (
for (const folder of allFolders) { a: Pick<typeof allFolders[number], 'sortPriority' | 'updatedAt'>,
if (folder.folderId === parentId && !folderIds.has(folder.id)) { b: Pick<typeof allFolders[number], 'sortPriority' | 'updatedAt'>,
folderIds.add(folder.id); ) => {
addDescendants(folder.id); if (a.sortPriority === b.sortPriority) {
return a.updatedAt > b.updatedAt ? 1 : -1;
}
return a.sortPriority - b.sortPriority;
};
const childrenByFolderId = new Map<string, Array<typeof allFolders[number] | typeof allRequests[number]>>();
for (const folder of allFolders) {
if (folder.folderId == null) continue;
const children = childrenByFolderId.get(folder.folderId) ?? [];
children.push(folder);
childrenByFolderId.set(folder.folderId, children);
}
for (const request of allRequests) {
if (request.folderId == null) continue;
const children = childrenByFolderId.get(request.folderId) ?? [];
children.push(request);
childrenByFolderId.set(request.folderId, children);
}
const requestsToSend: typeof allRequests = [];
const collectRequests = (folderId: string) => {
const children = (childrenByFolderId.get(folderId) ?? []).slice().sort(compareByOrder);
for (const child of children) {
if (child.model === 'folder') {
collectRequests(child.id);
} else if (child.model === 'http_request') {
requestsToSend.push(child);
} }
} }
}; };
addDescendants(targetFolder.id); collectRequests(targetFolder.id);
// Filter HTTP requests to those in the target folder or its descendants
const requestsToSend = allRequests.filter(
(req) => req.folderId != null && folderIds.has(req.folderId),
);
if (requestsToSend.length === 0) { if (requestsToSend.length === 0) {
await ctx.toast.show({ await ctx.toast.show({
@@ -40,7 +62,7 @@ export const plugin: PluginDefinition = {
return; return;
} }
// Send each request sequentially // Send requests sequentially in the calculated folder order.
let successCount = 0; let successCount = 0;
let errorCount = 0; let errorCount = 0;

View File

@@ -72,6 +72,10 @@ export const plugin: PluginDefinition = {
name: 'header', name: 'header',
label: 'Header Name', label: 'Header Name',
async dynamic(ctx, args) { async dynamic(ctx, args) {
// Dynamic form config also runs during send-time rendering.
// Keep this preview-only to avoid side-effect request sends.
if (args.purpose !== 'preview') return null;
const response = await getResponse(ctx, { const response = await getResponse(ctx, {
requestId: String(args.values.request || ''), requestId: String(args.values.request || ''),
purpose: args.purpose, purpose: args.purpose,
@@ -146,6 +150,10 @@ export const plugin: PluginDefinition = {
label: 'JSONPath or XPath', label: 'JSONPath or XPath',
placeholder: '$.books[0].id or /books[0]/id', placeholder: '$.books[0].id or /books[0]/id',
dynamic: async (ctx, args) => { dynamic: async (ctx, args) => {
// Dynamic form config also runs during send-time rendering.
// Keep this preview-only to avoid side-effect request sends.
if (args.purpose !== 'preview') return null;
const resp = await getResponse(ctx, { const resp = await getResponse(ctx, {
requestId: String(args.values.request || ''), requestId: String(args.values.request || ''),
purpose: 'preview', purpose: 'preview',