diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-22 11:07:42 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-22 11:15:29 +0900 |
| commit | 9f0d210021c54f63c9984446862b6ec68834bc63 (patch) | |
| tree | d1522b8047c60bc7ee7a9d832178dd24e1b07636 | |
| parent | 2c243a3cb814939bbe40fda1608781825ab0d77d (diff) | |
| download | php-mozart-9f0d210021c54f63c9984446862b6ec68834bc63.tar.gz php-mozart-9f0d210021c54f63c9984446862b6ec68834bc63.tar.zst php-mozart-9f0d210021c54f63c9984446862b6ec68834bc63.zip | |
refactor(async): migrate from blocking HTTP to async/await with tokio
Replace reqwest::blocking with async reqwest across the entire codebase.
All command execute functions, registry API calls (packagist, downloader,
resolver, lockfile), and the main entry point now use async/await with
the tokio runtime. The pubgrub resolver runs on spawn_blocking since its
DependencyProvider trait is synchronous, using Handle::block_on for
async I/O within that context.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
42 files changed, 389 insertions, 328 deletions
@@ -537,7 +537,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] @@ -547,12 +546,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -571,10 +564,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", - "futures-sink", "futures-task", - "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1059,6 +1049,7 @@ dependencies = [ "serde_json", "sha1", "tempfile", + "tokio", "tracing", "tracing-subscriber", ] @@ -1460,9 +1451,7 @@ dependencies = [ "base64", "bytes", "encoding_rs", - "futures-channel", "futures-core", - "futures-util", "h2", "http", "http-body", @@ -23,7 +23,7 @@ flate2 = "1" md5 = "0.7" pubgrub = "0.3.0" regex = "1.12.3" -reqwest = { version = "0.13.2", features = ["blocking", "json"] } +reqwest = { version = "0.13.2", features = ["json"] } self-replace = "1" serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" diff --git a/crates/mozart-registry/src/downloader.rs b/crates/mozart-registry/src/downloader.rs index cfed951..9a5ed24 100644 --- a/crates/mozart-registry/src/downloader.rs +++ b/crates/mozart-registry/src/downloader.rs @@ -79,7 +79,7 @@ impl DownloadProgress { /// the `Content-Length` response header. /// If `files_cache` is provided, the downloaded bytes are cached by URL; cache hits skip /// the network request entirely. -pub fn download_dist( +pub async fn download_dist( url: &str, expected_shasum: Option<&str>, progress: Option<&mut DownloadProgress>, @@ -108,7 +108,7 @@ pub fn download_dist( } } - let response = reqwest::blocking::get(url)?; + let response = reqwest::get(url).await?; if !response.status().is_success() { anyhow::bail!( @@ -123,20 +123,15 @@ pub fn download_dist( if let Some(content_length) = response.content_length() { pb.set_total(content_length); } - let mut reader = response; let mut buf = Vec::new(); - let mut chunk = [0u8; 8192]; - loop { - let n = reader.read(&mut chunk)?; - if n == 0 { - break; - } - buf.extend_from_slice(&chunk[..n]); - pb.inc(n as u64); + let mut stream = response; + while let Some(chunk) = stream.chunk().await? { + buf.extend_from_slice(&chunk); + pb.inc(chunk.len() as u64); } buf } else { - response.bytes()?.to_vec() + response.bytes().await?.to_vec() }; // Verify SHA-1 checksum if provided @@ -325,7 +320,7 @@ pub fn extract_tar_gz(data: &[u8], target_dir: &Path) -> anyhow::Result<()> { /// - `package_name`: e.g. `"monolog/monolog"` /// - `progress`: optional mutable progress tracker to update during download /// - `files_cache`: optional files cache; if provided, the archive bytes are cached by URL -pub fn install_package( +pub async fn install_package( dist_url: &str, dist_type: &str, dist_shasum: Option<&str>, @@ -342,7 +337,7 @@ pub fn install_package( } fs::create_dir_all(&target)?; - let bytes = download_dist(dist_url, dist_shasum, progress, files_cache)?; + let bytes = download_dist(dist_url, dist_shasum, progress, files_cache).await?; match dist_type { "zip" => extract_zip(&bytes, &target)?, diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index 16337c4..9064109 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -392,11 +392,12 @@ fn extract_platform_requirements(requirements: &BTreeMap<String, String>) -> ser /// 2. Separates packages into production vs dev-only /// 3. Computes the content-hash /// 4. Assembles the complete `LockFile` struct -pub fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow::Result<LockFile> { +pub async fn generate_lock_file(request: &LockFileGenerationRequest) -> anyhow::Result<LockFile> { // 1. Fetch full metadata for all resolved packages let mut package_metadata: HashMap<String, PackagistVersion> = HashMap::new(); for pkg in &request.resolved_packages { - let versions = packagist::fetch_package_versions(&pkg.name, request.repo_cache.as_ref())?; + let versions = + packagist::fetch_package_versions(&pkg.name, request.repo_cache.as_ref()).await?; // Find the exact version matching pkg.version_normalized let matching = versions .into_iter() @@ -913,8 +914,8 @@ mod tests { assert_eq!(platform, serde_json::json!({})); } - #[test] - fn test_generate_lock_file_minimal() { + #[tokio::test] + async fn test_generate_lock_file_minimal() { let composer_json_content = r#"{"name": "test/project", "require": {"php": ">=8.1"}}"#.to_string(); let composer_json: RawPackageData = serde_json::from_str(&composer_json_content).unwrap(); @@ -927,7 +928,7 @@ mod tests { repo_cache: None, }; - let lock = generate_lock_file(&request).unwrap(); + let lock = generate_lock_file(&request).await.unwrap(); assert_eq!(lock.packages.len(), 0); assert_eq!(lock.packages_dev.as_ref().unwrap().len(), 0); @@ -1009,9 +1010,9 @@ mod tests { assert_eq!(packages[1].name, "vendor/zebra"); } - #[test] + #[tokio::test] #[ignore] - fn test_generate_lock_file_monolog() { + async fn test_generate_lock_file_monolog() { use crate::resolver::PlatformConfig; use crate::resolver::{ResolveRequest, resolve}; use mozart_core::package::Stability; @@ -1031,7 +1032,9 @@ mod tests { repo_cache: None, }; - let resolved = resolve(&resolve_request).expect("Resolution should succeed"); + let resolved = resolve(&resolve_request) + .await + .expect("Resolution should succeed"); assert!(!resolved.is_empty()); let composer_json_content = @@ -1046,7 +1049,9 @@ mod tests { repo_cache: None, }; - let lock = generate_lock_file(&gen_request).expect("Lock file generation should succeed"); + let lock = generate_lock_file(&gen_request) + .await + .expect("Lock file generation should succeed"); // Verify monolog is in packages assert!( diff --git a/crates/mozart-registry/src/packagist.rs b/crates/mozart-registry/src/packagist.rs index ba80e7e..95d02ad 100644 --- a/crates/mozart-registry/src/packagist.rs +++ b/crates/mozart-registry/src/packagist.rs @@ -128,7 +128,7 @@ pub fn parse_p2_response(json: &str, package_name: &str) -> anyhow::Result<Vec<P /// If `repo_cache` is provided, the JSON response is cached on disk under the /// key `"provider-{vendor}~{package}.json"`. Subsequent calls for the same /// package are served from cache without a network request. -pub fn fetch_package_versions( +pub async fn fetch_package_versions( package_name: &str, repo_cache: Option<&Cache>, ) -> anyhow::Result<Vec<PackagistVersion>> { @@ -144,7 +144,7 @@ pub fn fetch_package_versions( // Cache miss — fetch from Packagist let url = format!("https://repo.packagist.org/p2/{package_name}.json"); - let response = reqwest::blocking::get(&url)?; + let response = reqwest::get(&url).await?; if !response.status().is_success() { anyhow::bail!( @@ -153,7 +153,7 @@ pub fn fetch_package_versions( ); } - let body = response.text()?; + let body = response.text().await?; // Write to cache if let Some(cache) = repo_cache { @@ -209,11 +209,11 @@ fn url_encode(s: &str) -> String { /// /// Fetches up to `SEARCH_MAX_PAGES` pages of results and returns the full list. /// An optional `package_type` filter can narrow results (e.g. `"library"`). -pub fn search_packages( +pub async fn search_packages( query: &str, package_type: Option<&str>, ) -> anyhow::Result<(Vec<SearchResult>, u64)> { - let client = reqwest::blocking::Client::builder() + let client = reqwest::Client::builder() .user_agent("mozart/0.1.0") .build()?; @@ -224,11 +224,11 @@ pub fn search_packages( loop { let response: SearchResponse = if let Some(ref url) = next_url { - let resp = client.get(url).send()?; + let resp = client.get(url).send().await?; if !resp.status().is_success() { anyhow::bail!("Packagist search request failed (HTTP {})", resp.status()); } - resp.json()? + resp.json().await? } else { let encoded_query = url_encode(query); let mut url = format!("https://packagist.org/search.json?q={encoded_query}"); @@ -237,11 +237,11 @@ pub fn search_packages( url.push_str(&url_encode(t)); } - let resp = client.get(&url).send()?; + let resp = client.get(&url).send().await?; if !resp.status().is_success() { anyhow::bail!("Packagist search request failed (HTTP {})", resp.status()); } - resp.json()? + resp.json().await? }; if page == 1 { @@ -321,10 +321,10 @@ pub struct SecurityAdvisoriesResponse { /// /// If the package list is very large (500+), requests are batched in chunks of /// 500 names per request and the results are merged. -pub fn fetch_security_advisories( +pub async fn fetch_security_advisories( package_names: &[&str], ) -> anyhow::Result<BTreeMap<String, Vec<SecurityAdvisory>>> { - let client = reqwest::blocking::Client::builder() + let client = reqwest::Client::builder() .user_agent("mozart/0.1.0") .build()?; @@ -343,7 +343,8 @@ pub fn fetch_security_advisories( .post("https://packagist.org/api/security-advisories/") .header("Content-Type", "application/x-www-form-urlencoded") .body(body) - .send()?; + .send() + .await?; if !response.status().is_success() { anyhow::bail!( @@ -352,7 +353,7 @@ pub fn fetch_security_advisories( ); } - let parsed: SecurityAdvisoriesResponse = response.json()?; + let parsed: SecurityAdvisoriesResponse = response.json().await?; for (pkg_name, advisories) in parsed.advisories { if !advisories.is_empty() { diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index eb4f6e5..84e6a96 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -586,6 +586,9 @@ struct VersionDependencies { /// pubgrub `DependencyProvider` that fetches package metadata from Packagist. pub struct MozartProvider { + /// Tokio runtime handle for calling async functions from sync trait methods. + handle: tokio::runtime::Handle, + /// Cache of fetched package metadata. Populated lazily from Packagist. package_cache: RefCell<HashMap<String, PackageVersions>>, @@ -632,13 +635,16 @@ impl MozartProvider { } // Fetch from Packagist (with optional on-disk repo cache) - let packagist_versions = packagist::fetch_package_versions( - package_name, - self.repo_cache.as_ref(), - ) - .map_err(|e| { - ResolverError::PackagistError(format!("Failed to fetch {}: {}", package_name, e)) - })?; + // Uses block_on because pubgrub's DependencyProvider trait is synchronous. + let packagist_versions = self + .handle + .block_on(packagist::fetch_package_versions( + package_name, + self.repo_cache.as_ref(), + )) + .map_err(|e| { + ResolverError::PackagistError(format!("Failed to fetch {}: {}", package_name, e)) + })?; // Convert and filter let mut versions = BTreeMap::new(); @@ -975,8 +981,8 @@ pub struct ResolvedPackage { /// /// Returns a list of resolved packages (excluding root and platform packages), /// or a human-readable error. -pub fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, ResolveError> { - // 1. Build root dependencies +pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, ResolveError> { + // 1. Build root dependencies (parsing is CPU-only, no async needed) let mut root_deps: Vec<(PackageName, ComposerVS)> = Vec::new(); let root_conflicts: Vec<(PackageName, ComposerVS)> = Vec::new(); @@ -1012,79 +1018,94 @@ pub fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, Resolve } } - // 2. Build the provider - let provider = MozartProvider { - package_cache: RefCell::new(HashMap::new()), - repo_cache: request.repo_cache.clone(), - platform_packages: request.platform.to_versions(), - minimum_stability: request.minimum_stability, - stability_flags: request.stability_flags.clone(), - prefer_stable: request.prefer_stable, - prefer_lowest: request.prefer_lowest, - root_dependencies: root_deps, - root_conflicts, - ignore_platform_reqs: request.ignore_platform_reqs, - ignore_platform_req_list: request.ignore_platform_req_list.clone(), - }; + // Capture the current tokio Handle so the provider can call async functions + // from within pubgrub's synchronous DependencyProvider trait methods. + let handle = tokio::runtime::Handle::current(); - // 3. Run pubgrub - let root = PackageName::root(); - let root_version = ComposerVersion::stable(0, 0, 0, 0); + // Clone data needed by spawn_blocking (which requires 'static) + let repo_cache = request.repo_cache.clone(); + let platform_packages = request.platform.to_versions(); + let minimum_stability = request.minimum_stability; + let stability_flags = request.stability_flags.clone(); + let prefer_stable = request.prefer_stable; + let prefer_lowest = request.prefer_lowest; + let ignore_platform_reqs = request.ignore_platform_reqs; + let ignore_platform_req_list = request.ignore_platform_req_list.clone(); - match pubgrub::resolve(&provider, root, root_version) { - Ok(solution) => { - // 4. Convert solution to ResolvedPackage list - let mut result = Vec::new(); - for (pkg, version) in solution { - // Skip root and platform packages - if pkg.is_root() || pkg.is_platform() { - continue; - } + // 2. Run pubgrub on a blocking thread (it is CPU-bound + uses block_on for I/O) + tokio::task::spawn_blocking(move || { + let provider = MozartProvider { + handle, + package_cache: RefCell::new(HashMap::new()), + repo_cache, + platform_packages, + minimum_stability, + stability_flags, + prefer_stable, + prefer_lowest, + root_dependencies: root_deps, + root_conflicts, + ignore_platform_reqs, + ignore_platform_req_list, + }; - // Look up the original version string from the cache - let cache = provider.package_cache.borrow(); - let (version_str, version_normalized) = if let Some(pvs) = cache.get(&pkg.0) { - if let Some(vd) = pvs.versions.get(&version) { - (vd.version_string.clone(), vd.version_normalized.clone()) + let root = PackageName::root(); + let root_version = ComposerVersion::stable(0, 0, 0, 0); + + match pubgrub::resolve(&provider, root, root_version) { + Ok(solution) => { + let mut result = Vec::new(); + for (pkg, version) in solution { + if pkg.is_root() || pkg.is_platform() { + continue; + } + + let cache = provider.package_cache.borrow(); + let (version_str, version_normalized) = if let Some(pvs) = cache.get(&pkg.0) { + if let Some(vd) = pvs.versions.get(&version) { + (vd.version_string.clone(), vd.version_normalized.clone()) + } else { + (version.to_string(), version.to_string()) + } } else { (version.to_string(), version.to_string()) - } - } else { - (version.to_string(), version.to_string()) - }; + }; - result.push(ResolvedPackage { - name: pkg.0.clone(), - version: version_str, - version_normalized, - is_dev: version.stability < STABILITY_ALPHA_BASE, - }); + result.push(ResolvedPackage { + name: pkg.0.clone(), + version: version_str, + version_normalized, + is_dev: version.stability < STABILITY_ALPHA_BASE, + }); + } + Ok(result) + } + Err(PubGrubError::NoSolution(mut derivation_tree)) => { + derivation_tree.collapse_no_versions(); + let report = DefaultStringReporter::report(&derivation_tree); + Err(ResolveError::NoSolution(report)) + } + Err(PubGrubError::ErrorRetrievingDependencies { + package, + version, + source, + }) => Err(ResolveError::DependencyFetchError(format!( + "Error retrieving dependencies for {} {}: {}", + package, version, source + ))), + Err(PubGrubError::ErrorChoosingVersion { package, source }) => { + Err(ResolveError::DependencyFetchError(format!( + "Error choosing version for {}: {}", + package, source + ))) + } + Err(PubGrubError::ErrorInShouldCancel(e)) => { + Err(ResolveError::Internal(format!("Resolver cancelled: {}", e))) } - Ok(result) - } - Err(PubGrubError::NoSolution(mut derivation_tree)) => { - derivation_tree.collapse_no_versions(); - let report = DefaultStringReporter::report(&derivation_tree); - Err(ResolveError::NoSolution(report)) - } - Err(PubGrubError::ErrorRetrievingDependencies { - package, - version, - source, - }) => Err(ResolveError::DependencyFetchError(format!( - "Error retrieving dependencies for {} {}: {}", - package, version, source - ))), - Err(PubGrubError::ErrorChoosingVersion { package, source }) => { - Err(ResolveError::DependencyFetchError(format!( - "Error choosing version for {}: {}", - package, source - ))) - } - Err(PubGrubError::ErrorInShouldCancel(e)) => { - Err(ResolveError::Internal(format!("Resolver cancelled: {}", e))) } - } + }) + .await + .unwrap() } // ───────────────────────────────────────────────────────────────────────────── @@ -1096,6 +1117,13 @@ mod tests { use super::*; use pubgrub::{OfflineDependencyProvider, Ranges}; + fn test_handle() -> tokio::runtime::Handle { + static RT: std::sync::OnceLock<tokio::runtime::Runtime> = std::sync::OnceLock::new(); + RT.get_or_init(|| tokio::runtime::Runtime::new().unwrap()) + .handle() + .clone() + } + // ──────────── ComposerVersion parsing ──────────── #[test] @@ -1532,6 +1560,7 @@ mod tests { #[test] fn test_stability_filter() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1585,6 +1614,7 @@ mod tests { #[test] fn test_stability_filter_beta() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Beta, @@ -1630,6 +1660,7 @@ mod tests { #[test] fn test_stability_filter_dev() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Dev, @@ -1656,6 +1687,7 @@ mod tests { #[test] fn test_skip_platform_dep() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1677,6 +1709,7 @@ mod tests { #[test] fn test_skip_specific_platform_dep() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1699,6 +1732,7 @@ mod tests { #[test] fn test_root_package_choose_version() { let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: HashMap::new(), minimum_stability: Stability::Stable, @@ -1726,6 +1760,7 @@ mod tests { platform.insert("php".to_string(), php_v); let provider = MozartProvider { + handle: test_handle(), package_cache: RefCell::new(HashMap::new()), platform_packages: platform, minimum_stability: Stability::Stable, @@ -1884,9 +1919,9 @@ mod tests { // ──────────── End-to-end tests (require network, marked #[ignore]) ──────────── - #[test] + #[tokio::test] #[ignore] - fn test_resolve_monolog_e2e() { + async fn test_resolve_monolog_e2e() { let request = ResolveRequest { require: vec![("monolog/monolog".to_string(), "^3.0".to_string())], require_dev: vec![], @@ -1901,7 +1936,7 @@ mod tests { repo_cache: None, }; - let result = resolve(&request); + let result = resolve(&request).await; match result { Ok(packages) => { println!("Resolved {} packages:", packages.len()); diff --git a/crates/mozart/Cargo.toml b/crates/mozart/Cargo.toml index ec35e24..18b34bf 100644 --- a/crates/mozart/Cargo.toml +++ b/crates/mozart/Cargo.toml @@ -20,6 +20,7 @@ serde.workspace = true serde_json.workspace = true sha1.workspace = true tempfile.workspace = true +tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true diff --git a/crates/mozart/src/commands.rs b/crates/mozart/src/commands.rs index 7b94f4d..cf3300f 100644 --- a/crates/mozart/src/commands.rs +++ b/crates/mozart/src/commands.rs @@ -238,7 +238,7 @@ impl Commands { } #[tracing::instrument(skip(cli), fields(command = cli.command.name()))] -pub fn execute(cli: &Cli) -> anyhow::Result<()> { +pub async fn execute(cli: &Cli) -> anyhow::Result<()> { let console = mozart_core::console::Console::new( cli.verbose, cli.quiet, @@ -247,38 +247,40 @@ pub fn execute(cli: &Cli) -> anyhow::Result<()> { cli.no_interaction, ); match &cli.command { - Commands::About(args) => about::execute(args, cli, &console), - Commands::Archive(args) => archive::execute(args, cli, &console), - Commands::Audit(args) => audit::execute(args, cli, &console), - Commands::Browse(args) => browse::execute(args, cli, &console), - Commands::Bump(args) => bump::execute(args, cli, &console), - Commands::CheckPlatformReqs(args) => check_platform_reqs::execute(args, cli, &console), - Commands::ClearCache(args) => clear_cache::execute(args, cli, &console), - Commands::Completion(args) => completion::execute(args, cli, &console), - Commands::Config(args) => config::execute(args, cli, &console), - Commands::CreateProject(args) => create_project::execute(args, cli, &console), - Commands::Depends(args) => depends::execute(args, cli, &console), - Commands::Diagnose(args) => diagnose::execute(args, cli, &console), - Commands::DumpAutoload(args) => dump_autoload::execute(args, cli, &console), - Commands::Exec(args) => exec::execute(args, cli, &console), - Commands::Fund(args) => fund::execute(args, cli, &console), - Commands::Global(args) => global::execute(args, cli, &console), - Commands::Init(args) => init::execute(args, cli, &console), - Commands::Install(args) => install::execute(args, cli, &console), - Commands::Licenses(args) => licenses::execute(args, cli, &console), - Commands::Outdated(args) => outdated::execute(args, cli, &console), - Commands::Prohibits(args) => prohibits::execute(args, cli, &console), - Commands::Reinstall(args) => reinstall::execute(args, cli, &console), - Commands::Remove(args) => remove::execute(args, cli, &console), - Commands::Repository(args) => repository::execute(args, cli, &console), - Commands::Require(args) => require::execute(args, cli, &console), - Commands::RunScript(args) => run_script::execute(args, cli, &console), - Commands::Search(args) => search::execute(args, cli, &console), - Commands::SelfUpdate(args) => self_update::execute(args, cli, &console), - Commands::Show(args) => show::execute(args, cli, &console), - Commands::Status(args) => status::execute(args, cli, &console), - Commands::Suggests(args) => suggests::execute(args, cli, &console), - Commands::Update(args) => update::execute(args, cli, &console), - Commands::Validate(args) => validate::execute(args, cli, &console), + Commands::About(args) => about::execute(args, cli, &console).await, + Commands::Archive(args) => archive::execute(args, cli, &console).await, + Commands::Audit(args) => audit::execute(args, cli, &console).await, + Commands::Browse(args) => browse::execute(args, cli, &console).await, + Commands::Bump(args) => bump::execute(args, cli, &console).await, + Commands::CheckPlatformReqs(args) => { + check_platform_reqs::execute(args, cli, &console).await + } + Commands::ClearCache(args) => clear_cache::execute(args, cli, &console).await, + Commands::Completion(args) => completion::execute(args, cli, &console).await, + Commands::Config(args) => config::execute(args, cli, &console).await, + Commands::CreateProject(args) => create_project::execute(args, cli, &console).await, + Commands::Depends(args) => depends::execute(args, cli, &console).await, + Commands::Diagnose(args) => diagnose::execute(args, cli, &console).await, + Commands::DumpAutoload(args) => dump_autoload::execute(args, cli, &console).await, + Commands::Exec(args) => exec::execute(args, cli, &console).await, + Commands::Fund(args) => fund::execute(args, cli, &console).await, + Commands::Global(args) => global::execute(args, cli, &console).await, + Commands::Init(args) => init::execute(args, cli, &console).await, + Commands::Install(args) => install::execute(args, cli, &console).await, + Commands::Licenses(args) => licenses::execute(args, cli, &console).await, + Commands::Outdated(args) => outdated::execute(args, cli, &console).await, + Commands::Prohibits(args) => prohibits::execute(args, cli, &console).await, + Commands::Reinstall(args) => reinstall::execute(args, cli, &console).await, + Commands::Remove(args) => remove::execute(args, cli, &console).await, + Commands::Repository(args) => repository::execute(args, cli, &console).await, + Commands::Require(args) => require::execute(args, cli, &console).await, + Commands::RunScript(args) => run_script::execute(args, cli, &console).await, + Commands::Search(args) => search::execute(args, cli, &console).await, + Commands::SelfUpdate(args) => self_update::execute(args, cli, &console).await, + Commands::Show(args) => show::execute(args, cli, &console).await, + Commands::Status(args) => status::execute(args, cli, &console).await, + Commands::Suggests(args) => suggests::execute(args, cli, &console).await, + Commands::Update(args) => update::execute(args, cli, &console).await, + Commands::Validate(args) => validate::execute(args, cli, &console).await, } } diff --git a/crates/mozart/src/commands/about.rs b/crates/mozart/src/commands/about.rs index d436526..9c3ca95 100644 --- a/crates/mozart/src/commands/about.rs +++ b/crates/mozart/src/commands/about.rs @@ -4,7 +4,7 @@ use mozart_core::console; #[derive(Args)] pub struct AboutArgs {} -pub fn execute( +pub async fn execute( _args: &AboutArgs, _cli: &super::Cli, _console: &console::Console, diff --git a/crates/mozart/src/commands/archive.rs b/crates/mozart/src/commands/archive.rs index 687e116..08e6bcf 100644 --- a/crates/mozart/src/commands/archive.rs +++ b/crates/mozart/src/commands/archive.rs @@ -82,7 +82,7 @@ impl Drop for PackageMeta { // ─── Main entry point ───────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &ArchiveArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -148,7 +148,7 @@ pub fn execute( // 5. Determine source directory and package metadata let meta: PackageMeta = if let Some(ref pkg_name) = args.package { // Remote package mode - resolve_remote_package(pkg_name, args.version.as_deref())? + resolve_remote_package(pkg_name, args.version.as_deref()).await? } else { // Root package mode if !composer_json_path.exists() { @@ -239,7 +239,7 @@ pub fn execute( // ─── Remote package resolution ──────────────────────────────────────────────── -fn resolve_remote_package( +async fn resolve_remote_package( package_name: &str, version_constraint: Option<&str>, ) -> anyhow::Result<PackageMeta> { @@ -247,7 +247,7 @@ fn resolve_remote_package( use mozart_registry::version::find_best_candidate; // Fetch versions from Packagist - let versions = mozart_registry::packagist::fetch_package_versions(package_name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(package_name, None).await?; if versions.is_empty() { anyhow::bail!("No versions found for package \"{}\"", package_name); } @@ -293,7 +293,8 @@ fn resolve_remote_package( std::fs::create_dir_all(&temp_dir)?; let bytes = - mozart_registry::downloader::download_dist(&dist.url, dist.shasum.as_deref(), None, None)?; + mozart_registry::downloader::download_dist(&dist.url, dist.shasum.as_deref(), None, None) + .await?; match dist.dist_type.as_str() { "zip" => mozart_registry::downloader::extract_zip(&bytes, &temp_dir)?, diff --git a/crates/mozart/src/commands/audit.rs b/crates/mozart/src/commands/audit.rs index 7fd271f..8f15ef0 100644 --- a/crates/mozart/src/commands/audit.rs +++ b/crates/mozart/src/commands/audit.rs @@ -70,7 +70,7 @@ struct AuditResult { // ─── Main entry point ───────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &AuditArgs, cli: &super::Cli, _console: &mozart_core::console::Console, @@ -111,7 +111,7 @@ pub fn execute( // Fetch advisories let names: Vec<&str> = packages.iter().map(|p| p.name.as_str()).collect(); - let all_advisories = match mozart_registry::packagist::fetch_security_advisories(&names) { + let all_advisories = match mozart_registry::packagist::fetch_security_advisories(&names).await { Ok(a) => a, Err(e) => { if args.ignore_unreachable { diff --git a/crates/mozart/src/commands/browse.rs b/crates/mozart/src/commands/browse.rs index d662ec0..51ca340 100644 --- a/crates/mozart/src/commands/browse.rs +++ b/crates/mozart/src/commands/browse.rs @@ -18,7 +18,7 @@ pub struct BrowseArgs { // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &BrowseArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -45,7 +45,7 @@ pub fn execute( let mut exit_code = 0i32; for package_name in &packages { - match resolve_url(package_name, &working_dir, args.homepage)? { + match resolve_url(package_name, &working_dir, args.homepage).await? { Some(url) => { if args.show { println!("{}", url); @@ -76,7 +76,7 @@ pub fn execute( // ─── URL resolution ─────────────────────────────────────────────────────────── -fn resolve_url( +async fn resolve_url( package_name: &str, working_dir: &Path, prefer_homepage: bool, @@ -109,7 +109,7 @@ fn resolve_url( } // 3. Fall back to Packagist API - match mozart_registry::packagist::fetch_package_versions(package_name, None) { + match mozart_registry::packagist::fetch_package_versions(package_name, None).await { Ok(versions) => { // Find the latest stable version (first non-dev, or fallback to first) let best = versions diff --git a/crates/mozart/src/commands/bump.rs b/crates/mozart/src/commands/bump.rs index af2809d..42b0d82 100644 --- a/crates/mozart/src/commands/bump.rs +++ b/crates/mozart/src/commands/bump.rs @@ -22,7 +22,7 @@ pub struct BumpArgs { // ─── Main entry point ───────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &BumpArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -324,8 +324,8 @@ mod tests { // ── Basic bump ───────────────────────────────────────────────────────── - #[test] - fn test_basic_bump_modifies_composer_json() { + #[tokio::test] + async fn test_basic_bump_modifies_composer_json() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -351,7 +351,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); let updated = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&updated).unwrap(); @@ -360,8 +360,8 @@ mod tests { // ── Dry run ──────────────────────────────────────────────────────────── - #[test] - fn test_dry_run_does_not_modify_files() { + #[tokio::test] + async fn test_dry_run_does_not_modify_files() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -387,7 +387,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); // composer.json should be unchanged let content = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); @@ -397,8 +397,8 @@ mod tests { // ── No changes ───────────────────────────────────────────────────────── - #[test] - fn test_no_changes_when_already_bumped() { + #[tokio::test] + async fn test_no_changes_when_already_bumped() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -424,7 +424,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); // No changes should be made let content = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); @@ -434,8 +434,8 @@ mod tests { // ── Dev-only flag ────────────────────────────────────────────────────── - #[test] - fn test_dev_only_flag_only_bumps_require_dev() { + #[tokio::test] + async fn test_dev_only_flag_only_bumps_require_dev() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -467,7 +467,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); let content = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&content).unwrap(); @@ -479,8 +479,8 @@ mod tests { // ── No-dev-only flag ─────────────────────────────────────────────────── - #[test] - fn test_no_dev_only_flag_only_bumps_require() { + #[tokio::test] + async fn test_no_dev_only_flag_only_bumps_require() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -512,7 +512,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); let content = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&content).unwrap(); @@ -569,8 +569,8 @@ mod tests { // ── Lock file hash updated ───────────────────────────────────────────── - #[test] - fn test_lock_file_hash_updated_after_bump() { + #[tokio::test] + async fn test_lock_file_hash_updated_after_bump() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -596,7 +596,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); // The lock file content-hash should now match the updated composer.json let updated_composer = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); @@ -609,8 +609,8 @@ mod tests { // ── Package filter ───────────────────────────────────────────────────── - #[test] - fn test_package_filter_only_bumps_specified_packages() { + #[tokio::test] + async fn test_package_filter_only_bumps_specified_packages() { let dir = tempdir().unwrap(); let composer_json = r#"{ "name": "test/project", @@ -643,7 +643,7 @@ mod tests { verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; - execute(&args, &cli, &console).unwrap(); + execute(&args, &cli, &console).await.unwrap(); let content = std::fs::read_to_string(dir.path().join("composer.json")).unwrap(); let parsed: serde_json::Value = serde_json::from_str(&content).unwrap(); diff --git a/crates/mozart/src/commands/check_platform_reqs.rs b/crates/mozart/src/commands/check_platform_reqs.rs index 71728d3..295f9b7 100644 --- a/crates/mozart/src/commands/check_platform_reqs.rs +++ b/crates/mozart/src/commands/check_platform_reqs.rs @@ -52,7 +52,7 @@ struct CheckResult { // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &CheckPlatformReqsArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/clear_cache.rs b/crates/mozart/src/commands/clear_cache.rs index afab64d..e0ae111 100644 --- a/crates/mozart/src/commands/clear_cache.rs +++ b/crates/mozart/src/commands/clear_cache.rs @@ -8,7 +8,7 @@ pub struct ClearCacheArgs { pub gc: bool, } -pub fn execute( +pub async fn execute( args: &ClearCacheArgs, cli: &super::Cli, console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/completion.rs b/crates/mozart/src/commands/completion.rs index 4c2f4a8..d095ce6 100644 --- a/crates/mozart/src/commands/completion.rs +++ b/crates/mozart/src/commands/completion.rs @@ -9,7 +9,7 @@ pub struct CompletionArgs { pub shell: Shell, } -pub fn execute( +pub async fn execute( args: &CompletionArgs, _cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/config.rs b/crates/mozart/src/commands/config.rs index 2a3ab85..2b7d7ba 100644 --- a/crates/mozart/src/commands/config.rs +++ b/crates/mozart/src/commands/config.rs @@ -603,7 +603,7 @@ fn render_value(v: &serde_json::Value) -> String { // ─── execute() ─────────────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &ConfigArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index e9a1911..d5a9b06 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -170,7 +170,7 @@ fn is_dir_non_empty(path: &Path) -> bool { .unwrap_or(false) } -pub fn execute( +pub async fn execute( args: &CreateProjectArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -278,7 +278,7 @@ pub fn execute( )); console.info("Loading composer repositories with package information"); - let versions = packagist::fetch_package_versions(&package_name, None)?; + let versions = packagist::fetch_package_versions(&package_name, None).await?; // Find the best candidate matching the version constraint and stability let best = if let Some(ref constraint) = version_constraint { @@ -331,7 +331,8 @@ pub fn execute( ); let bytes = - downloader::download_dist(&dist.url, dist.shasum.as_deref(), Some(&mut progress), None)?; + downloader::download_dist(&dist.url, dist.shasum.as_deref(), Some(&mut progress), None) + .await?; progress.finish(); @@ -422,7 +423,7 @@ pub fn execute( console.info("Resolving dependencies..."); - let resolved = resolver::resolve(&request).map_err(|e| { + let resolved = resolver::resolve(&request).await.map_err(|e| { mozart_core::exit_code::bail( mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, e.to_string(), @@ -437,7 +438,8 @@ pub fn execute( composer_json: raw.clone(), include_dev: dev_mode, repo_cache: None, - })?; + }) + .await?; // Print change report (all will be installs for a new project) let changes = super::update::compute_update_changes(None, &new_lock, dev_mode); @@ -498,7 +500,8 @@ pub fn execute( apcu_autoloader: false, apcu_autoloader_prefix: None, }, - )?; + ) + .await?; Ok(()) } diff --git a/crates/mozart/src/commands/depends.rs b/crates/mozart/src/commands/depends.rs index 91b3829..7e7ad70 100644 --- a/crates/mozart/src/commands/depends.rs +++ b/crates/mozart/src/commands/depends.rs @@ -19,7 +19,7 @@ pub struct DependsArgs { pub locked: bool, } -pub fn execute( +pub async fn execute( args: &DependsArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/diagnose.rs b/crates/mozart/src/commands/diagnose.rs index 606d00e..da37137 100644 --- a/crates/mozart/src/commands/diagnose.rs +++ b/crates/mozart/src/commands/diagnose.rs @@ -76,12 +76,12 @@ fn check_version() -> CheckResult { /// Check 2 & 3: HTTP/HTTPS connectivity to Packagist. /// /// Returns Ok if reachable, Fail if not, Skip if network is disabled. -fn check_http_connectivity(url: &str) -> CheckResult { +async fn check_http_connectivity(url: &str) -> CheckResult { if std::env::var("COMPOSER_DISABLE_NETWORK").is_ok() { return CheckResult::Skip("COMPOSER_DISABLE_NETWORK is set".to_string()); } - let client = match reqwest::blocking::Client::builder() + let client = match reqwest::Client::builder() .timeout(std::time::Duration::from_secs(10)) .user_agent(concat!("mozart/", env!("CARGO_PKG_VERSION"))) .build() @@ -90,7 +90,7 @@ fn check_http_connectivity(url: &str) -> CheckResult { Err(e) => return CheckResult::Fail(format!("Could not build HTTP client: {e}")), }; - match client.get(url).send() { + match client.get(url).send().await { Ok(resp) => { let status = resp.status(); if status.is_success() || status.is_redirection() { @@ -104,12 +104,12 @@ fn check_http_connectivity(url: &str) -> CheckResult { } /// Check 4: GitHub API connectivity. -fn check_github_api() -> CheckResult { +async fn check_github_api() -> CheckResult { if std::env::var("COMPOSER_DISABLE_NETWORK").is_ok() { return CheckResult::Skip("COMPOSER_DISABLE_NETWORK is set".to_string()); } - let client = match reqwest::blocking::Client::builder() + let client = match reqwest::Client::builder() .timeout(std::time::Duration::from_secs(10)) .user_agent(concat!("mozart/", env!("CARGO_PKG_VERSION"))) .build() @@ -119,7 +119,7 @@ fn check_github_api() -> CheckResult { }; let url = "https://api.github.com/"; - match client.get(url).send() { + match client.get(url).send().await { Ok(resp) => { let status = resp.status(); if status.is_success() || status.is_redirection() { @@ -371,7 +371,7 @@ fn check_cache_dir(cache_dir: &Path) -> CheckResult { // ─── Main execute function ───────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( _args: &DiagnoseArgs, cli: &super::Cli, _console: &mozart_core::console::Console, @@ -402,7 +402,7 @@ pub fn execute( println!(); // 2. HTTPS connectivity to Packagist - let https_result = check_http_connectivity("https://repo.packagist.org/packages.json"); + let https_result = check_http_connectivity("https://repo.packagist.org/packages.json").await; print_check( "https connectivity to packagist", &https_result, @@ -410,7 +410,7 @@ pub fn execute( ); // 3. HTTP connectivity to Packagist - let http_result = check_http_connectivity("http://repo.packagist.org/packages.json"); + let http_result = check_http_connectivity("http://repo.packagist.org/packages.json").await; print_check( "http connectivity to packagist", &http_result, @@ -418,7 +418,7 @@ pub fn execute( ); // 4. GitHub API connectivity - let github_result = check_github_api(); + let github_result = check_github_api().await; print_check("github.com connectivity", &github_result, &mut exit_code); // 5. HTTP proxy config @@ -723,30 +723,30 @@ mod tests { // ── network tests (ignored by default) ─────────────────────────────────── - #[test] + #[tokio::test] #[ignore] - fn test_check_https_packagist_connectivity() { - let result = check_http_connectivity("https://repo.packagist.org/packages.json"); + async fn test_check_https_packagist_connectivity() { + let result = check_http_connectivity("https://repo.packagist.org/packages.json").await; assert!( matches!(result, CheckResult::Ok(_)), "expected Ok for HTTPS Packagist connectivity" ); } - #[test] + #[tokio::test] #[ignore] - fn test_check_http_packagist_connectivity() { - let result = check_http_connectivity("http://repo.packagist.org/packages.json"); + async fn test_check_http_packagist_connectivity() { + let result = check_http_connectivity("http://repo.packagist.org/packages.json").await; assert!( matches!(result, CheckResult::Ok(_) | CheckResult::Warning(_)), "expected Ok or Warning for HTTP Packagist connectivity" ); } - #[test] + #[tokio::test] #[ignore] - fn test_check_github_api_connectivity() { - let result = check_github_api(); + async fn test_check_github_api_connectivity() { + let result = check_github_api().await; assert!( matches!(result, CheckResult::Ok(_)), "expected Ok for GitHub API connectivity" diff --git a/crates/mozart/src/commands/dump_autoload.rs b/crates/mozart/src/commands/dump_autoload.rs index a920f5a..43108ab 100644 --- a/crates/mozart/src/commands/dump_autoload.rs +++ b/crates/mozart/src/commands/dump_autoload.rs @@ -48,7 +48,7 @@ pub struct DumpAutoloadArgs { pub strict_ambiguous: bool, } -pub fn execute( +pub async fn execute( args: &DumpAutoloadArgs, cli: &super::Cli, console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/exec.rs b/crates/mozart/src/commands/exec.rs index 1e785cb..66bc93c 100644 --- a/crates/mozart/src/commands/exec.rs +++ b/crates/mozart/src/commands/exec.rs @@ -17,7 +17,7 @@ pub struct ExecArgs { // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &ExecArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/fund.rs b/crates/mozart/src/commands/fund.rs index ad91d6b..181e6e0 100644 --- a/crates/mozart/src/commands/fund.rs +++ b/crates/mozart/src/commands/fund.rs @@ -23,7 +23,7 @@ struct FundingEntry { // ─── Main entry point ─────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &FundArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/global.rs b/crates/mozart/src/commands/global.rs index 1cde2c1..a646f7a 100644 --- a/crates/mozart/src/commands/global.rs +++ b/crates/mozart/src/commands/global.rs @@ -13,7 +13,7 @@ pub struct GlobalArgs { // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &GlobalArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -40,7 +40,7 @@ pub fn execute( argv.extend(args.args.iter().cloned()); let new_cli = super::Cli::try_parse_from(&argv)?; - crate::commands::execute(&new_cli) + Box::pin(crate::commands::execute(&new_cli)).await } // ─── Helpers ───────────────────────────────────────────────────────────────── diff --git a/crates/mozart/src/commands/init.rs b/crates/mozart/src/commands/init.rs index 25cc70e..1e8688f 100644 --- a/crates/mozart/src/commands/init.rs +++ b/crates/mozart/src/commands/init.rs @@ -55,7 +55,7 @@ pub struct InitArgs { pub autoload: Option<String>, } -pub fn execute( +pub async fn execute( args: &InitArgs, cli: &super::Cli, console: &console::Console, diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 1094d99..d55ba72 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -310,7 +310,7 @@ fn make_progress(show: bool, pkg_name: &str, version: &str) -> downloader::Downl /// 7. Writes vendor/composer/installed.json /// 8. Cleans up empty vendor directories /// 9. Generates the autoloader (unless no_autoloader) -pub fn install_from_lock( +pub async fn install_from_lock( lock: &lockfile::LockFile, working_dir: &Path, vendor_dir: &Path, @@ -418,7 +418,8 @@ pub fn install_from_lock( &pkg.name, Some(&mut progress), None, - )?; + ) + .await?; progress.finish(); } @@ -496,7 +497,7 @@ pub fn install_from_lock( Ok(()) } -pub fn execute( +pub async fn execute( args: &InstallArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -590,6 +591,7 @@ pub fn execute( apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), }, ) + .await } #[cfg(test)] diff --git a/crates/mozart/src/commands/licenses.rs b/crates/mozart/src/commands/licenses.rs index 4ffd928..e8e298a 100644 --- a/crates/mozart/src/commands/licenses.rs +++ b/crates/mozart/src/commands/licenses.rs @@ -27,7 +27,7 @@ struct LicenseEntry { // ─── Main entry point ─────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &LicensesArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/outdated.rs b/crates/mozart/src/commands/outdated.rs index 49c541f..eba0d52 100644 --- a/crates/mozart/src/commands/outdated.rs +++ b/crates/mozart/src/commands/outdated.rs @@ -96,7 +96,7 @@ struct OutdatedEntry { // ─── Main entry point ─────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &OutdatedArgs, cli: &super::Cli, _console: &mozart_core::console::Console, @@ -179,7 +179,7 @@ pub fn execute( } // Fetch latest version from Packagist - let latest = match fetch_latest_version(&pkg.name) { + let latest = match fetch_latest_version(&pkg.name).await { Ok(v) => v, Err(_) => { // Skip packages we can't fetch (platform packages, private, etc.) @@ -333,11 +333,11 @@ fn load_locked_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec< // ─── Version fetching ──────────────────────────────────────────────────────── -fn fetch_latest_version(name: &str) -> anyhow::Result<PackageInfo> { +async fn fetch_latest_version(name: &str) -> anyhow::Result<PackageInfo> { use mozart_core::package::Stability; use mozart_registry::version::find_best_candidate; - let versions = mozart_registry::packagist::fetch_package_versions(name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(name, None).await?; let best = find_best_candidate(&versions, Stability::Stable) .ok_or_else(|| anyhow::anyhow!("No stable version found for {name}"))?; diff --git a/crates/mozart/src/commands/prohibits.rs b/crates/mozart/src/commands/prohibits.rs index 3ec01a7..1f45b27 100644 --- a/crates/mozart/src/commands/prohibits.rs +++ b/crates/mozart/src/commands/prohibits.rs @@ -22,7 +22,7 @@ pub struct ProhibitsArgs { pub locked: bool, } -pub fn execute( +pub async fn execute( args: &ProhibitsArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/reinstall.rs b/crates/mozart/src/commands/reinstall.rs index d064136..91c2aaf 100644 --- a/crates/mozart/src/commands/reinstall.rs +++ b/crates/mozart/src/commands/reinstall.rs @@ -65,7 +65,7 @@ pub struct ReinstallArgs { // ─── Main entry point ───────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &ReinstallArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -215,7 +215,8 @@ pub fn execute( &locked.name, Some(&mut progress), Some(&files_cache), - )?; + ) + .await?; progress.finish(); reinstalled_count += 1; diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index de4b77b..f869d12 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -96,7 +96,7 @@ pub struct RemoveArgs { pub apcu_autoloader_prefix: Option<String>, } -pub fn execute( +pub async fn execute( args: &RemoveArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -285,7 +285,7 @@ pub fn execute( console.info("Resolving dependencies..."); // Run resolver - let mut resolved = resolver::resolve(&request).map_err(|e| { + let mut resolved = resolver::resolve(&request).await.map_err(|e| { mozart_core::exit_code::bail( mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, e.to_string(), @@ -370,7 +370,8 @@ pub fn execute( composer_json: raw.clone(), include_dev: dev_mode, repo_cache: None, - })?; + }) + .await?; // Compute and print change report let changes = super::update::compute_update_changes(old_lock.as_ref(), &new_lock, dev_mode); @@ -468,7 +469,8 @@ pub fn execute( apcu_autoloader: false, apcu_autoloader_prefix: None, }, - )?; + ) + .await?; } Ok(()) @@ -681,9 +683,9 @@ mod tests { // ──────────── Integration tests (network, #[ignore]) ──────────── - #[test] + #[tokio::test] #[ignore] - fn test_remove_full_e2e() { + async fn test_remove_full_e2e() { use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; use mozart_registry::resolver::{ResolveRequest, resolve}; use std::collections::HashMap; @@ -714,7 +716,9 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, }; - let resolved = resolve(&request).expect("initial resolution should succeed"); + let resolved = resolve(&request) + .await + .expect("initial resolution should succeed"); let initial_lock = generate_lock_file(&LockFileGenerationRequest { resolved_packages: resolved, composer_json_content: content.to_string(), @@ -722,6 +726,7 @@ mod tests { include_dev: false, repo_cache: None, }) + .await .expect("initial lock file generation should succeed"); initial_lock .write_to_file(&lock_path) @@ -745,7 +750,9 @@ mod tests { ignore_platform_req_list: vec![], repo_cache: None, }; - let resolved2 = resolve(&request2).expect("post-remove resolution should succeed"); + let resolved2 = resolve(&request2) + .await + .expect("post-remove resolution should succeed"); let composer_json_content2 = std::fs::read_to_string(&composer_path).unwrap(); let new_lock = generate_lock_file(&LockFileGenerationRequest { @@ -755,6 +762,7 @@ mod tests { include_dev: false, repo_cache: None, }) + .await .expect("post-remove lock file generation should succeed"); // psr/log should no longer be in the new lock diff --git a/crates/mozart/src/commands/repository.rs b/crates/mozart/src/commands/repository.rs index 0974e29..e5d43ec 100644 --- a/crates/mozart/src/commands/repository.rs +++ b/crates/mozart/src/commands/repository.rs @@ -35,7 +35,7 @@ pub struct RepositoryArgs { pub after: Option<String>, } -pub fn execute( +pub async fn execute( _args: &RepositoryArgs, _cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 960182f..0182bb6 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -131,7 +131,7 @@ pub struct RequireArgs { /// /// Returns a list of `"vendor/package:constraint"` strings that the user confirmed, /// or an empty vec if the user typed nothing / pressed Ctrl-D immediately. -fn interactive_search_packages( +async fn interactive_search_packages( already_required: &std::collections::HashSet<String>, preferred_stability: Stability, fixed: bool, @@ -165,7 +165,7 @@ fn interactive_search_packages( } // Search Packagist - let (results, total) = match packagist::search_packages(&query, None) { + let (results, total) = match packagist::search_packages(&query, None).await { Ok(r) => r, Err(e) => { eprintln!( @@ -276,7 +276,7 @@ fn interactive_search_packages( )) ); - match packagist::fetch_package_versions(&package_name, None) { + match packagist::fetch_package_versions(&package_name, None).await { Ok(versions) => { match version::find_best_candidate(&versions, preferred_stability) { Some(best) => { @@ -343,7 +343,7 @@ fn interactive_search_packages( Ok(selected) } -pub fn execute( +pub async fn execute( args: &RequireArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -389,7 +389,7 @@ pub fn execute( .unwrap_or(Stability::Stable); let found = - interactive_search_packages(&already_required, preferred_stability, args.fixed)?; + interactive_search_packages(&already_required, preferred_stability, args.fixed).await?; if found.is_empty() { // Nothing selected — exit cleanly @@ -466,7 +466,7 @@ pub fn execute( )) ); - let versions = packagist::fetch_package_versions(&name, None)?; + let versions = packagist::fetch_package_versions(&name, None).await?; let best = version::find_best_candidate(&versions, preferred_stability) .ok_or_else(|| { anyhow::anyhow!( @@ -606,7 +606,7 @@ pub fn execute( console.info("Resolving dependencies..."); // Run resolver - let mut resolved = match resolver::resolve(&request) { + let mut resolved = match resolver::resolve(&request).await { Ok(packages) => packages, Err(e) => { return Err(mozart_core::exit_code::bail( @@ -671,7 +671,8 @@ pub fn execute( composer_json: raw.clone(), include_dev: dev_mode, repo_cache: None, - })?; + }) + .await?; // Compute and print change report let changes = super::update::compute_update_changes(old_lock.as_ref(), &new_lock, dev_mode); @@ -779,7 +780,8 @@ pub fn execute( apcu_autoloader: false, apcu_autoloader_prefix: None, }, - )?; + ) + .await?; } Ok(()) @@ -912,9 +914,9 @@ mod tests { // Integration tests (network, #[ignore]) // ───────────────────────────────────────────────────────────────────────── - #[test] + #[tokio::test] #[ignore] - fn test_require_full_e2e() { + async fn test_require_full_e2e() { use mozart_core::package::RawPackageData; use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; @@ -935,7 +937,9 @@ mod tests { repo_cache: None, }; - let resolved = resolver::resolve(&request).expect("Resolution should succeed"); + let resolved = resolver::resolve(&request) + .await + .expect("Resolution should succeed"); assert!(!resolved.is_empty()); assert!(resolved.iter().any(|p| p.name == "psr/log")); @@ -946,6 +950,7 @@ mod tests { include_dev: false, repo_cache: None, }) + .await .expect("Lock file generation should succeed"); assert!(!lock.content_hash.is_empty()); @@ -953,9 +958,9 @@ mod tests { assert!(lock.packages.iter().any(|p| p.name == "psr/log")); } - #[test] + #[tokio::test] #[ignore] - fn test_require_no_install_writes_lock_only() { + async fn test_require_no_install_writes_lock_only() { use mozart_core::package::RawPackageData; use tempfile::tempdir; @@ -983,7 +988,9 @@ mod tests { repo_cache: None, }; - let resolved = resolver::resolve(&request).expect("Resolution should succeed"); + let resolved = resolver::resolve(&request) + .await + .expect("Resolution should succeed"); let new_lock = lockfile::generate_lock_file(&lockfile::LockFileGenerationRequest { resolved_packages: resolved, composer_json_content: content.to_string(), @@ -991,6 +998,7 @@ mod tests { include_dev: false, repo_cache: None, }) + .await .expect("Lock file generation should succeed"); // Simulate --no-install: write lock but don't install diff --git a/crates/mozart/src/commands/run_script.rs b/crates/mozart/src/commands/run_script.rs index acef421..78dfd69 100644 --- a/crates/mozart/src/commands/run_script.rs +++ b/crates/mozart/src/commands/run_script.rs @@ -46,7 +46,7 @@ const INTERNAL_ONLY_EVENTS: &[&str] = &[ // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &RunScriptArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/search.rs b/crates/mozart/src/commands/search.rs index 98189ff..a8edc86 100644 --- a/crates/mozart/src/commands/search.rs +++ b/crates/mozart/src/commands/search.rs @@ -59,7 +59,7 @@ fn passes_only_vendor(result: &SearchResult, query: &str) -> bool { vendor.eq_ignore_ascii_case(query) } -pub fn execute( +pub async fn execute( args: &SearchArgs, _cli: &super::Cli, _console: &mozart_core::console::Console, @@ -67,7 +67,7 @@ pub fn execute( let query = args.tokens.join(" "); let (all_results, total) = - mozart_registry::packagist::search_packages(&query, args.r#type.as_deref())?; + mozart_registry::packagist::search_packages(&query, args.r#type.as_deref()).await?; // Apply client-side filters let mut results: Vec<&SearchResult> = all_results.iter().collect(); diff --git a/crates/mozart/src/commands/self_update.rs b/crates/mozart/src/commands/self_update.rs index 03d2643..9f6a7fe 100644 --- a/crates/mozart/src/commands/self_update.rs +++ b/crates/mozart/src/commands/self_update.rs @@ -1,5 +1,5 @@ use clap::Args; -use std::io::{Read, Write}; +use std::io::Write; use std::path::{Path, PathBuf}; // ─── CLI args ───────────────────────────────────────────────────────────────── @@ -50,7 +50,7 @@ const BACKUP_EXTENSION: &str = ".old"; // ─── Public entry point ─────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &SelfUpdateArgs, _cli: &super::Cli, _console: &mozart_core::console::Console, @@ -69,7 +69,7 @@ pub fn execute( if args.rollback { rollback(¤t_exe, &data_dir) } else { - update(args, ¤t_exe, &data_dir) + update(args, ¤t_exe, &data_dir).await } } @@ -128,10 +128,10 @@ fn platform_asset_name() -> anyhow::Result<String> { // ─── GitHub fetching ────────────────────────────────────────────────────────── -fn fetch_releases(include_prerelease: bool) -> anyhow::Result<Vec<GitHubRelease>> { +async fn fetch_releases(include_prerelease: bool) -> anyhow::Result<Vec<GitHubRelease>> { let url = format!("{GITHUB_API_BASE}/{GITHUB_REPO}/releases"); - let client = reqwest::blocking::Client::builder() + let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(30)) .user_agent(concat!("mozart/", env!("CARGO_PKG_VERSION"))) .build() @@ -140,6 +140,7 @@ fn fetch_releases(include_prerelease: bool) -> anyhow::Result<Vec<GitHubRelease> let response = client .get(&url) .send() + .await .map_err(|e| anyhow::anyhow!("Could not fetch releases from GitHub: {e}"))?; if !response.status().is_success() { @@ -151,6 +152,7 @@ fn fetch_releases(include_prerelease: bool) -> anyhow::Result<Vec<GitHubRelease> let mut releases: Vec<GitHubRelease> = response .json() + .await .map_err(|e| anyhow::anyhow!("Could not parse GitHub releases response: {e}"))?; if !include_prerelease { @@ -203,16 +205,21 @@ fn find_asset<'a>(release: &'a GitHubRelease, asset_name: &str) -> anyhow::Resul // ─── Download ───────────────────────────────────────────────────────────────── -fn download_asset(asset: &GitHubAsset, dest: &Path, show_progress: bool) -> anyhow::Result<()> { - let client = reqwest::blocking::Client::builder() +async fn download_asset( + asset: &GitHubAsset, + dest: &Path, + show_progress: bool, +) -> anyhow::Result<()> { + let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(300)) .user_agent(concat!("mozart/", env!("CARGO_PKG_VERSION"))) .build() .map_err(|e| anyhow::anyhow!("Could not build HTTP client: {e}"))?; - let mut response = client + let response = client .get(&asset.browser_download_url) .send() + .await .map_err(|e| anyhow::anyhow!("Could not download asset: {e}"))?; if !response.status().is_success() { @@ -228,18 +235,16 @@ fn download_asset(asset: &GitHubAsset, dest: &Path, show_progress: bool) -> anyh let total_bytes = asset.size; let mut downloaded: u64 = 0; - let mut buf = [0u8; 8192]; + let mut stream = response; - loop { - let n = response - .read(&mut buf) - .map_err(|e| anyhow::anyhow!("Error reading download stream: {e}"))?; - if n == 0 { - break; - } - file.write_all(&buf[..n]) + while let Some(chunk) = stream + .chunk() + .await + .map_err(|e| anyhow::anyhow!("Error reading download stream: {e}"))? + { + file.write_all(&chunk) .map_err(|e| anyhow::anyhow!("Error writing to destination file: {e}"))?; - downloaded += n as u64; + downloaded += chunk.len() as u64; if show_progress && total_bytes > 0 { let pct = (downloaded * 100) / total_bytes; @@ -257,13 +262,13 @@ fn download_asset(asset: &GitHubAsset, dest: &Path, show_progress: bool) -> anyh // ─── Core update flow ───────────────────────────────────────────────────────── -fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { +async fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { let current_version = get_current_version(); println!("Updating Mozart..."); // Fetch releases - let releases = fetch_releases(args.preview)?; + let releases = fetch_releases(args.preview).await?; // Find target release let target_release = find_target_release(&releases, args.version.as_deref())?; @@ -298,7 +303,7 @@ fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> anyhow: .map_err(|e| anyhow::anyhow!("Could not create temporary file: {e}"))?; let tmp_path = tmp.path().to_path_buf(); - download_asset(asset, &tmp_path, !args.no_progress)?; + download_asset(asset, &tmp_path, !args.no_progress).await?; // Set executable permission on Unix #[cfg(unix)] diff --git a/crates/mozart/src/commands/show.rs b/crates/mozart/src/commands/show.rs index c6a446d..d329984 100644 --- a/crates/mozart/src/commands/show.rs +++ b/crates/mozart/src/commands/show.rs @@ -99,7 +99,7 @@ pub struct ShowArgs { pub ignore_platform_reqs: bool, } -pub fn execute( +pub async fn execute( args: &ShowArgs, cli: &super::Cli, _console: &mozart_core::console::Console, @@ -126,21 +126,21 @@ pub fn execute( // --available: show available versions for installed packages if args.available { - return show_available(args, &working_dir); + return show_available(args, &working_dir).await; } // --locked: show from lock file if args.locked { - return execute_locked(args, &working_dir); + return execute_locked(args, &working_dir).await; } // Default: installed mode - execute_installed(args, &working_dir) + execute_installed(args, &working_dir).await } // ─── Installed mode ──────────────────────────────────────────────────────── -fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { +async fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { let vendor_dir = working_dir.join("vendor"); let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; @@ -193,7 +193,7 @@ fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> if let Some(ref package_filter) = args.package { if package_filter.contains('*') { packages.retain(|p| matches_wildcard(&p.name, package_filter)); - show_installed_package_list(&packages, args, &vendor_dir)?; + show_installed_package_list(&packages, args, &vendor_dir).await?; return Ok(()); } else { // Single package detail view @@ -212,7 +212,7 @@ fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> } // List view - show_installed_package_list(&packages, args, &vendor_dir) + show_installed_package_list(&packages, args, &vendor_dir).await } fn filter_installed_packages<'a>( @@ -253,7 +253,7 @@ fn filter_installed_packages<'a>( Ok(packages) } -fn show_installed_package_list( +async fn show_installed_package_list( packages: &[&mozart_registry::installed::InstalledPackageEntry], args: &ShowArgs, _vendor_dir: &Path, @@ -289,7 +289,7 @@ fn show_installed_package_list( let description = get_installed_description(pkg); let latest_info = if show_latest { - fetch_latest_for_package(&pkg.name).ok() + fetch_latest_for_package(&pkg.name).await.ok() } else { None }; @@ -472,11 +472,11 @@ fn extract_major(version_normalized: &str) -> u64 { .unwrap_or(0) } -fn fetch_latest_for_package(name: &str) -> anyhow::Result<LatestInfo> { +async fn fetch_latest_for_package(name: &str) -> anyhow::Result<LatestInfo> { use mozart_core::package::Stability; use mozart_registry::version::find_best_candidate; - let versions = mozart_registry::packagist::fetch_package_versions(name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(name, None).await?; let best = find_best_candidate(&versions, Stability::Stable) .ok_or_else(|| anyhow::anyhow!("No stable version found for {name}"))?; @@ -644,7 +644,7 @@ fn show_installed_package_detail( // ─── Locked mode ─────────────────────────────────────────────────────────── -fn execute_locked(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { +async fn execute_locked(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { let lock_path = working_dir.join("composer.lock"); if !lock_path.exists() { anyhow::bail!( @@ -684,18 +684,18 @@ fn execute_locked(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if let Some(ref package_filter) = args.package { if package_filter.contains('*') { packages.retain(|p| matches_wildcard(&p.name, package_filter)); - show_locked_package_list(&packages, args)?; + show_locked_package_list(&packages, args).await?; } else { show_locked_package_detail(&lock, package_filter)?; } } else { - show_locked_package_list(&packages, args)?; + show_locked_package_list(&packages, args).await?; } Ok(()) } -fn show_locked_package_list( +async fn show_locked_package_list( packages: &[&mozart_registry::lockfile::LockedPackage], args: &ShowArgs, ) -> anyhow::Result<()> { @@ -729,7 +729,7 @@ fn show_locked_package_list( let description = pkg.description.as_deref().unwrap_or("").to_string(); let latest_info = if show_latest { - fetch_latest_for_package(&pkg.name).ok() + fetch_latest_for_package(&pkg.name).await.ok() } else { None }; @@ -1346,10 +1346,10 @@ fn show_platform(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { // ─── Available mode ───────────────────────────────────────────────────────── -fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { +async fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { // If a specific package name is given, show available versions for it if let Some(ref pkg_name) = args.package { - return show_available_versions(pkg_name, args); + return show_available_versions(pkg_name, args).await; } // Otherwise, show all installed packages with their available (latest) versions @@ -1384,7 +1384,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if is_platform_package(&pkg.name) { continue; } - show_available_versions_inline(&pkg.name); + show_available_versions_inline(&pkg.name).await; } return Ok(()); } @@ -1413,7 +1413,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if is_platform_package(&pkg.name) { continue; } - match mozart_registry::packagist::fetch_package_versions(&pkg.name, None) { + match mozart_registry::packagist::fetch_package_versions(&pkg.name, None).await { Ok(versions) => { let version_strings: Vec<String> = versions.iter().map(|v| v.version.clone()).collect(); @@ -1441,14 +1441,14 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if is_platform_package(&pkg.name) { continue; } - show_available_versions_inline(&pkg.name); + show_available_versions_inline(&pkg.name).await; } Ok(()) } -fn show_available_versions(pkg_name: &str, args: &ShowArgs) -> anyhow::Result<()> { - let versions = mozart_registry::packagist::fetch_package_versions(pkg_name, None)?; +async fn show_available_versions(pkg_name: &str, args: &ShowArgs) -> anyhow::Result<()> { + let versions = mozart_registry::packagist::fetch_package_versions(pkg_name, None).await?; if versions.is_empty() { println!("No versions found for {pkg_name}"); return Ok(()); @@ -1475,8 +1475,8 @@ fn show_available_versions(pkg_name: &str, args: &ShowArgs) -> anyhow::Result<() Ok(()) } -fn show_available_versions_inline(pkg_name: &str) { - match mozart_registry::packagist::fetch_package_versions(pkg_name, None) { +async fn show_available_versions_inline(pkg_name: &str) { + match mozart_registry::packagist::fetch_package_versions(pkg_name, None).await { Ok(versions) => { if versions.is_empty() { println!( diff --git a/crates/mozart/src/commands/status.rs b/crates/mozart/src/commands/status.rs index ad6dac1..e29eda3 100644 --- a/crates/mozart/src/commands/status.rs +++ b/crates/mozart/src/commands/status.rs @@ -44,7 +44,7 @@ struct PackageStatus { // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &StatusArgs, cli: &super::Cli, _console: &mozart_core::console::Console, @@ -104,7 +104,8 @@ pub fn execute( dist.shasum.as_deref(), None, Some(&files_cache), - ); + ) + .await; let bytes = match downloaded { Ok(b) => b, diff --git a/crates/mozart/src/commands/suggests.rs b/crates/mozart/src/commands/suggests.rs index d528171..0268fe8 100644 --- a/crates/mozart/src/commands/suggests.rs +++ b/crates/mozart/src/commands/suggests.rs @@ -38,7 +38,7 @@ struct Suggestion { // ─── Main entry point ──────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &SuggestsArgs, cli: &super::Cli, _console: &mozart_core::console::Console, diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index 3a7c423..53ee9d7 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -630,7 +630,7 @@ pub fn apply_minimal_changes( // Main execute function // ───────────────────────────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &UpdateArgs, cli: &super::Cli, console: &mozart_core::console::Console, @@ -743,7 +743,7 @@ pub fn execute( } console.info("Resolving dependencies..."); - let mut resolved = match resolver::resolve(&request) { + let mut resolved = match resolver::resolve(&request).await { Ok(packages) => packages, Err(e) => { return Err(mozart_core::exit_code::bail( @@ -859,7 +859,8 @@ pub fn execute( composer_json: composer_json.clone(), include_dev: dev_mode, repo_cache: None, - })?; + }) + .await?; // Step 10: Compute and print change report let changes = compute_update_changes(old_lock.as_ref(), &new_lock, dev_mode); @@ -968,7 +969,8 @@ pub fn execute( apcu_autoloader: false, apcu_autoloader_prefix: None, }, - )?; + ) + .await?; } Ok(()) @@ -1657,9 +1659,9 @@ mod tests { // ──────────── Integration test (network, #[ignore]) ──────────── - #[test] + #[tokio::test] #[ignore] - fn test_update_full_e2e() { + async fn test_update_full_e2e() { use mozart_core::package::RawPackageData; use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; use mozart_registry::resolver::{ResolveRequest, resolve}; @@ -1682,7 +1684,7 @@ mod tests { repo_cache: None, }; - let resolved = resolve(&request).expect("Resolution should succeed"); + let resolved = resolve(&request).await.expect("Resolution should succeed"); assert!(!resolved.is_empty()); assert!(resolved.iter().any(|p| p.name == "monolog/monolog")); @@ -1693,6 +1695,7 @@ mod tests { include_dev: false, repo_cache: None, }) + .await .expect("Lock file generation should succeed"); assert!(!lock.content_hash.is_empty()); diff --git a/crates/mozart/src/commands/validate.rs b/crates/mozart/src/commands/validate.rs index 50e3cce..6ed4cee 100644 --- a/crates/mozart/src/commands/validate.rs +++ b/crates/mozart/src/commands/validate.rs @@ -67,7 +67,7 @@ impl ValidationResult { // ─── Entry point ───────────────────────────────────────────────────────────── -pub fn execute( +pub async fn execute( args: &ValidateArgs, cli: &super::Cli, console: &mozart_core::console::Console, diff --git a/crates/mozart/src/main.rs b/crates/mozart/src/main.rs index ebe84e5..04dd6d1 100644 --- a/crates/mozart/src/main.rs +++ b/crates/mozart/src/main.rs @@ -1,7 +1,7 @@ use clap::Parser; use mozart::commands; use mozart_core::exit_code; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; +use tracing_subscriber::{EnvFilter, fmt, prelude::*}; fn init_tracing(profile: bool, verbose: u8, quiet: bool) { // MOZART_LOG environment variable takes highest priority. @@ -37,10 +37,11 @@ fn init_tracing(profile: bool, verbose: u8, quiet: bool) { // Otherwise: no subscriber installed → tracing macros are effectively zero-cost no-ops. } -fn main() { +#[tokio::main] +async fn main() { let cli = commands::Cli::parse(); init_tracing(cli.profile, cli.verbose, cli.quiet); - match commands::execute(&cli) { + match commands::execute(&cli).await { Ok(()) => {} Err(e) => { // Check if this is a structured MozartError with a specific exit code. |
