diff options
Diffstat (limited to 'crates/mozart/src/commands')
| -rw-r--r-- | crates/mozart/src/commands/create_project.rs | 15 | ||||
| -rw-r--r-- | crates/mozart/src/commands/install.rs | 332 | ||||
| -rw-r--r-- | crates/mozart/src/commands/remove.rs | 76 | ||||
| -rw-r--r-- | crates/mozart/src/commands/require.rs | 60 | ||||
| -rw-r--r-- | crates/mozart/src/commands/update.rs | 59 |
5 files changed, 293 insertions, 249 deletions
diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index a137868..92081d0 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -419,9 +419,11 @@ pub async fn execute( platform: PlatformConfig::new(), ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), temporary_constraints: HashMap::new(), - repositories: raw.repositories.clone(), + raw_repositories: raw.repositories.clone(), }; console.info("Resolving dependencies..."); @@ -440,7 +442,9 @@ pub async fn execute( composer_json_content: composer_json_content.clone(), composer_json: raw.clone(), include_dev: dev_mode, - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), }) .await?; @@ -497,6 +501,9 @@ pub async fn execute( .and_then(|v| v.as_bool()) .unwrap_or(false); + let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); + let files_cache = mozart_registry::cache::Cache::files(&cache_config); + let mut executor = mozart_registry::installer_executor::FilesystemExecutor::new(files_cache); super::install::install_from_lock( &new_lock, &target_dir, @@ -514,9 +521,9 @@ pub async fn execute( apcu_autoloader_prefix: None, download_only: false, prefer_source: args.prefer_source, - no_cache: cli.no_cache, }, console, + &mut executor, ) .await?; diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index 1cc4e6f..dbfeb92 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -1,8 +1,10 @@ use clap::Args; use mozart_core::console; use mozart_core::console_format; -use mozart_registry::downloader; use mozart_registry::installed; +use mozart_registry::installer_executor::{ + ExecuteContext, FilesystemExecutor, InstallerExecutor, PackageOperation, +}; use mozart_registry::lockfile; use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; @@ -119,8 +121,6 @@ pub struct InstallConfig { pub download_only: bool, /// Prefer installing from VCS source rather than dist archives. pub prefer_source: bool, - /// Disable the files cache entirely. - pub no_cache: bool, } impl Default for InstallConfig { @@ -138,7 +138,6 @@ impl Default for InstallConfig { apcu_autoloader_prefix: None, download_only: false, prefer_source: false, - no_cache: false, } } } @@ -168,15 +167,23 @@ pub fn resolve_working_dir(cli: &super::Cli) -> PathBuf { /// Compute install operations by comparing locked packages against installed packages. /// /// Returns a tuple of (ops, removals) where: -/// - ops: list of (package, action) for each locked package +/// - ops: list of (package, action) ordered topologically — every package's +/// lock-internal `require` deps appear before it, so installs run in +/// dependency-first order to match Composer's `Transaction::calculateOperations`. /// - removals: list of package names that are installed but not locked pub fn compute_operations<'a>( locked: &[&'a lockfile::LockedPackage], installed: &installed::InstalledPackages, ) -> (Vec<(&'a lockfile::LockedPackage, Action)>, Vec<String>) { - let mut ops: Vec<(&'a lockfile::LockedPackage, Action)> = Vec::new(); + // Topo-sort `locked` so each package's deps (within the lock set) come + // before it. Composer's solver yields operations in this order via the + // Transaction; Mozart writes the lock alphabetically, so the install + // loop must re-order before emitting trace lines or invoking the + // executor. + let ordered = topological_sort(locked); - for pkg in locked { + let mut ops: Vec<(&'a lockfile::LockedPackage, Action)> = Vec::new(); + for pkg in ordered { if installed.is_installed(&pkg.name, &pkg.version) { ops.push((pkg, Action::Skip)); } else if installed @@ -203,6 +210,72 @@ pub fn compute_operations<'a>( (ops, removals) } +/// Order a slice of locked packages so every package's `require` deps that +/// are present in the same slice come before it. Cycles fall back to the +/// input order (Composer rejects cycles earlier in the resolver, so Mozart +/// shouldn't see them here in practice). Mirrors the topological sort +/// inside `Composer\DependencyResolver\Transaction::calculateOperations`. +fn topological_sort<'a>( + packages: &[&'a lockfile::LockedPackage], +) -> Vec<&'a lockfile::LockedPackage> { + use std::collections::BTreeMap; + + let names: HashSet<String> = packages.iter().map(|p| p.name.to_lowercase()).collect(); + let mut by_name: BTreeMap<String, &'a lockfile::LockedPackage> = BTreeMap::new(); + for pkg in packages { + by_name.insert(pkg.name.to_lowercase(), *pkg); + } + + let mut visited: HashSet<String> = HashSet::new(); + let mut on_stack: HashSet<String> = HashSet::new(); + let mut ordered: Vec<&'a lockfile::LockedPackage> = Vec::with_capacity(packages.len()); + + fn visit<'b>( + name: &str, + names: &HashSet<String>, + by_name: &BTreeMap<String, &'b lockfile::LockedPackage>, + visited: &mut HashSet<String>, + on_stack: &mut HashSet<String>, + ordered: &mut Vec<&'b lockfile::LockedPackage>, + ) { + if visited.contains(name) || on_stack.contains(name) { + return; + } + let Some(pkg) = by_name.get(name) else { + return; + }; + on_stack.insert(name.to_string()); + for dep in pkg.require.keys() { + let dep_lower = dep.to_lowercase(); + if names.contains(&dep_lower) { + visit(&dep_lower, names, by_name, visited, on_stack, ordered); + } + } + on_stack.remove(name); + visited.insert(name.to_string()); + ordered.push(*pkg); + } + + // Seed iteration in the input order so two packages with no relation + // come out in the order Mozart's lock writer produced them + // (alphabetical), matching Composer's deterministic output. + for pkg in packages { + let lower = pkg.name.to_lowercase(); + if !visited.contains(&lower) { + visit( + &lower, + &names, + &by_name, + &mut visited, + &mut on_stack, + &mut ordered, + ); + } + } + + ordered +} + /// Convert a LockedPackage to an InstalledPackageEntry. /// /// `LockedPackage::extra_fields` is forwarded verbatim so flags like @@ -236,27 +309,6 @@ pub fn locked_to_installed_entry( } } -/// Clean up empty vendor namespace directories after removals. -pub fn cleanup_empty_vendor_dirs(vendor_dir: &Path) -> anyhow::Result<()> { - if let Ok(entries) = std::fs::read_dir(vendor_dir) { - for entry in entries.flatten() { - let path = entry.path(); - if path.is_dir() { - let name = entry.file_name().to_string_lossy().to_string(); - // Skip "composer" dir and "bin" dir - if name == "composer" || name == "bin" { - continue; - } - // If the namespace dir is empty, remove it - if std::fs::read_dir(&path)?.next().is_none() { - std::fs::remove_dir(&path)?; - } - } - } - } - Ok(()) -} - /// Check whether a package name refers to a platform package. /// /// Platform packages are: names starting with "php", "ext-", or "lib-". @@ -435,66 +487,14 @@ fn warn_platform_requirements( } } -/// Create a download progress tracker for a package. -fn make_progress(show: bool, pkg_name: &str, version: &str) -> downloader::DownloadProgress { - downloader::DownloadProgress::new(show, format!("{pkg_name} ({version})")) -} - -/// Install a package from VCS source (git/svn/hg). -fn install_from_source( - source_type: &str, - url: &str, - reference: &str, - vendor_dir: &Path, - package_name: &str, -) -> anyhow::Result<()> { - let target = vendor_dir.join(package_name); - if target.exists() { - std::fs::remove_dir_all(&target)?; - } - - match source_type { - "git" => { - let process = mozart_vcs::process::ProcessExecutor::new(); - let git_util = - mozart_vcs::util::git::GitUtil::new(process, vendor_dir.join(".cache").join("git")); - let downloader = mozart_vcs::downloader::git::GitDownloader::new(git_util); - use mozart_vcs::downloader::VcsDownloader; - downloader.download(url, reference, &target)?; - downloader.install(url, reference, &target)?; - } - "svn" => { - let process = mozart_vcs::process::ProcessExecutor::new(); - let svn_util = mozart_vcs::util::svn::SvnUtil::new(process); - let downloader = mozart_vcs::downloader::svn::SvnDownloader::new(svn_util); - use mozart_vcs::downloader::VcsDownloader; - downloader.install(url, reference, &target)?; - } - "hg" => { - let process = mozart_vcs::process::ProcessExecutor::new(); - let hg_util = mozart_vcs::util::hg::HgUtil::new(process); - let downloader = mozart_vcs::downloader::hg::HgDownloader::new(hg_util); - use mozart_vcs::downloader::VcsDownloader; - downloader.install(url, reference, &target)?; - } - _ => { - anyhow::bail!("Unsupported source type for VCS install: {}", source_type); - } - } - - Ok(()) -} - pub async fn install_from_lock( lock: &lockfile::LockFile, working_dir: &Path, vendor_dir: &Path, config: &InstallConfig, console: &mozart_core::console::Console, + executor: &mut dyn InstallerExecutor, ) -> anyhow::Result<()> { - let cache_config = mozart_registry::cache::build_cache_config(config.no_cache); - let files_cache = mozart_registry::cache::Cache::files(&cache_config); - let dev_mode = config.dev_mode; // Step 1: Determine which packages to install @@ -575,8 +575,14 @@ pub async fn install_from_lock( console.info(&console_format!(" - Would remove <info>{}</info>", name)); } } else { + let exec_ctx = ExecuteContext { + vendor_dir: vendor_dir.to_path_buf(), + no_progress: config.no_progress, + prefer_source: config.prefer_source, + }; + for (pkg, action) in &ops { - match action { + let op = match action { Action::Skip => continue, Action::Install => { console.info(&console_format!( @@ -584,6 +590,7 @@ pub async fn install_from_lock( pkg.name, pkg.version )); + PackageOperation::Install { package: pkg } } Action::Update => { console.info(&console_format!( @@ -591,69 +598,39 @@ pub async fn install_from_lock( pkg.name, pkg.version )); + // Pull the previously-installed version from installed.json + // so the trace recorder can format + // `Upgrading pkg (oldVersion => newVersion)`. + let from_version = installed + .packages + .iter() + .find(|p| p.name.eq_ignore_ascii_case(&pkg.name)) + .map(|p| p.version.as_str()) + .unwrap_or(""); + PackageOperation::Update { + from_version, + package: pkg, + } } - } - - // Try source install if --prefer-source and source info is available - if config.prefer_source - && let Some(source) = &pkg.source - { - install_from_source( - &source.source_type, - &source.url, - source.reference.as_deref().unwrap_or("HEAD"), - vendor_dir, - &pkg.name, - )?; - continue; - } - - // A package with neither dist nor source has no install action. - // This covers Composer's `type: metapackage` (modeled explicitly - // as "no installer") and inline `type: package` definitions used - // in test fixtures that intentionally omit download metadata. - // Mozart records the operation and the installed.json entry but - // performs no filesystem work, mirroring Composer's - // MetapackageInstaller. - if pkg.dist.is_none() && pkg.source.is_none() { - continue; - } - - let dist = pkg.dist.as_ref().ok_or_else(|| { - anyhow::anyhow!( - "Package {} has no dist information. Use --prefer-source to install from VCS.", - pkg.name, - ) - })?; - - let mut progress = make_progress(!config.no_progress, &pkg.name, &pkg.version); - - downloader::install_package( - &dist.url, - &dist.dist_type, - dist.shasum.as_deref(), - vendor_dir, - &pkg.name, - Some(&mut progress), - &files_cache, - ) - .await?; - - progress.finish(); + }; + executor.install_package(op, &exec_ctx).await?; } // Handle removals for name in &removals { console.info(&console_format!(" - Removing <info>{}</info>", name)); - let pkg_dir = vendor_dir.join(name); - if pkg_dir.exists() { - std::fs::remove_dir_all(&pkg_dir)?; - } + let from_version = installed + .packages + .iter() + .find(|p| p.name.eq_ignore_ascii_case(name)) + .map(|p| p.version.as_str()) + .unwrap_or(""); + executor.uninstall_package(name, from_version, &exec_ctx)?; } // Step 7: Clean up empty vendor namespace directories if !removals.is_empty() { - cleanup_empty_vendor_dirs(vendor_dir)?; + executor.cleanup_after_uninstalls(&exec_ctx)?; } // Step 8: Write updated vendor/composer/installed.json (unless download_only) @@ -710,14 +687,36 @@ pub async fn install_from_lock( Ok(()) } +/// CLI entry point. Builds production [`mozart_registry::repository::RepositorySet`] +/// (Packagist) and [`FilesystemExecutor`] from `cli`, then dispatches to [`run`]. pub async fn execute( args: &InstallArgs, cli: &super::Cli, console: &mozart_core::console::Console, ) -> anyhow::Result<()> { - // Step 1: Resolve the working directory + let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); + let repositories = + std::sync::Arc::new(mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::repo(&cache_config), + )); + let mut executor = FilesystemExecutor::new(mozart_registry::cache::Cache::files(&cache_config)); let working_dir = resolve_working_dir(cli); + run(&working_dir, args, console, repositories, &mut executor).await +} +/// Library entry point — pure logic, no `Cli` access. +/// +/// In-process tests construct an empty `RepositorySet` (Composer's +/// `'packagist' => false` test config) and a tracing `InstallerExecutor`, +/// then call this function directly to exercise the install flow without +/// spawning the binary. +pub async fn run( + working_dir: &Path, + args: &InstallArgs, + console: &mozart_core::console::Console, + repositories: std::sync::Arc<mozart_registry::repository::RepositorySet>, + executor: &mut dyn InstallerExecutor, +) -> anyhow::Result<()> { // Step 2: Validate arguments if args.prefer_install.is_some() && (args.prefer_source || args.prefer_dist) { return Err(mozart_core::exit_code::bail( @@ -795,7 +794,10 @@ pub async fn execute( root_reqs: false, bump_after_update: None, }; - return super::update::execute(&update_args, cli, console).await; + // Forward the caller's repositories + executor so in-process tests + // see their mocks honored across the install→update fallback edge. + return super::update::run(working_dir, &update_args, console, repositories, executor) + .await; } let lock = lockfile::LockFile::read_from_file(&lock_path)?; @@ -862,9 +864,10 @@ pub async fn execute( let vendor_dir = working_dir.join("vendor"); // Step 7: Delegate to shared install_from_lock() + let _ = repositories; // unused — install_from_lock has no resolver phase install_from_lock( &lock, - &working_dir, + working_dir, &vendor_dir, &InstallConfig { dev_mode, @@ -879,9 +882,9 @@ pub async fn execute( apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: args.download_only, prefer_source, - no_cache: cli.no_cache, }, console, + executor, ) .await } @@ -1233,57 +1236,6 @@ mod tests { } // ----------------------------------------------------------------------- - // cleanup_empty_vendor_dirs tests - // ----------------------------------------------------------------------- - - #[test] - fn test_cleanup_empty_vendor_dirs_removes_empty() { - let dir = tempdir().unwrap(); - let vendor_dir = dir.path().join("vendor"); - std::fs::create_dir_all(&vendor_dir).unwrap(); - - // Create an empty namespace dir - let empty_ns = vendor_dir.join("old-vendor"); - std::fs::create_dir_all(&empty_ns).unwrap(); - - // Create a non-empty namespace dir - let nonempty_ns = vendor_dir.join("psr"); - std::fs::create_dir_all(nonempty_ns.join("log")).unwrap(); - - // Create the composer dir (should be skipped) - std::fs::create_dir_all(vendor_dir.join("composer")).unwrap(); - - cleanup_empty_vendor_dirs(&vendor_dir).unwrap(); - - assert!(!empty_ns.exists(), "empty namespace dir should be removed"); - assert!( - vendor_dir.join("psr").exists(), - "non-empty namespace dir should remain" - ); - assert!( - vendor_dir.join("composer").exists(), - "composer dir should be preserved" - ); - } - - #[test] - fn test_cleanup_empty_vendor_dirs_skips_bin() { - let dir = tempdir().unwrap(); - let vendor_dir = dir.path().join("vendor"); - std::fs::create_dir_all(&vendor_dir).unwrap(); - - let bin_dir = vendor_dir.join("bin"); - std::fs::create_dir_all(&bin_dir).unwrap(); - - cleanup_empty_vendor_dirs(&vendor_dir).unwrap(); - - assert!( - bin_dir.exists(), - "bin dir should be preserved even if empty" - ); - } - - // ----------------------------------------------------------------------- // Platform requirement check tests // ----------------------------------------------------------------------- diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index 7afa51d..e499af0 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -253,9 +253,11 @@ pub async fn execute( platform: PlatformConfig::new(), ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), temporary_constraints: HashMap::new(), - repositories: raw.repositories.clone(), + raw_repositories: raw.repositories.clone(), }; // Print header messages @@ -346,7 +348,9 @@ pub async fn execute( composer_json_content: composer_json_content.clone(), composer_json: raw.clone(), include_dev: dev_mode, - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), }) .await?; @@ -427,6 +431,10 @@ pub async fn execute( // Install packages (unless --no-install or --dry-run) if !args.no_install && !args.dry_run { + let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); + let files_cache = mozart_registry::cache::Cache::files(&cache_config); + let mut executor = + mozart_registry::installer_executor::FilesystemExecutor::new(files_cache); super::install::install_from_lock( &new_lock, &working_dir, @@ -444,9 +452,9 @@ pub async fn execute( apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, prefer_source: false, - no_cache: cli.no_cache, }, console, + &mut executor, ) .await?; } @@ -505,9 +513,11 @@ async fn remove_unused( platform: PlatformConfig::new(), ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), temporary_constraints: HashMap::new(), - repositories: raw.repositories.clone(), + raw_repositories: raw.repositories.clone(), }; console.info("Resolving dependencies to detect unused packages..."); @@ -562,7 +572,9 @@ async fn remove_unused( composer_json_content, composer_json: raw.clone(), include_dev: dev_mode, - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), }) .await?; @@ -572,6 +584,10 @@ async fn remove_unused( // Install if !args.no_install { let vendor_dir = working_dir.join("vendor"); + let cache_config = mozart_registry::cache::build_cache_config(no_cache); + let files_cache = mozart_registry::cache::Cache::files(&cache_config); + let mut executor = + mozart_registry::installer_executor::FilesystemExecutor::new(files_cache); super::install::install_from_lock( &new_lock, working_dir, @@ -589,9 +605,9 @@ async fn remove_unused( apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, prefer_source: false, - no_cache, }, console, + &mut executor, ) .await?; } @@ -838,12 +854,16 @@ mod tests { platform: mozart_registry::resolver::PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), temporary_constraints: HashMap::new(), - repositories: vec![], + raw_repositories: vec![], }; let resolved = resolve(&request) .await @@ -853,9 +873,13 @@ mod tests { composer_json_content: content.to_string(), composer_json: raw.clone(), include_dev: false, - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), }) .await @@ -881,12 +905,16 @@ mod tests { platform: mozart_registry::resolver::PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), temporary_constraints: HashMap::new(), - repositories: vec![], + raw_repositories: vec![], }; let resolved2 = resolve(&request2) .await @@ -898,9 +926,13 @@ mod tests { composer_json_content: composer_json_content2, composer_json: raw, include_dev: false, - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), }) .await diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 016a536..50aa29f 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -642,9 +642,11 @@ pub async fn execute( platform: PlatformConfig::new(), ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), temporary_constraints: HashMap::new(), - repositories: raw.repositories.clone(), + raw_repositories: raw.repositories.clone(), }; // Print header messages @@ -736,7 +738,9 @@ pub async fn execute( composer_json_content: composer_json_content.clone(), composer_json: raw.clone(), include_dev: dev_mode, - repo_cache: repo_cache.clone(), + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist(repo_cache.clone()), + ), }) .await?; @@ -843,6 +847,10 @@ pub async fn execute( .and_then(|v| v.as_bool()) .unwrap_or(false); + let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); + let files_cache = mozart_registry::cache::Cache::files(&cache_config); + let mut executor = + mozart_registry::installer_executor::FilesystemExecutor::new(files_cache); super::install::install_from_lock( &new_lock, &working_dir, @@ -864,9 +872,9 @@ pub async fn execute( apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, prefer_source: args.prefer_source, - no_cache: cli.no_cache, }, console, + &mut executor, ) .await?; } @@ -1022,12 +1030,16 @@ mod tests { platform: PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), temporary_constraints: HashMap::new(), - repositories: vec![], + raw_repositories: vec![], }; let resolved = resolver::resolve(&request) @@ -1041,9 +1053,13 @@ mod tests { composer_json_content: composer_json_content.to_string(), composer_json, include_dev: false, - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), }) .await @@ -1082,12 +1098,16 @@ mod tests { platform: PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), temporary_constraints: HashMap::new(), - repositories: vec![], + raw_repositories: vec![], }; let resolved = resolver::resolve(&request) @@ -1098,9 +1118,13 @@ mod tests { composer_json_content: content.to_string(), composer_json: raw, include_dev: false, - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), }) .await diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index b58155e..b4a3246 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -720,17 +720,38 @@ fn major_minor(version: &str) -> (u64, u64) { // Main execute function // ───────────────────────────────────────────────────────────────────────────── +/// CLI entry point. Builds production [`RepositorySet`] (Packagist) and +/// [`FilesystemExecutor`] from `cli`, then dispatches to [`run`]. pub async fn execute( args: &UpdateArgs, cli: &super::Cli, console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); - let repo_cache = mozart_registry::cache::Cache::repo(&cache_config); - - // Step 1: Resolve the working directory + let repositories = + std::sync::Arc::new(mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::repo(&cache_config), + )); + let mut executor = mozart_registry::installer_executor::FilesystemExecutor::new( + mozart_registry::cache::Cache::files(&cache_config), + ); let working_dir = super::install::resolve_working_dir(cli); + run(&working_dir, args, console, repositories, &mut executor).await +} +/// Library entry point — pure logic, no CLI / Cli access. +/// +/// In-process tests construct a `RepositorySet` without `PackagistRepository` +/// (Composer's `'packagist' => false` test config) and a tracing +/// `InstallerExecutor`, then call this function directly to exercise the +/// update flow without spawning the binary. +pub async fn run( + working_dir: &std::path::Path, + args: &UpdateArgs, + console: &mozart_core::console::Console, + repositories: std::sync::Arc<mozart_registry::repository::RepositorySet>, + executor: &mut dyn mozart_registry::installer_executor::InstallerExecutor, +) -> anyhow::Result<()> { // Step 2: Handle deprecated flags if args.dev { console.info(&console_format!( @@ -863,9 +884,9 @@ pub async fn execute( platform: PlatformConfig::new(), ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), - repo_cache: repo_cache.clone(), + repositories: repositories.clone(), temporary_constraints, - repositories: composer_json.repositories.clone(), + raw_repositories: composer_json.repositories.clone(), }; // Step 6: Print header and run resolver @@ -1021,7 +1042,7 @@ pub async fn execute( composer_json_content: composer_json_content.clone(), composer_json: composer_json.clone(), include_dev: dev_mode, - repo_cache: repo_cache.clone(), + repositories: repositories.clone(), }) .await?; @@ -1220,7 +1241,7 @@ pub async fn execute( super::install::install_from_lock( &new_lock, - &working_dir, + working_dir, &vendor_dir, &super::install::InstallConfig { dev_mode, @@ -1235,9 +1256,9 @@ pub async fn execute( apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, prefer_source, - no_cache: cli.no_cache, }, console, + executor, ) .await?; } @@ -1960,12 +1981,16 @@ mod tests { platform: PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), temporary_constraints: HashMap::new(), - repositories: vec![], + raw_repositories: vec![], }; let resolved = resolve(&request).await.expect("Resolution should succeed"); @@ -1977,9 +2002,13 @@ mod tests { composer_json_content: composer_json_content.to_string(), composer_json, include_dev: false, - repo_cache: mozart_registry::cache::Cache::new( - std::env::temp_dir().join("mozart-test-cache"), - false, + repositories: std::sync::Arc::new( + mozart_registry::repository::RepositorySet::with_packagist( + mozart_registry::cache::Cache::new( + std::env::temp_dir().join("mozart-test-cache"), + false, + ), + ), ), }) .await |
