Merge main into proxy foundation

This commit is contained in:
Gregory Schier
2026-05-07 14:16:35 -07:00
39 changed files with 512 additions and 346 deletions
-1
View File
@@ -1 +0,0 @@
20
+8
View File
@@ -0,0 +1,8 @@
{
"printWidth": 100,
"ignorePatterns": [
"**/bindings/**",
"crates/yaak-templates/pkg/**",
"apps/yaak-client/routeTree.gen.ts"
]
}
Generated
+8 -8
View File
@@ -1169,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [ dependencies = [
"lazy_static 1.5.0", "lazy_static 1.5.0",
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -5791,9 +5791,9 @@ dependencies = [
[[package]] [[package]]
name = "quinn-proto" name = "quinn-proto"
version = "0.11.12" version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [ dependencies = [
"bytes", "bytes",
"getrandom 0.3.3", "getrandom 0.3.3",
@@ -6856,9 +6856,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.7" version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"ring", "ring",
@@ -7746,9 +7746,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.44" version = "0.4.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" checksum = "22692a6476a21fa75fdfc11d452fda482af402c008cdbaf3476414e122040973"
dependencies = [ dependencies = [
"filetime", "filetime",
"libc", "libc",
@@ -9549,7 +9549,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys 0.48.0",
] ]
[[package]] [[package]]
@@ -67,6 +67,13 @@
@apply bg-selection !important; @apply bg-selection !important;
} }
/* Fix WebKit/WKWebView rendering bug where selection layer leaves a ghost
residual line below wrapped lines after deselecting (CodeMirror issue #1600, #1627).
The layer div must be hidden when empty to force a repaint. */
.cm-selectionLayer:empty {
display: none;
}
/* Style gutters */ /* Style gutters */
.cm-gutters { .cm-gutters {
@@ -57,7 +57,7 @@ export function HttpMethodTagRaw({
let label = method.toUpperCase(); let label = method.toUpperCase();
if (short) { if (short) {
label = methodNames[method.toLowerCase()] ?? method.slice(0, 4); label = methodNames[method.toLowerCase()] ?? method.slice(0, 4);
label = label.padStart(4, " "); label = label.padEnd(4, " ");
} }
const m = method.toUpperCase(); const m = method.toUpperCase();
+9 -9
View File
@@ -59,9 +59,9 @@
"nanoid": "^5.0.9", "nanoid": "^5.0.9",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"parse-color": "^1.0.0", "parse-color": "^1.0.0",
"react": "^19.1.0", "react": "^19.2.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-dom": "^19.1.0", "react-dom": "^19.2.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-pdf": "^10.0.1", "react-pdf": "^10.0.1",
"react-syntax-highlighter": "^16.1.0", "react-syntax-highlighter": "^16.1.0",
@@ -70,10 +70,10 @@
"remark-frontmatter": "^5.0.0", "remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"uuid": "^11.1.0", "uuid": "^14.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"whatwg-mimetype": "^4.0.0", "whatwg-mimetype": "^4.0.0",
"yaml": "^2.6.1" "yaml": "^2.8.3"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.8.0", "@lezer/generator": "^1.8.0",
@@ -83,12 +83,12 @@
"@types/node": "^24.0.13", "@types/node": "^24.0.13",
"@types/papaparse": "^5.3.16", "@types/papaparse": "^5.3.16",
"@types/parse-color": "^1.0.3", "@types/parse-color": "^1.0.3",
"@types/react": "^19.1.8", "@types/react": "^19.2.0",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.2.0",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2", "@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0", "@vitejs/plugin-react": "^6.0.0",
"@yaakapp-internal/theme": "^1.0.0", "@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0", "@yaakapp-internal/ui": "^1.0.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
@@ -98,8 +98,8 @@
"postcss-nesting": "^13.0.2", "postcss-nesting": "^13.0.2",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"vite": "^7.0.8", "vite": "^7.0.8",
"vite-plugin-static-copy": "^3.1.2", "vite-plugin-static-copy": "^3.3.0",
"vite-plugin-svgr": "^4.3.0", "vite-plugin-svgr": "^4.5.0",
"vite-plugin-top-level-await": "^1.5.0", "vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.5.0" "vite-plugin-wasm": "^3.5.0"
} }
+5 -5
View File
@@ -19,13 +19,13 @@
"classnames": "^2.5.1", "classnames": "^2.5.1",
"jotai": "^2.18.0", "jotai": "^2.18.0",
"motion": "^12.4.7", "motion": "^12.4.7",
"react": "^19.1.0", "react": "^19.2.0",
"react-dom": "^19.1.0" "react-dom": "^19.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^19.1.8", "@types/react": "^19.2.0",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^4.6.0", "@vitejs/plugin-react": "^6.0.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.8" "vite": "^7.0.8"
} }
+3 -2
View File
@@ -2,9 +2,10 @@
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "bundler",
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"noUncheckedIndexedAccess": true "noUncheckedIndexedAccess": true,
"skipLibCheck": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }
+1 -1
View File
@@ -1 +1 @@
/// <reference types="vite/client" /> /// <reference types="vite-plus/client" />
-80
View File
@@ -1,80 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"a11y": {
"useKeyWithClickEvents": "off"
},
"style": {
"noRestrictedImports": {
"level": "error",
"options": {
"paths": {
"@tauri-apps/api/core": "Use lib/tauri.ts instead of importing @tauri-apps directly",
"@tauri-apps/api/event": "Use lib/tauri.ts instead of importing @tauri-apps directly",
"@tauri-apps/api/webviewWindow": "Use lib/tauri.ts instead of importing @tauri-apps directly",
"@tauri-apps/plugin-os": "Use lib/tauri.ts instead of importing @tauri-apps directly"
}
}
}
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100,
"bracketSpacing": true
},
"css": {
"parser": {
"tailwindDirectives": true
},
"linter": {
"enabled": false
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "all",
"semicolons": "always"
}
},
"overrides": [
{
"includes": ["apps/yaak-proxy/lib/tauri.ts"],
"linter": {
"rules": {
"style": {
"noRestrictedImports": "off"
}
}
}
}
],
"files": {
"includes": [
"**",
"!**/node_modules",
"!**/dist",
"!**/build",
"!target",
"!scripts",
"!crates",
"!crates-tauri",
"!apps/yaak-client/tailwind.config.cjs",
"!apps/yaak-client/postcss.config.cjs",
"!apps/yaak-client/vite.config.ts",
"!apps/yaak-client/routeTree.gen.ts",
"!packages/plugin-runtime-types/lib",
"!**/bindings",
"!flatpak",
"!npm"
]
}
}
+3 -5
View File
@@ -14,6 +14,7 @@ use yaak::plugin_events::{
use yaak::render::{render_grpc_request, render_http_request}; use yaak::render::{render_grpc_request, render_http_request};
use yaak::send::{SendHttpRequestWithPluginsParams, send_http_request_with_plugins}; use yaak::send::{SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
use yaak_crypto::manager::EncryptionManager; use yaak_crypto::manager::EncryptionManager;
use yaak_http::cookies::get_cookie_value_from_jar;
use yaak_models::blob_manager::BlobManager; use yaak_models::blob_manager::BlobManager;
use yaak_models::models::Environment; use yaak_models::models::Environment;
use yaak_models::queries::any_request::AnyRequest; use yaak_models::queries::any_request::AnyRequest;
@@ -496,10 +497,8 @@ async fn build_plugin_reply(
} }
}; };
let value = cookie_jar.cookies.into_iter().find_map(|c| { let value =
let (name, value) = parse_cookie_name_value(&c.raw_cookie)?; get_cookie_value_from_jar(cookie_jar.cookies, &req.name, req.domain.as_deref());
if name == req.name { Some(value) } else { None }
});
Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })) Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))
} }
HostRequest::WindowInfo(req) => { HostRequest::WindowInfo(req) => {
@@ -532,7 +531,6 @@ async fn render_json_value_for_cli<T: TemplateCallback>(
render_json_value_raw(value, vars, cb, opt).await render_json_value_raw(value, vars, cb, opt).await
} }
fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> { fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
let first_part = raw_cookie.split(';').next()?.trim(); let first_part = raw_cookie.split(';').next()?.trim();
let (name, value) = first_part.split_once('=')?; let (name, value) = first_part.split_once('=')?;
+41 -3
View File
@@ -1,9 +1,10 @@
use crate::PluginContextExt; use crate::PluginContextExt;
use crate::error::Result; use crate::error::{Error, Result};
use crate::models_ext::QueryManagerExt; use crate::models_ext::QueryManagerExt;
use log::info; use log::info;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::fs::read_to_string; use std::fs::read_to_string;
use std::io::ErrorKind;
use tauri::{Manager, Runtime, WebviewWindow}; use tauri::{Manager, Runtime, WebviewWindow};
use yaak_core::WorkspaceContext; use yaak_core::WorkspaceContext;
use yaak_models::models::{ use yaak_models::models::{
@@ -18,8 +19,7 @@ pub(crate) async fn import_data<R: Runtime>(
file_path: &str, file_path: &str,
) -> Result<BatchUpsertResult> { ) -> Result<BatchUpsertResult> {
let plugin_manager = window.state::<PluginManager>(); let plugin_manager = window.state::<PluginManager>();
let file = let file = read_import_file(file_path)?;
read_to_string(file_path).unwrap_or_else(|_| panic!("Unable to read file {}", file_path));
let file_contents = file.as_str(); let file_contents = file.as_str();
let import_result = plugin_manager.import_data(&window.plugin_context(), file_contents).await?; let import_result = plugin_manager.import_data(&window.plugin_context(), file_contents).await?;
@@ -127,3 +127,41 @@ pub(crate) async fn import_data<R: Runtime>(
Ok(upserted) Ok(upserted)
} }
fn read_import_file(file_path: &str) -> Result<String> {
read_to_string(file_path).map_err(|err| {
if err.kind() == ErrorKind::InvalidData {
Error::GenericError(format!(
"Import file must be UTF-8 text; binary files are not supported: {file_path}"
))
} else {
Error::GenericError(format!("Unable to read import file {file_path}: {err}"))
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{remove_file, write};
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn read_import_file_returns_error_for_binary_file() {
let path = std::env::temp_dir().join(format!(
"yaak-import-binary-{}.pftrace",
SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("system time before unix epoch")
.as_nanos()
));
write(&path, [0xff, 0xfe, 0xfd]).expect("write binary fixture");
let err = read_import_file(path.to_str().expect("temp path is utf-8"))
.expect_err("binary import should return an error");
assert!(err.to_string().contains("binary files are not supported"));
remove_file(path).expect("remove binary fixture");
}
}
+53 -7
View File
@@ -34,8 +34,7 @@ use tokio::time;
use yaak_common::command::new_checked_command; use yaak_common::command::new_checked_command;
use yaak_crypto::manager::EncryptionManager; use yaak_crypto::manager::EncryptionManager;
use yaak_grpc::manager::{GrpcConfig, GrpcHandle}; use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
use yaak_templates::strip_json_comments::strip_json_comments; use yaak_grpc::{Code, ServiceDefinition};
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
use yaak_mac_window::AppHandleMacWindowExt; use yaak_mac_window::AppHandleMacWindowExt;
use yaak_models::models::{ use yaak_models::models::{
AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent, AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent,
@@ -60,6 +59,7 @@ use yaak_plugins::template_callback::PluginTemplateCallback;
use yaak_sse::sse::ServerSentEvent; use yaak_sse::sse::ServerSentEvent;
use yaak_tauri_utils::window::WorkspaceWindowTrait; use yaak_tauri_utils::window::WorkspaceWindowTrait;
use yaak_templates::format_json::format_json; use yaak_templates::format_json::format_json;
use yaak_templates::strip_json_comments::strip_json_comments;
use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args}; use yaak_templates::{RenderErrorBehavior, RenderOptions, Tokens, transform_args};
use yaak_tls::find_client_certificate; use yaak_tls::find_client_certificate;
@@ -591,7 +591,7 @@ async fn cmd_grpc_go<R: Runtime>(
&method, &method,
in_msg_stream, in_msg_stream,
&metadata, &metadata,
client_cert, client_cert.clone(),
on_message.clone(), on_message.clone(),
) )
.await, .await,
@@ -607,7 +607,7 @@ async fn cmd_grpc_go<R: Runtime>(
&method, &method,
in_msg_stream, in_msg_stream,
&metadata, &metadata,
client_cert, client_cert.clone(),
on_message.clone(), on_message.clone(),
) )
.await, .await,
@@ -620,7 +620,9 @@ async fn cmd_grpc_go<R: Runtime>(
(false, false) => ( (false, false) => (
None, None,
Some( Some(
connection.unary(&service, &method, &msg, &metadata, client_cert).await, connection
.unary(&service, &method, &msg, &metadata, client_cert.clone())
.await,
), ),
), ),
}; };
@@ -658,11 +660,34 @@ async fn cmd_grpc_go<R: Runtime>(
&UpdateSource::from_window_label(window.label()), &UpdateSource::from_window_label(window.label()),
) )
.unwrap(); .unwrap();
let response_message = msg.into_inner();
let content = match connection
.serialize_message(&response_message, &metadata, client_cert.clone())
.await
{
Ok(content) => content,
Err(err) => {
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: "Failed to read response".to_string(),
error: Some(err.to_string()),
status: Some(Code::Internal as i32),
event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
},
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
return;
}
};
app_handle app_handle
.db() .db()
.upsert_grpc_event( .upsert_grpc_event(
&GrpcEvent { &GrpcEvent {
content: serialize_message(&msg.into_inner()).unwrap(), content,
event_type: GrpcEventType::ServerMessage, event_type: GrpcEventType::ServerMessage,
..base_event.clone() ..base_event.clone()
}, },
@@ -797,7 +822,28 @@ async fn cmd_grpc_go<R: Runtime>(
loop { loop {
match stream.message().await { match stream.message().await {
Ok(Some(msg)) => { Ok(Some(msg)) => {
let message = serialize_message(&msg).unwrap(); let message = match connection
.serialize_message(&msg, &metadata, client_cert.clone())
.await
{
Ok(message) => message,
Err(err) => {
app_handle
.db()
.upsert_grpc_event(
&GrpcEvent {
content: "Failed to read response".to_string(),
error: Some(err.to_string()),
status: Some(Code::Internal as i32),
event_type: GrpcEventType::ConnectionEnd,
..base_event.clone()
},
&UpdateSource::from_window_label(window.label()),
)
.unwrap();
break;
}
};
app_handle app_handle
.db() .db()
.upsert_grpc_event( .upsert_grpc_event(
@@ -19,6 +19,7 @@ use yaak::plugin_events::{
GroupedPluginEvent, HostRequest, SharedPluginEventContext, handle_shared_plugin_event, GroupedPluginEvent, HostRequest, SharedPluginEventContext, handle_shared_plugin_event,
}; };
use yaak_crypto::manager::EncryptionManager; use yaak_crypto::manager::EncryptionManager;
use yaak_http::cookies::get_cookie_value_from_jar;
use yaak_models::models::{HttpResponse, Plugin}; use yaak_models::models::{HttpResponse, Plugin};
use yaak_models::queries::any_request::AnyRequest; use yaak_models::queries::any_request::AnyRequest;
use yaak_models::util::UpdateSource; use yaak_models::util::UpdateSource;
@@ -422,12 +423,7 @@ async fn handle_host_plugin_request<R: Runtime>(
let window = get_window_from_plugin_context(app_handle, plugin_context)?; let window = get_window_from_plugin_context(app_handle, plugin_context)?;
let value = match cookie_jar_from_window(&window) { let value = match cookie_jar_from_window(&window) {
None => None, None => None,
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) { Some(j) => get_cookie_value_from_jar(j.cookies, &req.name, req.domain.as_deref()),
Ok(c) if c.name().to_string().eq(&req.name) => {
Some(c.value_trimmed().to_string())
}
_ => None,
}),
}; };
Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))) Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
} }
+1 -1
View File
@@ -37,7 +37,7 @@ pub struct MethodDefinition {
static SERIALIZE_OPTIONS: &'static SerializeOptions = static SERIALIZE_OPTIONS: &'static SerializeOptions =
&SerializeOptions::new().skip_default_fields(false).stringify_64_bit_integers(false); &SerializeOptions::new().skip_default_fields(false).stringify_64_bit_integers(false);
pub fn serialize_message(msg: &DynamicMessage) -> Result<String, String> { pub(crate) fn serialize_dynamic_message_json(msg: &DynamicMessage) -> Result<String, String> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut se = serde_json::Serializer::pretty(&mut buf); let mut se = serde_json::Serializer::pretty(&mut buf);
msg.serialize_with_options(&mut se, SERIALIZE_OPTIONS).map_err(|e| e.to_string())?; msg.serialize_with_options(&mut se, SERIALIZE_OPTIONS).map_err(|e| e.to_string())?;
+37 -1
View File
@@ -2,7 +2,8 @@ use crate::codec::DynamicCodec;
use crate::error::Error::GenericError; use crate::error::Error::GenericError;
use crate::error::Result; use crate::error::Result;
use crate::reflection::{ use crate::reflection::{
fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path, reflect_types_for_message, fill_pool_from_files, fill_pool_from_reflection, method_desc_to_path,
reflect_types_for_dynamic_message, reflect_types_for_message,
}; };
use crate::transport::get_transport; use crate::transport::get_transport;
use crate::{MethodDefinition, ServiceDefinition, json_schema}; use crate::{MethodDefinition, ServiceDefinition, json_schema};
@@ -11,8 +12,11 @@ use hyper_util::client::legacy::Client;
use hyper_util::client::legacy::connect::HttpConnector; use hyper_util::client::legacy::connect::HttpConnector;
use log::{info, warn}; use log::{info, warn};
pub use prost_reflect::DynamicMessage; pub use prost_reflect::DynamicMessage;
use prost_reflect::ReflectMessage;
use prost_reflect::prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor}; use prost_reflect::{DescriptorPool, MethodDescriptor, ServiceDescriptor};
use serde_json::Deserializer; use serde_json::Deserializer;
use std::borrow::Cow;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
@@ -115,6 +119,38 @@ impl GrpcConnection {
Ok(client.unary(req, path, codec).await?) Ok(client.unary(req, path, codec).await?)
} }
pub async fn serialize_message(
&self,
message: &DynamicMessage,
metadata: &BTreeMap<String, String>,
client_cert: Option<ClientCertificateConfig>,
) -> Result<String> {
let message = if self.use_reflection {
reflect_types_for_dynamic_message(
self.pool.clone(),
&self.uri,
message,
metadata,
client_cert,
)
.await?;
let message_name = message.descriptor().full_name().to_string();
let message_desc = {
let pool = self.pool.read().await;
pool.get_message_by_name(&message_name)
.ok_or(GenericError(format!("Failed to find message {message_name}")))?
};
let mut message_with_updated_pool = DynamicMessage::new(message_desc);
message_with_updated_pool.merge(message.encode_to_vec().as_slice())?;
Cow::Owned(message_with_updated_pool)
} else {
Cow::Borrowed(message)
};
crate::serialize_dynamic_message_json(message.as_ref()).map_err(GenericError)
}
pub async fn streaming<F>( pub async fn streaming<F>(
&self, &self,
service: &str, service: &str,
+78 -1
View File
@@ -7,7 +7,7 @@ use anyhow::anyhow;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use log::{debug, info, warn}; use log::{debug, info, warn};
use prost::Message; use prost::Message;
use prost_reflect::{DescriptorPool, MethodDescriptor}; use prost_reflect::{DescriptorPool, DynamicMessage, MethodDescriptor, ReflectMessage, Value};
use prost_types::{FileDescriptorProto, FileDescriptorSet}; use prost_types::{FileDescriptorProto, FileDescriptorSet};
use std::collections::{BTreeMap, HashSet}; use std::collections::{BTreeMap, HashSet};
use std::env::temp_dir; use std::env::temp_dir;
@@ -233,6 +233,83 @@ pub(crate) async fn reflect_types_for_message(
Ok(()) Ok(())
} }
pub(crate) async fn reflect_types_for_dynamic_message(
pool: Arc<RwLock<DescriptorPool>>,
uri: &Uri,
message: &DynamicMessage,
metadata: &BTreeMap<String, String>,
client_cert: Option<ClientCertificateConfig>,
) -> Result<()> {
let mut extra_types = HashSet::new();
collect_any_types_from_dynamic_message(message, &mut extra_types);
if extra_types.is_empty() {
return Ok(());
}
let mut client = AutoReflectionClient::new(uri, false, client_cert)?;
for extra_type in extra_types {
{
let guard = pool.read().await;
if guard.get_message_by_name(&extra_type).is_some() {
continue;
}
}
info!("Adding response file descriptor for {:?} from reflection", extra_type);
let req = MessageRequest::FileContainingSymbol(extra_type.clone().into());
let resp = match client.send_reflection_request(req, metadata).await {
Ok(r) => r,
Err(e) => {
return Err(GenericError(format!(
"Error sending reflection request for response @type \"{extra_type}\": {e:?}",
)));
}
};
let files = match resp {
MessageResponse::FileDescriptorResponse(resp) => resp.file_descriptor_proto,
_ => panic!("Expected a FileDescriptorResponse variant"),
};
{
let mut guard = pool.write().await;
add_file_descriptors_to_pool(files, &mut *guard, &mut client, metadata).await;
}
}
Ok(())
}
fn collect_any_types_from_dynamic_message(message: &DynamicMessage, out: &mut HashSet<String>) {
if message.descriptor().full_name() == "google.protobuf.Any" {
if let Some(Value::String(type_url)) = message.get_field_by_name("type_url").as_deref() {
if let Some(full_name) = type_url.rsplit_once('/').map(|(_, name)| name) {
out.insert(full_name.to_string());
}
}
}
for (_, value) in message.fields() {
collect_any_types_from_value(value, out);
}
}
fn collect_any_types_from_value(value: &Value, out: &mut HashSet<String>) {
match value {
Value::Message(message) => collect_any_types_from_dynamic_message(message, out),
Value::List(values) => {
for value in values {
collect_any_types_from_value(value, out);
}
}
Value::Map(values) => {
for value in values.values() {
collect_any_types_from_value(value, out);
}
}
_ => {}
}
}
#[async_recursion] #[async_recursion]
pub(crate) async fn add_file_descriptors_to_pool( pub(crate) async fn add_file_descriptors_to_pool(
fds: Vec<Vec<u8>>, fds: Vec<Vec<u8>>,
+30 -6
View File
@@ -1,11 +1,36 @@
use crate::dns::LocalhostResolver; use crate::dns::LocalhostResolver;
use crate::error::Result; use crate::error::Result;
use log::{debug, info, warn}; use log::{debug, info, warn};
use reqwest::{Client, Proxy, redirect}; use reqwest::{Client, ClientBuilder, Proxy, redirect};
use std::sync::Arc; use std::sync::Arc;
use yaak_models::models::DnsOverride; use yaak_models::models::DnsOverride;
use yaak_tls::{ClientCertificateConfig, get_tls_config}; use yaak_tls::{ClientCertificateConfig, get_tls_config};
pub const HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE: u32 = 1024 * 1024;
fn client_builder() -> ClientBuilder {
Client::builder().http2_max_header_list_size(HTTP2_MAX_RESPONSE_HEADER_LIST_SIZE)
}
#[derive(Clone)]
pub struct ConfiguredClient {
inner: Client,
}
impl ConfiguredClient {
pub(crate) fn build_default() -> Result<Self> {
Ok(Self { inner: client_builder().build()? })
}
pub(crate) fn from_inner(inner: Client) -> Self {
Self { inner }
}
pub(crate) fn inner(&self) -> &Client {
&self.inner
}
}
/// Build a native-tls connector for maximum compatibility when certificate /// Build a native-tls connector for maximum compatibility when certificate
/// validation is disabled. Unlike rustls, native-tls uses the OS TLS stack /// validation is disabled. Unlike rustls, native-tls uses the OS TLS stack
/// (Secure Transport on macOS, SChannel on Windows, OpenSSL on Linux) which /// (Secure Transport on macOS, SChannel on Windows, OpenSSL on Linux) which
@@ -87,8 +112,8 @@ impl HttpConnectionOptions {
/// Build a reqwest Client and return it along with the DNS resolver. /// Build a reqwest Client and return it along with the DNS resolver.
/// The resolver is returned separately so it can be configured per-request /// The resolver is returned separately so it can be configured per-request
/// to emit DNS timing events to the appropriate channel. /// to emit DNS timing events to the appropriate channel.
pub(crate) fn build_client(&self) -> Result<(Client, Arc<LocalhostResolver>)> { pub(crate) fn build_client(&self) -> Result<(ConfiguredClient, Arc<LocalhostResolver>)> {
let mut client = Client::builder() let mut client = client_builder()
.connection_verbose(true) .connection_verbose(true)
.redirect(redirect::Policy::none()) .redirect(redirect::Policy::none())
// Decompression is handled by HttpTransaction, not reqwest // Decompression is handled by HttpTransaction, not reqwest
@@ -108,8 +133,7 @@ impl HttpConnectionOptions {
client = client.use_preconfigured_tls(config); client = client.use_preconfigured_tls(config);
} else { } else {
// Use native TLS for maximum compatibility (supports TLS 1.0+) // Use native TLS for maximum compatibility (supports TLS 1.0+)
let connector = let connector = build_native_tls_connector(self.client_certificate.clone())?;
build_native_tls_connector(self.client_certificate.clone())?;
client = client.use_preconfigured_tls(connector); client = client.use_preconfigured_tls(connector);
} }
@@ -136,7 +160,7 @@ impl HttpConnectionOptions {
self.client_certificate.is_some() self.client_certificate.is_some()
); );
Ok((client.build()?, resolver)) Ok((ConfiguredClient::from_inner(client.build()?), resolver))
} }
} }
+93
View File
@@ -124,6 +124,30 @@ impl CookieStore {
} }
} }
/// Get a stored cookie value by name, optionally scoped to an exact stored domain.
pub fn get_cookie_value_from_jar(
cookies: impl IntoIterator<Item = Cookie>,
name: &str,
domain: Option<&str>,
) -> Option<String> {
let domain = domain.and_then(normalize_cookie_domain_filter);
cookies.into_iter().find_map(|cookie| {
let (cookie_name, value) = parse_cookie_name_value(&cookie.raw_cookie)?;
if cookie_name != name {
return None;
}
if let Some(domain) = domain.as_deref() {
if !cookie_domain_matches_filter(&cookie.domain, domain) {
return None;
}
}
Some(value)
})
}
/// Parse name=value from a cookie string (raw_cookie format) /// Parse name=value from a cookie string (raw_cookie format)
fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> { fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
// The raw_cookie typically looks like "name=value" or "name=value; attr1; attr2=..." // The raw_cookie typically looks like "name=value" or "name=value; attr1; attr2=..."
@@ -135,6 +159,20 @@ fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
if name.is_empty() { None } else { Some((name, value)) } if name.is_empty() { None } else { Some((name, value)) }
} }
fn normalize_cookie_domain_filter(domain: &str) -> Option<String> {
let domain = domain.trim().trim_start_matches('.').to_lowercase();
if domain.is_empty() { None } else { Some(domain) }
}
fn cookie_domain_matches_filter(cookie_domain: &CookieDomain, domain: &str) -> bool {
match cookie_domain {
CookieDomain::HostOnly(cookie_domain) | CookieDomain::Suffix(cookie_domain) => {
normalize_cookie_domain_filter(cookie_domain).is_some_and(|d| d == domain)
}
CookieDomain::NotPresent | CookieDomain::Empty => false,
}
}
/// Parse a Set-Cookie header into a Cookie /// Parse a Set-Cookie header into a Cookie
fn parse_set_cookie(header_value: &str, request_url: &Url) -> Option<Cookie> { fn parse_set_cookie(header_value: &str, request_url: &Url) -> Option<Cookie> {
let parsed = cookie::Cookie::parse(header_value).ok()?; let parsed = cookie::Cookie::parse(header_value).ok()?;
@@ -278,6 +316,15 @@ fn is_localhost(domain: &str) -> bool {
mod tests { mod tests {
use super::*; use super::*;
fn cookie(raw_cookie: &str, domain: CookieDomain) -> Cookie {
Cookie {
raw_cookie: raw_cookie.to_string(),
domain,
expires: CookieExpires::SessionEnd,
path: ("/".to_string(), false),
}
}
#[test] #[test]
fn test_parse_cookie_name_value() { fn test_parse_cookie_name_value() {
assert_eq!( assert_eq!(
@@ -387,6 +434,52 @@ mod tests {
assert_eq!(store.get_all_cookies().len(), 1); assert_eq!(store.get_all_cookies().len(), 1);
} }
#[test]
fn test_get_cookie_value_preserves_name_only_first_match() {
let cookies = vec![
cookie("co-auth=", CookieDomain::HostOnly("foo.example.com".to_string())),
cookie("co-auth=token", CookieDomain::Suffix("example.com".to_string())),
];
assert_eq!(get_cookie_value_from_jar(cookies, "co-auth", None), Some("".to_string()));
}
#[test]
fn test_get_cookie_value_matches_domain() {
let cookies = vec![
cookie("co-auth=", CookieDomain::HostOnly("foo.example.com".to_string())),
cookie("co-auth=token", CookieDomain::Suffix("example.com".to_string())),
];
assert_eq!(
get_cookie_value_from_jar(cookies, "co-auth", Some("example.com")),
Some("token".to_string())
);
}
#[test]
fn test_get_cookie_value_normalizes_domain_filter() {
let cookies = vec![cookie(
"co-auth=token",
CookieDomain::Suffix("Example.COM".to_string()),
)];
assert_eq!(
get_cookie_value_from_jar(cookies, "co-auth", Some(" .example.com ")),
Some("token".to_string())
);
}
#[test]
fn test_get_cookie_value_requires_exact_stored_domain_match() {
let cookies = vec![cookie(
"co-auth=token",
CookieDomain::HostOnly("foo.example.com".to_string()),
)];
assert_eq!(get_cookie_value_from_jar(cookies, "co-auth", Some("example.com")), None);
}
#[test] #[test]
fn test_is_single_component_domain() { fn test_is_single_component_domain() {
// Single-component domains (TLDs) // Single-component domains (TLDs)
+2 -3
View File
@@ -1,7 +1,6 @@
use crate::client::HttpConnectionOptions; use crate::client::{ConfiguredClient, HttpConnectionOptions};
use crate::dns::LocalhostResolver; use crate::dns::LocalhostResolver;
use crate::error::Result; use crate::error::Result;
use reqwest::Client;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
@@ -10,7 +9,7 @@ use tokio::sync::RwLock;
/// A cached HTTP client along with its DNS resolver. /// A cached HTTP client along with its DNS resolver.
/// The resolver is needed to set the event sender per-request. /// The resolver is needed to set the event sender per-request.
pub struct CachedClient { pub struct CachedClient {
pub client: Client, pub client: ConfiguredClient,
pub resolver: Arc<LocalhostResolver>, pub resolver: Arc<LocalhostResolver>,
} }
+7 -7
View File
@@ -5,7 +5,7 @@ use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use futures_util::StreamExt; use futures_util::StreamExt;
use http_body::{Body as HttpBody, Frame, SizeHint}; use http_body::{Body as HttpBody, Frame, SizeHint};
use reqwest::{Client, Method, Version}; use reqwest::{Method, Version};
use std::fmt::Display; use std::fmt::Display;
use std::pin::Pin; use std::pin::Pin;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
@@ -411,18 +411,18 @@ pub trait HttpSender: Send + Sync {
/// Reqwest-based implementation of HttpSender /// Reqwest-based implementation of HttpSender
pub struct ReqwestSender { pub struct ReqwestSender {
client: Client, client: crate::client::ConfiguredClient,
} }
impl ReqwestSender { impl ReqwestSender {
/// Create a new ReqwestSender with a default client /// Create a new ReqwestSender with a default client
pub fn new() -> Result<Self> { pub fn new() -> Result<Self> {
let client = Client::builder().build().map_err(Error::Client)?; let client = crate::client::ConfiguredClient::build_default()?;
Ok(Self { client }) Ok(Self { client })
} }
/// Create a new ReqwestSender with a custom client /// Create a new ReqwestSender with a configured client
pub fn with_client(client: Client) -> Self { pub fn with_client(client: crate::client::ConfiguredClient) -> Self {
Self { client } Self { client }
} }
} }
@@ -444,7 +444,7 @@ impl HttpSender for ReqwestSender {
.map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?; .map_err(|e| Error::RequestError(format!("Invalid HTTP method: {}", e)))?;
// Build the request // Build the request
let mut req_builder = self.client.request(method, &request.url); let mut req_builder = self.client.inner().request(method, &request.url);
// Add headers // Add headers
for header in request.headers { for header in request.headers {
@@ -513,7 +513,7 @@ impl HttpSender for ReqwestSender {
send_event(HttpResponseEvent::Info("Sending request to server".to_string())); send_event(HttpResponseEvent::Info("Sending request to server".to_string()));
// Map some errors to our own, so they look nicer // Map some errors to our own, so they look nicer
let response = self.client.execute(sendable_req).await.map_err(|e| { let response = self.client.inner().execute(sendable_req).await.map_err(|e| {
if reqwest::Error::is_timeout(&e) { if reqwest::Error::is_timeout(&e) {
Error::RequestTimeout( Error::RequestTimeout(
request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(), request.options.timeout.unwrap_or(Duration::from_secs(0)).clone(),
+2 -4
View File
@@ -226,10 +226,8 @@ async fn build_body(
let (body, content_type) = match body_type.as_str() { let (body, content_type) = match body_type.as_str() {
"binary" => (build_binary_body(&body).await?, None), "binary" => (build_binary_body(&body).await?, None),
"graphql" => (build_graphql_body(&method, &body), Some("application/json".to_string())), "graphql" => (build_graphql_body(&method, &body), None),
"application/x-www-form-urlencoded" => { "application/x-www-form-urlencoded" => (build_form_body(&body), None),
(build_form_body(&body), Some("application/x-www-form-urlencoded".to_string()))
}
"multipart/form-data" => build_multipart_body(&body, &headers).await?, "multipart/form-data" => build_multipart_body(&body, &headers).await?,
_ if body.contains_key("text") => (build_text_body(&body, body_type), None), _ if body.contains_key("text") => (build_text_body(&body, body_type), None),
t => { t => {
+4 -3
View File
@@ -144,9 +144,10 @@ export function duplicateModel<M extends AnyModel["model"], T extends ExtractMod
throw new Error("Failed to duplicate null model"); throw new Error("Failed to duplicate null model");
} }
// If the model has a name, try to duplicate it with a name that doesn't conflict // If the model has an explicit (non-empty) name, try to duplicate it with a name that doesn't conflict.
let name = "name" in model ? resolvedModelName(model) : undefined; // When the name is empty, keep it empty so the display falls back to the URL.
if (name != null) { let name = "name" in model ? model.name : undefined;
if (name) {
const existingModels = listModels(model.model); const existingModels = listModels(model.model);
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
const hasConflict = existingModels.some((m) => { const hasConflict = existingModels.some((m) => {
+1 -1
View File
@@ -396,7 +396,7 @@ description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, }; export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, }; export type GetCookieValueRequest = { name: string, domain?: string | null, };
export type GetCookieValueResponse = { value: string | null, }; export type GetCookieValueResponse = { value: string | null, };
+3
View File
@@ -307,6 +307,9 @@ pub struct ListCookieNamesResponse {
#[ts(export, export_to = "gen_events.ts")] #[ts(export, export_to = "gen_events.ts")]
pub struct GetCookieValueRequest { pub struct GetCookieValueRequest {
pub name: String, pub name: String,
#[ts(optional = nullable)]
pub domain: Option<String>,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]
+1 -1
View File
@@ -1,5 +1,5 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export function unescape_template(template: string): any;
export function escape_template(template: string): any; export function escape_template(template: string): any;
export function parse_template(template: string): any; export function parse_template(template: string): any;
export function unescape_template(template: string): any;
+14 -14
View File
@@ -161,6 +161,20 @@ function takeFromExternrefTable0(idx) {
wasm.__externref_table_dealloc(idx); wasm.__externref_table_dealloc(idx);
return value; return value;
} }
/**
* @param {string} template
* @returns {any}
*/
export function unescape_template(template) {
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.unescape_template(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
/** /**
* @param {string} template * @param {string} template
* @returns {any} * @returns {any}
@@ -189,20 +203,6 @@ export function parse_template(template) {
return takeFromExternrefTable0(ret[0]); return takeFromExternrefTable0(ret[0]);
} }
/**
* @param {string} template
* @returns {any}
*/
export function unescape_template(template) {
const ptr0 = passStringToWasm0(template, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.unescape_template(ptr0, len0);
if (ret[2]) {
throw takeFromExternrefTable0(ret[1]);
}
return takeFromExternrefTable0(ret[0]);
}
export function __wbg_new_405e22f390576ce2() { export function __wbg_new_405e22f390576ce2() {
const ret = new Object(); const ret = new Object();
return ret; return ret;
Binary file not shown.
+69 -164
View File
@@ -142,9 +142,9 @@
"nanoid": "^5.0.9", "nanoid": "^5.0.9",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"parse-color": "^1.0.0", "parse-color": "^1.0.0",
"react": "^19.1.0", "react": "^19.2.0",
"react-colorful": "^5.6.1", "react-colorful": "^5.6.1",
"react-dom": "^19.1.0", "react-dom": "^19.2.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-pdf": "^10.0.1", "react-pdf": "^10.0.1",
"react-syntax-highlighter": "^16.1.0", "react-syntax-highlighter": "^16.1.0",
@@ -153,10 +153,10 @@
"remark-frontmatter": "^5.0.0", "remark-frontmatter": "^5.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"uuid": "^11.1.0", "uuid": "^14.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"whatwg-mimetype": "^4.0.0", "whatwg-mimetype": "^4.0.0",
"yaml": "^2.6.1" "yaml": "^2.8.3"
}, },
"devDependencies": { "devDependencies": {
"@lezer/generator": "^1.8.0", "@lezer/generator": "^1.8.0",
@@ -166,12 +166,12 @@
"@types/node": "^24.0.13", "@types/node": "^24.0.13",
"@types/papaparse": "^5.3.16", "@types/papaparse": "^5.3.16",
"@types/parse-color": "^1.0.3", "@types/parse-color": "^1.0.3",
"@types/react": "^19.1.8", "@types/react": "^19.2.0",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.2.0",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",
"@types/whatwg-mimetype": "^3.0.2", "@types/whatwg-mimetype": "^3.0.2",
"@vitejs/plugin-react": "^4.6.0", "@vitejs/plugin-react": "^6.0.0",
"@yaakapp-internal/theme": "^1.0.0", "@yaakapp-internal/theme": "^1.0.0",
"@yaakapp-internal/ui": "^1.0.0", "@yaakapp-internal/ui": "^1.0.0",
"autoprefixer": "^10.4.21", "autoprefixer": "^10.4.21",
@@ -181,8 +181,8 @@
"postcss-nesting": "^13.0.2", "postcss-nesting": "^13.0.2",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"vite": "^7.0.8", "vite": "^7.0.8",
"vite-plugin-static-copy": "^3.1.2", "vite-plugin-static-copy": "^3.3.0",
"vite-plugin-svgr": "^4.3.0", "vite-plugin-svgr": "^4.5.0",
"vite-plugin-top-level-await": "^1.5.0", "vite-plugin-top-level-await": "^1.5.0",
"vite-plugin-wasm": "^3.5.0" "vite-plugin-wasm": "^3.5.0"
} }
@@ -213,14 +213,16 @@
} }
}, },
"apps/yaak-client/node_modules/uuid": { "apps/yaak-client/node_modules/uuid": {
"version": "11.1.0", "version": "14.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"funding": [ "funding": [
"https://github.com/sponsors/broofa", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan" "https://github.com/sponsors/ctavan"
], ],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"uuid": "dist/esm/bin/uuid" "uuid": "dist-node/bin/uuid"
} }
}, },
"apps/yaak-proxy": { "apps/yaak-proxy": {
@@ -237,13 +239,13 @@
"classnames": "^2.5.1", "classnames": "^2.5.1",
"jotai": "^2.18.0", "jotai": "^2.18.0",
"motion": "^12.4.7", "motion": "^12.4.7",
"react": "^19.1.0", "react": "^19.2.0",
"react-dom": "^19.1.0" "react-dom": "^19.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^19.1.8", "@types/react": "^19.2.0",
"@types/react-dom": "^19.1.6", "@types/react-dom": "^19.2.0",
"@vitejs/plugin-react": "^4.6.0", "@vitejs/plugin-react": "^6.0.0",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.8" "vite": "^7.0.8"
} }
@@ -592,38 +594,6 @@
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
} }
}, },
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.28.4", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
@@ -1490,9 +1460,9 @@
} }
}, },
"node_modules/@hono/node-server": { "node_modules/@hono/node-server": {
"version": "1.19.9", "version": "1.19.14",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz",
"integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=18.14.1" "node": ">=18.14.1"
@@ -2928,9 +2898,9 @@
} }
}, },
"node_modules/@rolldown/pluginutils": { "node_modules/@rolldown/pluginutils": {
"version": "1.0.0-beta.27", "version": "1.0.0-rc.7",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz",
"integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -4457,51 +4427,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
"@types/babel__generator": "*",
"@types/babel__template": "*",
"@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/chai": { "node_modules/@types/chai": {
"version": "5.2.3", "version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -4723,24 +4648,29 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/@vitejs/plugin-react": { "node_modules/@vitejs/plugin-react": {
"version": "4.7.0", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz",
"integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/core": "^7.28.0", "@rolldown/pluginutils": "1.0.0-rc.7"
"@babel/plugin-transform-react-jsx-self": "^7.27.1",
"@babel/plugin-transform-react-jsx-source": "^7.27.1",
"@rolldown/pluginutils": "1.0.0-beta.27",
"@types/babel__core": "^7.20.5",
"react-refresh": "^0.17.0"
}, },
"engines": { "engines": {
"node": "^14.18.0 || >=16.0.0" "node": "^20.19.0 || >=22.12.0"
}, },
"peerDependencies": { "peerDependencies": {
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
"babel-plugin-react-compiler": "^1.0.0",
"vite": "^8.0.0"
},
"peerDependenciesMeta": {
"@rolldown/plugin-babel": {
"optional": true
},
"babel-plugin-react-compiler": {
"optional": true
}
} }
}, },
"node_modules/@vitest/expect": { "node_modules/@vitest/expect": {
@@ -5130,9 +5060,9 @@
} }
}, },
"node_modules/@xmldom/xmldom": { "node_modules/@xmldom/xmldom": {
"version": "0.9.8", "version": "0.9.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.10.tgz",
"integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==", "integrity": "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14.6" "node": ">=14.6"
@@ -8942,9 +8872,9 @@
} }
}, },
"node_modules/hono": { "node_modules/hono": {
"version": "4.11.10", "version": "4.12.18",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.10.tgz", "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.18.tgz",
"integrity": "sha512-kyWP5PAiMooEvGrA9jcD3IXF7ATu8+o7B3KCbPXid5se52NPqnOpM/r9qeW2heMnOekF4kqR1fXJqCYeCLKrZg==", "integrity": "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=16.9.0" "node": ">=16.9.0"
@@ -13753,16 +13683,6 @@
} }
} }
}, },
"node_modules/react-refresh": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
"integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-syntax-highlighter": { "node_modules/react-syntax-highlighter": {
"version": "16.1.0", "version": "16.1.0",
"resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz", "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz",
@@ -16544,22 +16464,26 @@
} }
}, },
"node_modules/vite-plugin-static-copy": { "node_modules/vite-plugin-static-copy": {
"version": "3.1.4", "version": "3.4.0",
"resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.4.tgz", "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.4.0.tgz",
"integrity": "sha512-iCmr4GSw4eSnaB+G8zc2f4dxSuDjbkjwpuBLLGvQYR9IW7rnDzftnUjOH5p4RYR+d4GsiBqXRvzuFhs5bnzVyw==", "integrity": "sha512-ekryzCw0ouAOE8tw4RvVL/dfqguXzumsV3FBKoKso4MQ1MUUrUXtl5RI4KpJQUNGqFEsg9kxl4EvDl02YtA9VQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"p-map": "^7.0.3", "p-map": "^7.0.4",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"tinyglobby": "^0.2.15" "tinyglobby": "^0.2.15"
}, },
"engines": { "engines": {
"node": "^18.0.0 || >=20.0.0" "node": "^18.0.0 || >=20.0.0"
}, },
"funding": {
"type": "github",
"url": "https://github.com/sponsors/sapphi-red"
},
"peerDependencies": { "peerDependencies": {
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
} }
}, },
"node_modules/vite-plugin-svgr": { "node_modules/vite-plugin-svgr": {
@@ -17086,9 +17010,9 @@
"license": "ISC" "license": "ISC"
}, },
"node_modules/yaml": { "node_modules/yaml": {
"version": "2.8.2", "version": "2.8.4",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
"integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
"license": "ISC", "license": "ISC",
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
@@ -17257,9 +17181,9 @@
"version": "0.2.1", "version": "0.2.1",
"dependencies": { "dependencies": {
"@hono/mcp": "^0.2.3", "@hono/mcp": "^0.2.3",
"@hono/node-server": "^1.19.10", "@hono/node-server": "^1.19.13",
"@modelcontextprotocol/sdk": "^1.26.0", "@modelcontextprotocol/sdk": "^1.26.0",
"hono": "^4.12.4", "hono": "^4.12.14",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
"devDependencies": { "devDependencies": {
@@ -17267,27 +17191,6 @@
"typescript": "^5.9.3" "typescript": "^5.9.3"
} }
}, },
"plugins-external/mcp-server/node_modules/@hono/node-server": {
"version": "1.19.11",
"resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz",
"integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==",
"license": "MIT",
"engines": {
"node": ">=18.14.1"
},
"peerDependencies": {
"hono": "^4"
}
},
"plugins-external/mcp-server/node_modules/hono": {
"version": "4.12.7",
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz",
"integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==",
"license": "MIT",
"engines": {
"node": ">=16.9.0"
}
},
"plugins/action-copy-curl": { "plugins/action-copy-curl": {
"name": "@yaak/action-copy-curl", "name": "@yaak/action-copy-curl",
"version": "0.1.0" "version": "0.1.0"
@@ -17370,7 +17273,7 @@
"name": "@yaak/filter-xpath", "name": "@yaak/filter-xpath",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.9.8", "@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34" "xpath": "^0.0.34"
} }
}, },
@@ -17385,7 +17288,7 @@
"name": "@yaak/importer-insomnia", "name": "@yaak/importer-insomnia",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"yaml": "^2.4.2" "yaml": "^2.8.3"
} }
}, },
"plugins/importer-openapi": { "plugins/importer-openapi": {
@@ -17393,7 +17296,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"openapi-to-postmanv2": "^5.8.0", "openapi-to-postmanv2": "^5.8.0",
"yaml": "^2.4.2" "yaml": "^2.8.3"
}, },
"devDependencies": { "devDependencies": {
"@types/openapi-to-postmanv2": "^5.0.0" "@types/openapi-to-postmanv2": "^5.0.0"
@@ -17489,25 +17392,27 @@
"name": "@yaak/template-function-uuid", "name": "@yaak/template-function-uuid",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"uuid": "^11.1.0" "uuid": "^14.0.0"
} }
}, },
"plugins/template-function-uuid/node_modules/uuid": { "plugins/template-function-uuid/node_modules/uuid": {
"version": "11.1.0", "version": "14.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz",
"integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==",
"funding": [ "funding": [
"https://github.com/sponsors/broofa", "https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan" "https://github.com/sponsors/ctavan"
], ],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"uuid": "dist/esm/bin/uuid" "uuid": "dist-node/bin/uuid"
} }
}, },
"plugins/template-function-xml": { "plugins/template-function-xml": {
"name": "@yaak/template-function-xml", "name": "@yaak/template-function-xml",
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.9.8", "@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34" "xpath": "^0.0.34"
} }
}, },
+1 -1
View File
@@ -396,7 +396,7 @@ description?: string, };
export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, }; export type GenericCompletionOption = { label: string, detail?: string, info?: string, type?: CompletionOptionType, boost?: number, };
export type GetCookieValueRequest = { name: string, }; export type GetCookieValueRequest = { name: string, domain?: string | null, };
export type GetCookieValueResponse = { value: string | null, }; export type GetCookieValueResponse = { value: string | null, };
@@ -5,6 +5,7 @@
"lib": ["es2021", "dom"], "lib": ["es2021", "dom"],
"declaration": true, "declaration": true,
"declarationDir": "./lib", "declarationDir": "./lib",
"rootDir": "./src",
"outDir": "./lib", "outDir": "./lib",
"strict": true, "strict": true,
"types": ["node"] "types": ["node"]
+2 -2
View File
@@ -15,9 +15,9 @@
}, },
"dependencies": { "dependencies": {
"@hono/mcp": "^0.2.3", "@hono/mcp": "^0.2.3",
"@hono/node-server": "^1.19.10", "@hono/node-server": "^1.19.13",
"@modelcontextprotocol/sdk": "^1.26.0", "@modelcontextprotocol/sdk": "^1.26.0",
"hono": "^4.12.4", "hono": "^4.12.14",
"zod": "^3.25.76" "zod": "^3.25.76"
}, },
"devDependencies": { "devDependencies": {
+1 -1
View File
@@ -9,7 +9,7 @@
"dev": "yaakcli dev" "dev": "yaakcli dev"
}, },
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.9.8", "@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34" "xpath": "^0.0.34"
} }
} }
+1 -1
View File
@@ -10,6 +10,6 @@
"test": "vp test --run tests" "test": "vp test --run tests"
}, },
"dependencies": { "dependencies": {
"yaml": "^2.4.2" "yaml": "^2.8.3"
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@
}, },
"dependencies": { "dependencies": {
"openapi-to-postmanv2": "^5.8.0", "openapi-to-postmanv2": "^5.8.0",
"yaml": "^2.4.2" "yaml": "^2.8.3"
}, },
"devDependencies": { "devDependencies": {
"@types/openapi-to-postmanv2": "^5.0.0" "@types/openapi-to-postmanv2": "^5.0.0"
+15 -1
View File
@@ -11,12 +11,26 @@ export const plugin: PluginDefinition = {
type: "text", type: "text",
name: "name", name: "name",
label: "Cookie Name", label: "Cookie Name",
placeholder: "cookie_name",
},
{
type: "text",
name: "domain",
label: "Domain",
placeholder: "example.com",
description: "Optionally filter by domain, useful if multiple cookies with the same name.",
optional: true,
}, },
], ],
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> { async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
// The legacy name was cookie_name, but we changed it // The legacy name was cookie_name, but we changed it
const name = args.values.cookie_name ?? args.values.name; const name = args.values.cookie_name ?? args.values.name;
return ctx.cookies.getValue({ name: String(name) }); const domain = String(args.values.domain ?? "").trim();
return ctx.cookies.getValue({
name: String(name),
...(domain.length > 0 ? { domain } : {}),
});
}, },
}, },
], ],
+1 -1
View File
@@ -9,6 +9,6 @@
"dev": "yaakcli dev" "dev": "yaakcli dev"
}, },
"dependencies": { "dependencies": {
"uuid": "^11.1.0" "uuid": "^14.0.0"
} }
} }
+1 -1
View File
@@ -11,7 +11,7 @@
"dev": "yaakcli dev" "dev": "yaakcli dev"
}, },
"dependencies": { "dependencies": {
"@xmldom/xmldom": "^0.9.8", "@xmldom/xmldom": "^0.9.10",
"xpath": "^0.0.34" "xpath": "^0.0.34"
} }
} }
+5 -3
View File
@@ -10,10 +10,12 @@
"noUncheckedIndexedAccess": true, "noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "bundler",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx",
} "types": ["node"]
},
"exclude": ["flatpak", "npm", "crates/yaak-templates/pkg"]
} }