mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-27 19:17:03 +02:00
Compare commits
13 Commits
v2026.3.0-
...
gschier/cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2439ffd28c | ||
|
|
ad31755dbb | ||
|
|
b01b3a4c57 | ||
|
|
ef63b88710 | ||
|
|
fb5ad8c7f7 | ||
|
|
3c12074db6 | ||
|
|
851f12f149 | ||
|
|
cc0d31fdbb | ||
|
|
bab4fe899b | ||
|
|
0b250ff5b5 | ||
|
|
fbf0473b20 | ||
|
|
876b7ef454 | ||
|
|
96e8572758 |
126
Cargo.lock
generated
126
Cargo.lock
generated
@@ -1200,7 +1200,7 @@ dependencies = [
|
|||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"libc",
|
"libc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1405,6 +1405,31 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"parking_lot",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@@ -2294,6 +2319,15 @@ dependencies = [
|
|||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuzzy-matcher"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
|
||||||
|
dependencies = [
|
||||||
|
"thread_local 1.1.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fxhash"
|
name = "fxhash"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -3164,6 +3198,24 @@ dependencies = [
|
|||||||
"generic-array",
|
"generic-array",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "inquire"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"crossterm",
|
||||||
|
"dyn-clone",
|
||||||
|
"fuzzy-matcher",
|
||||||
|
"fxhash",
|
||||||
|
"newline-converter",
|
||||||
|
"once_cell",
|
||||||
|
"tempfile",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width 0.1.14",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "interfaces"
|
name = "interfaces"
|
||||||
version = "0.0.8"
|
version = "0.0.8"
|
||||||
@@ -3756,6 +3808,18 @@ dependencies = [
|
|||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"log 0.4.29",
|
||||||
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
|
"windows-sys 0.48.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
@@ -3851,6 +3915,15 @@ version = "1.0.6"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "newline-converter"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-segmentation",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nibble_vec"
|
name = "nibble_vec"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -3942,7 +4015,7 @@ dependencies = [
|
|||||||
"kqueue",
|
"kqueue",
|
||||||
"libc",
|
"libc",
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"notify-types",
|
"notify-types",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
@@ -4501,7 +4574,7 @@ dependencies = [
|
|||||||
"textwrap",
|
"textwrap",
|
||||||
"thiserror 2.0.17",
|
"thiserror 2.0.17",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -6171,7 +6244,7 @@ version = "0.5.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77dff57c9de498bb1eb5b1ce682c2e3a0ae956b266fa0933c3e151b87b078967"
|
checksum = "77dff57c9de498bb1eb5b1ce682c2e3a0ae956b266fa0933c3e151b87b078967"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
"yansi",
|
"yansi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -6196,7 +6269,7 @@ dependencies = [
|
|||||||
"kqueue",
|
"kqueue",
|
||||||
"libc",
|
"libc",
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"rolldown-notify-types",
|
"rolldown-notify-types",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
@@ -7173,6 +7246,27 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio 0.8.11",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.5"
|
version = "1.4.5"
|
||||||
@@ -8068,7 +8162,7 @@ checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"smawk",
|
"smawk",
|
||||||
"unicode-linebreak",
|
"unicode-linebreak",
|
||||||
"unicode-width",
|
"unicode-width 0.2.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -8215,7 +8309,7 @@ checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio 1.0.4",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
"socket2 0.6.1",
|
"socket2 0.6.1",
|
||||||
@@ -8785,6 +8879,12 @@ version = "1.12.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
@@ -9562,6 +9662,15 @@ dependencies = [
|
|||||||
"windows-targets 0.42.2",
|
"windows-targets 0.42.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@@ -10141,6 +10250,7 @@ dependencies = [
|
|||||||
name = "yaak-cli"
|
name = "yaak-cli"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arboard",
|
||||||
"assert_cmd",
|
"assert_cmd",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -10150,6 +10260,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"hex",
|
"hex",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
|
"inquire",
|
||||||
"keyring",
|
"keyring",
|
||||||
"log 0.4.29",
|
"log 0.4.29",
|
||||||
"oxc_resolver",
|
"oxc_resolver",
|
||||||
@@ -10166,6 +10277,7 @@ dependencies = [
|
|||||||
"walkdir",
|
"walkdir",
|
||||||
"webbrowser",
|
"webbrowser",
|
||||||
"yaak",
|
"yaak",
|
||||||
|
"yaak-api",
|
||||||
"yaak-crypto",
|
"yaak-crypto",
|
||||||
"yaak-http",
|
"yaak-http",
|
||||||
"yaak-models",
|
"yaak-models",
|
||||||
|
|||||||
@@ -9,12 +9,14 @@ name = "yaak"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
arboard = "3"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
console = "0.15"
|
console = "0.15"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
inquire = { version = "0.7", features = ["editor"] }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
include_dir = "0.7"
|
include_dir = "0.7"
|
||||||
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
keyring = { workspace = true, features = ["apple-native", "windows-native", "sync-secret-service"] }
|
||||||
@@ -32,6 +34,7 @@ walkdir = "2"
|
|||||||
webbrowser = "1"
|
webbrowser = "1"
|
||||||
zip = "4"
|
zip = "4"
|
||||||
yaak = { workspace = true }
|
yaak = { workspace = true }
|
||||||
|
yaak-api = { workspace = true }
|
||||||
yaak-crypto = { workspace = true }
|
yaak-crypto = { workspace = true }
|
||||||
yaak-http = { workspace = true }
|
yaak-http = { workspace = true }
|
||||||
yaak-models = { workspace = true }
|
yaak-models = { workspace = true }
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ pub struct Cli {
|
|||||||
#[arg(long, short, global = true)]
|
#[arg(long, short, global = true)]
|
||||||
pub environment: Option<String>,
|
pub environment: Option<String>,
|
||||||
|
|
||||||
|
/// Cookie jar ID to use when sending requests
|
||||||
|
#[arg(long = "cookie-jar", global = true, value_name = "COOKIE_JAR_ID")]
|
||||||
|
pub cookie_jar: Option<String>,
|
||||||
|
|
||||||
/// Enable verbose send output (events and streamed response body)
|
/// Enable verbose send output (events and streamed response body)
|
||||||
#[arg(long, short, global = true)]
|
#[arg(long, short, global = true)]
|
||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
@@ -47,9 +51,20 @@ pub enum Commands {
|
|||||||
#[command(hide = true)]
|
#[command(hide = true)]
|
||||||
Dev(PluginPathArg),
|
Dev(PluginPathArg),
|
||||||
|
|
||||||
|
/// Backward-compatible alias for `plugin generate`
|
||||||
|
#[command(hide = true)]
|
||||||
|
Generate(GenerateArgs),
|
||||||
|
|
||||||
|
/// Backward-compatible alias for `plugin publish`
|
||||||
|
#[command(hide = true)]
|
||||||
|
Publish(PluginPathArg),
|
||||||
|
|
||||||
/// Send a request, folder, or workspace by ID
|
/// Send a request, folder, or workspace by ID
|
||||||
Send(SendArgs),
|
Send(SendArgs),
|
||||||
|
|
||||||
|
/// Cookie jar commands
|
||||||
|
CookieJar(CookieJarArgs),
|
||||||
|
|
||||||
/// Workspace commands
|
/// Workspace commands
|
||||||
Workspace(WorkspaceArgs),
|
Workspace(WorkspaceArgs),
|
||||||
|
|
||||||
@@ -77,6 +92,22 @@ pub struct SendArgs {
|
|||||||
pub fail_fast: bool,
|
pub fail_fast: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args)]
|
||||||
|
#[command(disable_help_subcommand = true)]
|
||||||
|
pub struct CookieJarArgs {
|
||||||
|
#[command(subcommand)]
|
||||||
|
pub command: CookieJarCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Subcommand)]
|
||||||
|
pub enum CookieJarCommands {
|
||||||
|
/// List cookie jars in a workspace
|
||||||
|
List {
|
||||||
|
/// Workspace ID (optional when exactly one workspace exists)
|
||||||
|
workspace_id: Option<String>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Args)]
|
#[derive(Args)]
|
||||||
#[command(disable_help_subcommand = true)]
|
#[command(disable_help_subcommand = true)]
|
||||||
pub struct WorkspaceArgs {
|
pub struct WorkspaceArgs {
|
||||||
@@ -150,8 +181,8 @@ pub struct RequestArgs {
|
|||||||
pub enum RequestCommands {
|
pub enum RequestCommands {
|
||||||
/// List requests in a workspace
|
/// List requests in a workspace
|
||||||
List {
|
List {
|
||||||
/// Workspace ID
|
/// Workspace ID (optional when exactly one workspace exists)
|
||||||
workspace_id: String,
|
workspace_id: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Show a request as JSON
|
/// Show a request as JSON
|
||||||
@@ -259,8 +290,8 @@ pub struct FolderArgs {
|
|||||||
pub enum FolderCommands {
|
pub enum FolderCommands {
|
||||||
/// List folders in a workspace
|
/// List folders in a workspace
|
||||||
List {
|
List {
|
||||||
/// Workspace ID
|
/// Workspace ID (optional when exactly one workspace exists)
|
||||||
workspace_id: String,
|
workspace_id: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Show a folder as JSON
|
/// Show a folder as JSON
|
||||||
@@ -316,8 +347,8 @@ pub struct EnvironmentArgs {
|
|||||||
pub enum EnvironmentCommands {
|
pub enum EnvironmentCommands {
|
||||||
/// List environments in a workspace
|
/// List environments in a workspace
|
||||||
List {
|
List {
|
||||||
/// Workspace ID
|
/// Workspace ID (optional when exactly one workspace exists)
|
||||||
workspace_id: String,
|
workspace_id: Option<String>,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Output JSON schema for environment create/update payloads
|
/// Output JSON schema for environment create/update payloads
|
||||||
@@ -413,6 +444,9 @@ pub enum PluginCommands {
|
|||||||
/// Generate a "Hello World" Yaak plugin
|
/// Generate a "Hello World" Yaak plugin
|
||||||
Generate(GenerateArgs),
|
Generate(GenerateArgs),
|
||||||
|
|
||||||
|
/// Install a plugin from a local directory or from the registry
|
||||||
|
Install(InstallPluginArgs),
|
||||||
|
|
||||||
/// Publish a Yaak plugin version to the plugin registry
|
/// Publish a Yaak plugin version to the plugin registry
|
||||||
Publish(PluginPathArg),
|
Publish(PluginPathArg),
|
||||||
}
|
}
|
||||||
@@ -433,3 +467,9 @@ pub struct GenerateArgs {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub dir: Option<PathBuf>,
|
pub dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Args, Clone)]
|
||||||
|
pub struct InstallPluginArgs {
|
||||||
|
/// Local plugin directory path, or registry plugin spec (@org/plugin[@version])
|
||||||
|
pub source: String,
|
||||||
|
}
|
||||||
|
|||||||
42
crates-cli/yaak-cli/src/commands/cookie_jar.rs
Normal file
42
crates-cli/yaak-cli/src/commands/cookie_jar.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use crate::cli::{CookieJarArgs, CookieJarCommands};
|
||||||
|
use crate::context::CliContext;
|
||||||
|
use crate::utils::workspace::resolve_workspace_id;
|
||||||
|
|
||||||
|
type CommandResult<T = ()> = std::result::Result<T, String>;
|
||||||
|
|
||||||
|
pub fn run(ctx: &CliContext, args: CookieJarArgs) -> i32 {
|
||||||
|
let result = match args.command {
|
||||||
|
CookieJarCommands::List { workspace_id } => list(ctx, workspace_id.as_deref()),
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(()) => 0,
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(ctx: &CliContext, workspace_id: Option<&str>) -> CommandResult {
|
||||||
|
let workspace_id = resolve_workspace_id(ctx, workspace_id, "cookie-jar list")?;
|
||||||
|
let cookie_jars = ctx
|
||||||
|
.db()
|
||||||
|
.list_cookie_jars(&workspace_id)
|
||||||
|
.map_err(|e| format!("Failed to list cookie jars: {e}"))?;
|
||||||
|
|
||||||
|
if cookie_jars.is_empty() {
|
||||||
|
println!("No cookie jars found in workspace {}", workspace_id);
|
||||||
|
} else {
|
||||||
|
for cookie_jar in cookie_jars {
|
||||||
|
println!(
|
||||||
|
"{} - {} ({} cookies)",
|
||||||
|
cookie_jar.id,
|
||||||
|
cookie_jar.name,
|
||||||
|
cookie_jar.cookies.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use crate::utils::json::{
|
|||||||
parse_required_json, require_id, validate_create_id,
|
parse_required_json, require_id, validate_create_id,
|
||||||
};
|
};
|
||||||
use crate::utils::schema::append_agent_hints;
|
use crate::utils::schema::append_agent_hints;
|
||||||
|
use crate::utils::workspace::resolve_workspace_id;
|
||||||
use schemars::schema_for;
|
use schemars::schema_for;
|
||||||
use yaak_models::models::Environment;
|
use yaak_models::models::Environment;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
@@ -14,7 +15,7 @@ type CommandResult<T = ()> = std::result::Result<T, String>;
|
|||||||
|
|
||||||
pub fn run(ctx: &CliContext, args: EnvironmentArgs) -> i32 {
|
pub fn run(ctx: &CliContext, args: EnvironmentArgs) -> i32 {
|
||||||
let result = match args.command {
|
let result = match args.command {
|
||||||
EnvironmentCommands::List { workspace_id } => list(ctx, &workspace_id),
|
EnvironmentCommands::List { workspace_id } => list(ctx, workspace_id.as_deref()),
|
||||||
EnvironmentCommands::Schema { pretty } => schema(pretty),
|
EnvironmentCommands::Schema { pretty } => schema(pretty),
|
||||||
EnvironmentCommands::Show { environment_id } => show(ctx, &environment_id),
|
EnvironmentCommands::Show { environment_id } => show(ctx, &environment_id),
|
||||||
EnvironmentCommands::Create { workspace_id, name, json } => {
|
EnvironmentCommands::Create { workspace_id, name, json } => {
|
||||||
@@ -45,10 +46,11 @@ fn schema(pretty: bool) -> CommandResult {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
|
fn list(ctx: &CliContext, workspace_id: Option<&str>) -> CommandResult {
|
||||||
|
let workspace_id = resolve_workspace_id(ctx, workspace_id, "environment list")?;
|
||||||
let environments = ctx
|
let environments = ctx
|
||||||
.db()
|
.db()
|
||||||
.list_environments_ensure_base(workspace_id)
|
.list_environments_ensure_base(&workspace_id)
|
||||||
.map_err(|e| format!("Failed to list environments: {e}"))?;
|
.map_err(|e| format!("Failed to list environments: {e}"))?;
|
||||||
|
|
||||||
if environments.is_empty() {
|
if environments.is_empty() {
|
||||||
@@ -92,8 +94,14 @@ fn create(
|
|||||||
validate_create_id(&payload, "environment")?;
|
validate_create_id(&payload, "environment")?;
|
||||||
let mut environment: Environment = serde_json::from_value(payload)
|
let mut environment: Environment = serde_json::from_value(payload)
|
||||||
.map_err(|e| format!("Failed to parse environment create JSON: {e}"))?;
|
.map_err(|e| format!("Failed to parse environment create JSON: {e}"))?;
|
||||||
|
let fallback_workspace_id =
|
||||||
|
if workspace_id_arg.is_none() && environment.workspace_id.is_empty() {
|
||||||
|
Some(resolve_workspace_id(ctx, None, "environment create")?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
merge_workspace_id_arg(
|
merge_workspace_id_arg(
|
||||||
workspace_id_arg.as_deref(),
|
workspace_id_arg.as_deref().or(fallback_workspace_id.as_deref()),
|
||||||
&mut environment.workspace_id,
|
&mut environment.workspace_id,
|
||||||
"environment create",
|
"environment create",
|
||||||
)?;
|
)?;
|
||||||
@@ -111,9 +119,8 @@ fn create(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace_id = workspace_id_arg.ok_or_else(|| {
|
let workspace_id =
|
||||||
"environment create requires workspace_id unless JSON payload is provided".to_string()
|
resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "environment create")?;
|
||||||
})?;
|
|
||||||
let name = name.ok_or_else(|| {
|
let name = name.ok_or_else(|| {
|
||||||
"environment create requires --name unless JSON payload is provided".to_string()
|
"environment create requires --name unless JSON payload is provided".to_string()
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use crate::utils::json::{
|
|||||||
apply_merge_patch, is_json_shorthand, merge_workspace_id_arg, parse_optional_json,
|
apply_merge_patch, is_json_shorthand, merge_workspace_id_arg, parse_optional_json,
|
||||||
parse_required_json, require_id, validate_create_id,
|
parse_required_json, require_id, validate_create_id,
|
||||||
};
|
};
|
||||||
|
use crate::utils::workspace::resolve_workspace_id;
|
||||||
use yaak_models::models::Folder;
|
use yaak_models::models::Folder;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
|
|
||||||
@@ -12,7 +13,7 @@ type CommandResult<T = ()> = std::result::Result<T, String>;
|
|||||||
|
|
||||||
pub fn run(ctx: &CliContext, args: FolderArgs) -> i32 {
|
pub fn run(ctx: &CliContext, args: FolderArgs) -> i32 {
|
||||||
let result = match args.command {
|
let result = match args.command {
|
||||||
FolderCommands::List { workspace_id } => list(ctx, &workspace_id),
|
FolderCommands::List { workspace_id } => list(ctx, workspace_id.as_deref()),
|
||||||
FolderCommands::Show { folder_id } => show(ctx, &folder_id),
|
FolderCommands::Show { folder_id } => show(ctx, &folder_id),
|
||||||
FolderCommands::Create { workspace_id, name, json } => {
|
FolderCommands::Create { workspace_id, name, json } => {
|
||||||
create(ctx, workspace_id, name, json)
|
create(ctx, workspace_id, name, json)
|
||||||
@@ -30,9 +31,10 @@ pub fn run(ctx: &CliContext, args: FolderArgs) -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
|
fn list(ctx: &CliContext, workspace_id: Option<&str>) -> CommandResult {
|
||||||
|
let workspace_id = resolve_workspace_id(ctx, workspace_id, "folder list")?;
|
||||||
let folders =
|
let folders =
|
||||||
ctx.db().list_folders(workspace_id).map_err(|e| format!("Failed to list folders: {e}"))?;
|
ctx.db().list_folders(&workspace_id).map_err(|e| format!("Failed to list folders: {e}"))?;
|
||||||
if folders.is_empty() {
|
if folders.is_empty() {
|
||||||
println!("No folders found in workspace {}", workspace_id);
|
println!("No folders found in workspace {}", workspace_id);
|
||||||
} else {
|
} else {
|
||||||
@@ -72,8 +74,14 @@ fn create(
|
|||||||
validate_create_id(&payload, "folder")?;
|
validate_create_id(&payload, "folder")?;
|
||||||
let mut folder: Folder = serde_json::from_value(payload)
|
let mut folder: Folder = serde_json::from_value(payload)
|
||||||
.map_err(|e| format!("Failed to parse folder create JSON: {e}"))?;
|
.map_err(|e| format!("Failed to parse folder create JSON: {e}"))?;
|
||||||
|
let fallback_workspace_id = if workspace_id_arg.is_none() && folder.workspace_id.is_empty()
|
||||||
|
{
|
||||||
|
Some(resolve_workspace_id(ctx, None, "folder create")?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
merge_workspace_id_arg(
|
merge_workspace_id_arg(
|
||||||
workspace_id_arg.as_deref(),
|
workspace_id_arg.as_deref().or(fallback_workspace_id.as_deref()),
|
||||||
&mut folder.workspace_id,
|
&mut folder.workspace_id,
|
||||||
"folder create",
|
"folder create",
|
||||||
)?;
|
)?;
|
||||||
@@ -87,9 +95,7 @@ fn create(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace_id = workspace_id_arg.ok_or_else(|| {
|
let workspace_id = resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "folder create")?;
|
||||||
"folder create requires workspace_id unless JSON payload is provided".to_string()
|
|
||||||
})?;
|
|
||||||
let name = name.ok_or_else(|| {
|
let name = name.ok_or_else(|| {
|
||||||
"folder create requires --name unless JSON payload is provided".to_string()
|
"folder create requires --name unless JSON payload is provided".to_string()
|
||||||
})?;
|
})?;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
|
pub mod cookie_jar;
|
||||||
pub mod environment;
|
pub mod environment;
|
||||||
pub mod folder;
|
pub mod folder;
|
||||||
pub mod plugin;
|
pub mod plugin;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use crate::cli::{GenerateArgs, PluginArgs, PluginCommands, PluginPathArg};
|
use crate::cli::{GenerateArgs, InstallPluginArgs, PluginPathArg};
|
||||||
|
use crate::context::CliContext;
|
||||||
use crate::ui;
|
use crate::ui;
|
||||||
use crate::utils::http;
|
use crate::utils::http;
|
||||||
use keyring::Entry;
|
use keyring::Entry;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rolldown::{
|
use rolldown::{
|
||||||
Bundler, BundlerOptions, ExperimentalOptions, InputItem, LogLevel, OutputFormat, Platform,
|
BundleEvent, Bundler, BundlerOptions, ExperimentalOptions, InputItem, LogLevel, OutputFormat,
|
||||||
WatchOption, Watcher,
|
Platform, WatchOption, Watcher, WatcherEvent,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -15,6 +16,11 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
use yaak_api::{ApiClientKind, yaak_api_client};
|
||||||
|
use yaak_models::models::{Plugin, PluginSource};
|
||||||
|
use yaak_models::util::UpdateSource;
|
||||||
|
use yaak_plugins::events::PluginContext;
|
||||||
|
use yaak_plugins::install::download_and_install;
|
||||||
use zip::CompressionMethod;
|
use zip::CompressionMethod;
|
||||||
use zip::write::SimpleFileOptions;
|
use zip::write::SimpleFileOptions;
|
||||||
|
|
||||||
@@ -57,12 +63,13 @@ pub async fn run_build(args: PluginPathArg) -> i32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(args: PluginArgs) -> i32 {
|
pub async fn run_install(context: &CliContext, args: InstallPluginArgs) -> i32 {
|
||||||
match args.command {
|
match install(context, args).await {
|
||||||
PluginCommands::Build(args) => run_build(args).await,
|
Ok(()) => 0,
|
||||||
PluginCommands::Dev(args) => run_dev(args).await,
|
Err(error) => {
|
||||||
PluginCommands::Generate(args) => run_generate(args).await,
|
ui::error(&error);
|
||||||
PluginCommands::Publish(args) => run_publish(args).await,
|
1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,12 +121,53 @@ async fn dev(args: PluginPathArg) -> CommandResult {
|
|||||||
ensure_plugin_build_inputs(&plugin_dir)?;
|
ensure_plugin_build_inputs(&plugin_dir)?;
|
||||||
|
|
||||||
ui::info(&format!("Watching plugin {}...", plugin_dir.display()));
|
ui::info(&format!("Watching plugin {}...", plugin_dir.display()));
|
||||||
ui::info("Press Ctrl-C to stop");
|
|
||||||
|
|
||||||
let bundler = Bundler::new(bundler_options(&plugin_dir, true))
|
let bundler = Bundler::new(bundler_options(&plugin_dir, true))
|
||||||
.map_err(|err| format!("Failed to initialize Rolldown watcher: {err}"))?;
|
.map_err(|err| format!("Failed to initialize Rolldown watcher: {err}"))?;
|
||||||
let watcher = Watcher::new(vec![Arc::new(Mutex::new(bundler))], None)
|
let watcher = Watcher::new(vec![Arc::new(Mutex::new(bundler))], None)
|
||||||
.map_err(|err| format!("Failed to start Rolldown watcher: {err}"))?;
|
.map_err(|err| format!("Failed to start Rolldown watcher: {err}"))?;
|
||||||
|
let emitter = watcher.emitter();
|
||||||
|
let watch_root = plugin_dir.clone();
|
||||||
|
let _event_logger = tokio::spawn(async move {
|
||||||
|
loop {
|
||||||
|
let event = {
|
||||||
|
let rx = emitter.rx.lock().await;
|
||||||
|
rx.recv()
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(event) = event else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
match event {
|
||||||
|
WatcherEvent::Change(change) => {
|
||||||
|
let changed_path = Path::new(change.path.as_str());
|
||||||
|
let display_path = changed_path
|
||||||
|
.strip_prefix(&watch_root)
|
||||||
|
.map(|p| p.display().to_string())
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
changed_path
|
||||||
|
.file_name()
|
||||||
|
.map(|name| name.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|| "unknown".to_string())
|
||||||
|
});
|
||||||
|
ui::info(&format!("Rebuilding plugin {display_path}"));
|
||||||
|
}
|
||||||
|
WatcherEvent::Event(BundleEvent::BundleEnd(_)) => {}
|
||||||
|
WatcherEvent::Event(BundleEvent::Error(event)) => {
|
||||||
|
if event.error.diagnostics.is_empty() {
|
||||||
|
ui::error("Plugin build failed");
|
||||||
|
} else {
|
||||||
|
for diagnostic in event.error.diagnostics {
|
||||||
|
ui::error(&diagnostic.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WatcherEvent::Close => break,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
watcher.start().await;
|
watcher.start().await;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -209,6 +257,113 @@ async fn publish(args: PluginPathArg) -> CommandResult {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn install(context: &CliContext, args: InstallPluginArgs) -> CommandResult {
|
||||||
|
if args.source.starts_with('@') {
|
||||||
|
let (name, version) =
|
||||||
|
parse_registry_install_spec(args.source.as_str()).ok_or_else(|| {
|
||||||
|
"Invalid registry plugin spec. Expected format: @org/plugin or @org/plugin@version"
|
||||||
|
.to_string()
|
||||||
|
})?;
|
||||||
|
return install_from_registry(context, name, version).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
install_from_directory(context, args.source.as_str()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install_from_registry(
|
||||||
|
context: &CliContext,
|
||||||
|
name: String,
|
||||||
|
version: Option<String>,
|
||||||
|
) -> CommandResult {
|
||||||
|
let current_version = crate::version::cli_version();
|
||||||
|
let http_client = yaak_api_client(ApiClientKind::Cli, current_version)
|
||||||
|
.map_err(|err| format!("Failed to initialize API client: {err}"))?;
|
||||||
|
let installing_version = version.clone().unwrap_or_else(|| "latest".to_string());
|
||||||
|
ui::info(&format!("Installing registry plugin {name}@{installing_version}"));
|
||||||
|
|
||||||
|
let plugin_context = PluginContext::new(Some("cli".to_string()), None);
|
||||||
|
let installed = download_and_install(
|
||||||
|
context.plugin_manager(),
|
||||||
|
context.query_manager(),
|
||||||
|
&http_client,
|
||||||
|
&plugin_context,
|
||||||
|
name.as_str(),
|
||||||
|
version,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to install plugin: {err}"))?;
|
||||||
|
|
||||||
|
ui::success(&format!("Installed plugin {}@{}", installed.name, installed.version));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn install_from_directory(context: &CliContext, source: &str) -> CommandResult {
|
||||||
|
let plugin_dir = resolve_plugin_dir(Some(PathBuf::from(source)))?;
|
||||||
|
let plugin_dir_str = plugin_dir
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!("Plugin directory path is not valid UTF-8: {}", plugin_dir.display())
|
||||||
|
})?
|
||||||
|
.to_string();
|
||||||
|
ui::info(&format!("Installing plugin from directory {}", plugin_dir.display()));
|
||||||
|
|
||||||
|
let plugin = context
|
||||||
|
.db()
|
||||||
|
.upsert_plugin(
|
||||||
|
&Plugin {
|
||||||
|
directory: plugin_dir_str,
|
||||||
|
url: None,
|
||||||
|
enabled: true,
|
||||||
|
source: PluginSource::Filesystem,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
&UpdateSource::Background,
|
||||||
|
)
|
||||||
|
.map_err(|err| format!("Failed to save plugin in database: {err}"))?;
|
||||||
|
|
||||||
|
let plugin_context = PluginContext::new(Some("cli".to_string()), None);
|
||||||
|
context
|
||||||
|
.plugin_manager()
|
||||||
|
.add_plugin(&plugin_context, &plugin)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to load plugin runtime: {err}"))?;
|
||||||
|
|
||||||
|
ui::success(&format!("Installed plugin from {}", plugin.directory));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_registry_install_spec(source: &str) -> Option<(String, Option<String>)> {
|
||||||
|
if !source.starts_with('@') || !source.contains('/') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let rest = source.get(1..)?;
|
||||||
|
let version_split = rest.rfind('@').map(|idx| idx + 1);
|
||||||
|
let (name, version) = match version_split {
|
||||||
|
Some(at_idx) => {
|
||||||
|
let (name, version) = source.split_at(at_idx);
|
||||||
|
let version = version.strip_prefix('@').unwrap_or_default();
|
||||||
|
if version.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
(name.to_string(), Some(version.to_string()))
|
||||||
|
}
|
||||||
|
None => (source.to_string(), None),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !name.starts_with('@') {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let without_scope = name.get(1..)?;
|
||||||
|
let (scope, plugin_name) = without_scope.split_once('/')?;
|
||||||
|
if scope.is_empty() || plugin_name.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some((name, version))
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct PublishResponse {
|
struct PublishResponse {
|
||||||
version: String,
|
version: String,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::utils::json::{
|
|||||||
parse_required_json, require_id, validate_create_id,
|
parse_required_json, require_id, validate_create_id,
|
||||||
};
|
};
|
||||||
use crate::utils::schema::append_agent_hints;
|
use crate::utils::schema::append_agent_hints;
|
||||||
|
use crate::utils::workspace::resolve_workspace_id;
|
||||||
use schemars::schema_for;
|
use schemars::schema_for;
|
||||||
use serde_json::{Map, Value, json};
|
use serde_json::{Map, Value, json};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -24,13 +25,16 @@ pub async fn run(
|
|||||||
ctx: &CliContext,
|
ctx: &CliContext,
|
||||||
args: RequestArgs,
|
args: RequestArgs,
|
||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
|
cookie_jar_id: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
let result = match args.command {
|
let result = match args.command {
|
||||||
RequestCommands::List { workspace_id } => list(ctx, &workspace_id),
|
RequestCommands::List { workspace_id } => list(ctx, workspace_id.as_deref()),
|
||||||
RequestCommands::Show { request_id } => show(ctx, &request_id),
|
RequestCommands::Show { request_id } => show(ctx, &request_id),
|
||||||
RequestCommands::Send { request_id } => {
|
RequestCommands::Send { request_id } => {
|
||||||
return match send_request_by_id(ctx, &request_id, environment, verbose).await {
|
return match send_request_by_id(ctx, &request_id, environment, cookie_jar_id, verbose)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(()) => 0,
|
Ok(()) => 0,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("Error: {error}");
|
eprintln!("Error: {error}");
|
||||||
@@ -63,10 +67,11 @@ pub async fn run(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list(ctx: &CliContext, workspace_id: &str) -> CommandResult {
|
fn list(ctx: &CliContext, workspace_id: Option<&str>) -> CommandResult {
|
||||||
|
let workspace_id = resolve_workspace_id(ctx, workspace_id, "request list")?;
|
||||||
let requests = ctx
|
let requests = ctx
|
||||||
.db()
|
.db()
|
||||||
.list_http_requests(workspace_id)
|
.list_http_requests(&workspace_id)
|
||||||
.map_err(|e| format!("Failed to list requests: {e}"))?;
|
.map_err(|e| format!("Failed to list requests: {e}"))?;
|
||||||
if requests.is_empty() {
|
if requests.is_empty() {
|
||||||
println!("No requests found in workspace {}", workspace_id);
|
println!("No requests found in workspace {}", workspace_id);
|
||||||
@@ -350,8 +355,14 @@ fn create(
|
|||||||
validate_create_id(&payload, "request")?;
|
validate_create_id(&payload, "request")?;
|
||||||
let mut request: HttpRequest = serde_json::from_value(payload)
|
let mut request: HttpRequest = serde_json::from_value(payload)
|
||||||
.map_err(|e| format!("Failed to parse request create JSON: {e}"))?;
|
.map_err(|e| format!("Failed to parse request create JSON: {e}"))?;
|
||||||
|
let fallback_workspace_id = if workspace_id_arg.is_none() && request.workspace_id.is_empty()
|
||||||
|
{
|
||||||
|
Some(resolve_workspace_id(ctx, None, "request create")?)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
merge_workspace_id_arg(
|
merge_workspace_id_arg(
|
||||||
workspace_id_arg.as_deref(),
|
workspace_id_arg.as_deref().or(fallback_workspace_id.as_deref()),
|
||||||
&mut request.workspace_id,
|
&mut request.workspace_id,
|
||||||
"request create",
|
"request create",
|
||||||
)?;
|
)?;
|
||||||
@@ -365,9 +376,7 @@ fn create(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let workspace_id = workspace_id_arg.ok_or_else(|| {
|
let workspace_id = resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "request create")?;
|
||||||
"request create requires workspace_id unless JSON payload is provided".to_string()
|
|
||||||
})?;
|
|
||||||
let name = name.unwrap_or_default();
|
let name = name.unwrap_or_default();
|
||||||
let url = url.unwrap_or_default();
|
let url = url.unwrap_or_default();
|
||||||
let method = method.unwrap_or_else(|| "GET".to_string());
|
let method = method.unwrap_or_else(|| "GET".to_string());
|
||||||
@@ -436,6 +445,7 @@ pub async fn send_request_by_id(
|
|||||||
ctx: &CliContext,
|
ctx: &CliContext,
|
||||||
request_id: &str,
|
request_id: &str,
|
||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
|
cookie_jar_id: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let request =
|
let request =
|
||||||
@@ -447,6 +457,7 @@ pub async fn send_request_by_id(
|
|||||||
&http_request.id,
|
&http_request.id,
|
||||||
&http_request.workspace_id,
|
&http_request.workspace_id,
|
||||||
environment,
|
environment,
|
||||||
|
cookie_jar_id,
|
||||||
verbose,
|
verbose,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@@ -465,9 +476,13 @@ async fn send_http_request_by_id(
|
|||||||
request_id: &str,
|
request_id: &str,
|
||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
|
cookie_jar_id: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let plugin_context = PluginContext::new(None, Some(workspace_id.to_string()));
|
let cookie_jar_id = resolve_cookie_jar_id(ctx, workspace_id, cookie_jar_id)?;
|
||||||
|
|
||||||
|
let plugin_context =
|
||||||
|
PluginContext::new(Some("cli".to_string()), Some(workspace_id.to_string()));
|
||||||
|
|
||||||
let (event_tx, mut event_rx) = mpsc::channel::<SenderHttpResponseEvent>(100);
|
let (event_tx, mut event_rx) = mpsc::channel::<SenderHttpResponseEvent>(100);
|
||||||
let (body_chunk_tx, mut body_chunk_rx) = mpsc::unbounded_channel::<Vec<u8>>();
|
let (body_chunk_tx, mut body_chunk_rx) = mpsc::unbounded_channel::<Vec<u8>>();
|
||||||
@@ -495,7 +510,7 @@ async fn send_http_request_by_id(
|
|||||||
request_id,
|
request_id,
|
||||||
environment_id: environment,
|
environment_id: environment,
|
||||||
update_source: UpdateSource::Sync,
|
update_source: UpdateSource::Sync,
|
||||||
cookie_jar_id: None,
|
cookie_jar_id,
|
||||||
response_dir: &response_dir,
|
response_dir: &response_dir,
|
||||||
emit_events_to: Some(event_tx),
|
emit_events_to: Some(event_tx),
|
||||||
emit_response_body_chunks_to: Some(body_chunk_tx),
|
emit_response_body_chunks_to: Some(body_chunk_tx),
|
||||||
@@ -512,3 +527,22 @@ async fn send_http_request_by_id(
|
|||||||
result.map_err(|e| e.to_string())?;
|
result.map_err(|e| e.to_string())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn resolve_cookie_jar_id(
|
||||||
|
ctx: &CliContext,
|
||||||
|
workspace_id: &str,
|
||||||
|
explicit_cookie_jar_id: Option<&str>,
|
||||||
|
) -> Result<Option<String>, String> {
|
||||||
|
if let Some(cookie_jar_id) = explicit_cookie_jar_id {
|
||||||
|
return Ok(Some(cookie_jar_id.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_cookie_jar = ctx
|
||||||
|
.db()
|
||||||
|
.list_cookie_jars(workspace_id)
|
||||||
|
.map_err(|e| format!("Failed to list cookie jars: {e}"))?
|
||||||
|
.into_iter()
|
||||||
|
.min_by_key(|jar| jar.created_at)
|
||||||
|
.map(|jar| jar.id);
|
||||||
|
Ok(default_cookie_jar)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use crate::cli::SendArgs;
|
|||||||
use crate::commands::request;
|
use crate::commands::request;
|
||||||
use crate::context::CliContext;
|
use crate::context::CliContext;
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
|
use yaak_models::queries::any_request::AnyRequest;
|
||||||
|
|
||||||
enum ExecutionMode {
|
enum ExecutionMode {
|
||||||
Sequential,
|
Sequential,
|
||||||
@@ -12,9 +13,10 @@ pub async fn run(
|
|||||||
ctx: &CliContext,
|
ctx: &CliContext,
|
||||||
args: SendArgs,
|
args: SendArgs,
|
||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
|
cookie_jar_id: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> i32 {
|
) -> i32 {
|
||||||
match send_target(ctx, args, environment, verbose).await {
|
match send_target(ctx, args, environment, cookie_jar_id, verbose).await {
|
||||||
Ok(()) => 0,
|
Ok(()) => 0,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
eprintln!("Error: {error}");
|
eprintln!("Error: {error}");
|
||||||
@@ -27,30 +29,70 @@ async fn send_target(
|
|||||||
ctx: &CliContext,
|
ctx: &CliContext,
|
||||||
args: SendArgs,
|
args: SendArgs,
|
||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
|
cookie_jar_id: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mode = if args.parallel { ExecutionMode::Parallel } else { ExecutionMode::Sequential };
|
let mode = if args.parallel { ExecutionMode::Parallel } else { ExecutionMode::Sequential };
|
||||||
|
|
||||||
if ctx.db().get_any_request(&args.id).is_ok() {
|
if let Ok(request) = ctx.db().get_any_request(&args.id) {
|
||||||
return request::send_request_by_id(ctx, &args.id, environment, verbose).await;
|
let workspace_id = match &request {
|
||||||
|
AnyRequest::HttpRequest(r) => r.workspace_id.clone(),
|
||||||
|
AnyRequest::GrpcRequest(r) => r.workspace_id.clone(),
|
||||||
|
AnyRequest::WebsocketRequest(r) => r.workspace_id.clone(),
|
||||||
|
};
|
||||||
|
let resolved_cookie_jar_id =
|
||||||
|
request::resolve_cookie_jar_id(ctx, &workspace_id, cookie_jar_id)?;
|
||||||
|
|
||||||
|
return request::send_request_by_id(
|
||||||
|
ctx,
|
||||||
|
&args.id,
|
||||||
|
environment,
|
||||||
|
resolved_cookie_jar_id.as_deref(),
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.db().get_folder(&args.id).is_ok() {
|
if let Ok(folder) = ctx.db().get_folder(&args.id) {
|
||||||
|
let resolved_cookie_jar_id =
|
||||||
|
request::resolve_cookie_jar_id(ctx, &folder.workspace_id, cookie_jar_id)?;
|
||||||
|
|
||||||
let request_ids = collect_folder_request_ids(ctx, &args.id)?;
|
let request_ids = collect_folder_request_ids(ctx, &args.id)?;
|
||||||
if request_ids.is_empty() {
|
if request_ids.is_empty() {
|
||||||
println!("No requests found in folder {}", args.id);
|
println!("No requests found in folder {}", args.id);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
return send_many(ctx, request_ids, mode, args.fail_fast, environment, verbose).await;
|
return send_many(
|
||||||
|
ctx,
|
||||||
|
request_ids,
|
||||||
|
mode,
|
||||||
|
args.fail_fast,
|
||||||
|
environment,
|
||||||
|
resolved_cookie_jar_id.as_deref(),
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.db().get_workspace(&args.id).is_ok() {
|
if let Ok(workspace) = ctx.db().get_workspace(&args.id) {
|
||||||
|
let resolved_cookie_jar_id =
|
||||||
|
request::resolve_cookie_jar_id(ctx, &workspace.id, cookie_jar_id)?;
|
||||||
|
|
||||||
let request_ids = collect_workspace_request_ids(ctx, &args.id)?;
|
let request_ids = collect_workspace_request_ids(ctx, &args.id)?;
|
||||||
if request_ids.is_empty() {
|
if request_ids.is_empty() {
|
||||||
println!("No requests found in workspace {}", args.id);
|
println!("No requests found in workspace {}", args.id);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
return send_many(ctx, request_ids, mode, args.fail_fast, environment, verbose).await;
|
return send_many(
|
||||||
|
ctx,
|
||||||
|
request_ids,
|
||||||
|
mode,
|
||||||
|
args.fail_fast,
|
||||||
|
environment,
|
||||||
|
resolved_cookie_jar_id.as_deref(),
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(format!("Could not resolve ID '{}' as request, folder, or workspace", args.id))
|
Err(format!("Could not resolve ID '{}' as request, folder, or workspace", args.id))
|
||||||
@@ -131,6 +173,7 @@ async fn send_many(
|
|||||||
mode: ExecutionMode,
|
mode: ExecutionMode,
|
||||||
fail_fast: bool,
|
fail_fast: bool,
|
||||||
environment: Option<&str>,
|
environment: Option<&str>,
|
||||||
|
cookie_jar_id: Option<&str>,
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let mut success_count = 0usize;
|
let mut success_count = 0usize;
|
||||||
@@ -139,7 +182,15 @@ async fn send_many(
|
|||||||
match mode {
|
match mode {
|
||||||
ExecutionMode::Sequential => {
|
ExecutionMode::Sequential => {
|
||||||
for request_id in request_ids {
|
for request_id in request_ids {
|
||||||
match request::send_request_by_id(ctx, &request_id, environment, verbose).await {
|
match request::send_request_by_id(
|
||||||
|
ctx,
|
||||||
|
&request_id,
|
||||||
|
environment,
|
||||||
|
cookie_jar_id,
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(()) => success_count += 1,
|
Ok(()) => success_count += 1,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
failures.push((request_id, error));
|
failures.push((request_id, error));
|
||||||
@@ -156,7 +207,14 @@ async fn send_many(
|
|||||||
.map(|request_id| async move {
|
.map(|request_id| async move {
|
||||||
(
|
(
|
||||||
request_id.clone(),
|
request_id.clone(),
|
||||||
request::send_request_by_id(ctx, request_id, environment, verbose).await,
|
request::send_request_by_id(
|
||||||
|
ctx,
|
||||||
|
request_id,
|
||||||
|
environment,
|
||||||
|
cookie_jar_id,
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|||||||
@@ -31,18 +31,13 @@ pub fn run(ctx: &CliContext, args: WorkspaceArgs) -> i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn schema(pretty: bool) -> CommandResult {
|
fn schema(pretty: bool) -> CommandResult {
|
||||||
let mut schema =
|
let mut schema = serde_json::to_value(schema_for!(Workspace))
|
||||||
serde_json::to_value(schema_for!(Workspace)).map_err(|e| format!(
|
.map_err(|e| format!("Failed to serialize workspace schema: {e}"))?;
|
||||||
"Failed to serialize workspace schema: {e}"
|
|
||||||
))?;
|
|
||||||
append_agent_hints(&mut schema);
|
append_agent_hints(&mut schema);
|
||||||
|
|
||||||
let output = if pretty {
|
let output =
|
||||||
serde_json::to_string_pretty(&schema)
|
if pretty { serde_json::to_string_pretty(&schema) } else { serde_json::to_string(&schema) }
|
||||||
} else {
|
.map_err(|e| format!("Failed to format workspace schema JSON: {e}"))?;
|
||||||
serde_json::to_string(&schema)
|
|
||||||
}
|
|
||||||
.map_err(|e| format!("Failed to format workspace schema JSON: {e}"))?;
|
|
||||||
println!("{output}");
|
println!("{output}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ const EMBEDDED_PLUGIN_RUNTIME: &str = include_str!(concat!(
|
|||||||
static EMBEDDED_VENDORED_PLUGINS: Dir<'_> =
|
static EMBEDDED_VENDORED_PLUGINS: Dir<'_> =
|
||||||
include_dir!("$CARGO_MANIFEST_DIR/../../crates-tauri/yaak-app/vendored/plugins");
|
include_dir!("$CARGO_MANIFEST_DIR/../../crates-tauri/yaak-app/vendored/plugins");
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct CliExecutionContext {
|
||||||
|
pub request_id: Option<String>,
|
||||||
|
pub workspace_id: Option<String>,
|
||||||
|
pub environment_id: Option<String>,
|
||||||
|
pub cookie_jar_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct CliContext {
|
pub struct CliContext {
|
||||||
data_dir: PathBuf,
|
data_dir: PathBuf,
|
||||||
query_manager: QueryManager,
|
query_manager: QueryManager,
|
||||||
@@ -28,15 +36,14 @@ pub struct CliContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl CliContext {
|
impl CliContext {
|
||||||
pub async fn initialize(data_dir: PathBuf, app_id: &str, with_plugins: bool) -> Self {
|
pub async fn new(
|
||||||
let db_path = data_dir.join("db.sqlite");
|
data_dir: PathBuf,
|
||||||
let blob_path = data_dir.join("blobs.sqlite");
|
query_manager: QueryManager,
|
||||||
|
blob_manager: BlobManager,
|
||||||
let (query_manager, blob_manager, _rx) = yaak_models::init_standalone(&db_path, &blob_path)
|
encryption_manager: Arc<EncryptionManager>,
|
||||||
.expect("Failed to initialize database");
|
with_plugins: bool,
|
||||||
|
execution_context: CliExecutionContext,
|
||||||
let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id));
|
) -> Self {
|
||||||
|
|
||||||
let plugin_manager = if with_plugins {
|
let plugin_manager = if with_plugins {
|
||||||
let vendored_plugin_dir = data_dir.join("vendored-plugins");
|
let vendored_plugin_dir = data_dir.join("vendored-plugins");
|
||||||
let installed_plugin_dir = data_dir.join("installed-plugins");
|
let installed_plugin_dir = data_dir.join("installed-plugins");
|
||||||
@@ -73,7 +80,17 @@ impl CliContext {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let plugin_event_bridge = if let Some(plugin_manager) = &plugin_manager {
|
let plugin_event_bridge = if let Some(plugin_manager) = &plugin_manager {
|
||||||
Some(CliPluginEventBridge::start(plugin_manager.clone(), query_manager.clone()).await)
|
Some(
|
||||||
|
CliPluginEventBridge::start(
|
||||||
|
plugin_manager.clone(),
|
||||||
|
query_manager.clone(),
|
||||||
|
blob_manager.clone(),
|
||||||
|
encryption_manager.clone(),
|
||||||
|
data_dir.clone(),
|
||||||
|
execution_context.clone(),
|
||||||
|
)
|
||||||
|
.await,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,14 +5,19 @@ mod plugin_events;
|
|||||||
mod ui;
|
mod ui;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod version;
|
mod version;
|
||||||
|
mod version_check;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use cli::{Cli, Commands, RequestCommands};
|
use cli::{Cli, Commands, PluginCommands, RequestCommands};
|
||||||
use context::CliContext;
|
use context::{CliContext, CliExecutionContext};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use yaak_crypto::manager::EncryptionManager;
|
||||||
|
use yaak_models::queries::any_request::AnyRequest;
|
||||||
|
use yaak_models::query_manager::QueryManager;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let Cli { data_dir, environment, verbose, log, command } = Cli::parse();
|
let Cli { data_dir, environment, cookie_jar, verbose, log, command } = Cli::parse();
|
||||||
|
|
||||||
if let Some(log_level) = log {
|
if let Some(log_level) = log {
|
||||||
match log_level {
|
match log_level {
|
||||||
@@ -32,70 +37,301 @@ async fn main() {
|
|||||||
dirs::data_dir().expect("Could not determine data directory").join(app_id)
|
dirs::data_dir().expect("Could not determine data directory").join(app_id)
|
||||||
});
|
});
|
||||||
|
|
||||||
let needs_context = matches!(
|
version_check::maybe_check_for_updates().await;
|
||||||
&command,
|
|
||||||
Commands::Send(_)
|
|
||||||
| Commands::Workspace(_)
|
|
||||||
| Commands::Request(_)
|
|
||||||
| Commands::Folder(_)
|
|
||||||
| Commands::Environment(_)
|
|
||||||
);
|
|
||||||
|
|
||||||
let needs_plugins = matches!(
|
let db_path = data_dir.join("db.sqlite");
|
||||||
&command,
|
let blob_path = data_dir.join("blobs.sqlite");
|
||||||
Commands::Send(_)
|
let (query_manager, blob_manager, _rx) =
|
||||||
| Commands::Request(cli::RequestArgs {
|
match yaak_models::init_standalone(&db_path, &blob_path) {
|
||||||
command: RequestCommands::Send { .. } | RequestCommands::Schema { .. },
|
Ok(v) => v,
|
||||||
})
|
Err(err) => {
|
||||||
);
|
eprintln!("Error: Failed to initialize database: {err}");
|
||||||
|
std::process::exit(1);
|
||||||
let context = if needs_context {
|
}
|
||||||
Some(CliContext::initialize(data_dir, app_id, needs_plugins).await)
|
};
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let exit_code = match command {
|
let exit_code = match command {
|
||||||
Commands::Auth(args) => commands::auth::run(args).await,
|
Commands::Auth(args) => commands::auth::run(args).await,
|
||||||
Commands::Plugin(args) => commands::plugin::run(args).await,
|
Commands::Plugin(args) => match args.command {
|
||||||
|
PluginCommands::Build(args) => commands::plugin::run_build(args).await,
|
||||||
|
PluginCommands::Dev(args) => commands::plugin::run_dev(args).await,
|
||||||
|
PluginCommands::Generate(args) => commands::plugin::run_generate(args).await,
|
||||||
|
PluginCommands::Publish(args) => commands::plugin::run_publish(args).await,
|
||||||
|
PluginCommands::Install(install_args) => {
|
||||||
|
let query_manager = query_manager.clone();
|
||||||
|
let blob_manager = blob_manager.clone();
|
||||||
|
let execution_context = CliExecutionContext::default();
|
||||||
|
let context = CliContext::new(
|
||||||
|
data_dir.clone(),
|
||||||
|
query_manager.clone(),
|
||||||
|
blob_manager,
|
||||||
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
true,
|
||||||
|
execution_context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let exit_code = commands::plugin::run_install(&context, install_args).await;
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
},
|
||||||
Commands::Build(args) => commands::plugin::run_build(args).await,
|
Commands::Build(args) => commands::plugin::run_build(args).await,
|
||||||
Commands::Dev(args) => commands::plugin::run_dev(args).await,
|
Commands::Dev(args) => commands::plugin::run_dev(args).await,
|
||||||
|
Commands::Generate(args) => commands::plugin::run_generate(args).await,
|
||||||
|
Commands::Publish(args) => commands::plugin::run_publish(args).await,
|
||||||
Commands::Send(args) => {
|
Commands::Send(args) => {
|
||||||
commands::send::run(
|
let query_manager = query_manager.clone();
|
||||||
context.as_ref().expect("context initialized for send"),
|
let blob_manager = blob_manager.clone();
|
||||||
args,
|
|
||||||
|
let execution_context_result = resolve_send_execution_context(
|
||||||
|
&query_manager,
|
||||||
|
&args.id,
|
||||||
environment.as_deref(),
|
environment.as_deref(),
|
||||||
verbose,
|
cookie_jar.as_deref(),
|
||||||
)
|
);
|
||||||
.await
|
match execution_context_result {
|
||||||
|
Ok(execution_context) => {
|
||||||
|
let context = CliContext::new(
|
||||||
|
data_dir.clone(),
|
||||||
|
query_manager.clone(),
|
||||||
|
blob_manager,
|
||||||
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
true,
|
||||||
|
execution_context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let exit_code = commands::send::run(
|
||||||
|
&context,
|
||||||
|
args,
|
||||||
|
environment.as_deref(),
|
||||||
|
cookie_jar.as_deref(),
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Commands::Workspace(args) => commands::workspace::run(
|
Commands::CookieJar(args) => {
|
||||||
context.as_ref().expect("context initialized for workspace"),
|
let query_manager = query_manager.clone();
|
||||||
args,
|
let blob_manager = blob_manager.clone();
|
||||||
),
|
let execution_context = CliExecutionContext::default();
|
||||||
Commands::Request(args) => {
|
|
||||||
commands::request::run(
|
let context = CliContext::new(
|
||||||
context.as_ref().expect("context initialized for request"),
|
data_dir.clone(),
|
||||||
args,
|
query_manager.clone(),
|
||||||
environment.as_deref(),
|
blob_manager,
|
||||||
verbose,
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
false,
|
||||||
|
execution_context,
|
||||||
)
|
)
|
||||||
.await
|
.await;
|
||||||
|
let exit_code = commands::cookie_jar::run(&context, args);
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
Commands::Workspace(args) => {
|
||||||
|
let query_manager = query_manager.clone();
|
||||||
|
let blob_manager = blob_manager.clone();
|
||||||
|
let execution_context = CliExecutionContext::default();
|
||||||
|
|
||||||
|
let context = CliContext::new(
|
||||||
|
data_dir.clone(),
|
||||||
|
query_manager.clone(),
|
||||||
|
blob_manager,
|
||||||
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
false,
|
||||||
|
execution_context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let exit_code = commands::workspace::run(&context, args);
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
Commands::Request(args) => {
|
||||||
|
let query_manager = query_manager.clone();
|
||||||
|
let blob_manager = blob_manager.clone();
|
||||||
|
|
||||||
|
let execution_context_result = match &args.command {
|
||||||
|
RequestCommands::Send { request_id } => resolve_request_execution_context(
|
||||||
|
&query_manager,
|
||||||
|
request_id,
|
||||||
|
environment.as_deref(),
|
||||||
|
cookie_jar.as_deref(),
|
||||||
|
),
|
||||||
|
_ => Ok(CliExecutionContext::default()),
|
||||||
|
};
|
||||||
|
match execution_context_result {
|
||||||
|
Ok(execution_context) => {
|
||||||
|
let with_plugins = matches!(
|
||||||
|
&args.command,
|
||||||
|
RequestCommands::Send { .. } | RequestCommands::Schema { .. }
|
||||||
|
);
|
||||||
|
let context = CliContext::new(
|
||||||
|
data_dir.clone(),
|
||||||
|
query_manager.clone(),
|
||||||
|
blob_manager,
|
||||||
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
with_plugins,
|
||||||
|
execution_context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let exit_code = commands::request::run(
|
||||||
|
&context,
|
||||||
|
args,
|
||||||
|
environment.as_deref(),
|
||||||
|
cookie_jar.as_deref(),
|
||||||
|
verbose,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
eprintln!("Error: {error}");
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Commands::Folder(args) => {
|
Commands::Folder(args) => {
|
||||||
commands::folder::run(context.as_ref().expect("context initialized for folder"), args)
|
let query_manager = query_manager.clone();
|
||||||
}
|
let blob_manager = blob_manager.clone();
|
||||||
Commands::Environment(args) => commands::environment::run(
|
let execution_context = CliExecutionContext::default();
|
||||||
context.as_ref().expect("context initialized for environment"),
|
|
||||||
args,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(context) = &context {
|
let context = CliContext::new(
|
||||||
context.shutdown().await;
|
data_dir.clone(),
|
||||||
}
|
query_manager.clone(),
|
||||||
|
blob_manager,
|
||||||
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
false,
|
||||||
|
execution_context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let exit_code = commands::folder::run(&context, args);
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
Commands::Environment(args) => {
|
||||||
|
let query_manager = query_manager.clone();
|
||||||
|
let blob_manager = blob_manager.clone();
|
||||||
|
let execution_context = CliExecutionContext::default();
|
||||||
|
|
||||||
|
let context = CliContext::new(
|
||||||
|
data_dir.clone(),
|
||||||
|
query_manager.clone(),
|
||||||
|
blob_manager,
|
||||||
|
Arc::new(EncryptionManager::new(query_manager, app_id)),
|
||||||
|
false,
|
||||||
|
execution_context,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let exit_code = commands::environment::run(&context, args);
|
||||||
|
context.shutdown().await;
|
||||||
|
exit_code
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if exit_code != 0 {
|
if exit_code != 0 {
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn resolve_send_execution_context(
|
||||||
|
query_manager: &QueryManager,
|
||||||
|
id: &str,
|
||||||
|
environment: Option<&str>,
|
||||||
|
explicit_cookie_jar_id: Option<&str>,
|
||||||
|
) -> Result<CliExecutionContext, String> {
|
||||||
|
if let Ok(request) = query_manager.connect().get_any_request(id) {
|
||||||
|
let (request_id, workspace_id) = match request {
|
||||||
|
AnyRequest::HttpRequest(r) => (Some(r.id), r.workspace_id),
|
||||||
|
AnyRequest::GrpcRequest(r) => (Some(r.id), r.workspace_id),
|
||||||
|
AnyRequest::WebsocketRequest(r) => (Some(r.id), r.workspace_id),
|
||||||
|
};
|
||||||
|
let cookie_jar_id =
|
||||||
|
resolve_cookie_jar_id(query_manager, &workspace_id, explicit_cookie_jar_id)?;
|
||||||
|
return Ok(CliExecutionContext {
|
||||||
|
request_id,
|
||||||
|
workspace_id: Some(workspace_id),
|
||||||
|
environment_id: environment.map(str::to_string),
|
||||||
|
cookie_jar_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(folder) = query_manager.connect().get_folder(id) {
|
||||||
|
let cookie_jar_id =
|
||||||
|
resolve_cookie_jar_id(query_manager, &folder.workspace_id, explicit_cookie_jar_id)?;
|
||||||
|
return Ok(CliExecutionContext {
|
||||||
|
request_id: None,
|
||||||
|
workspace_id: Some(folder.workspace_id),
|
||||||
|
environment_id: environment.map(str::to_string),
|
||||||
|
cookie_jar_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(workspace) = query_manager.connect().get_workspace(id) {
|
||||||
|
let cookie_jar_id =
|
||||||
|
resolve_cookie_jar_id(query_manager, &workspace.id, explicit_cookie_jar_id)?;
|
||||||
|
return Ok(CliExecutionContext {
|
||||||
|
request_id: None,
|
||||||
|
workspace_id: Some(workspace.id),
|
||||||
|
environment_id: environment.map(str::to_string),
|
||||||
|
cookie_jar_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(format!("Could not resolve ID '{}' as request, folder, or workspace", id))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_request_execution_context(
|
||||||
|
query_manager: &QueryManager,
|
||||||
|
request_id: &str,
|
||||||
|
environment: Option<&str>,
|
||||||
|
explicit_cookie_jar_id: Option<&str>,
|
||||||
|
) -> Result<CliExecutionContext, String> {
|
||||||
|
let request = query_manager
|
||||||
|
.connect()
|
||||||
|
.get_any_request(request_id)
|
||||||
|
.map_err(|e| format!("Failed to get request: {e}"))?;
|
||||||
|
|
||||||
|
let workspace_id = match request {
|
||||||
|
AnyRequest::HttpRequest(r) => r.workspace_id,
|
||||||
|
AnyRequest::GrpcRequest(r) => r.workspace_id,
|
||||||
|
AnyRequest::WebsocketRequest(r) => r.workspace_id,
|
||||||
|
};
|
||||||
|
let cookie_jar_id =
|
||||||
|
resolve_cookie_jar_id(query_manager, &workspace_id, explicit_cookie_jar_id)?;
|
||||||
|
|
||||||
|
Ok(CliExecutionContext {
|
||||||
|
request_id: Some(request_id.to_string()),
|
||||||
|
workspace_id: Some(workspace_id),
|
||||||
|
environment_id: environment.map(str::to_string),
|
||||||
|
cookie_jar_id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_cookie_jar_id(
|
||||||
|
query_manager: &QueryManager,
|
||||||
|
workspace_id: &str,
|
||||||
|
explicit_cookie_jar_id: Option<&str>,
|
||||||
|
) -> Result<Option<String>, String> {
|
||||||
|
if let Some(cookie_jar_id) = explicit_cookie_jar_id {
|
||||||
|
return Ok(Some(cookie_jar_id.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let default_cookie_jar = query_manager
|
||||||
|
.connect()
|
||||||
|
.list_cookie_jars(workspace_id)
|
||||||
|
.map_err(|e| format!("Failed to list cookie jars: {e}"))?
|
||||||
|
.into_iter()
|
||||||
|
.min_by_key(|jar| jar.created_at)
|
||||||
|
.map(|jar| jar.id);
|
||||||
|
Ok(default_cookie_jar)
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,6 +17,14 @@ pub fn warning(message: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn warning_stderr(message: &str) {
|
||||||
|
if io::stderr().is_terminal() {
|
||||||
|
eprintln!("{:<8} {}", style("WARNING").yellow().bold(), style(message).yellow());
|
||||||
|
} else {
|
||||||
|
eprintln!("WARNING {message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn success(message: &str) {
|
pub fn success(message: &str) {
|
||||||
if io::stdout().is_terminal() {
|
if io::stdout().is_terminal() {
|
||||||
println!("{:<8} {}", style("SUCCESS").green().bold(), style(message).green());
|
println!("{:<8} {}", style("SUCCESS").green().bold(), style(message).green());
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ pub mod confirm;
|
|||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod json;
|
pub mod json;
|
||||||
pub mod schema;
|
pub mod schema;
|
||||||
|
pub mod workspace;
|
||||||
|
|||||||
19
crates-cli/yaak-cli/src/utils/workspace.rs
Normal file
19
crates-cli/yaak-cli/src/utils/workspace.rs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
use crate::context::CliContext;
|
||||||
|
|
||||||
|
pub fn resolve_workspace_id(
|
||||||
|
ctx: &CliContext,
|
||||||
|
workspace_id: Option<&str>,
|
||||||
|
command_name: &str,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
if let Some(workspace_id) = workspace_id {
|
||||||
|
return Ok(workspace_id.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let workspaces =
|
||||||
|
ctx.db().list_workspaces().map_err(|e| format!("Failed to list workspaces: {e}"))?;
|
||||||
|
match workspaces.as_slice() {
|
||||||
|
[] => Err(format!("No workspaces found. {command_name} requires a workspace ID.")),
|
||||||
|
[workspace] => Ok(workspace.id.clone()),
|
||||||
|
_ => Err(format!("Multiple workspaces found. {command_name} requires a workspace ID.")),
|
||||||
|
}
|
||||||
|
}
|
||||||
226
crates-cli/yaak-cli/src/version_check.rs
Normal file
226
crates-cli/yaak-cli/src/version_check.rs
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
use crate::ui;
|
||||||
|
use crate::version;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fs;
|
||||||
|
use std::io::IsTerminal;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
use yaak_api::{ApiClientKind, yaak_api_client};
|
||||||
|
|
||||||
|
const CACHE_FILE_NAME: &str = "cli-version-check.json";
|
||||||
|
const CHECK_INTERVAL_SECS: u64 = 24 * 60 * 60;
|
||||||
|
const REQUEST_TIMEOUT: Duration = Duration::from_millis(800);
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
#[serde(default)]
|
||||||
|
struct VersionCheckResponse {
|
||||||
|
outdated: bool,
|
||||||
|
latest_version: Option<String>,
|
||||||
|
upgrade_hint: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
struct CacheRecord {
|
||||||
|
checked_at_epoch_secs: u64,
|
||||||
|
response: VersionCheckResponse,
|
||||||
|
last_warned_at_epoch_secs: Option<u64>,
|
||||||
|
last_warned_version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CacheRecord {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
checked_at_epoch_secs: 0,
|
||||||
|
response: VersionCheckResponse::default(),
|
||||||
|
last_warned_at_epoch_secs: None,
|
||||||
|
last_warned_version: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct VersionCheckRequest<'a> {
|
||||||
|
current_version: &'a str,
|
||||||
|
channel: String,
|
||||||
|
install_source: String,
|
||||||
|
platform: &'a str,
|
||||||
|
arch: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn maybe_check_for_updates() {
|
||||||
|
if should_skip_check() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = unix_epoch_secs();
|
||||||
|
let cache_path = cache_path();
|
||||||
|
let cached = read_cache(&cache_path);
|
||||||
|
|
||||||
|
if let Some(cache) = cached.as_ref().filter(|c| !is_expired(c.checked_at_epoch_secs, now)) {
|
||||||
|
let mut record = cache.clone();
|
||||||
|
maybe_warn_outdated(&mut record, now);
|
||||||
|
write_cache(&cache_path, &record);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fresh = fetch_version_check().await;
|
||||||
|
match fresh {
|
||||||
|
Some(response) => {
|
||||||
|
let mut record = CacheRecord {
|
||||||
|
checked_at_epoch_secs: now,
|
||||||
|
response: response.clone(),
|
||||||
|
last_warned_at_epoch_secs: cached
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.last_warned_at_epoch_secs),
|
||||||
|
last_warned_version: cached.as_ref().and_then(|c| c.last_warned_version.clone()),
|
||||||
|
};
|
||||||
|
maybe_warn_outdated(&mut record, now);
|
||||||
|
write_cache(&cache_path, &record);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let fallback = cached.as_ref().map(|cache| cache.response.clone()).unwrap_or_default();
|
||||||
|
let mut record = CacheRecord {
|
||||||
|
checked_at_epoch_secs: now,
|
||||||
|
response: fallback,
|
||||||
|
last_warned_at_epoch_secs: cached
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|c| c.last_warned_at_epoch_secs),
|
||||||
|
last_warned_version: cached.as_ref().and_then(|c| c.last_warned_version.clone()),
|
||||||
|
};
|
||||||
|
maybe_warn_outdated(&mut record, now);
|
||||||
|
write_cache(&cache_path, &record);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_skip_check() -> bool {
|
||||||
|
if std::env::var("YAAK_CLI_NO_UPDATE_CHECK")
|
||||||
|
.is_ok_and(|v| v == "1" || v.eq_ignore_ascii_case("true"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if std::env::var("CI").is_ok() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
!std::io::stdout().is_terminal()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn fetch_version_check() -> Option<VersionCheckResponse> {
|
||||||
|
let api_url = format!("{}/cli/check", update_base_url());
|
||||||
|
let current_version = version::cli_version();
|
||||||
|
let payload = VersionCheckRequest {
|
||||||
|
current_version,
|
||||||
|
channel: release_channel(current_version),
|
||||||
|
install_source: install_source(),
|
||||||
|
platform: std::env::consts::OS,
|
||||||
|
arch: std::env::consts::ARCH,
|
||||||
|
};
|
||||||
|
|
||||||
|
let client = yaak_api_client(ApiClientKind::Cli, current_version).ok()?;
|
||||||
|
let request = client.post(api_url).json(&payload);
|
||||||
|
|
||||||
|
let response = tokio::time::timeout(REQUEST_TIMEOUT, request.send()).await.ok()?.ok()?;
|
||||||
|
if !response.status().is_success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
tokio::time::timeout(REQUEST_TIMEOUT, response.json::<VersionCheckResponse>()).await.ok()?.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn release_channel(version: &str) -> String {
|
||||||
|
version
|
||||||
|
.split_once('-')
|
||||||
|
.and_then(|(_, suffix)| suffix.split('.').next())
|
||||||
|
.unwrap_or("stable")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn install_source() -> String {
|
||||||
|
std::env::var("YAAK_CLI_INSTALL_SOURCE")
|
||||||
|
.ok()
|
||||||
|
.filter(|s| !s.trim().is_empty())
|
||||||
|
.unwrap_or_else(|| "source".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_base_url() -> &'static str {
|
||||||
|
match std::env::var("ENVIRONMENT").ok().as_deref() {
|
||||||
|
Some("development") => "http://localhost:9444",
|
||||||
|
_ => "https://update.yaak.app",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_warn_outdated(record: &mut CacheRecord, now: u64) {
|
||||||
|
if !record.response.outdated {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let latest =
|
||||||
|
record.response.latest_version.clone().unwrap_or_else(|| "a newer release".to_string());
|
||||||
|
let warn_suppressed = record.last_warned_version.as_deref() == Some(latest.as_str())
|
||||||
|
&& record.last_warned_at_epoch_secs.is_some_and(|t| !is_expired(t, now));
|
||||||
|
if warn_suppressed {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let hint = record.response.upgrade_hint.clone().unwrap_or_else(default_upgrade_hint);
|
||||||
|
ui::warning_stderr(&format!("A newer Yaak CLI version is available ({latest}). {hint}"));
|
||||||
|
record.last_warned_version = Some(latest);
|
||||||
|
record.last_warned_at_epoch_secs = Some(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_upgrade_hint() -> String {
|
||||||
|
if install_source() == "npm" {
|
||||||
|
let channel = release_channel(version::cli_version());
|
||||||
|
if channel == "stable" {
|
||||||
|
return "Run `npm install -g @yaakapp/cli@latest` to update.".to_string();
|
||||||
|
}
|
||||||
|
return format!("Run `npm install -g @yaakapp/cli@{channel}` to update.");
|
||||||
|
}
|
||||||
|
|
||||||
|
"Update your Yaak CLI installation to the latest release.".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cache_path() -> PathBuf {
|
||||||
|
std::env::temp_dir().join("yaak-cli").join(format!("{}-{CACHE_FILE_NAME}", environment_name()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn environment_name() -> &'static str {
|
||||||
|
match std::env::var("ENVIRONMENT").ok().as_deref() {
|
||||||
|
Some("staging") => "staging",
|
||||||
|
Some("development") => "development",
|
||||||
|
_ => "production",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_cache(path: &Path) -> Option<CacheRecord> {
|
||||||
|
let contents = fs::read_to_string(path).ok()?;
|
||||||
|
serde_json::from_str::<CacheRecord>(&contents).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_cache(path: &Path, record: &CacheRecord) {
|
||||||
|
let Some(parent) = path.parent() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if fs::create_dir_all(parent).is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(json) = serde_json::to_string(record) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let _ = fs::write(path, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_expired(checked_at_epoch_secs: u64, now: u64) -> bool {
|
||||||
|
now.saturating_sub(checked_at_epoch_secs) >= CHECK_INTERVAL_SECS
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unix_epoch_secs() -> u64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_else(|_| Duration::from_secs(0))
|
||||||
|
.as_secs()
|
||||||
|
}
|
||||||
@@ -70,6 +70,8 @@ fn workspace_schema_outputs_json_schema() {
|
|||||||
.stdout(contains("\"type\":\"object\""))
|
.stdout(contains("\"type\":\"object\""))
|
||||||
.stdout(contains("\"x-yaak-agent-hints\""))
|
.stdout(contains("\"x-yaak-agent-hints\""))
|
||||||
.stdout(contains("\"templateVariableSyntax\":\"${[ my_var ]}\""))
|
.stdout(contains("\"templateVariableSyntax\":\"${[ my_var ]}\""))
|
||||||
.stdout(contains("\"templateFunctionSyntax\":\"${[ namespace.my_func(a='aaa',b='bbb') ]}\""))
|
.stdout(contains(
|
||||||
|
"\"templateFunctionSyntax\":\"${[ namespace.my_func(a='aaa',b='bbb') ]}\"",
|
||||||
|
))
|
||||||
.stdout(contains("\"name\""));
|
.stdout(contains("\"name\""));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
|||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tokio::task::block_in_place;
|
use tokio::task::block_in_place;
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
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_grpc::{Code, ServiceDefinition, serialize_message};
|
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
|
||||||
@@ -97,6 +98,7 @@ impl<R: Runtime> PluginContextExt<R> for WebviewWindow<R> {
|
|||||||
struct AppMetaData {
|
struct AppMetaData {
|
||||||
is_dev: bool,
|
is_dev: bool,
|
||||||
version: String,
|
version: String,
|
||||||
|
cli_version: Option<String>,
|
||||||
name: String,
|
name: String,
|
||||||
app_data_dir: String,
|
app_data_dir: String,
|
||||||
app_log_dir: String,
|
app_log_dir: String,
|
||||||
@@ -113,9 +115,11 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
|||||||
let vendored_plugin_dir =
|
let vendored_plugin_dir =
|
||||||
app_handle.path().resolve("vendored/plugins", BaseDirectory::Resource)?;
|
app_handle.path().resolve("vendored/plugins", BaseDirectory::Resource)?;
|
||||||
let default_project_dir = app_handle.path().home_dir()?.join("YaakProjects");
|
let default_project_dir = app_handle.path().home_dir()?.join("YaakProjects");
|
||||||
|
let cli_version = detect_cli_version().await;
|
||||||
Ok(AppMetaData {
|
Ok(AppMetaData {
|
||||||
is_dev: is_dev(),
|
is_dev: is_dev(),
|
||||||
version: app_handle.package_info().version.to_string(),
|
version: app_handle.package_info().version.to_string(),
|
||||||
|
cli_version,
|
||||||
name: app_handle.package_info().name.to_string(),
|
name: app_handle.package_info().name.to_string(),
|
||||||
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
||||||
app_log_dir: app_log_dir.to_string_lossy().to_string(),
|
app_log_dir: app_log_dir.to_string_lossy().to_string(),
|
||||||
@@ -126,6 +130,24 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn detect_cli_version() -> Option<String> {
|
||||||
|
detect_cli_version_for_binary("yaak").await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn detect_cli_version_for_binary(program: &str) -> Option<String> {
|
||||||
|
let mut cmd = new_checked_command(program, "--version").await.ok()?;
|
||||||
|
let out = cmd.arg("--version").output().await.ok()?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = String::from_utf8(out.stdout).ok()?;
|
||||||
|
let line = line.lines().find(|l| !l.trim().is_empty())?.trim();
|
||||||
|
let mut parts = line.split_whitespace();
|
||||||
|
let _name = parts.next();
|
||||||
|
Some(parts.next().unwrap_or(line).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_template_tokens_to_string<R: Runtime>(
|
async fn cmd_template_tokens_to_string<R: Runtime>(
|
||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use yaak_api::yaak_api_client;
|
use yaak_api::{ApiClientKind, yaak_api_client};
|
||||||
use yaak_common::platform::get_os_str;
|
use yaak_common::platform::get_os_str;
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ impl YaakNotifier {
|
|||||||
|
|
||||||
let launch_info = get_or_upsert_launch_info(app_handle);
|
let launch_info = get_or_upsert_launch_info(app_handle);
|
||||||
let app_version = app_handle.package_info().version.to_string();
|
let app_version = app_handle.package_info().version.to_string();
|
||||||
let req = yaak_api_client(&app_version)?
|
let req = yaak_api_client(ApiClientKind::App, &app_version)?
|
||||||
.request(Method::GET, "https://notify.yaak.app/notifications")
|
.request(Method::GET, "https://notify.yaak.app/notifications")
|
||||||
.query(&[
|
.query(&[
|
||||||
("version", &launch_info.current_version),
|
("version", &launch_info.current_version),
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ async fn handle_host_plugin_request<R: Runtime>(
|
|||||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||||
message: format!("Reloaded plugin {}@{}", info.name, info.version),
|
message: format!("Reloaded plugin {}@{}", info.name, info.version),
|
||||||
icon: Some(Icon::Info),
|
icon: Some(Icon::Info),
|
||||||
timeout: Some(3000),
|
timeout: Some(5000),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
None,
|
None,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ use tauri::{
|
|||||||
};
|
};
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use yaak_api::yaak_api_client;
|
use yaak_api::{ApiClientKind, yaak_api_client};
|
||||||
use yaak_models::models::{Plugin, PluginSource};
|
use yaak_models::models::{Plugin, PluginSource};
|
||||||
use yaak_models::util::UpdateSource;
|
use yaak_models::util::UpdateSource;
|
||||||
use yaak_plugins::api::{
|
use yaak_plugins::api::{
|
||||||
@@ -73,7 +73,7 @@ impl PluginUpdater {
|
|||||||
info!("Checking for plugin updates");
|
info!("Checking for plugin updates");
|
||||||
|
|
||||||
let app_version = window.app_handle().package_info().version.to_string();
|
let app_version = window.app_handle().package_info().version.to_string();
|
||||||
let http_client = yaak_api_client(&app_version)?;
|
let http_client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let plugins = window.app_handle().db().list_plugins()?;
|
let plugins = window.app_handle().db().list_plugins()?;
|
||||||
let updates = check_plugin_updates(&http_client, plugins.clone()).await?;
|
let updates = check_plugin_updates(&http_client, plugins.clone()).await?;
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ pub async fn cmd_plugins_search<R: Runtime>(
|
|||||||
query: &str,
|
query: &str,
|
||||||
) -> Result<PluginSearchResponse> {
|
) -> Result<PluginSearchResponse> {
|
||||||
let app_version = app_handle.package_info().version.to_string();
|
let app_version = app_handle.package_info().version.to_string();
|
||||||
let http_client = yaak_api_client(&app_version)?;
|
let http_client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
Ok(search_plugins(&http_client, query).await?)
|
Ok(search_plugins(&http_client, query).await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ pub async fn cmd_plugins_install<R: Runtime>(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let plugin_manager = Arc::new((*window.state::<PluginManager>()).clone());
|
let plugin_manager = Arc::new((*window.state::<PluginManager>()).clone());
|
||||||
let app_version = window.app_handle().package_info().version.to_string();
|
let app_version = window.app_handle().package_info().version.to_string();
|
||||||
let http_client = yaak_api_client(&app_version)?;
|
let http_client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let query_manager = window.state::<yaak_models::query_manager::QueryManager>();
|
let query_manager = window.state::<yaak_models::query_manager::QueryManager>();
|
||||||
let plugin_context = window.plugin_context();
|
let plugin_context = window.plugin_context();
|
||||||
download_and_install(
|
download_and_install(
|
||||||
@@ -203,7 +203,7 @@ pub async fn cmd_plugins_updates<R: Runtime>(
|
|||||||
app_handle: AppHandle<R>,
|
app_handle: AppHandle<R>,
|
||||||
) -> Result<PluginUpdatesResponse> {
|
) -> Result<PluginUpdatesResponse> {
|
||||||
let app_version = app_handle.package_info().version.to_string();
|
let app_version = app_handle.package_info().version.to_string();
|
||||||
let http_client = yaak_api_client(&app_version)?;
|
let http_client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let plugins = app_handle.db().list_plugins()?;
|
let plugins = app_handle.db().list_plugins()?;
|
||||||
Ok(check_plugin_updates(&http_client, plugins).await?)
|
Ok(check_plugin_updates(&http_client, plugins).await?)
|
||||||
}
|
}
|
||||||
@@ -213,7 +213,7 @@ pub async fn cmd_plugins_update_all<R: Runtime>(
|
|||||||
window: WebviewWindow<R>,
|
window: WebviewWindow<R>,
|
||||||
) -> Result<Vec<PluginNameVersion>> {
|
) -> Result<Vec<PluginNameVersion>> {
|
||||||
let app_version = window.app_handle().package_info().version.to_string();
|
let app_version = window.app_handle().package_info().version.to_string();
|
||||||
let http_client = yaak_api_client(&app_version)?;
|
let http_client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let plugins = window.db().list_plugins()?;
|
let plugins = window.db().list_plugins()?;
|
||||||
|
|
||||||
// Get list of available updates (already filtered to only registry plugins)
|
// Get list of available updates (already filtered to only registry plugins)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use std::fs;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Url};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, Url};
|
||||||
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
|
use tauri_plugin_dialog::{DialogExt, MessageDialogButtons, MessageDialogKind};
|
||||||
use yaak_api::yaak_api_client;
|
use yaak_api::{ApiClientKind, yaak_api_client};
|
||||||
use yaak_models::util::generate_id;
|
use yaak_models::util::generate_id;
|
||||||
use yaak_plugins::events::{Color, ShowToastRequest};
|
use yaak_plugins::events::{Color, ShowToastRequest};
|
||||||
use yaak_plugins::install::download_and_install;
|
use yaak_plugins::install::download_and_install;
|
||||||
@@ -47,7 +47,7 @@ pub(crate) async fn handle_deep_link<R: Runtime>(
|
|||||||
let plugin_manager = Arc::new((*window.state::<PluginManager>()).clone());
|
let plugin_manager = Arc::new((*window.state::<PluginManager>()).clone());
|
||||||
let query_manager = app_handle.db_manager();
|
let query_manager = app_handle.db_manager();
|
||||||
let app_version = app_handle.package_info().version.to_string();
|
let app_version = app_handle.package_info().version.to_string();
|
||||||
let http_client = yaak_api_client(&app_version)?;
|
let http_client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let plugin_context = window.plugin_context();
|
let plugin_context = window.plugin_context();
|
||||||
let pv = download_and_install(
|
let pv = download_and_install(
|
||||||
plugin_manager,
|
plugin_manager,
|
||||||
@@ -88,7 +88,8 @@ pub(crate) async fn handle_deep_link<R: Runtime>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let app_version = app_handle.package_info().version.to_string();
|
let app_version = app_handle.package_info().version.to_string();
|
||||||
let resp = yaak_api_client(&app_version)?.get(file_url).send().await?;
|
let resp =
|
||||||
|
yaak_api_client(ApiClientKind::App, &app_version)?.get(file_url).send().await?;
|
||||||
let json = resp.bytes().await?;
|
let json = resp.bytes().await?;
|
||||||
let p = app_handle
|
let p = app_handle
|
||||||
.path()
|
.path()
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use std::ops::Add;
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow, is_dev};
|
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow, is_dev};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
use yaak_api::yaak_api_client;
|
use yaak_api::{ApiClientKind, yaak_api_client};
|
||||||
use yaak_common::platform::get_os_str;
|
use yaak_common::platform::get_os_str;
|
||||||
use yaak_models::db_context::DbContext;
|
use yaak_models::db_context::DbContext;
|
||||||
use yaak_models::query_manager::QueryManager;
|
use yaak_models::query_manager::QueryManager;
|
||||||
@@ -119,7 +119,7 @@ pub async fn activate_license<R: Runtime>(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
info!("Activating license {}", license_key);
|
info!("Activating license {}", license_key);
|
||||||
let app_version = window.app_handle().package_info().version.to_string();
|
let app_version = window.app_handle().package_info().version.to_string();
|
||||||
let client = yaak_api_client(&app_version)?;
|
let client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let payload = ActivateLicenseRequestPayload {
|
let payload = ActivateLicenseRequestPayload {
|
||||||
license_key: license_key.to_string(),
|
license_key: license_key.to_string(),
|
||||||
app_platform: get_os_str().to_string(),
|
app_platform: get_os_str().to_string(),
|
||||||
@@ -157,7 +157,7 @@ pub async fn deactivate_license<R: Runtime>(window: &WebviewWindow<R>) -> Result
|
|||||||
let activation_id = get_activation_id(app_handle).await;
|
let activation_id = get_activation_id(app_handle).await;
|
||||||
|
|
||||||
let app_version = window.app_handle().package_info().version.to_string();
|
let app_version = window.app_handle().package_info().version.to_string();
|
||||||
let client = yaak_api_client(&app_version)?;
|
let client = yaak_api_client(ApiClientKind::App, &app_version)?;
|
||||||
let path = format!("/licenses/activations/{}/deactivate", activation_id);
|
let path = format!("/licenses/activations/{}/deactivate", activation_id);
|
||||||
let payload =
|
let payload =
|
||||||
DeactivateLicenseRequestPayload { app_platform: get_os_str().to_string(), app_version };
|
DeactivateLicenseRequestPayload { app_platform: get_os_str().to_string(), app_version };
|
||||||
@@ -203,7 +203,7 @@ pub async fn check_license<R: Runtime>(window: &WebviewWindow<R>) -> Result<Lice
|
|||||||
(true, _) => {
|
(true, _) => {
|
||||||
info!("Checking license activation");
|
info!("Checking license activation");
|
||||||
// A license has been activated, so let's check the license server
|
// A license has been activated, so let's check the license server
|
||||||
let client = yaak_api_client(&payload.app_version)?;
|
let client = yaak_api_client(ApiClientKind::App, &payload.app_version)?;
|
||||||
let path = format!("/licenses/activations/{activation_id}/check-v2");
|
let path = format!("/licenses/activations/{activation_id}/check-v2");
|
||||||
let response = client.post(build_url(&path)).json(&payload).send().await?;
|
let response = client.post(build_url(&path)).json(&payload).send().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -8,14 +8,24 @@ use reqwest::header::{HeaderMap, HeaderValue};
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use yaak_common::platform::{get_ua_arch, get_ua_platform};
|
use yaak_common::platform::{get_ua_arch, get_ua_platform};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum ApiClientKind {
|
||||||
|
App,
|
||||||
|
Cli,
|
||||||
|
}
|
||||||
|
|
||||||
/// Build a reqwest Client configured for Yaak's own API calls.
|
/// Build a reqwest Client configured for Yaak's own API calls.
|
||||||
///
|
///
|
||||||
/// Includes a custom User-Agent, JSON accept header, 20s timeout, gzip,
|
/// Includes a custom User-Agent, JSON accept header, 20s timeout, gzip,
|
||||||
/// and automatic OS-level proxy detection via sysproxy.
|
/// and automatic OS-level proxy detection via sysproxy.
|
||||||
pub fn yaak_api_client(version: &str) -> Result<Client> {
|
pub fn yaak_api_client(kind: ApiClientKind, version: &str) -> Result<Client> {
|
||||||
let platform = get_ua_platform();
|
let platform = get_ua_platform();
|
||||||
let arch = get_ua_arch();
|
let arch = get_ua_arch();
|
||||||
let ua = format!("Yaak/{version} ({platform}; {arch})");
|
let product = match kind {
|
||||||
|
ApiClientKind::App => "Yaak",
|
||||||
|
ApiClientKind::Cli => "YaakCli",
|
||||||
|
};
|
||||||
|
let ua = format!("{product}/{version} ({platform}; {arch})");
|
||||||
|
|
||||||
let mut default_headers = HeaderMap::new();
|
let mut default_headers = HeaderMap::new();
|
||||||
default_headers.insert("Accept", HeaderValue::from_str("application/json").unwrap());
|
default_headers.insert("Accept", HeaderValue::from_str("application/json").unwrap());
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use std::ffi::OsStr;
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::io::{self, ErrorKind};
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||||
@@ -14,3 +16,27 @@ pub fn new_xplatform_command<S: AsRef<OsStr>>(program: S) -> tokio::process::Com
|
|||||||
}
|
}
|
||||||
cmd
|
cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a command only if the binary exists and can be invoked with the given probe argument.
|
||||||
|
pub async fn new_checked_command<S: AsRef<OsStr>>(
|
||||||
|
program: S,
|
||||||
|
probe_arg: &str,
|
||||||
|
) -> io::Result<tokio::process::Command> {
|
||||||
|
let program: OsString = program.as_ref().to_os_string();
|
||||||
|
|
||||||
|
let mut probe = new_xplatform_command(&program);
|
||||||
|
probe.arg(probe_arg).stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||||
|
|
||||||
|
let status = probe.status().await?;
|
||||||
|
if !status.success() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
format!(
|
||||||
|
"'{}' is not available on PATH or failed to execute",
|
||||||
|
program.to_string_lossy()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(new_xplatform_command(&program))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use crate::error::Error::GitNotFound;
|
use crate::error::Error::GitNotFound;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Stdio;
|
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use yaak_common::command::new_xplatform_command;
|
use yaak_common::command::new_checked_command;
|
||||||
|
|
||||||
/// Create a git command that runs in the specified directory
|
/// Create a git command that runs in the specified directory
|
||||||
pub(crate) async fn new_binary_command(dir: &Path) -> Result<Command> {
|
pub(crate) async fn new_binary_command(dir: &Path) -> Result<Command> {
|
||||||
@@ -14,17 +13,5 @@ pub(crate) async fn new_binary_command(dir: &Path) -> Result<Command> {
|
|||||||
|
|
||||||
/// Create a git command without a specific directory (for global operations)
|
/// Create a git command without a specific directory (for global operations)
|
||||||
pub(crate) async fn new_binary_command_global() -> Result<Command> {
|
pub(crate) async fn new_binary_command_global() -> Result<Command> {
|
||||||
// 1. Probe that `git` exists and is runnable
|
new_checked_command("git", "--version").await.map_err(|_| GitNotFound)
|
||||||
let mut probe = new_xplatform_command("git");
|
|
||||||
probe.arg("--version").stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
|
||||||
|
|
||||||
let status = probe.status().await.map_err(|_| GitNotFound)?;
|
|
||||||
|
|
||||||
if !status.success() {
|
|
||||||
return Err(GitNotFound);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Build the reusable git command
|
|
||||||
let cmd = new_xplatform_command("git");
|
|
||||||
Ok(cmd)
|
|
||||||
}
|
}
|
||||||
|
|||||||
36
crates/yaak-plugins/bindings/gen_models.ts
generated
36
crates/yaak-plugins/bindings/gen_models.ts
generated
@@ -18,7 +18,12 @@ export type EditorKeymap = "default" | "vim" | "vscode" | "emacs";
|
|||||||
|
|
||||||
export type EncryptedKey = { encryptedKey: string, };
|
export type EncryptedKey = { encryptedKey: string, };
|
||||||
|
|
||||||
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null, variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, };
|
export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, public: boolean, parentModel: string, parentId: string | null,
|
||||||
|
/**
|
||||||
|
* Variables defined in this environment scope.
|
||||||
|
* Child environments override parent variables by name.
|
||||||
|
*/
|
||||||
|
variables: Array<EnvironmentVariable>, color: string | null, sortPriority: number, };
|
||||||
|
|
||||||
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
@@ -34,9 +39,17 @@ export type GrpcEvent = { model: "grpc_event", id: string, createdAt: string, up
|
|||||||
|
|
||||||
export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end";
|
export type GrpcEventType = "info" | "error" | "client_message" | "server_message" | "connection_start" | "connection_end";
|
||||||
|
|
||||||
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number, url: string, };
|
export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record<string, any>, description: string, message: string, metadata: Array<HttpRequestHeader>, method: string | null, name: string, service: string | null, sortPriority: number,
|
||||||
|
/**
|
||||||
|
* Server URL (http for plaintext or https for secure)
|
||||||
|
*/
|
||||||
|
url: string, };
|
||||||
|
|
||||||
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, body: Record<string, any>, bodyType: string | null, description: string, headers: Array<HttpRequestHeader>, method: string, name: string, sortPriority: number, url: string,
|
||||||
|
/**
|
||||||
|
* URL parameters used for both path placeholders (`:id`) and query string entries.
|
||||||
|
*/
|
||||||
|
urlParameters: Array<HttpUrlParameter>, };
|
||||||
|
|
||||||
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, id?: string, };
|
||||||
|
|
||||||
@@ -55,11 +68,18 @@ export type HttpResponseHeader = { name: string, value: string, };
|
|||||||
|
|
||||||
export type HttpResponseState = "initialized" | "connected" | "closed";
|
export type HttpResponseState = "initialized" | "connected" | "closed";
|
||||||
|
|
||||||
export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, id?: string, };
|
export type HttpUrlParameter = { enabled?: boolean,
|
||||||
|
/**
|
||||||
|
* Colon-prefixed parameters are treated as path parameters if they match, like `/users/:id`
|
||||||
|
* Other entries are appended as query parameters
|
||||||
|
*/
|
||||||
|
name: string, value: string, id?: string, };
|
||||||
|
|
||||||
export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
|
export type KeyValue = { model: "key_value", id: string, createdAt: string, updatedAt: string, key: string, namespace: string, value: string, };
|
||||||
|
|
||||||
export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, };
|
export type Plugin = { model: "plugin", id: string, createdAt: string, updatedAt: string, checkedAt: string | null, directory: string, enabled: boolean, url: string | null, source: PluginSource, };
|
||||||
|
|
||||||
|
export type PluginSource = "bundled" | "filesystem" | "registry";
|
||||||
|
|
||||||
export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" };
|
export type ProxySetting = { "type": "enabled", http: string, https: string, auth: ProxySettingAuth | null, bypass: string, disabled: boolean, } | { "type": "disabled" };
|
||||||
|
|
||||||
@@ -77,7 +97,11 @@ export type WebsocketEvent = { model: "websocket_event", id: string, createdAt:
|
|||||||
|
|
||||||
export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text";
|
export type WebsocketEventType = "binary" | "close" | "frame" | "open" | "ping" | "pong" | "text";
|
||||||
|
|
||||||
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string, urlParameters: Array<HttpUrlParameter>, };
|
export type WebsocketRequest = { model: "websocket_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, message: string, name: string, sortPriority: number, url: string,
|
||||||
|
/**
|
||||||
|
* URL parameters used for both path placeholders (`:id`) and query string entries.
|
||||||
|
*/
|
||||||
|
urlParameters: Array<HttpUrlParameter>, };
|
||||||
|
|
||||||
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array<DnsOverride>, };
|
export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, authentication: Record<string, any>, authenticationType: string | null, description: string, headers: Array<HttpRequestHeader>, name: string, encryptionKeyChallenge: string | null, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, settingDnsOverrides: Array<DnsOverride>, };
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ function getBinaryPath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = childProcess.spawnSync(getBinaryPath(), process.argv.slice(2), {
|
const result = childProcess.spawnSync(getBinaryPath(), process.argv.slice(2), {
|
||||||
stdio: "inherit"
|
stdio: "inherit",
|
||||||
|
env: { ...process.env, YAAK_CLI_INSTALL_SOURCE: process.env.YAAK_CLI_INSTALL_SOURCE ?? "npm" },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ function getBinaryPath() {
|
|||||||
|
|
||||||
module.exports.runBinary = function runBinary(...args) {
|
module.exports.runBinary = function runBinary(...args) {
|
||||||
childProcess.execFileSync(getBinaryPath(), args, {
|
childProcess.execFileSync(getBinaryPath(), args, {
|
||||||
stdio: "inherit"
|
stdio: "inherit",
|
||||||
|
env: { ...process.env, YAAK_CLI_INSTALL_SOURCE: process.env.YAAK_CLI_INSTALL_SOURCE ?? "npm" },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -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",
|
"@yaakapp/cli": "^0.5.1",
|
||||||
"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",
|
||||||
@@ -4303,9 +4303,9 @@
|
|||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli": {
|
"node_modules/@yaakapp/cli": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.5.1.tgz",
|
||||||
"integrity": "sha512-8xnu2oFWlgV+xeIAHMuEgsqX6Sxq4UYrSH2WbafwDLbSep6fxpO74tiBH7xp4wakt/7Bcy9a2Q5R9nkAc1ZUdA==",
|
"integrity": "sha512-kAhX9SvjAiEsg2xwCuyuEOJRyEIg7jEGzGFCGzWy9I9Ew2hD0huIDGy9l4IMJUR84gh1V/62dxeiZREptWpIFg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4313,18 +4313,18 @@
|
|||||||
"yaakcli": "bin/cli.js"
|
"yaakcli": "bin/cli.js"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@yaakapp/cli-darwin-arm64": "0.4.0",
|
"@yaakapp/cli-darwin-arm64": "0.5.1",
|
||||||
"@yaakapp/cli-darwin-x64": "0.4.0",
|
"@yaakapp/cli-darwin-x64": "0.5.1",
|
||||||
"@yaakapp/cli-linux-arm64": "0.4.0",
|
"@yaakapp/cli-linux-arm64": "0.5.1",
|
||||||
"@yaakapp/cli-linux-x64": "0.4.0",
|
"@yaakapp/cli-linux-x64": "0.5.1",
|
||||||
"@yaakapp/cli-win32-arm64": "0.4.0",
|
"@yaakapp/cli-win32-arm64": "0.5.1",
|
||||||
"@yaakapp/cli-win32-x64": "0.4.0"
|
"@yaakapp/cli-win32-x64": "0.5.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli-darwin-arm64": {
|
"node_modules/@yaakapp/cli-darwin-arm64": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.5.1.tgz",
|
||||||
"integrity": "sha512-bl8+VQNPMabXNGQCa7u6w0JGe3CmzYZPsGE8Q+5wGSxa3trGf1bmq/fMW5JXrMi1P7Laepnyad0TGGP/2C8uwQ==",
|
"integrity": "sha512-08FJ35vYGUXVC5r4/kLchEUI8YJN0iiB6KPZ9NuNHD0QmEWAN1451roUrXzz+dGFUw9tLb7HDGuZ6c4YL4Va7A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4335,9 +4335,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli-darwin-x64": {
|
"node_modules/@yaakapp/cli-darwin-x64": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.5.1.tgz",
|
||||||
"integrity": "sha512-R+ETXNBWvmA3W88ZoTk/JtG/PZaUb85y3SwBgMbwcgdhBVwNS/g+DbCspcTFI5zs8Txsf5VuiFU+dW9M9olZ6A==",
|
"integrity": "sha512-UqN9Bn2Z5Ns9ATWWQyvhlCJ3qdk1rM5b9CbGzV61F/LkfcPbvBuTFGAWprQVTun7iy7PjI35R6Cfj126+Z/ehA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4348,9 +4348,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli-linux-arm64": {
|
"node_modules/@yaakapp/cli-linux-arm64": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.5.1.tgz",
|
||||||
"integrity": "sha512-Pf7VyQf4r85FsI0qYnnst7URQF8/RxSZZj79cXLai0FnN3fDiypX4CmHx765bJxgfQZlBvqVmvPAaMW/TeiJEQ==",
|
"integrity": "sha512-YdQXNNPLSzkmwWEqnv2V8C8Bl9atQFQYI3FtPTYa2Ljp54omgMxuikn0gauhsHFMtFg8GOStVEENbBFW0W0Ovg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4361,9 +4361,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli-linux-x64": {
|
"node_modules/@yaakapp/cli-linux-x64": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.5.1.tgz",
|
||||||
"integrity": "sha512-bYWWfHAIW81A+ydJChjH1Qo3+aihz9gFLh7/9MOa6CJgnC6H3V5cnapmh50Hddt9l5ic02aA1FB8ORQOXxb01A==",
|
"integrity": "sha512-03sVYmD3ksH6lJr6YEwqEQCADoVP5fzq6vBkoCBSrQOj9iInk06DuFHME7IjEa7uKXBaF4WeUms/yQEA03jHBA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
@@ -4374,9 +4374,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli-win32-arm64": {
|
"node_modules/@yaakapp/cli-win32-arm64": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.5.1.tgz",
|
||||||
"integrity": "sha512-8X12xkyidyYZ5vtarZGFSYR6HJbUMFUsNxYPNQccnYJIY+soNkjJHOWDjaRvBzCbR8MLT9N04Y5PE/Jv20gXpA==",
|
"integrity": "sha512-bYBe0PpgvjEx/4jvDKOA9/Kg9qzAP1NmUCcPWZueOJItqPdPk2b/1/7pX1eeoh80Qsj7nSmz5PbJaxkygfa0IQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
@@ -4387,9 +4387,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@yaakapp/cli-win32-x64": {
|
"node_modules/@yaakapp/cli-win32-x64": {
|
||||||
"version": "0.4.0",
|
"version": "0.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.5.1.tgz",
|
||||||
"integrity": "sha512-wansfrCCycFcFclowQQxfsNLIAyATyqnnbITED5gUfUrBf8NFHrG0sWVCWlXUhHU7YvpmqL7CsdtlMkIGiZCPQ==",
|
"integrity": "sha512-Mx/LelqQ7X8Fz9og3qOauuU8kemqja5kQzVQ0pHDJhb9bbmhXUK4dSBYTLdkW3a/X+ofo3K5Z/PkS7FMyXxwdA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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",
|
"@yaakapp/cli": "^0.5.1",
|
||||||
"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",
|
||||||
|
|||||||
@@ -76,10 +76,10 @@ export class PluginInstance {
|
|||||||
this.#mod = {};
|
this.#mod = {};
|
||||||
|
|
||||||
const fileChangeCallback = async () => {
|
const fileChangeCallback = async () => {
|
||||||
await this.#mod?.dispose?.();
|
|
||||||
this.#importModule();
|
|
||||||
const ctx = this.#newCtx(workerData.context);
|
const ctx = this.#newCtx(workerData.context);
|
||||||
try {
|
try {
|
||||||
|
await this.#mod?.dispose?.();
|
||||||
|
this.#importModule();
|
||||||
await this.#mod?.init?.(ctx);
|
await this.#mod?.init?.(ctx);
|
||||||
this.#sendPayload(
|
this.#sendPayload(
|
||||||
workerData.context,
|
workerData.context,
|
||||||
@@ -90,7 +90,7 @@ export class PluginInstance {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
ctx.toast.show({
|
await ctx.toast.show({
|
||||||
message: `Failed to initialize plugin ${this.#workerData.bootRequest.dir.split('/').pop()}: ${err}`,
|
message: `Failed to initialize plugin ${this.#workerData.bootRequest.dir.split('/').pop()}: ${err}`,
|
||||||
color: 'notice',
|
color: 'notice',
|
||||||
icon: 'alert_triangle',
|
icon: 'alert_triangle',
|
||||||
@@ -1003,6 +1003,7 @@ function watchFile(filepath: string, cb: () => void) {
|
|||||||
const stat = statSync(filepath, { throwIfNoEntry: false });
|
const stat = statSync(filepath, { throwIfNoEntry: false });
|
||||||
if (stat == null || stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
|
if (stat == null || stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
|
||||||
watchedFiles[filepath] = stat ?? null;
|
watchedFiles[filepath] = stat ?? null;
|
||||||
|
console.log('[plugin-runtime] watchFile triggered', filepath);
|
||||||
cb();
|
cb();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,12 +13,16 @@ describe('template-function-faker', () => {
|
|||||||
it('renders date results as unquoted ISO strings', async () => {
|
it('renders date results as unquoted ISO strings', async () => {
|
||||||
const { plugin } = await import('../src/index');
|
const { plugin } = await import('../src/index');
|
||||||
const fn = plugin.templateFunctions?.find((fn) => fn.name === 'faker.date.future');
|
const fn = plugin.templateFunctions?.find((fn) => fn.name === 'faker.date.future');
|
||||||
|
const onRender = fn?.onRender;
|
||||||
|
|
||||||
expect(fn?.onRender).toBeTypeOf('function');
|
expect(onRender).toBeTypeOf('function');
|
||||||
|
if (onRender == null) {
|
||||||
|
throw new Error("Expected template function 'faker.date.future' to define onRender");
|
||||||
|
}
|
||||||
|
|
||||||
const result = await fn!.onRender!(
|
const result = await onRender(
|
||||||
{} as Parameters<NonNullable<typeof fn.onRender>>[0],
|
{} as Parameters<typeof onRender>[0],
|
||||||
{ values: {} } as Parameters<NonNullable<typeof fn.onRender>>[1],
|
{ values: {} } as Parameters<typeof onRender>[1],
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
expect(result).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
||||||
|
|||||||
@@ -206,7 +206,10 @@ export const plugin: PluginDefinition = {
|
|||||||
// Create snippet generator
|
// Create snippet generator
|
||||||
const snippet = new HTTPSnippet(harRequest);
|
const snippet = new HTTPSnippet(harRequest);
|
||||||
const generateSnippet = (target: string, client: string): string => {
|
const generateSnippet = (target: string, client: string): string => {
|
||||||
const result = snippet.convert(target as any, client);
|
const result = snippet.convert(
|
||||||
|
target as Parameters<typeof snippet.convert>[0],
|
||||||
|
client as Parameters<typeof snippet.convert>[1],
|
||||||
|
);
|
||||||
return (Array.isArray(result) ? result.join('\n') : result || '').replace(/\r\n/g, '\n');
|
return (Array.isArray(result) ? result.join('\n') : result || '').replace(/\r\n/g, '\n');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,9 @@ function splitCommands(rawData: string): string[] {
|
|||||||
let inDollarQuote = false;
|
let inDollarQuote = false;
|
||||||
|
|
||||||
for (let i = 0; i < joined.length; i++) {
|
for (let i = 0; i < joined.length; i++) {
|
||||||
const ch = joined[i]!;
|
if (joined[i] === undefined) break; // Make TS happy
|
||||||
|
|
||||||
|
const ch = joined[i];
|
||||||
const next = joined[i + 1];
|
const next = joined[i + 1];
|
||||||
|
|
||||||
// Track quoting state to avoid splitting inside quoted strings
|
// Track quoting state to avoid splitting inside quoted strings
|
||||||
@@ -121,7 +123,11 @@ function splitCommands(rawData: string): string[] {
|
|||||||
const inQuote = inSingleQuote || inDoubleQuote || inDollarQuote;
|
const inQuote = inSingleQuote || inDoubleQuote || inDollarQuote;
|
||||||
|
|
||||||
// Split on ;, newline, or CRLF when not inside quotes and not escaped
|
// Split on ;, newline, or CRLF when not inside quotes and not escaped
|
||||||
if (!inQuote && !isEscaped(i) && (ch === ';' || ch === '\n' || (ch === '\r' && next === '\n'))) {
|
if (
|
||||||
|
!inQuote &&
|
||||||
|
!isEscaped(i) &&
|
||||||
|
(ch === ';' || ch === '\n' || (ch === '\r' && next === '\n'))
|
||||||
|
) {
|
||||||
if (ch === '\r') i++; // Skip the \n in \r\n
|
if (ch === '\r') i++; // Skip the \n in \r\n
|
||||||
if (current.trim()) {
|
if (current.trim()) {
|
||||||
commands.push(current.trim());
|
commands.push(current.trim());
|
||||||
|
|||||||
@@ -317,7 +317,8 @@ async function getResponse(
|
|||||||
finalBehavior === 'always' ||
|
finalBehavior === 'always' ||
|
||||||
(finalBehavior === BEHAVIOR_TTL && shouldSendExpired(response, ttl))
|
(finalBehavior === BEHAVIOR_TTL && shouldSendExpired(response, ttl))
|
||||||
) {
|
) {
|
||||||
// NOTE: Render inside this conditional, or we'll get infinite recursion (render->render->...)
|
// Explicitly render the request before send (instead of relying on send() to render) so that we can
|
||||||
|
// preserve the render purpose.
|
||||||
const renderedHttpRequest = await ctx.httpRequest.render({ httpRequest, purpose });
|
const renderedHttpRequest = await ctx.httpRequest.render({ httpRequest, purpose });
|
||||||
response = await ctx.httpRequest.send({ httpRequest: renderedHttpRequest });
|
response = await ctx.httpRequest.send({ httpRequest: renderedHttpRequest });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
|||||||
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
||||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||||
|
import { appInfo } from '../lib/appInfo';
|
||||||
|
import { copyToClipboard } from '../lib/copy';
|
||||||
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
||||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||||
import { showDialog } from '../lib/dialog';
|
import { showDialog } from '../lib/dialog';
|
||||||
@@ -162,6 +164,14 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
|||||||
label: 'Send Request',
|
label: 'Send Request',
|
||||||
onSelect: () => sendRequest(activeRequest.id),
|
onSelect: () => sendRequest(activeRequest.id),
|
||||||
});
|
});
|
||||||
|
if (appInfo.cliVersion != null) {
|
||||||
|
commands.push({
|
||||||
|
key: 'request.copy_cli_send',
|
||||||
|
searchText: `copy cli send yaak request send ${activeRequest.id}`,
|
||||||
|
label: 'Copy CLI Send Command',
|
||||||
|
onSelect: () => copyToClipboard(`yaak request send ${activeRequest.id}`),
|
||||||
|
});
|
||||||
|
}
|
||||||
httpRequestActions.forEach((a, i) => {
|
httpRequestActions.forEach((a, i) => {
|
||||||
commands.push({
|
commands.push({
|
||||||
key: `http_request_action.${i}`,
|
key: `http_request_action.${i}`,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useHttpResponseEvents } from '../hooks/useHttpResponseEvents';
|
|||||||
import { Editor } from './core/Editor/LazyEditor';
|
import { Editor } from './core/Editor/LazyEditor';
|
||||||
import { type EventDetailAction, EventDetailHeader, EventViewer } from './core/EventViewer';
|
import { type EventDetailAction, EventDetailHeader, EventViewer } from './core/EventViewer';
|
||||||
import { EventViewerRow } from './core/EventViewerRow';
|
import { EventViewerRow } from './core/EventViewerRow';
|
||||||
import { HttpMethodTagRaw } from './core/HttpMethodTag';
|
|
||||||
import { HttpStatusTagRaw } from './core/HttpStatusTag';
|
import { HttpStatusTagRaw } from './core/HttpStatusTag';
|
||||||
import { Icon, type IconProps } from './core/Icon';
|
import { Icon, type IconProps } from './core/Icon';
|
||||||
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
|
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
|
||||||
|
|||||||
@@ -41,11 +41,7 @@ function SettingsLicenseCmp() {
|
|||||||
|
|
||||||
case 'trialing':
|
case 'trialing':
|
||||||
return (
|
return (
|
||||||
<Banner color="info" className="@container flex items-center gap-x-5 max-w-xl">
|
<Banner color="info" className="max-w-lg">
|
||||||
<LocalImage
|
|
||||||
src="static/greg.jpeg"
|
|
||||||
className="hidden @sm:block rounded-full h-14 w-14"
|
|
||||||
/>
|
|
||||||
<p className="w-full">
|
<p className="w-full">
|
||||||
<strong>
|
<strong>
|
||||||
{pluralizeCount('day', differenceInDays(check.data.data.end, new Date()))}
|
{pluralizeCount('day', differenceInDays(check.data.data.end, new Date()))}
|
||||||
@@ -55,10 +51,6 @@ function SettingsLicenseCmp() {
|
|||||||
<span className="opacity-50">Personal use is always free, forever.</span>
|
<span className="opacity-50">Personal use is always free, forever.</span>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||||
<Link noUnderline href="mailto:support@yaak.app">
|
|
||||||
Contact Support
|
|
||||||
</Link>
|
|
||||||
<Icon icon="dot" size="sm" color="secondary" />
|
|
||||||
<Link noUnderline href={`https://yaak.app/pricing?s=learn&t=${check.data.status}`}>
|
<Link noUnderline href={`https://yaak.app/pricing?s=learn&t=${check.data.status}`}>
|
||||||
Learn More
|
Learn More
|
||||||
</Link>
|
</Link>
|
||||||
@@ -69,24 +61,16 @@ function SettingsLicenseCmp() {
|
|||||||
|
|
||||||
case 'personal_use':
|
case 'personal_use':
|
||||||
return (
|
return (
|
||||||
<Banner color="notice" className="@container flex items-center gap-x-5 max-w-xl">
|
<Banner color="notice" className="max-w-lg">
|
||||||
<LocalImage
|
|
||||||
src="static/greg.jpeg"
|
|
||||||
className="hidden @sm:block rounded-full h-14 w-14"
|
|
||||||
/>
|
|
||||||
<p className="w-full">
|
<p className="w-full">
|
||||||
Your commercial-use trial has ended.
|
Your commercial-use trial has ended.
|
||||||
<br />
|
<br />
|
||||||
<span className="opacity-50">
|
<span className="opacity-50">
|
||||||
You may continue using Yaak for personal use free, forever.
|
You may continue using Yaak for personal use only.
|
||||||
<br />A license is required for commercial use.
|
<br />A license is required for commercial use.
|
||||||
</span>
|
</span>
|
||||||
<Separator className="my-2" />
|
<Separator className="my-2" />
|
||||||
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
<div className="flex flex-wrap items-center gap-x-2 text-sm text-notice">
|
||||||
<Link noUnderline href="mailto:support@yaak.app">
|
|
||||||
Contact Support
|
|
||||||
</Link>
|
|
||||||
<Icon icon="dot" size="sm" color="secondary" />
|
|
||||||
<Link noUnderline href={`https://yaak.app/pricing?s=learn&t=${check.data.status}`}>
|
<Link noUnderline href={`https://yaak.app/pricing?s=learn&t=${check.data.status}`}>
|
||||||
Learn More
|
Learn More
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -64,19 +64,27 @@ export function SettingsDropdown() {
|
|||||||
onSelect: () => openUrl('https://yaak.app/button/new'),
|
onSelect: () => openUrl('https://yaak.app/button/new'),
|
||||||
},
|
},
|
||||||
{ type: 'separator', label: `Yaak v${appInfo.version}` },
|
{ type: 'separator', label: `Yaak v${appInfo.version}` },
|
||||||
{
|
|
||||||
label: 'Purchase License',
|
|
||||||
color: 'success',
|
|
||||||
hidden: check.data == null || check.data.status === 'active',
|
|
||||||
leftSlot: <Icon icon="circle_dollar_sign" />,
|
|
||||||
onSelect: () => openSettings.mutate('license'),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Check for Updates',
|
label: 'Check for Updates',
|
||||||
leftSlot: <Icon icon="update" />,
|
leftSlot: <Icon icon="update" />,
|
||||||
hidden: !appInfo.featureUpdater,
|
hidden: !appInfo.featureUpdater,
|
||||||
onSelect: () => checkForUpdates.mutate(),
|
onSelect: () => checkForUpdates.mutate(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Purchase License',
|
||||||
|
color: 'success',
|
||||||
|
hidden: check.data == null || check.data.status === 'active',
|
||||||
|
leftSlot: <Icon icon="circle_dollar_sign" />,
|
||||||
|
rightSlot: <Icon icon="external_link" color="success" className="opacity-60" />,
|
||||||
|
onSelect: () => openUrl('https://yaak.app/pricing'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Install CLI',
|
||||||
|
hidden: appInfo.cliVersion != null,
|
||||||
|
leftSlot: <Icon icon="square_terminal" />,
|
||||||
|
rightSlot: <Icon icon="external_link" color="secondary" />,
|
||||||
|
onSelect: () => openUrl('https://yaak.app/docs/cli'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Feedback',
|
label: 'Feedback',
|
||||||
leftSlot: <Icon icon="chat" />,
|
leftSlot: <Icon icon="chat" />,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { invokeCmd } from './tauri';
|
|||||||
export interface AppInfo {
|
export interface AppInfo {
|
||||||
isDev: boolean;
|
isDev: boolean;
|
||||||
version: string;
|
version: string;
|
||||||
|
cliVersion: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
appDataDir: string;
|
appDataDir: string;
|
||||||
appLogDir: string;
|
appLogDir: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user