mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-21 00:49:17 +01:00
CLI plugin host: handle send/render HTTP requests (#415)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,10 @@ pub struct Cli {
|
||||
#[arg(long, short, global = true)]
|
||||
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)
|
||||
#[arg(long, short, global = true)]
|
||||
pub verbose: bool,
|
||||
@@ -58,6 +62,9 @@ pub enum Commands {
|
||||
/// Send a request, folder, or workspace by ID
|
||||
Send(SendArgs),
|
||||
|
||||
/// Cookie jar commands
|
||||
CookieJar(CookieJarArgs),
|
||||
|
||||
/// Workspace commands
|
||||
Workspace(WorkspaceArgs),
|
||||
|
||||
@@ -85,6 +92,22 @@ pub struct SendArgs {
|
||||
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)]
|
||||
#[command(disable_help_subcommand = true)]
|
||||
pub struct WorkspaceArgs {
|
||||
@@ -158,8 +181,8 @@ pub struct RequestArgs {
|
||||
pub enum RequestCommands {
|
||||
/// List requests in a workspace
|
||||
List {
|
||||
/// Workspace ID
|
||||
workspace_id: String,
|
||||
/// Workspace ID (optional when exactly one workspace exists)
|
||||
workspace_id: Option<String>,
|
||||
},
|
||||
|
||||
/// Show a request as JSON
|
||||
@@ -267,8 +290,8 @@ pub struct FolderArgs {
|
||||
pub enum FolderCommands {
|
||||
/// List folders in a workspace
|
||||
List {
|
||||
/// Workspace ID
|
||||
workspace_id: String,
|
||||
/// Workspace ID (optional when exactly one workspace exists)
|
||||
workspace_id: Option<String>,
|
||||
},
|
||||
|
||||
/// Show a folder as JSON
|
||||
@@ -324,8 +347,8 @@ pub struct EnvironmentArgs {
|
||||
pub enum EnvironmentCommands {
|
||||
/// List environments in a workspace
|
||||
List {
|
||||
/// Workspace ID
|
||||
workspace_id: String,
|
||||
/// Workspace ID (optional when exactly one workspace exists)
|
||||
workspace_id: Option<String>,
|
||||
},
|
||||
|
||||
/// Output JSON schema for environment create/update payloads
|
||||
@@ -421,6 +444,9 @@ pub enum PluginCommands {
|
||||
/// Generate a "Hello World" Yaak plugin
|
||||
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(PluginPathArg),
|
||||
}
|
||||
@@ -441,3 +467,9 @@ pub struct GenerateArgs {
|
||||
#[arg(long)]
|
||||
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,
|
||||
};
|
||||
use crate::utils::schema::append_agent_hints;
|
||||
use crate::utils::workspace::resolve_workspace_id;
|
||||
use schemars::schema_for;
|
||||
use yaak_models::models::Environment;
|
||||
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 {
|
||||
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::Show { environment_id } => show(ctx, &environment_id),
|
||||
EnvironmentCommands::Create { workspace_id, name, json } => {
|
||||
@@ -45,10 +46,11 @@ fn schema(pretty: bool) -> CommandResult {
|
||||
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
|
||||
.db()
|
||||
.list_environments_ensure_base(workspace_id)
|
||||
.list_environments_ensure_base(&workspace_id)
|
||||
.map_err(|e| format!("Failed to list environments: {e}"))?;
|
||||
|
||||
if environments.is_empty() {
|
||||
@@ -92,8 +94,14 @@ fn create(
|
||||
validate_create_id(&payload, "environment")?;
|
||||
let mut environment: Environment = serde_json::from_value(payload)
|
||||
.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(
|
||||
workspace_id_arg.as_deref(),
|
||||
workspace_id_arg.as_deref().or(fallback_workspace_id.as_deref()),
|
||||
&mut environment.workspace_id,
|
||||
"environment create",
|
||||
)?;
|
||||
@@ -111,9 +119,8 @@ fn create(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let workspace_id = workspace_id_arg.ok_or_else(|| {
|
||||
"environment create requires workspace_id unless JSON payload is provided".to_string()
|
||||
})?;
|
||||
let workspace_id =
|
||||
resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "environment create")?;
|
||||
let name = name.ok_or_else(|| {
|
||||
"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,
|
||||
parse_required_json, require_id, validate_create_id,
|
||||
};
|
||||
use crate::utils::workspace::resolve_workspace_id;
|
||||
use yaak_models::models::Folder;
|
||||
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 {
|
||||
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::Create { 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 =
|
||||
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() {
|
||||
println!("No folders found in workspace {}", workspace_id);
|
||||
} else {
|
||||
@@ -72,8 +74,14 @@ fn create(
|
||||
validate_create_id(&payload, "folder")?;
|
||||
let mut folder: Folder = serde_json::from_value(payload)
|
||||
.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(
|
||||
workspace_id_arg.as_deref(),
|
||||
workspace_id_arg.as_deref().or(fallback_workspace_id.as_deref()),
|
||||
&mut folder.workspace_id,
|
||||
"folder create",
|
||||
)?;
|
||||
@@ -87,9 +95,7 @@ fn create(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let workspace_id = workspace_id_arg.ok_or_else(|| {
|
||||
"folder create requires workspace_id unless JSON payload is provided".to_string()
|
||||
})?;
|
||||
let workspace_id = resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "folder create")?;
|
||||
let name = name.ok_or_else(|| {
|
||||
"folder create requires --name unless JSON payload is provided".to_string()
|
||||
})?;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod auth;
|
||||
pub mod cookie_jar;
|
||||
pub mod environment;
|
||||
pub mod folder;
|
||||
pub mod plugin;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::cli::{GenerateArgs, PluginArgs, PluginCommands, PluginPathArg};
|
||||
use crate::cli::{GenerateArgs, InstallPluginArgs, PluginPathArg};
|
||||
use crate::context::CliContext;
|
||||
use crate::ui;
|
||||
use crate::utils::http;
|
||||
use keyring::Entry;
|
||||
@@ -15,6 +16,11 @@ use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
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::write::SimpleFileOptions;
|
||||
|
||||
@@ -57,12 +63,13 @@ pub async fn run_build(args: PluginPathArg) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(args: PluginArgs) -> i32 {
|
||||
match args.command {
|
||||
PluginCommands::Build(args) => run_build(args).await,
|
||||
PluginCommands::Dev(args) => run_dev(args).await,
|
||||
PluginCommands::Generate(args) => run_generate(args).await,
|
||||
PluginCommands::Publish(args) => run_publish(args).await,
|
||||
pub async fn run_install(context: &CliContext, args: InstallPluginArgs) -> i32 {
|
||||
match install(context, args).await {
|
||||
Ok(()) => 0,
|
||||
Err(error) => {
|
||||
ui::error(&error);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,6 +257,113 @@ async fn publish(args: PluginPathArg) -> CommandResult {
|
||||
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)]
|
||||
struct PublishResponse {
|
||||
version: String,
|
||||
|
||||
@@ -6,6 +6,7 @@ use crate::utils::json::{
|
||||
parse_required_json, require_id, validate_create_id,
|
||||
};
|
||||
use crate::utils::schema::append_agent_hints;
|
||||
use crate::utils::workspace::resolve_workspace_id;
|
||||
use schemars::schema_for;
|
||||
use serde_json::{Map, Value, json};
|
||||
use std::collections::HashMap;
|
||||
@@ -24,13 +25,16 @@ pub async fn run(
|
||||
ctx: &CliContext,
|
||||
args: RequestArgs,
|
||||
environment: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> i32 {
|
||||
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::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,
|
||||
Err(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
|
||||
.db()
|
||||
.list_http_requests(workspace_id)
|
||||
.list_http_requests(&workspace_id)
|
||||
.map_err(|e| format!("Failed to list requests: {e}"))?;
|
||||
if requests.is_empty() {
|
||||
println!("No requests found in workspace {}", workspace_id);
|
||||
@@ -350,8 +355,14 @@ fn create(
|
||||
validate_create_id(&payload, "request")?;
|
||||
let mut request: HttpRequest = serde_json::from_value(payload)
|
||||
.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(
|
||||
workspace_id_arg.as_deref(),
|
||||
workspace_id_arg.as_deref().or(fallback_workspace_id.as_deref()),
|
||||
&mut request.workspace_id,
|
||||
"request create",
|
||||
)?;
|
||||
@@ -365,9 +376,7 @@ fn create(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let workspace_id = workspace_id_arg.ok_or_else(|| {
|
||||
"request create requires workspace_id unless JSON payload is provided".to_string()
|
||||
})?;
|
||||
let workspace_id = resolve_workspace_id(ctx, workspace_id_arg.as_deref(), "request create")?;
|
||||
let name = name.unwrap_or_default();
|
||||
let url = url.unwrap_or_default();
|
||||
let method = method.unwrap_or_else(|| "GET".to_string());
|
||||
@@ -436,6 +445,7 @@ pub async fn send_request_by_id(
|
||||
ctx: &CliContext,
|
||||
request_id: &str,
|
||||
environment: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> Result<(), String> {
|
||||
let request =
|
||||
@@ -447,6 +457,7 @@ pub async fn send_request_by_id(
|
||||
&http_request.id,
|
||||
&http_request.workspace_id,
|
||||
environment,
|
||||
cookie_jar_id,
|
||||
verbose,
|
||||
)
|
||||
.await
|
||||
@@ -465,9 +476,13 @@ async fn send_http_request_by_id(
|
||||
request_id: &str,
|
||||
workspace_id: &str,
|
||||
environment: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> 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 (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,
|
||||
environment_id: environment,
|
||||
update_source: UpdateSource::Sync,
|
||||
cookie_jar_id: None,
|
||||
cookie_jar_id,
|
||||
response_dir: &response_dir,
|
||||
emit_events_to: Some(event_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())?;
|
||||
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::context::CliContext;
|
||||
use futures::future::join_all;
|
||||
use yaak_models::queries::any_request::AnyRequest;
|
||||
|
||||
enum ExecutionMode {
|
||||
Sequential,
|
||||
@@ -12,9 +13,10 @@ pub async fn run(
|
||||
ctx: &CliContext,
|
||||
args: SendArgs,
|
||||
environment: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> i32 {
|
||||
match send_target(ctx, args, environment, verbose).await {
|
||||
match send_target(ctx, args, environment, cookie_jar_id, verbose).await {
|
||||
Ok(()) => 0,
|
||||
Err(error) => {
|
||||
eprintln!("Error: {error}");
|
||||
@@ -27,30 +29,70 @@ async fn send_target(
|
||||
ctx: &CliContext,
|
||||
args: SendArgs,
|
||||
environment: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> Result<(), String> {
|
||||
let mode = if args.parallel { ExecutionMode::Parallel } else { ExecutionMode::Sequential };
|
||||
|
||||
if ctx.db().get_any_request(&args.id).is_ok() {
|
||||
return request::send_request_by_id(ctx, &args.id, environment, verbose).await;
|
||||
if let Ok(request) = ctx.db().get_any_request(&args.id) {
|
||||
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)?;
|
||||
if request_ids.is_empty() {
|
||||
println!("No requests found in folder {}", args.id);
|
||||
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)?;
|
||||
if request_ids.is_empty() {
|
||||
println!("No requests found in workspace {}", args.id);
|
||||
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))
|
||||
@@ -131,6 +173,7 @@ async fn send_many(
|
||||
mode: ExecutionMode,
|
||||
fail_fast: bool,
|
||||
environment: Option<&str>,
|
||||
cookie_jar_id: Option<&str>,
|
||||
verbose: bool,
|
||||
) -> Result<(), String> {
|
||||
let mut success_count = 0usize;
|
||||
@@ -139,7 +182,15 @@ async fn send_many(
|
||||
match mode {
|
||||
ExecutionMode::Sequential => {
|
||||
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,
|
||||
Err(error) => {
|
||||
failures.push((request_id, error));
|
||||
@@ -156,7 +207,14 @@ async fn send_many(
|
||||
.map(|request_id| async move {
|
||||
(
|
||||
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<_>>();
|
||||
|
||||
@@ -18,6 +18,14 @@ const EMBEDDED_PLUGIN_RUNTIME: &str = include_str!(concat!(
|
||||
static EMBEDDED_VENDORED_PLUGINS: Dir<'_> =
|
||||
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 {
|
||||
data_dir: PathBuf,
|
||||
query_manager: QueryManager,
|
||||
@@ -28,63 +36,72 @@ pub struct CliContext {
|
||||
}
|
||||
|
||||
impl CliContext {
|
||||
pub async fn initialize(data_dir: PathBuf, app_id: &str, with_plugins: bool) -> Self {
|
||||
pub fn new(data_dir: PathBuf, app_id: &str) -> Self {
|
||||
let db_path = data_dir.join("db.sqlite");
|
||||
let blob_path = data_dir.join("blobs.sqlite");
|
||||
|
||||
let (query_manager, blob_manager, _rx) = yaak_models::init_standalone(&db_path, &blob_path)
|
||||
.expect("Failed to initialize database");
|
||||
|
||||
let encryption_manager = Arc::new(EncryptionManager::new(query_manager.clone(), app_id));
|
||||
|
||||
let plugin_manager = if with_plugins {
|
||||
let vendored_plugin_dir = data_dir.join("vendored-plugins");
|
||||
let installed_plugin_dir = data_dir.join("installed-plugins");
|
||||
let node_bin_path = PathBuf::from("node");
|
||||
|
||||
prepare_embedded_vendored_plugins(&vendored_plugin_dir)
|
||||
.expect("Failed to prepare bundled plugins");
|
||||
|
||||
let plugin_runtime_main =
|
||||
std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| {
|
||||
prepare_embedded_plugin_runtime(&data_dir)
|
||||
.expect("Failed to prepare embedded plugin runtime")
|
||||
});
|
||||
|
||||
match PluginManager::new(
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
node_bin_path,
|
||||
plugin_runtime_main,
|
||||
&query_manager,
|
||||
&PluginContext::new_empty(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(plugin_manager) => Some(Arc::new(plugin_manager)),
|
||||
let (query_manager, blob_manager, _rx) =
|
||||
match yaak_models::init_standalone(&db_path, &blob_path) {
|
||||
Ok(v) => v,
|
||||
Err(err) => {
|
||||
eprintln!("Warning: Failed to initialize plugins: {err}");
|
||||
None
|
||||
eprintln!("Error: Failed to initialize database: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let plugin_event_bridge = if let Some(plugin_manager) = &plugin_manager {
|
||||
Some(CliPluginEventBridge::start(plugin_manager.clone(), query_manager.clone()).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
};
|
||||
let encryption_manager =
|
||||
Arc::new(EncryptionManager::new(query_manager.clone(), app_id));
|
||||
|
||||
Self {
|
||||
data_dir,
|
||||
query_manager,
|
||||
blob_manager,
|
||||
encryption_manager,
|
||||
plugin_manager,
|
||||
plugin_event_bridge: Mutex::new(plugin_event_bridge),
|
||||
plugin_manager: None,
|
||||
plugin_event_bridge: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init_plugins(&mut self, execution_context: CliExecutionContext) {
|
||||
let vendored_plugin_dir = self.data_dir.join("vendored-plugins");
|
||||
let installed_plugin_dir = self.data_dir.join("installed-plugins");
|
||||
let node_bin_path = PathBuf::from("node");
|
||||
|
||||
prepare_embedded_vendored_plugins(&vendored_plugin_dir)
|
||||
.expect("Failed to prepare bundled plugins");
|
||||
|
||||
let plugin_runtime_main =
|
||||
std::env::var("YAAK_PLUGIN_RUNTIME").map(PathBuf::from).unwrap_or_else(|_| {
|
||||
prepare_embedded_plugin_runtime(&self.data_dir)
|
||||
.expect("Failed to prepare embedded plugin runtime")
|
||||
});
|
||||
|
||||
match PluginManager::new(
|
||||
vendored_plugin_dir,
|
||||
installed_plugin_dir,
|
||||
node_bin_path,
|
||||
plugin_runtime_main,
|
||||
&self.query_manager,
|
||||
&PluginContext::new_empty(),
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(plugin_manager) => {
|
||||
let plugin_manager = Arc::new(plugin_manager);
|
||||
let plugin_event_bridge = CliPluginEventBridge::start(
|
||||
plugin_manager.clone(),
|
||||
self.query_manager.clone(),
|
||||
self.blob_manager.clone(),
|
||||
self.encryption_manager.clone(),
|
||||
self.data_dir.clone(),
|
||||
execution_context,
|
||||
)
|
||||
.await;
|
||||
self.plugin_manager = Some(plugin_manager);
|
||||
*self.plugin_event_bridge.lock().await = Some(plugin_event_bridge);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Warning: Failed to initialize plugins: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ mod version;
|
||||
mod version_check;
|
||||
|
||||
use clap::Parser;
|
||||
use cli::{Cli, Commands, RequestCommands};
|
||||
use context::CliContext;
|
||||
use cli::{Cli, Commands, PluginCommands, RequestCommands};
|
||||
use context::{CliContext, CliExecutionContext};
|
||||
use yaak_models::queries::any_request::AnyRequest;
|
||||
|
||||
#[tokio::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 {
|
||||
match log_level {
|
||||
@@ -35,72 +36,209 @@ async fn main() {
|
||||
|
||||
version_check::maybe_check_for_updates().await;
|
||||
|
||||
let needs_context = matches!(
|
||||
&command,
|
||||
Commands::Send(_)
|
||||
| Commands::Workspace(_)
|
||||
| Commands::Request(_)
|
||||
| Commands::Folder(_)
|
||||
| Commands::Environment(_)
|
||||
);
|
||||
|
||||
let needs_plugins = matches!(
|
||||
&command,
|
||||
Commands::Send(_)
|
||||
| Commands::Request(cli::RequestArgs {
|
||||
command: RequestCommands::Send { .. } | RequestCommands::Schema { .. },
|
||||
})
|
||||
);
|
||||
|
||||
let context = if needs_context {
|
||||
Some(CliContext::initialize(data_dir, app_id, needs_plugins).await)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let exit_code = match command {
|
||||
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 mut context = CliContext::new(data_dir.clone(), app_id);
|
||||
context.init_plugins(CliExecutionContext::default()).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::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"),
|
||||
args,
|
||||
let mut context = CliContext::new(data_dir.clone(), app_id);
|
||||
match resolve_send_execution_context(
|
||||
&context,
|
||||
&args.id,
|
||||
environment.as_deref(),
|
||||
verbose,
|
||||
)
|
||||
.await
|
||||
cookie_jar.as_deref(),
|
||||
) {
|
||||
Ok(execution_context) => {
|
||||
context.init_plugins(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::CookieJar(args) => {
|
||||
let context = CliContext::new(data_dir.clone(), app_id);
|
||||
let exit_code = commands::cookie_jar::run(&context, args);
|
||||
context.shutdown().await;
|
||||
exit_code
|
||||
}
|
||||
Commands::Workspace(args) => {
|
||||
let context = CliContext::new(data_dir.clone(), app_id);
|
||||
let exit_code = commands::workspace::run(&context, args);
|
||||
context.shutdown().await;
|
||||
exit_code
|
||||
}
|
||||
Commands::Workspace(args) => commands::workspace::run(
|
||||
context.as_ref().expect("context initialized for workspace"),
|
||||
args,
|
||||
),
|
||||
Commands::Request(args) => {
|
||||
commands::request::run(
|
||||
context.as_ref().expect("context initialized for request"),
|
||||
args,
|
||||
environment.as_deref(),
|
||||
verbose,
|
||||
)
|
||||
.await
|
||||
let mut context = CliContext::new(data_dir.clone(), app_id);
|
||||
let execution_context_result = match &args.command {
|
||||
RequestCommands::Send { request_id } => resolve_request_execution_context(
|
||||
&context,
|
||||
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 { .. }
|
||||
);
|
||||
if with_plugins {
|
||||
context.init_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::run(context.as_ref().expect("context initialized for folder"), args)
|
||||
let context = CliContext::new(data_dir.clone(), app_id);
|
||||
let exit_code = commands::folder::run(&context, args);
|
||||
context.shutdown().await;
|
||||
exit_code
|
||||
}
|
||||
Commands::Environment(args) => {
|
||||
let context = CliContext::new(data_dir.clone(), app_id);
|
||||
let exit_code = commands::environment::run(&context, args);
|
||||
context.shutdown().await;
|
||||
exit_code
|
||||
}
|
||||
Commands::Environment(args) => commands::environment::run(
|
||||
context.as_ref().expect("context initialized for environment"),
|
||||
args,
|
||||
),
|
||||
};
|
||||
|
||||
if let Some(context) = &context {
|
||||
context.shutdown().await;
|
||||
}
|
||||
|
||||
if exit_code != 0 {
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_send_execution_context(
|
||||
context: &CliContext,
|
||||
id: &str,
|
||||
environment: Option<&str>,
|
||||
explicit_cookie_jar_id: Option<&str>,
|
||||
) -> Result<CliExecutionContext, String> {
|
||||
if let Ok(request) = context.db().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(context, &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) = context.db().get_folder(id) {
|
||||
let cookie_jar_id =
|
||||
resolve_cookie_jar_id(context, &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) = context.db().get_workspace(id) {
|
||||
let cookie_jar_id =
|
||||
resolve_cookie_jar_id(context, &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(
|
||||
context: &CliContext,
|
||||
request_id: &str,
|
||||
environment: Option<&str>,
|
||||
explicit_cookie_jar_id: Option<&str>,
|
||||
) -> Result<CliExecutionContext, String> {
|
||||
let request = context
|
||||
.db()
|
||||
.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(context, &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(
|
||||
context: &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 = context
|
||||
.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)
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,3 +2,4 @@ pub mod confirm;
|
||||
pub mod http;
|
||||
pub mod json;
|
||||
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.")),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user