mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-02-26 19:59:20 -05:00
Compare commits
5 Commits
yaak-cli-0
...
codex/plug
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9ee6e936d8 | ||
|
|
ffc80d234c | ||
|
|
644c683714 | ||
|
|
d2c1bd79ac | ||
|
|
020589f2e6 |
@@ -19,13 +19,13 @@ use yaak::plugin_events::{
|
||||
GroupedPluginEvent, HostRequest, SharedPluginEventContext, handle_shared_plugin_event,
|
||||
};
|
||||
use yaak_crypto::manager::EncryptionManager;
|
||||
use yaak_models::models::{AnyModel, HttpResponse, Plugin};
|
||||
use yaak_models::models::{HttpResponse, Plugin};
|
||||
use yaak_models::queries::any_request::AnyRequest;
|
||||
use yaak_models::util::UpdateSource;
|
||||
use yaak_plugins::error::Error::PluginErr;
|
||||
use yaak_plugins::events::{
|
||||
Color, EmptyPayload, ErrorResponse, FindHttpResponsesResponse, GetCookieValueResponse, Icon,
|
||||
InternalEvent, InternalEventPayload, ListCookieNamesResponse, ListOpenWorkspacesResponse,
|
||||
Color, EmptyPayload, ErrorResponse, GetCookieValueResponse, Icon, InternalEvent,
|
||||
InternalEventPayload, ListCookieNamesResponse, ListOpenWorkspacesResponse,
|
||||
RenderGrpcRequestResponse, RenderHttpRequestResponse, SendHttpRequestResponse,
|
||||
ShowToastRequest, TemplateRenderResponse, WindowInfoResponse, WindowNavigateEvent,
|
||||
WorkspaceInfo,
|
||||
@@ -190,71 +190,6 @@ async fn handle_host_plugin_request<R: Runtime>(
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
HostRequest::FindHttpResponses(req) => {
|
||||
let http_responses = app_handle
|
||||
.db()
|
||||
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
|
||||
.unwrap_or_default();
|
||||
Ok(Some(InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
||||
http_responses,
|
||||
})))
|
||||
}
|
||||
HostRequest::UpsertModel(req) => {
|
||||
use AnyModel::*;
|
||||
let model = match &req.model {
|
||||
HttpRequest(m) => {
|
||||
HttpRequest(app_handle.db().upsert_http_request(m, &UpdateSource::Plugin)?)
|
||||
}
|
||||
GrpcRequest(m) => {
|
||||
GrpcRequest(app_handle.db().upsert_grpc_request(m, &UpdateSource::Plugin)?)
|
||||
}
|
||||
WebsocketRequest(m) => WebsocketRequest(
|
||||
app_handle.db().upsert_websocket_request(m, &UpdateSource::Plugin)?,
|
||||
),
|
||||
Folder(m) => Folder(app_handle.db().upsert_folder(m, &UpdateSource::Plugin)?),
|
||||
Environment(m) => {
|
||||
Environment(app_handle.db().upsert_environment(m, &UpdateSource::Plugin)?)
|
||||
}
|
||||
Workspace(m) => {
|
||||
Workspace(app_handle.db().upsert_workspace(m, &UpdateSource::Plugin)?)
|
||||
}
|
||||
_ => {
|
||||
return Err(PluginErr("Upsert not supported for this model type".into()).into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(InternalEventPayload::UpsertModelResponse(
|
||||
yaak_plugins::events::UpsertModelResponse { model },
|
||||
)))
|
||||
}
|
||||
HostRequest::DeleteModel(req) => {
|
||||
let model = match req.model.as_str() {
|
||||
"http_request" => AnyModel::HttpRequest(
|
||||
app_handle.db().delete_http_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||
),
|
||||
"grpc_request" => AnyModel::GrpcRequest(
|
||||
app_handle.db().delete_grpc_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||
),
|
||||
"websocket_request" => AnyModel::WebsocketRequest(
|
||||
app_handle
|
||||
.db()
|
||||
.delete_websocket_request_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||
),
|
||||
"folder" => AnyModel::Folder(
|
||||
app_handle.db().delete_folder_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||
),
|
||||
"environment" => AnyModel::Environment(
|
||||
app_handle.db().delete_environment_by_id(&req.id, &UpdateSource::Plugin)?,
|
||||
),
|
||||
_ => {
|
||||
return Err(PluginErr("Delete not supported for this model type".into()).into());
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Some(InternalEventPayload::DeleteModelResponse(
|
||||
yaak_plugins::events::DeleteModelResponse { model },
|
||||
)))
|
||||
}
|
||||
HostRequest::RenderGrpcRequest(req) => {
|
||||
let window = get_window_from_plugin_context(app_handle, plugin_context)?;
|
||||
|
||||
@@ -362,7 +297,7 @@ async fn handle_host_plugin_request<R: Runtime>(
|
||||
workspace_id: http_request.workspace_id.clone(),
|
||||
..Default::default()
|
||||
},
|
||||
&UpdateSource::Plugin,
|
||||
&UpdateSource::from_window_label(window.label()),
|
||||
&blobs,
|
||||
)?
|
||||
};
|
||||
|
||||
@@ -36,7 +36,6 @@ impl HttpConnectionManager {
|
||||
connections.retain(|_, (_, last_used)| last_used.elapsed() <= self.ttl);
|
||||
|
||||
if let Some((cached, last_used)) = connections.get_mut(&id) {
|
||||
info!("Re-using HTTP client {id}");
|
||||
*last_used = Instant::now();
|
||||
return Ok(CachedClient {
|
||||
client: cached.client.clone(),
|
||||
|
||||
@@ -273,6 +273,5 @@ pub fn find_client_certificate(
|
||||
});
|
||||
}
|
||||
|
||||
debug!("No matching client certificate found for {}", url_string);
|
||||
None
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
use yaak_models::models::AnyModel;
|
||||
use yaak_models::query_manager::QueryManager;
|
||||
use yaak_models::util::UpdateSource;
|
||||
use yaak_plugins::events::{
|
||||
CloseWindowRequest, CopyTextRequest, DeleteKeyValueRequest, DeleteKeyValueResponse,
|
||||
DeleteModelRequest, ErrorResponse, FindHttpResponsesRequest, GetCookieValueRequest,
|
||||
GetHttpRequestByIdRequest, GetHttpRequestByIdResponse, GetKeyValueRequest, GetKeyValueResponse,
|
||||
InternalEventPayload, ListCookieNamesRequest, ListFoldersRequest, ListFoldersResponse,
|
||||
ListHttpRequestsRequest, ListHttpRequestsResponse, ListOpenWorkspacesRequest,
|
||||
OpenExternalUrlRequest, OpenWindowRequest, PromptFormRequest, PromptTextRequest,
|
||||
ReloadResponse, RenderGrpcRequestRequest, RenderHttpRequestRequest, SendHttpRequestRequest,
|
||||
SetKeyValueRequest, ShowToastRequest, TemplateRenderRequest, UpsertModelRequest,
|
||||
WindowInfoRequest,
|
||||
DeleteModelRequest, DeleteModelResponse, ErrorResponse, FindHttpResponsesRequest,
|
||||
FindHttpResponsesResponse, GetCookieValueRequest, GetHttpRequestByIdRequest,
|
||||
GetHttpRequestByIdResponse, GetKeyValueRequest, GetKeyValueResponse, InternalEventPayload,
|
||||
ListCookieNamesRequest, ListFoldersRequest, ListFoldersResponse, ListHttpRequestsRequest,
|
||||
ListHttpRequestsResponse, ListOpenWorkspacesRequest, OpenExternalUrlRequest, OpenWindowRequest,
|
||||
PromptFormRequest, PromptTextRequest, ReloadResponse, RenderGrpcRequestRequest,
|
||||
RenderHttpRequestRequest, SendHttpRequestRequest, SetKeyValueRequest, ShowToastRequest,
|
||||
TemplateRenderRequest, UpsertModelRequest, UpsertModelResponse, WindowInfoRequest,
|
||||
};
|
||||
|
||||
pub struct SharedPluginEventContext<'a> {
|
||||
@@ -37,6 +39,9 @@ pub enum SharedRequest<'a> {
|
||||
GetHttpRequestById(&'a GetHttpRequestByIdRequest),
|
||||
ListFolders(&'a ListFoldersRequest),
|
||||
ListHttpRequests(&'a ListHttpRequestsRequest),
|
||||
FindHttpResponses(&'a FindHttpResponsesRequest),
|
||||
UpsertModel(&'a UpsertModelRequest),
|
||||
DeleteModel(&'a DeleteModelRequest),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -45,9 +50,6 @@ pub enum HostRequest<'a> {
|
||||
CopyText(&'a CopyTextRequest),
|
||||
PromptText(&'a PromptTextRequest),
|
||||
PromptForm(&'a PromptFormRequest),
|
||||
FindHttpResponses(&'a FindHttpResponsesRequest),
|
||||
UpsertModel(&'a UpsertModelRequest),
|
||||
DeleteModel(&'a DeleteModelRequest),
|
||||
RenderGrpcRequest(&'a RenderGrpcRequestRequest),
|
||||
RenderHttpRequest(&'a RenderHttpRequestRequest),
|
||||
TemplateRender(&'a TemplateRenderRequest),
|
||||
@@ -71,9 +73,6 @@ impl HostRequest<'_> {
|
||||
HostRequest::CopyText(_) => "copy_text_request".to_string(),
|
||||
HostRequest::PromptText(_) => "prompt_text_request".to_string(),
|
||||
HostRequest::PromptForm(_) => "prompt_form_request".to_string(),
|
||||
HostRequest::FindHttpResponses(_) => "find_http_responses_request".to_string(),
|
||||
HostRequest::UpsertModel(_) => "upsert_model_request".to_string(),
|
||||
HostRequest::DeleteModel(_) => "delete_model_request".to_string(),
|
||||
HostRequest::RenderGrpcRequest(_) => "render_grpc_request_request".to_string(),
|
||||
HostRequest::RenderHttpRequest(_) => "render_http_request_request".to_string(),
|
||||
HostRequest::TemplateRender(_) => "template_render_request".to_string(),
|
||||
@@ -135,13 +134,13 @@ impl<'a> From<&'a InternalEventPayload> for GroupedPluginRequest<'a> {
|
||||
GroupedPluginRequest::Host(HostRequest::PromptForm(req))
|
||||
}
|
||||
InternalEventPayload::FindHttpResponsesRequest(req) => {
|
||||
GroupedPluginRequest::Host(HostRequest::FindHttpResponses(req))
|
||||
GroupedPluginRequest::Shared(SharedRequest::FindHttpResponses(req))
|
||||
}
|
||||
InternalEventPayload::UpsertModelRequest(req) => {
|
||||
GroupedPluginRequest::Host(HostRequest::UpsertModel(req))
|
||||
GroupedPluginRequest::Shared(SharedRequest::UpsertModel(req))
|
||||
}
|
||||
InternalEventPayload::DeleteModelRequest(req) => {
|
||||
GroupedPluginRequest::Host(HostRequest::DeleteModel(req))
|
||||
GroupedPluginRequest::Shared(SharedRequest::DeleteModel(req))
|
||||
}
|
||||
InternalEventPayload::RenderGrpcRequestRequest(req) => {
|
||||
GroupedPluginRequest::Host(HostRequest::RenderGrpcRequest(req))
|
||||
@@ -275,17 +274,175 @@ fn build_shared_reply(
|
||||
http_requests,
|
||||
})
|
||||
}
|
||||
SharedRequest::FindHttpResponses(req) => {
|
||||
let http_responses = query_manager
|
||||
.connect()
|
||||
.list_http_responses_for_request(&req.request_id, req.limit.map(|l| l as u64))
|
||||
.unwrap_or_default();
|
||||
InternalEventPayload::FindHttpResponsesResponse(FindHttpResponsesResponse {
|
||||
http_responses,
|
||||
})
|
||||
}
|
||||
SharedRequest::UpsertModel(req) => {
|
||||
use AnyModel::*;
|
||||
|
||||
let model = match &req.model {
|
||||
HttpRequest(m) => {
|
||||
match query_manager.connect().upsert_http_request(m, &UpdateSource::Plugin) {
|
||||
Ok(model) => HttpRequest(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to upsert HTTP request: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
GrpcRequest(m) => {
|
||||
match query_manager.connect().upsert_grpc_request(m, &UpdateSource::Plugin) {
|
||||
Ok(model) => GrpcRequest(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to upsert gRPC request: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
WebsocketRequest(m) => {
|
||||
match query_manager.connect().upsert_websocket_request(m, &UpdateSource::Plugin)
|
||||
{
|
||||
Ok(model) => WebsocketRequest(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to upsert WebSocket request: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Folder(m) => {
|
||||
match query_manager.connect().upsert_folder(m, &UpdateSource::Plugin) {
|
||||
Ok(model) => Folder(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to upsert folder: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Environment(m) => {
|
||||
match query_manager.connect().upsert_environment(m, &UpdateSource::Plugin) {
|
||||
Ok(model) => Environment(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to upsert environment: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Workspace(m) => {
|
||||
match query_manager.connect().upsert_workspace(m, &UpdateSource::Plugin) {
|
||||
Ok(model) => Workspace(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to upsert workspace: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: "Upsert not supported for this model type".to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
InternalEventPayload::UpsertModelResponse(UpsertModelResponse { model })
|
||||
}
|
||||
SharedRequest::DeleteModel(req) => {
|
||||
let model = match req.model.as_str() {
|
||||
"http_request" => {
|
||||
match query_manager
|
||||
.connect()
|
||||
.delete_http_request_by_id(&req.id, &UpdateSource::Plugin)
|
||||
{
|
||||
Ok(model) => AnyModel::HttpRequest(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to delete HTTP request: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"grpc_request" => {
|
||||
match query_manager
|
||||
.connect()
|
||||
.delete_grpc_request_by_id(&req.id, &UpdateSource::Plugin)
|
||||
{
|
||||
Ok(model) => AnyModel::GrpcRequest(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to delete gRPC request: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"websocket_request" => {
|
||||
match query_manager
|
||||
.connect()
|
||||
.delete_websocket_request_by_id(&req.id, &UpdateSource::Plugin)
|
||||
{
|
||||
Ok(model) => AnyModel::WebsocketRequest(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to delete WebSocket request: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
"folder" => match query_manager
|
||||
.connect()
|
||||
.delete_folder_by_id(&req.id, &UpdateSource::Plugin)
|
||||
{
|
||||
Ok(model) => AnyModel::Folder(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to delete folder: {err}"),
|
||||
});
|
||||
}
|
||||
},
|
||||
"environment" => {
|
||||
match query_manager
|
||||
.connect()
|
||||
.delete_environment_by_id(&req.id, &UpdateSource::Plugin)
|
||||
{
|
||||
Ok(model) => AnyModel::Environment(model),
|
||||
Err(err) => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: format!("Failed to delete environment: {err}"),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
return InternalEventPayload::ErrorResponse(ErrorResponse {
|
||||
error: "Delete not supported for this model type".to_string(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
InternalEventPayload::DeleteModelResponse(DeleteModelResponse { model })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use yaak_models::models::{Folder, HttpRequest, Workspace};
|
||||
use tempfile::TempDir;
|
||||
use yaak_models::models::{AnyModel, Folder, HttpRequest, Workspace};
|
||||
use yaak_models::util::UpdateSource;
|
||||
|
||||
fn seed_query_manager() -> QueryManager {
|
||||
let temp_dir = tempfile::TempDir::new().expect("Failed to create temp dir");
|
||||
fn seed_query_manager() -> (QueryManager, TempDir) {
|
||||
let temp_dir = TempDir::new().expect("Failed to create temp dir");
|
||||
let db_path = temp_dir.path().join("db.sqlite");
|
||||
let blob_path = temp_dir.path().join("blobs.sqlite");
|
||||
let (query_manager, _blob_manager, _rx) =
|
||||
@@ -332,12 +489,12 @@ mod tests {
|
||||
)
|
||||
.expect("Failed to seed request");
|
||||
|
||||
query_manager
|
||||
(query_manager, temp_dir)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_requests_requires_workspace_when_folder_missing() {
|
||||
let query_manager = seed_query_manager();
|
||||
let (query_manager, _temp_dir) = seed_query_manager();
|
||||
let payload = InternalEventPayload::ListHttpRequestsRequest(
|
||||
yaak_plugins::events::ListHttpRequestsRequest { folder_id: None },
|
||||
);
|
||||
@@ -355,7 +512,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn list_requests_by_workspace_and_folder() {
|
||||
let query_manager = seed_query_manager();
|
||||
let (query_manager, _temp_dir) = seed_query_manager();
|
||||
|
||||
let by_workspace_payload = InternalEventPayload::ListHttpRequestsRequest(
|
||||
yaak_plugins::events::ListHttpRequestsRequest { folder_id: None },
|
||||
@@ -394,9 +551,83 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_http_responses_is_shared_handled() {
|
||||
let (query_manager, _temp_dir) = seed_query_manager();
|
||||
let payload = InternalEventPayload::FindHttpResponsesRequest(FindHttpResponsesRequest {
|
||||
request_id: "rq_test".to_string(),
|
||||
limit: Some(1),
|
||||
});
|
||||
|
||||
let result = handle_shared_plugin_event(
|
||||
&query_manager,
|
||||
&payload,
|
||||
SharedPluginEventContext { plugin_name: "@yaak/test", workspace_id: Some("wk_test") },
|
||||
);
|
||||
|
||||
match result {
|
||||
GroupedPluginEvent::Handled(Some(InternalEventPayload::FindHttpResponsesResponse(
|
||||
resp,
|
||||
))) => {
|
||||
assert!(resp.http_responses.is_empty());
|
||||
}
|
||||
other => panic!("unexpected find responses result: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upsert_and_delete_model_are_shared_handled() {
|
||||
let (query_manager, _temp_dir) = seed_query_manager();
|
||||
|
||||
let existing = query_manager
|
||||
.connect()
|
||||
.get_http_request("rq_test")
|
||||
.expect("Failed to load seeded request");
|
||||
let upsert_payload = InternalEventPayload::UpsertModelRequest(UpsertModelRequest {
|
||||
model: AnyModel::HttpRequest(HttpRequest {
|
||||
name: "Request Updated".to_string(),
|
||||
..existing
|
||||
}),
|
||||
});
|
||||
|
||||
let upsert_result = handle_shared_plugin_event(
|
||||
&query_manager,
|
||||
&upsert_payload,
|
||||
SharedPluginEventContext { plugin_name: "@yaak/test", workspace_id: Some("wk_test") },
|
||||
);
|
||||
match upsert_result {
|
||||
GroupedPluginEvent::Handled(Some(InternalEventPayload::UpsertModelResponse(resp))) => {
|
||||
match resp.model {
|
||||
AnyModel::HttpRequest(r) => assert_eq!(r.name, "Request Updated"),
|
||||
other => panic!("unexpected upsert model type: {other:?}"),
|
||||
}
|
||||
}
|
||||
other => panic!("unexpected upsert result: {other:?}"),
|
||||
}
|
||||
|
||||
let delete_payload = InternalEventPayload::DeleteModelRequest(DeleteModelRequest {
|
||||
model: "http_request".to_string(),
|
||||
id: "rq_test".to_string(),
|
||||
});
|
||||
let delete_result = handle_shared_plugin_event(
|
||||
&query_manager,
|
||||
&delete_payload,
|
||||
SharedPluginEventContext { plugin_name: "@yaak/test", workspace_id: Some("wk_test") },
|
||||
);
|
||||
match delete_result {
|
||||
GroupedPluginEvent::Handled(Some(InternalEventPayload::DeleteModelResponse(resp))) => {
|
||||
match resp.model {
|
||||
AnyModel::HttpRequest(r) => assert_eq!(r.id, "rq_test"),
|
||||
other => panic!("unexpected delete model type: {other:?}"),
|
||||
}
|
||||
}
|
||||
other => panic!("unexpected delete result: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_request_classification_works() {
|
||||
let query_manager = seed_query_manager();
|
||||
let (query_manager, _temp_dir) = seed_query_manager();
|
||||
let payload = InternalEventPayload::WindowInfoRequest(WindowInfoRequest {
|
||||
label: "main".to_string(),
|
||||
});
|
||||
|
||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -73,7 +73,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.4.0-beta.2",
|
||||
"@yaakapp/cli": "^0.4.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
@@ -4326,9 +4326,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp/cli": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-UXPxTS9oWVCIr4rShC7HjcAX+gSmw/BQ5F1Xp3Rub3vY/G7+513JJsc1HhLGVZqFfOVRSMEKRxtF9/9okSyiHg==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0.tgz",
|
||||
"integrity": "sha512-8xnu2oFWlgV+xeIAHMuEgsqX6Sxq4UYrSH2WbafwDLbSep6fxpO74tiBH7xp4wakt/7Bcy9a2Q5R9nkAc1ZUdA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -4336,18 +4336,18 @@
|
||||
"yaakcli": "bin/cli.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@yaakapp/cli-darwin-arm64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-darwin-x64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-linux-arm64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-linux-x64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-win32-arm64": "0.4.0-beta.2",
|
||||
"@yaakapp/cli-win32-x64": "0.4.0-beta.2"
|
||||
"@yaakapp/cli-darwin-arm64": "0.4.0",
|
||||
"@yaakapp/cli-darwin-x64": "0.4.0",
|
||||
"@yaakapp/cli-linux-arm64": "0.4.0",
|
||||
"@yaakapp/cli-linux-x64": "0.4.0",
|
||||
"@yaakapp/cli-win32-arm64": "0.4.0",
|
||||
"@yaakapp/cli-win32-x64": "0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@yaakapp/cli-darwin-arm64": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-mqkyH5tIPRLs9JumP9ZmzjB5gIwmOL1yCDoJ1qVU8DIJ7mwlcQaPGYTK98pVdBcKOjofVakBTcpol9P8rBv4qw==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0.tgz",
|
||||
"integrity": "sha512-bl8+VQNPMabXNGQCa7u6w0JGe3CmzYZPsGE8Q+5wGSxa3trGf1bmq/fMW5JXrMi1P7Laepnyad0TGGP/2C8uwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4358,9 +4358,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-darwin-x64": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-QI/H2yUF8CkJq+cnRthoUWWTEJPH4QPA78FYcGjFRhvBaj1m2G/GlCA5NkTXm/fvIjNkQEODSihXrhU+zoSSCw==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0.tgz",
|
||||
"integrity": "sha512-R+ETXNBWvmA3W88ZoTk/JtG/PZaUb85y3SwBgMbwcgdhBVwNS/g+DbCspcTFI5zs8Txsf5VuiFU+dW9M9olZ6A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4371,9 +4371,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-linux-arm64": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-nvAp97LkgRpqVHyMwDdpkzlKOWG2kJXezCLRZaRWaEpbnNuviSF+0yzCuFGZRHEEspj7B0TiM+sKGkpvjNlweA==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0.tgz",
|
||||
"integrity": "sha512-Pf7VyQf4r85FsI0qYnnst7URQF8/RxSZZj79cXLai0FnN3fDiypX4CmHx765bJxgfQZlBvqVmvPAaMW/TeiJEQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4384,9 +4384,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-linux-x64": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-9/qAMNrtE9glxih3XWGfFssIJpQ4mHNUTuWYKroc0aZZUrunnCw3tX1tQtFDxy0QRIZcGlBeBRtgxuuBd2fYbg==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0.tgz",
|
||||
"integrity": "sha512-bYWWfHAIW81A+ydJChjH1Qo3+aihz9gFLh7/9MOa6CJgnC6H3V5cnapmh50Hddt9l5ic02aA1FB8ORQOXxb01A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4397,9 +4397,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-win32-arm64": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-eM1zL+hl0y3NBLxWO90y9VyaFsAf0HAsECBWvhKhvEdd6KG4K1XzpXrC30cHQBGePIrCa/az8eSuvTde0Z2C/g==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0.tgz",
|
||||
"integrity": "sha512-8X12xkyidyYZ5vtarZGFSYR6HJbUMFUsNxYPNQccnYJIY+soNkjJHOWDjaRvBzCbR8MLT9N04Y5PE/Jv20gXpA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4410,9 +4410,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-win32-x64": {
|
||||
"version": "0.4.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0-beta.2.tgz",
|
||||
"integrity": "sha512-ySdiK0h216EqURkM5KZoqbPTgbIX4eNK/IgrKwSazxRb369HOZYQ8X68as+VRxEL4NCMmWlQNdbBDuf+apg/mg==",
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0.tgz",
|
||||
"integrity": "sha512-wansfrCCycFcFclowQQxfsNLIAyATyqnnbITED5gUfUrBf8NFHrG0sWVCWlXUhHU7YvpmqL7CsdtlMkIGiZCPQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.4.0-beta.2",
|
||||
"@yaakapp/cli": "^0.4.0",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@yaak/action-send-folder",
|
||||
"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": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mountain-loop/yaak.git",
|
||||
|
||||
@@ -14,22 +14,44 @@ export const plugin: PluginDefinition = {
|
||||
ctx.httpRequest.list(),
|
||||
]);
|
||||
|
||||
// Build a set of all folder IDs that are descendants of the target folder
|
||||
const folderIds = new Set<string>([targetFolder.id]);
|
||||
const addDescendants = (parentId: string) => {
|
||||
for (const folder of allFolders) {
|
||||
if (folder.folderId === parentId && !folderIds.has(folder.id)) {
|
||||
folderIds.add(folder.id);
|
||||
addDescendants(folder.id);
|
||||
// Build the send order to match tree ordering:
|
||||
// sort siblings by sortPriority then updatedAt, and traverse folders depth-first.
|
||||
const compareByOrder = (
|
||||
a: Pick<typeof allFolders[number], 'sortPriority' | 'updatedAt'>,
|
||||
b: Pick<typeof allFolders[number], 'sortPriority' | 'updatedAt'>,
|
||||
) => {
|
||||
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);
|
||||
|
||||
// Filter HTTP requests to those in the target folder or its descendants
|
||||
const requestsToSend = allRequests.filter(
|
||||
(req) => req.folderId != null && folderIds.has(req.folderId),
|
||||
);
|
||||
collectRequests(targetFolder.id);
|
||||
|
||||
if (requestsToSend.length === 0) {
|
||||
await ctx.toast.show({
|
||||
@@ -40,7 +62,7 @@ export const plugin: PluginDefinition = {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send each request sequentially
|
||||
// Send requests sequentially in the calculated folder order.
|
||||
let successCount = 0;
|
||||
let errorCount = 0;
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ export const plugin: PluginDefinition = {
|
||||
name: 'header',
|
||||
label: 'Header Name',
|
||||
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, {
|
||||
requestId: String(args.values.request || ''),
|
||||
purpose: args.purpose,
|
||||
@@ -146,6 +150,10 @@ export const plugin: PluginDefinition = {
|
||||
label: 'JSONPath or XPath',
|
||||
placeholder: '$.books[0].id or /books[0]/id',
|
||||
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, {
|
||||
requestId: String(args.values.request || ''),
|
||||
purpose: 'preview',
|
||||
|
||||
Reference in New Issue
Block a user