use std::path::PathBuf;
use clap::Args;
use socket_patch_core::api::client::ApiClientEnvOverrides;
use socket_patch_core::constants::{
DEFAULT_PATCH_API_PROXY_URL, DEFAULT_PATCH_MANIFEST_PATH, DEFAULT_SOCKET_API_URL,
};
#[derive(Args, Debug, Clone)]
pub struct GlobalArgs {
#[arg(long, env = "SOCKET_CWD", default_value = ".")]
pub cwd: PathBuf,
#[arg(
long = "manifest-path",
env = "SOCKET_MANIFEST_PATH",
default_value = DEFAULT_PATCH_MANIFEST_PATH,
)]
pub manifest_path: String,
#[arg(
long = "api-url",
env = "SOCKET_API_URL",
default_value = DEFAULT_SOCKET_API_URL,
)]
pub api_url: String,
#[arg(long = "api-token", env = "SOCKET_API_TOKEN")]
pub api_token: Option<String>,
#[arg(long = "org", short = 'o', env = "SOCKET_ORG_SLUG")]
pub org: Option<String>,
#[arg(
long = "proxy-url",
env = "SOCKET_PROXY_URL",
default_value = DEFAULT_PATCH_API_PROXY_URL,
)]
pub proxy_url: String,
#[arg(
long = "ecosystems",
short = 'e',
env = "SOCKET_ECOSYSTEMS",
value_delimiter = ',',
)]
pub ecosystems: Option<Vec<String>>,
#[arg(
long = "download-mode",
env = "SOCKET_DOWNLOAD_MODE",
default_value = "diff",
)]
pub download_mode: String,
#[arg(
long,
env = "SOCKET_OFFLINE",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub offline: bool,
#[arg(
long = "global",
short = 'g',
env = "SOCKET_GLOBAL",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub global: bool,
#[arg(long = "global-prefix", env = "SOCKET_GLOBAL_PREFIX")]
pub global_prefix: Option<PathBuf>,
#[arg(
long = "json",
short = 'j',
env = "SOCKET_JSON",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub json: bool,
#[arg(
long = "verbose",
short = 'v',
env = "SOCKET_VERBOSE",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub verbose: bool,
#[arg(
long = "silent",
short = 's',
env = "SOCKET_SILENT",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub silent: bool,
#[arg(
long = "dry-run",
env = "SOCKET_DRY_RUN",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub dry_run: bool,
#[arg(
long = "yes",
short = 'y',
env = "SOCKET_YES",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub yes: bool,
#[arg(long = "lock-timeout", env = "SOCKET_LOCK_TIMEOUT")]
pub lock_timeout: Option<u64>,
#[arg(
long = "break-lock",
env = "SOCKET_BREAK_LOCK",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub break_lock: bool,
#[arg(
long = "debug",
env = "SOCKET_DEBUG",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub debug: bool,
#[arg(
long = "no-telemetry",
env = "SOCKET_TELEMETRY_DISABLED",
default_value_t = false,
value_parser = clap::builder::BoolishValueParser::new(),
)]
pub no_telemetry: bool,
}
impl GlobalArgs {
pub fn resolved_manifest_path(&self) -> PathBuf {
socket_patch_core::manifest::operations::resolve_manifest_path(
&self.cwd,
&self.manifest_path,
)
}
pub fn api_client_overrides(&self) -> ApiClientEnvOverrides {
ApiClientEnvOverrides {
api_url: Some(self.api_url.clone()).filter(|s| !s.is_empty()),
api_token: self.api_token.clone().filter(|s| !s.is_empty()),
org_slug: self.org.clone().filter(|s| !s.is_empty()),
proxy_url: Some(self.proxy_url.clone()).filter(|s| !s.is_empty()),
}
}
}
pub fn apply_env_toggles(common: &GlobalArgs) {
if common.debug {
std::env::set_var("SOCKET_DEBUG", "1");
}
if common.no_telemetry {
std::env::set_var("SOCKET_TELEMETRY_DISABLED", "1");
}
}
impl Default for GlobalArgs {
fn default() -> Self {
Self {
cwd: PathBuf::from("."),
manifest_path: DEFAULT_PATCH_MANIFEST_PATH.to_string(),
api_url: String::new(),
api_token: None,
org: None,
proxy_url: String::new(),
ecosystems: None,
download_mode: "diff".to_string(),
offline: false,
global: false,
global_prefix: None,
json: false,
verbose: false,
silent: false,
dry_run: false,
yes: false,
lock_timeout: None,
break_lock: false,
debug: false,
no_telemetry: false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn api_client_overrides_forwards_set_values() {
let args = GlobalArgs {
api_url: "https://api.example.com".to_string(),
api_token: Some("tok123".to_string()),
org: Some("acme".to_string()),
proxy_url: "https://proxy.example.com".to_string(),
..GlobalArgs::default()
};
let o = args.api_client_overrides();
assert_eq!(o.api_url.as_deref(), Some("https://api.example.com"));
assert_eq!(o.api_token.as_deref(), Some("tok123"));
assert_eq!(o.org_slug.as_deref(), Some("acme"));
assert_eq!(o.proxy_url.as_deref(), Some("https://proxy.example.com"));
}
#[test]
fn api_client_overrides_default_is_all_none() {
let o = GlobalArgs::default().api_client_overrides();
assert!(o.api_url.is_none(), "empty api_url must not be forwarded");
assert!(o.proxy_url.is_none(), "empty proxy_url must not be forwarded");
assert!(o.api_token.is_none());
assert!(o.org_slug.is_none());
}
#[test]
fn api_client_overrides_filters_empty_strings() {
let args = GlobalArgs {
api_url: String::new(),
api_token: Some(String::new()),
org: Some(String::new()),
proxy_url: String::new(),
..GlobalArgs::default()
};
let o = args.api_client_overrides();
assert!(o.api_url.is_none());
assert!(o.api_token.is_none());
assert!(o.org_slug.is_none());
assert!(o.proxy_url.is_none());
}
#[test]
fn resolved_manifest_path_joins_relative_against_cwd() {
let args = GlobalArgs {
cwd: PathBuf::from("/work/project"),
manifest_path: ".socket/manifest.json".to_string(),
..GlobalArgs::default()
};
assert_eq!(
args.resolved_manifest_path(),
PathBuf::from("/work/project/.socket/manifest.json"),
);
}
#[test]
fn resolved_manifest_path_passes_absolute_through() {
let args = GlobalArgs {
cwd: PathBuf::from("/work/project"),
manifest_path: "/etc/socket/manifest.json".to_string(),
..GlobalArgs::default()
};
assert_eq!(
args.resolved_manifest_path(),
PathBuf::from("/etc/socket/manifest.json"),
);
}
#[test]
#[serial_test::serial]
fn apply_env_toggles_mirrors_flags_into_env() {
let saved_debug = std::env::var("SOCKET_DEBUG").ok();
let saved_telemetry = std::env::var("SOCKET_TELEMETRY_DISABLED").ok();
std::env::remove_var("SOCKET_DEBUG");
std::env::remove_var("SOCKET_TELEMETRY_DISABLED");
apply_env_toggles(&GlobalArgs::default());
assert!(std::env::var("SOCKET_DEBUG").is_err());
assert!(std::env::var("SOCKET_TELEMETRY_DISABLED").is_err());
let args = GlobalArgs {
debug: true,
no_telemetry: true,
..GlobalArgs::default()
};
apply_env_toggles(&args);
assert_eq!(std::env::var("SOCKET_DEBUG").as_deref(), Ok("1"));
assert_eq!(std::env::var("SOCKET_TELEMETRY_DISABLED").as_deref(), Ok("1"));
match saved_debug {
Some(v) => std::env::set_var("SOCKET_DEBUG", v),
None => std::env::remove_var("SOCKET_DEBUG"),
}
match saved_telemetry {
Some(v) => std::env::set_var("SOCKET_TELEMETRY_DISABLED", v),
None => std::env::remove_var("SOCKET_TELEMETRY_DISABLED"),
}
}
}