aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock13
-rw-r--r--Cargo.toml2
-rw-r--r--crates/mozart-registry/src/downloader.rs23
-rw-r--r--crates/mozart-registry/src/lockfile.rs23
-rw-r--r--crates/mozart-registry/src/packagist.rs27
-rw-r--r--crates/mozart-registry/src/resolver.rs189
-rw-r--r--crates/mozart/Cargo.toml1
-rw-r--r--crates/mozart/src/commands.rs70
-rw-r--r--crates/mozart/src/commands/about.rs2
-rw-r--r--crates/mozart/src/commands/archive.rs11
-rw-r--r--crates/mozart/src/commands/audit.rs4
-rw-r--r--crates/mozart/src/commands/browse.rs8
-rw-r--r--crates/mozart/src/commands/bump.rs44
-rw-r--r--crates/mozart/src/commands/check_platform_reqs.rs2
-rw-r--r--crates/mozart/src/commands/clear_cache.rs2
-rw-r--r--crates/mozart/src/commands/completion.rs2
-rw-r--r--crates/mozart/src/commands/config.rs2
-rw-r--r--crates/mozart/src/commands/create_project.rs15
-rw-r--r--crates/mozart/src/commands/depends.rs2
-rw-r--r--crates/mozart/src/commands/diagnose.rs38
-rw-r--r--crates/mozart/src/commands/dump_autoload.rs2
-rw-r--r--crates/mozart/src/commands/exec.rs2
-rw-r--r--crates/mozart/src/commands/fund.rs2
-rw-r--r--crates/mozart/src/commands/global.rs4
-rw-r--r--crates/mozart/src/commands/init.rs2
-rw-r--r--crates/mozart/src/commands/install.rs8
-rw-r--r--crates/mozart/src/commands/licenses.rs2
-rw-r--r--crates/mozart/src/commands/outdated.rs8
-rw-r--r--crates/mozart/src/commands/prohibits.rs2
-rw-r--r--crates/mozart/src/commands/reinstall.rs5
-rw-r--r--crates/mozart/src/commands/remove.rs24
-rw-r--r--crates/mozart/src/commands/repository.rs2
-rw-r--r--crates/mozart/src/commands/require.rs38
-rw-r--r--crates/mozart/src/commands/run_script.rs2
-rw-r--r--crates/mozart/src/commands/search.rs4
-rw-r--r--crates/mozart/src/commands/self_update.rs47
-rw-r--r--crates/mozart/src/commands/show.rs50
-rw-r--r--crates/mozart/src/commands/status.rs5
-rw-r--r--crates/mozart/src/commands/suggests.rs2
-rw-r--r--crates/mozart/src/commands/update.rs17
-rw-r--r--crates/mozart/src/commands/validate.rs2
-rw-r--r--crates/mozart/src/main.rs7
42 files changed, 389 insertions, 328 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 9051a53..61040d7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index 9d0691a..58ad592 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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(&current_exe, &data_dir)
} else {
- update(args, &current_exe, &data_dir)
+ update(args, &current_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.