mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-03 20:00:05 +01:00
Compare commits
7 Commits
yaak-cli-0
...
yaak-cli-0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
851f12f149 | ||
|
|
cc0d31fdbb | ||
|
|
bab4fe899b | ||
|
|
0b250ff5b5 | ||
|
|
fbf0473b20 | ||
|
|
876b7ef454 | ||
|
|
96e8572758 |
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -10166,6 +10166,7 @@ dependencies = [
|
||||
"walkdir",
|
||||
"webbrowser",
|
||||
"yaak",
|
||||
"yaak-api",
|
||||
"yaak-crypto",
|
||||
"yaak-http",
|
||||
"yaak-models",
|
||||
|
||||
@@ -32,6 +32,7 @@ walkdir = "2"
|
||||
webbrowser = "1"
|
||||
zip = "4"
|
||||
yaak = { workspace = true }
|
||||
yaak-api = { workspace = true }
|
||||
yaak-crypto = { workspace = true }
|
||||
yaak-http = { workspace = true }
|
||||
yaak-models = { workspace = true }
|
||||
|
||||
@@ -47,6 +47,14 @@ pub enum Commands {
|
||||
#[command(hide = true)]
|
||||
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(SendArgs),
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ use crate::utils::http;
|
||||
use keyring::Entry;
|
||||
use rand::Rng;
|
||||
use rolldown::{
|
||||
Bundler, BundlerOptions, ExperimentalOptions, InputItem, LogLevel, OutputFormat, Platform,
|
||||
WatchOption, Watcher,
|
||||
BundleEvent, Bundler, BundlerOptions, ExperimentalOptions, InputItem, LogLevel, OutputFormat,
|
||||
Platform, WatchOption, Watcher, WatcherEvent,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashSet;
|
||||
@@ -114,12 +114,53 @@ async fn dev(args: PluginPathArg) -> CommandResult {
|
||||
ensure_plugin_build_inputs(&plugin_dir)?;
|
||||
|
||||
ui::info(&format!("Watching plugin {}...", plugin_dir.display()));
|
||||
ui::info("Press Ctrl-C to stop");
|
||||
|
||||
let bundler = Bundler::new(bundler_options(&plugin_dir, true))
|
||||
.map_err(|err| format!("Failed to initialize Rolldown watcher: {err}"))?;
|
||||
let watcher = Watcher::new(vec![Arc::new(Mutex::new(bundler))], None)
|
||||
.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;
|
||||
Ok(())
|
||||
|
||||
@@ -31,18 +31,13 @@ pub fn run(ctx: &CliContext, args: WorkspaceArgs) -> i32 {
|
||||
}
|
||||
|
||||
fn schema(pretty: bool) -> CommandResult {
|
||||
let mut schema =
|
||||
serde_json::to_value(schema_for!(Workspace)).map_err(|e| format!(
|
||||
"Failed to serialize workspace schema: {e}"
|
||||
))?;
|
||||
let mut schema = serde_json::to_value(schema_for!(Workspace))
|
||||
.map_err(|e| format!("Failed to serialize workspace schema: {e}"))?;
|
||||
append_agent_hints(&mut schema);
|
||||
|
||||
let output = if pretty {
|
||||
serde_json::to_string_pretty(&schema)
|
||||
} else {
|
||||
serde_json::to_string(&schema)
|
||||
}
|
||||
.map_err(|e| format!("Failed to format workspace schema JSON: {e}"))?;
|
||||
let output =
|
||||
if pretty { serde_json::to_string_pretty(&schema) } else { serde_json::to_string(&schema) }
|
||||
.map_err(|e| format!("Failed to format workspace schema JSON: {e}"))?;
|
||||
println!("{output}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ mod plugin_events;
|
||||
mod ui;
|
||||
mod utils;
|
||||
mod version;
|
||||
mod version_check;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, RequestCommands};
|
||||
@@ -32,6 +33,8 @@ async fn main() {
|
||||
dirs::data_dir().expect("Could not determine data directory").join(app_id)
|
||||
});
|
||||
|
||||
version_check::maybe_check_for_updates().await;
|
||||
|
||||
let needs_context = matches!(
|
||||
&command,
|
||||
Commands::Send(_)
|
||||
@@ -60,6 +63,8 @@ async fn main() {
|
||||
Commands::Plugin(args) => commands::plugin::run(args).await,
|
||||
Commands::Build(args) => commands::plugin::run_build(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::run(
|
||||
context.as_ref().expect("context initialized for send"),
|
||||
|
||||
@@ -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) {
|
||||
if io::stdout().is_terminal() {
|
||||
println!("{:<8} {}", style("SUCCESS").green().bold(), style(message).green());
|
||||
|
||||
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("\"x-yaak-agent-hints\""))
|
||||
.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\""));
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ use tauri_plugin_window_state::{AppHandleExt, StateFlags};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::task::block_in_place;
|
||||
use tokio::time;
|
||||
use yaak_common::command::new_checked_command;
|
||||
use yaak_crypto::manager::EncryptionManager;
|
||||
use yaak_grpc::manager::{GrpcConfig, GrpcHandle};
|
||||
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
|
||||
@@ -97,6 +98,7 @@ impl<R: Runtime> PluginContextExt<R> for WebviewWindow<R> {
|
||||
struct AppMetaData {
|
||||
is_dev: bool,
|
||||
version: String,
|
||||
cli_version: Option<String>,
|
||||
name: String,
|
||||
app_data_dir: String,
|
||||
app_log_dir: String,
|
||||
@@ -113,9 +115,11 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
||||
let vendored_plugin_dir =
|
||||
app_handle.path().resolve("vendored/plugins", BaseDirectory::Resource)?;
|
||||
let default_project_dir = app_handle.path().home_dir()?.join("YaakProjects");
|
||||
let cli_version = detect_cli_version().await;
|
||||
Ok(AppMetaData {
|
||||
is_dev: is_dev(),
|
||||
version: app_handle.package_info().version.to_string(),
|
||||
cli_version,
|
||||
name: app_handle.package_info().name.to_string(),
|
||||
app_data_dir: app_data_dir.to_string_lossy().to_string(),
|
||||
app_log_dir: app_log_dir.to_string_lossy().to_string(),
|
||||
@@ -126,6 +130,28 @@ async fn cmd_metadata(app_handle: AppHandle) -> YaakResult<AppMetaData> {
|
||||
})
|
||||
}
|
||||
|
||||
async fn detect_cli_version() -> Option<String> {
|
||||
// Prefer `yaak`, but support the legacy `yaakcli` alias if present.
|
||||
if let Some(version) = detect_cli_version_for_binary("yaak").await {
|
||||
return Some(version);
|
||||
}
|
||||
detect_cli_version_for_binary("yaakcli").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]
|
||||
async fn cmd_template_tokens_to_string<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||
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_models::util::UpdateSource;
|
||||
|
||||
@@ -102,7 +102,7 @@ impl YaakNotifier {
|
||||
|
||||
let launch_info = get_or_upsert_launch_info(app_handle);
|
||||
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")
|
||||
.query(&[
|
||||
("version", &launch_info.current_version),
|
||||
|
||||
@@ -118,7 +118,7 @@ async fn handle_host_plugin_request<R: Runtime>(
|
||||
&InternalEventPayload::ShowToastRequest(ShowToastRequest {
|
||||
message: format!("Reloaded plugin {}@{}", info.name, info.version),
|
||||
icon: Some(Icon::Info),
|
||||
timeout: Some(3000),
|
||||
timeout: Some(5000),
|
||||
..Default::default()
|
||||
}),
|
||||
None,
|
||||
|
||||
@@ -21,7 +21,7 @@ use tauri::{
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
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::util::UpdateSource;
|
||||
use yaak_plugins::api::{
|
||||
@@ -73,7 +73,7 @@ impl PluginUpdater {
|
||||
info!("Checking for plugin updates");
|
||||
|
||||
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 updates = check_plugin_updates(&http_client, plugins.clone()).await?;
|
||||
|
||||
@@ -138,7 +138,7 @@ pub async fn cmd_plugins_search<R: Runtime>(
|
||||
query: &str,
|
||||
) -> Result<PluginSearchResponse> {
|
||||
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?)
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ pub async fn cmd_plugins_install<R: Runtime>(
|
||||
) -> Result<()> {
|
||||
let plugin_manager = Arc::new((*window.state::<PluginManager>()).clone());
|
||||
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 plugin_context = window.plugin_context();
|
||||
download_and_install(
|
||||
@@ -203,7 +203,7 @@ pub async fn cmd_plugins_updates<R: Runtime>(
|
||||
app_handle: AppHandle<R>,
|
||||
) -> Result<PluginUpdatesResponse> {
|
||||
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()?;
|
||||
Ok(check_plugin_updates(&http_client, plugins).await?)
|
||||
}
|
||||
@@ -213,7 +213,7 @@ pub async fn cmd_plugins_update_all<R: Runtime>(
|
||||
window: WebviewWindow<R>,
|
||||
) -> Result<Vec<PluginNameVersion>> {
|
||||
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()?;
|
||||
|
||||
// Get list of available updates (already filtered to only registry plugins)
|
||||
|
||||
@@ -8,7 +8,7 @@ use std::fs;
|
||||
use std::sync::Arc;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, Url};
|
||||
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_plugins::events::{Color, ShowToastRequest};
|
||||
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 query_manager = app_handle.db_manager();
|
||||
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 pv = download_and_install(
|
||||
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 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 p = app_handle
|
||||
.path()
|
||||
|
||||
@@ -7,7 +7,7 @@ use std::ops::Add;
|
||||
use std::time::Duration;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow, is_dev};
|
||||
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_models::db_context::DbContext;
|
||||
use yaak_models::query_manager::QueryManager;
|
||||
@@ -119,7 +119,7 @@ pub async fn activate_license<R: Runtime>(
|
||||
) -> Result<()> {
|
||||
info!("Activating license {}", license_key);
|
||||
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 {
|
||||
license_key: license_key.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 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 payload =
|
||||
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, _) => {
|
||||
info!("Checking license activation");
|
||||
// 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 response = client.post(build_url(&path)).json(&payload).send().await?;
|
||||
|
||||
|
||||
@@ -8,14 +8,24 @@ use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use std::time::Duration;
|
||||
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.
|
||||
///
|
||||
/// Includes a custom User-Agent, JSON accept header, 20s timeout, gzip,
|
||||
/// 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 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();
|
||||
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")]
|
||||
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
|
||||
}
|
||||
|
||||
/// 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::Result;
|
||||
use std::path::Path;
|
||||
use std::process::Stdio;
|
||||
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
|
||||
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)
|
||||
pub(crate) async fn new_binary_command_global() -> Result<Command> {
|
||||
// 1. Probe that `git` exists and is runnable
|
||||
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)
|
||||
new_checked_command("git", "--version").await.map_err(|_| GitNotFound)
|
||||
}
|
||||
|
||||
@@ -16,7 +16,8 @@ function getBinaryPath() {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -15,6 +15,7 @@ function getBinaryPath() {
|
||||
|
||||
module.exports.runBinary = function runBinary(...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": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.4.0",
|
||||
"@yaakapp/cli": "^0.5.1",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
@@ -4303,9 +4303,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@yaakapp/cli": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.4.0.tgz",
|
||||
"integrity": "sha512-8xnu2oFWlgV+xeIAHMuEgsqX6Sxq4UYrSH2WbafwDLbSep6fxpO74tiBH7xp4wakt/7Bcy9a2Q5R9nkAc1ZUdA==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli/-/cli-0.5.1.tgz",
|
||||
"integrity": "sha512-kAhX9SvjAiEsg2xwCuyuEOJRyEIg7jEGzGFCGzWy9I9Ew2hD0huIDGy9l4IMJUR84gh1V/62dxeiZREptWpIFg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@@ -4313,18 +4313,18 @@
|
||||
"yaakcli": "bin/cli.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@yaakapp/cli-darwin-arm64": "0.4.0",
|
||||
"@yaakapp/cli-darwin-x64": "0.4.0",
|
||||
"@yaakapp/cli-linux-arm64": "0.4.0",
|
||||
"@yaakapp/cli-linux-x64": "0.4.0",
|
||||
"@yaakapp/cli-win32-arm64": "0.4.0",
|
||||
"@yaakapp/cli-win32-x64": "0.4.0"
|
||||
"@yaakapp/cli-darwin-arm64": "0.5.1",
|
||||
"@yaakapp/cli-darwin-x64": "0.5.1",
|
||||
"@yaakapp/cli-linux-arm64": "0.5.1",
|
||||
"@yaakapp/cli-linux-x64": "0.5.1",
|
||||
"@yaakapp/cli-win32-arm64": "0.5.1",
|
||||
"@yaakapp/cli-win32-x64": "0.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@yaakapp/cli-darwin-arm64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.4.0.tgz",
|
||||
"integrity": "sha512-bl8+VQNPMabXNGQCa7u6w0JGe3CmzYZPsGE8Q+5wGSxa3trGf1bmq/fMW5JXrMi1P7Laepnyad0TGGP/2C8uwQ==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-arm64/-/cli-darwin-arm64-0.5.1.tgz",
|
||||
"integrity": "sha512-08FJ35vYGUXVC5r4/kLchEUI8YJN0iiB6KPZ9NuNHD0QmEWAN1451roUrXzz+dGFUw9tLb7HDGuZ6c4YL4Va7A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4335,9 +4335,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-darwin-x64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.4.0.tgz",
|
||||
"integrity": "sha512-R+ETXNBWvmA3W88ZoTk/JtG/PZaUb85y3SwBgMbwcgdhBVwNS/g+DbCspcTFI5zs8Txsf5VuiFU+dW9M9olZ6A==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-darwin-x64/-/cli-darwin-x64-0.5.1.tgz",
|
||||
"integrity": "sha512-UqN9Bn2Z5Ns9ATWWQyvhlCJ3qdk1rM5b9CbGzV61F/LkfcPbvBuTFGAWprQVTun7iy7PjI35R6Cfj126+Z/ehA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4348,9 +4348,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-linux-arm64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.4.0.tgz",
|
||||
"integrity": "sha512-Pf7VyQf4r85FsI0qYnnst7URQF8/RxSZZj79cXLai0FnN3fDiypX4CmHx765bJxgfQZlBvqVmvPAaMW/TeiJEQ==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-arm64/-/cli-linux-arm64-0.5.1.tgz",
|
||||
"integrity": "sha512-YdQXNNPLSzkmwWEqnv2V8C8Bl9atQFQYI3FtPTYa2Ljp54omgMxuikn0gauhsHFMtFg8GOStVEENbBFW0W0Ovg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4361,9 +4361,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-linux-x64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.4.0.tgz",
|
||||
"integrity": "sha512-bYWWfHAIW81A+ydJChjH1Qo3+aihz9gFLh7/9MOa6CJgnC6H3V5cnapmh50Hddt9l5ic02aA1FB8ORQOXxb01A==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-linux-x64/-/cli-linux-x64-0.5.1.tgz",
|
||||
"integrity": "sha512-03sVYmD3ksH6lJr6YEwqEQCADoVP5fzq6vBkoCBSrQOj9iInk06DuFHME7IjEa7uKXBaF4WeUms/yQEA03jHBA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -4374,9 +4374,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-win32-arm64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.4.0.tgz",
|
||||
"integrity": "sha512-8X12xkyidyYZ5vtarZGFSYR6HJbUMFUsNxYPNQccnYJIY+soNkjJHOWDjaRvBzCbR8MLT9N04Y5PE/Jv20gXpA==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-arm64/-/cli-win32-arm64-0.5.1.tgz",
|
||||
"integrity": "sha512-bYBe0PpgvjEx/4jvDKOA9/Kg9qzAP1NmUCcPWZueOJItqPdPk2b/1/7pX1eeoh80Qsj7nSmz5PbJaxkygfa0IQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -4387,9 +4387,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@yaakapp/cli-win32-x64": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.4.0.tgz",
|
||||
"integrity": "sha512-wansfrCCycFcFclowQQxfsNLIAyATyqnnbITED5gUfUrBf8NFHrG0sWVCWlXUhHU7YvpmqL7CsdtlMkIGiZCPQ==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@yaakapp/cli-win32-x64/-/cli-win32-x64-0.5.1.tgz",
|
||||
"integrity": "sha512-Mx/LelqQ7X8Fz9og3qOauuU8kemqja5kQzVQ0pHDJhb9bbmhXUK4dSBYTLdkW3a/X+ofo3K5Z/PkS7FMyXxwdA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@yaakapp/cli": "^0.4.0",
|
||||
"@yaakapp/cli": "^0.5.1",
|
||||
"dotenv-cli": "^11.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"nodejs-file-downloader": "^4.13.0",
|
||||
|
||||
@@ -76,10 +76,10 @@ export class PluginInstance {
|
||||
this.#mod = {};
|
||||
|
||||
const fileChangeCallback = async () => {
|
||||
await this.#mod?.dispose?.();
|
||||
this.#importModule();
|
||||
const ctx = this.#newCtx(workerData.context);
|
||||
try {
|
||||
await this.#mod?.dispose?.();
|
||||
this.#importModule();
|
||||
await this.#mod?.init?.(ctx);
|
||||
this.#sendPayload(
|
||||
workerData.context,
|
||||
@@ -90,7 +90,7 @@ export class PluginInstance {
|
||||
null,
|
||||
);
|
||||
} catch (err: unknown) {
|
||||
ctx.toast.show({
|
||||
await ctx.toast.show({
|
||||
message: `Failed to initialize plugin ${this.#workerData.bootRequest.dir.split('/').pop()}: ${err}`,
|
||||
color: 'notice',
|
||||
icon: 'alert_triangle',
|
||||
@@ -1003,6 +1003,7 @@ function watchFile(filepath: string, cb: () => void) {
|
||||
const stat = statSync(filepath, { throwIfNoEntry: false });
|
||||
if (stat == null || stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) {
|
||||
watchedFiles[filepath] = stat ?? null;
|
||||
console.log('[plugin-runtime] watchFile triggered', filepath);
|
||||
cb();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -13,12 +13,16 @@ describe('template-function-faker', () => {
|
||||
it('renders date results as unquoted ISO strings', async () => {
|
||||
const { plugin } = await import('../src/index');
|
||||
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!(
|
||||
{} as Parameters<NonNullable<typeof fn.onRender>>[0],
|
||||
{ values: {} } as Parameters<NonNullable<typeof fn.onRender>>[1],
|
||||
const result = await onRender(
|
||||
{} as Parameters<typeof onRender>[0],
|
||||
{ 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$/);
|
||||
|
||||
@@ -206,7 +206,10 @@ export const plugin: PluginDefinition = {
|
||||
// Create snippet generator
|
||||
const snippet = new HTTPSnippet(harRequest);
|
||||
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');
|
||||
};
|
||||
|
||||
|
||||
@@ -82,7 +82,9 @@ function splitCommands(rawData: string): string[] {
|
||||
let inDollarQuote = false;
|
||||
|
||||
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];
|
||||
|
||||
// Track quoting state to avoid splitting inside quoted strings
|
||||
@@ -121,7 +123,11 @@ function splitCommands(rawData: string): string[] {
|
||||
const inQuote = inSingleQuote || inDoubleQuote || inDollarQuote;
|
||||
|
||||
// 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 (current.trim()) {
|
||||
commands.push(current.trim());
|
||||
|
||||
@@ -32,6 +32,8 @@ import { useRecentWorkspaces } from '../hooks/useRecentWorkspaces';
|
||||
import { useScrollIntoView } from '../hooks/useScrollIntoView';
|
||||
import { useSendAnyHttpRequest } from '../hooks/useSendAnyHttpRequest';
|
||||
import { useSidebarHidden } from '../hooks/useSidebarHidden';
|
||||
import { appInfo } from '../lib/appInfo';
|
||||
import { copyToClipboard } from '../lib/copy';
|
||||
import { createRequestAndNavigate } from '../lib/createRequestAndNavigate';
|
||||
import { deleteModelWithConfirm } from '../lib/deleteModelWithConfirm';
|
||||
import { showDialog } from '../lib/dialog';
|
||||
@@ -162,6 +164,14 @@ export function CommandPaletteDialog({ onClose }: { onClose: () => void }) {
|
||||
label: 'Send Request',
|
||||
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) => {
|
||||
commands.push({
|
||||
key: `http_request_action.${i}`,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useHttpResponseEvents } from '../hooks/useHttpResponseEvents';
|
||||
import { Editor } from './core/Editor/LazyEditor';
|
||||
import { type EventDetailAction, EventDetailHeader, EventViewer } from './core/EventViewer';
|
||||
import { EventViewerRow } from './core/EventViewerRow';
|
||||
import { HttpMethodTagRaw } from './core/HttpMethodTag';
|
||||
import { HttpStatusTagRaw } from './core/HttpStatusTag';
|
||||
import { Icon, type IconProps } from './core/Icon';
|
||||
import { KeyValueRow, KeyValueRows } from './core/KeyValueRow';
|
||||
|
||||
@@ -4,6 +4,7 @@ import { invokeCmd } from './tauri';
|
||||
export interface AppInfo {
|
||||
isDev: boolean;
|
||||
version: string;
|
||||
cliVersion: string | null;
|
||||
name: string;
|
||||
appDataDir: string;
|
||||
appLogDir: string;
|
||||
|
||||
Reference in New Issue
Block a user