use std::collections::HashMap; use std::path::PathBuf; use serde::Deserialize; use serde_json::Value; use thiserror::Error; #[derive(Debug, Clone)] pub struct LaunchSpec { pub program: PathBuf, pub args: Vec, pub env: HashMap, } #[derive(Debug, Error)] pub enum RegistryError { #[error("invalid registry json: {0}")] InvalidJson(#[from] serde_json::Error), #[error("unable to resolve registry entry from blob")] UnsupportedBlob, #[error("registry blob has agents[] but no --registry-agent-id was provided")] MissingAgentId, #[error("agent '{0}' was not found in registry blob")] AgentNotFound(String), #[error("registry entry has no supported launch target")] MissingLaunchTarget, #[error("platform '{0}' is not present in distribution.binary")] UnsupportedPlatform(String), } impl LaunchSpec { pub fn from_registry_blob(blob: &str, agent_id: Option<&str>) -> Result { let value: Value = serde_json::from_str(blob)?; Self::from_registry_value(value, agent_id) } fn from_registry_value(value: Value, agent_id: Option<&str>) -> Result { if value.get("agents").is_some() { let doc: RegistryDocument = serde_json::from_value(value)?; let wanted = agent_id.ok_or(RegistryError::MissingAgentId)?; let agent = doc .agents .into_iter() .find(|a| a.id == wanted) .ok_or_else(|| RegistryError::AgentNotFound(wanted.to_string()))?; return Self::from_distribution(agent.distribution); } if value.get("distribution").is_some() { let entry: RegistryAgent = serde_json::from_value(value)?; return Self::from_distribution(entry.distribution); } if value.get("npx").is_some() || value.get("binary").is_some() { let distribution: RegistryDistribution = serde_json::from_value(value)?; return Self::from_distribution(distribution); } Err(RegistryError::UnsupportedBlob) } fn from_distribution(distribution: RegistryDistribution) -> Result { if let Some(npx) = distribution.npx { let mut args = vec!["-y".to_string(), npx.package]; args.extend(npx.args); return Ok(Self { program: PathBuf::from("npx"), args, env: npx.env, }); } if let Some(binary) = distribution.binary { let platform = platform_key().ok_or(RegistryError::UnsupportedPlatform(format!( "{}/{}", std::env::consts::OS, std::env::consts::ARCH )))?; let target = binary .get(platform) .ok_or_else(|| RegistryError::UnsupportedPlatform(platform.to_string()))?; return Ok(Self { program: PathBuf::from(&target.cmd), args: target.args.clone(), env: target.env.clone(), }); } Err(RegistryError::MissingLaunchTarget) } } fn platform_key() -> Option<&'static str> { match (std::env::consts::OS, std::env::consts::ARCH) { ("linux", "x86_64") => Some("linux-x86_64"), ("linux", "aarch64") => Some("linux-aarch64"), ("macos", "x86_64") => Some("darwin-x86_64"), ("macos", "aarch64") => Some("darwin-aarch64"), ("windows", "x86_64") => Some("windows-x86_64"), ("windows", "aarch64") => Some("windows-aarch64"), _ => None, } } #[derive(Debug, Deserialize)] struct RegistryDocument { agents: Vec, } #[derive(Debug, Deserialize)] struct RegistryAgent { #[allow(dead_code)] id: String, distribution: RegistryDistribution, } #[derive(Debug, Deserialize)] struct RegistryDistribution { #[serde(default)] npx: Option, #[serde(default)] binary: Option>, } #[derive(Debug, Deserialize)] struct RegistryNpx { package: String, #[serde(default)] args: Vec, #[serde(default)] env: HashMap, } #[derive(Debug, Deserialize)] struct RegistryBinaryTarget { #[allow(dead_code)] archive: Option, cmd: String, #[serde(default)] args: Vec, #[serde(default)] env: HashMap, }