diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-22 00:37:54 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-22 00:37:54 +0900 |
| commit | 0a8e5935e6305819bb02d8c69e2f046ff397913a (patch) | |
| tree | e5a288e679477b1603d7989e986ca22bbe590aa4 /crates/mozart/src | |
| parent | b5af594fec7da72b15c9a202c641af0494db6355 (diff) | |
| download | php-mozart-0a8e5935e6305819bb02d8c69e2f046ff397913a.tar.gz php-mozart-0a8e5935e6305819bb02d8c69e2f046ff397913a.tar.zst php-mozart-0a8e5935e6305819bb02d8c69e2f046ff397913a.zip | |
refactor(workspace): split monolithic crate into 6 workspace crates
Extract modules from the single `mozart` crate into 5 focused library
crates to improve compilation parallelism and architectural clarity:
- mozart-constraint: version constraint parser (independent)
- mozart-core: base types, console, validation, platform utilities
- mozart-archiver: archive creation (tar, zip, bzip2)
- mozart-registry: Packagist API, cache, resolver, downloader, lockfile
- mozart-autoload: autoloader generation and PHP scanner
Refactor Console::from_cli and build_cache_config to accept primitive
args instead of &Cli to break circular dependencies. Introduce
[workspace.dependencies] for centralized version management. Remove 9
unused direct dependencies from the CLI crate.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src')
48 files changed, 657 insertions, 2574 deletions
diff --git a/crates/mozart/src/archiver.rs b/crates/mozart/src/archiver.rs index 57985ef..d351e44 100644 --- a/crates/mozart/src/archiver.rs +++ b/crates/mozart/src/archiver.rs @@ -974,9 +974,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1043,9 +1043,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1098,9 +1098,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1152,9 +1152,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1210,9 +1210,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1280,9 +1280,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1351,9 +1351,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -1418,9 +1418,9 @@ mod tests { no_ansi: false, }; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; let result = execute(&args, &cli, &console); diff --git a/crates/mozart/src/autoload.rs b/crates/mozart/src/autoload.rs index 5d5d13c..2e4158c 100644 --- a/crates/mozart/src/autoload.rs +++ b/crates/mozart/src/autoload.rs @@ -1,5 +1,5 @@ -use crate::installed::InstalledPackages; -use crate::lockfile::LockedPackage; +use mozart_registry::installed::InstalledPackages; +use mozart_registry::lockfile::LockedPackage; use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; @@ -979,7 +979,7 @@ pub fn determine_suffix(working_dir: &Path, vendor_dir: &Path) -> anyhow::Result // Try composer.lock content-hash let lock_path = working_dir.join("composer.lock"); if lock_path.exists() { - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; return Ok(lock.content_hash); } @@ -1167,7 +1167,7 @@ pub fn generate(config: &AutoloadConfig) -> anyhow::Result<()> { installed .packages .iter() - .map(|p| crate::lockfile::LockedPackage { + .map(|p| mozart_registry::lockfile::LockedPackage { name: p.name.clone(), version: p.version.clone(), version_normalized: p.version_normalized.clone(), @@ -1262,7 +1262,7 @@ pub fn generate(config: &AutoloadConfig) -> anyhow::Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::installed::{InstalledPackageEntry, InstalledPackages}; + use mozart_registry::installed::{InstalledPackageEntry, InstalledPackages}; use std::collections::BTreeMap; use tempfile::tempdir; diff --git a/crates/mozart/src/cache.rs b/crates/mozart/src/cache.rs index 3e8d715..ac4b507 100644 --- a/crates/mozart/src/cache.rs +++ b/crates/mozart/src/cache.rs @@ -46,8 +46,8 @@ impl CacheConfig { /// /// Respects `$COMPOSER_CACHE_DIR` for the base directory, and /// `$COMPOSER_NO_CACHE` / `COMPOSER_CACHE_READ_ONLY` env vars. -pub fn build_cache_config(cli: &super::commands::Cli) -> CacheConfig { - let no_cache = std::env::var("COMPOSER_NO_CACHE").is_ok() || cli.no_cache; +pub fn build_cache_config(cli_no_cache: bool) -> CacheConfig { + let no_cache = std::env::var("COMPOSER_NO_CACHE").is_ok() || cli_no_cache; let read_only = std::env::var("COMPOSER_CACHE_READ_ONLY") .map(|v| v == "1" || v.eq_ignore_ascii_case("true")) diff --git a/crates/mozart/src/commands.rs b/crates/mozart/src/commands.rs index 285f65d..a745b3a 100644 --- a/crates/mozart/src/commands.rs +++ b/crates/mozart/src/commands.rs @@ -198,7 +198,13 @@ pub enum Commands { } pub fn execute(cli: &Cli) -> anyhow::Result<()> { - let console = crate::console::Console::from_cli(cli); + let console = mozart_core::console::Console::new( + cli.verbose, + cli.quiet, + cli.ansi, + cli.no_ansi, + cli.no_interaction, + ); match &cli.command { Commands::About(args) => about::execute(args, cli, &console), Commands::Archive(args) => archive::execute(args, cli, &console), diff --git a/crates/mozart/src/commands/about.rs b/crates/mozart/src/commands/about.rs index d60aecf..d436526 100644 --- a/crates/mozart/src/commands/about.rs +++ b/crates/mozart/src/commands/about.rs @@ -1,5 +1,5 @@ -use crate::console; use clap::Args; +use mozart_core::console; #[derive(Args)] pub struct AboutArgs {} diff --git a/crates/mozart/src/commands/archive.rs b/crates/mozart/src/commands/archive.rs index 9be45e9..687e116 100644 --- a/crates/mozart/src/commands/archive.rs +++ b/crates/mozart/src/commands/archive.rs @@ -85,9 +85,9 @@ impl Drop for PackageMeta { pub fn execute( args: &ArchiveArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { - use crate::archiver::{ + use mozart_archiver::{ ArchiveFormat, collect_archivable_files, create_archive, generate_archive_filename, parse_composer_excludes, parse_gitattributes, parse_gitignore_pattern, self_exclusion_patterns, @@ -154,7 +154,7 @@ pub fn execute( if !composer_json_path.exists() { anyhow::bail!("No composer.json found in {}", working_dir.display()); } - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; let (archive_name, archive_excludes) = read_archive_config(&composer_json_path)?; let version = root .extra_fields @@ -243,11 +243,11 @@ fn resolve_remote_package( package_name: &str, version_constraint: Option<&str>, ) -> anyhow::Result<PackageMeta> { - use crate::package::Stability; - use crate::version::find_best_candidate; + use mozart_core::package::Stability; + use mozart_registry::version::find_best_candidate; // Fetch versions from Packagist - let versions = crate::packagist::fetch_package_versions(package_name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(package_name, None)?; if versions.is_empty() { anyhow::bail!("No versions found for package \"{}\"", package_name); } @@ -292,11 +292,12 @@ fn resolve_remote_package( let temp_dir = temp_base.join(&unique); std::fs::create_dir_all(&temp_dir)?; - let bytes = crate::downloader::download_dist(&dist.url, dist.shasum.as_deref(), None, None)?; + let bytes = + mozart_registry::downloader::download_dist(&dist.url, dist.shasum.as_deref(), None, None)?; match dist.dist_type.as_str() { - "zip" => crate::downloader::extract_zip(&bytes, &temp_dir)?, - "tar" | "tar.gz" | "tgz" => crate::downloader::extract_tar_gz(&bytes, &temp_dir)?, + "zip" => mozart_registry::downloader::extract_zip(&bytes, &temp_dir)?, + "tar" | "tar.gz" | "tgz" => mozart_registry::downloader::extract_tar_gz(&bytes, &temp_dir)?, other => { let _ = std::fs::remove_dir_all(&temp_dir); anyhow::bail!("Unsupported dist type: {}", other); diff --git a/crates/mozart/src/commands/audit.rs b/crates/mozart/src/commands/audit.rs index 3e69bb3..7fd271f 100644 --- a/crates/mozart/src/commands/audit.rs +++ b/crates/mozart/src/commands/audit.rs @@ -2,7 +2,7 @@ use clap::Args; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; -use crate::packagist::SecurityAdvisory; +use mozart_registry::packagist::SecurityAdvisory; #[derive(Args)] pub struct AuditArgs { @@ -73,7 +73,7 @@ struct AuditResult { pub fn execute( args: &AuditArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Validate format let format = args.format.as_str(); @@ -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 crate::packagist::fetch_security_advisories(&names) { + let all_advisories = match mozart_registry::packagist::fetch_security_advisories(&names) { Ok(a) => a, Err(e) => { if args.ignore_unreachable { @@ -186,7 +186,7 @@ fn load_packages( fn load_installed_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec<PackageEntry>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; let dev_names: std::collections::HashSet<String> = installed .dev_package_names @@ -225,9 +225,10 @@ fn load_locked_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec< ); } - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if !no_dev && let Some(ref pkgs_dev) = lock.packages_dev { all_packages.extend(pkgs_dev.iter()); @@ -272,7 +273,7 @@ fn filter_advisories( .as_deref() .unwrap_or(pkg.version.as_str()); - let installed_ver = match crate::constraint::Version::parse(version_str) { + let installed_ver = match mozart_constraint::Version::parse(version_str) { Ok(v) => v, Err(_) => { eprintln!( @@ -297,7 +298,7 @@ fn filter_advisories( // Normalize single-pipe OR separators (`|`) to double-pipe (`||`) // since the Packagist API may use either form. let normalized_constraint = normalize_or_separator(&advisory.affected_versions); - let constraint = match crate::constraint::VersionConstraint::parse( + let constraint = match mozart_constraint::VersionConstraint::parse( &normalized_constraint, ) { Ok(c) => c, @@ -391,7 +392,7 @@ fn render_table(result: &AuditResult) { if result.total_advisory_count == 0 && result.abandoned.is_empty() { println!( "{}", - crate::console::info("No security vulnerability advisories found.") + mozart_core::console::info("No security vulnerability advisories found.") ); return; } @@ -406,7 +407,7 @@ fn render_table(result: &AuditResult) { "Found {} security vulnerability {} affecting {} package(s):", result.total_advisory_count, advisory_word, result.affected_package_count ); - println!("{}", crate::console::highlight(&header)); + println!("{}", mozart_core::console::highlight(&header)); println!(); for advisories in result.advisories.values() { @@ -456,7 +457,7 @@ fn render_table(result: &AuditResult) { if !result.abandoned.is_empty() { let header = format!("Found {} abandoned package(s):", result.abandoned.len()); - println!("{}", crate::console::highlight(&header)); + println!("{}", mozart_core::console::highlight(&header)); println!(); let label_width = 20usize; @@ -605,7 +606,7 @@ fn render_summary(result: &AuditResult) { #[cfg(test)] mod tests { use super::*; - use crate::packagist::{AdvisorySource, SecurityAdvisory}; + use mozart_registry::packagist::{AdvisorySource, SecurityAdvisory}; use std::collections::BTreeMap; fn make_advisory( @@ -782,8 +783,8 @@ mod tests { let working_dir = dir.path(); let vendor_dir = working_dir.join("vendor"); - let mut installed = crate::installed::InstalledPackages::new(); - installed.upsert(crate::installed::InstalledPackageEntry { + let mut installed = mozart_registry::installed::InstalledPackages::new(); + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "1.5.0".to_string(), version_normalized: Some("1.5.0.0".to_string()), @@ -811,8 +812,8 @@ mod tests { let working_dir = dir.path(); let vendor_dir = working_dir.join("vendor"); - let mut installed = crate::installed::InstalledPackages::new(); - installed.upsert(crate::installed::InstalledPackageEntry { + let mut installed = mozart_registry::installed::InstalledPackages::new(); + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "1.5.0".to_string(), version_normalized: None, @@ -824,7 +825,7 @@ mod tests { aliases: vec![], extra_fields: BTreeMap::new(), }); - installed.upsert(crate::installed::InstalledPackageEntry { + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "phpunit/phpunit".to_string(), version: "10.0.0".to_string(), version_normalized: None, @@ -848,7 +849,7 @@ mod tests { #[test] fn test_load_locked_packages() { - use crate::lockfile::{LockFile, LockedPackage}; + use mozart_registry::lockfile::{LockFile, LockedPackage}; use tempfile::tempdir; let dir = tempdir().unwrap(); @@ -902,7 +903,7 @@ mod tests { #[test] fn test_load_locked_packages_no_dev() { - use crate::lockfile::{LockFile, LockedPackage}; + use mozart_registry::lockfile::{LockFile, LockedPackage}; use tempfile::tempdir; let dir = tempdir().unwrap(); diff --git a/crates/mozart/src/commands/browse.rs b/crates/mozart/src/commands/browse.rs index 0a89ae7..d662ec0 100644 --- a/crates/mozart/src/commands/browse.rs +++ b/crates/mozart/src/commands/browse.rs @@ -21,7 +21,7 @@ pub struct BrowseArgs { pub fn execute( args: &BrowseArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -36,7 +36,7 @@ pub fn execute( "No composer.json found in the current directory and no package specified." ); } - let root = crate::package::read_from_file(&composer_json)?; + let root = mozart_core::package::read_from_file(&composer_json)?; vec![root.name.clone()] } else { args.packages.clone() @@ -57,7 +57,7 @@ pub fn execute( None => { console.info(&format!( "{}", - crate::console::warning(&format!( + mozart_core::console::warning(&format!( "No URL found for package \"{}\".", package_name )) @@ -84,7 +84,7 @@ fn resolve_url( // 1. Check root package (composer.json) let composer_json = working_dir.join("composer.json"); if composer_json.exists() - && let Ok(root) = crate::package::read_from_file(&composer_json) + && let Ok(root) = mozart_core::package::read_from_file(&composer_json) && root.name.eq_ignore_ascii_case(package_name) && let Some(url) = extract_url_from_root(&root, prefer_homepage) { @@ -94,7 +94,7 @@ fn resolve_url( // 2. Check lock file (composer.lock) let lock_path = working_dir.join("composer.lock"); if lock_path.exists() - && let Ok(lock) = crate::lockfile::LockFile::read_from_file(&lock_path) + && let Ok(lock) = mozart_registry::lockfile::LockFile::read_from_file(&lock_path) { let all_packages = lock .packages @@ -109,7 +109,7 @@ fn resolve_url( } // 3. Fall back to Packagist API - match crate::packagist::fetch_package_versions(package_name, None) { + match mozart_registry::packagist::fetch_package_versions(package_name, None) { Ok(versions) => { // Find the latest stable version (first non-dev, or fallback to first) let best = versions @@ -129,7 +129,7 @@ fn resolve_url( // ─── URL extraction ─────────────────────────────────────────────────────────── fn extract_url_from_locked( - pkg: &crate::lockfile::LockedPackage, + pkg: &mozart_registry::lockfile::LockedPackage, prefer_homepage: bool, ) -> Option<String> { if prefer_homepage { @@ -161,7 +161,7 @@ fn extract_url_from_locked( } fn extract_url_from_root( - root: &crate::package::RawPackageData, + root: &mozart_core::package::RawPackageData, prefer_homepage: bool, ) -> Option<String> { if prefer_homepage { @@ -187,7 +187,7 @@ fn extract_url_from_root( } fn extract_url_from_packagist( - pkg: &crate::packagist::PackagistVersion, + pkg: &mozart_registry::packagist::PackagistVersion, prefer_homepage: bool, ) -> Option<String> { if prefer_homepage { @@ -278,14 +278,14 @@ mod tests { source_url: Option<&str>, homepage: Option<&str>, support_source: Option<&str>, - ) -> crate::lockfile::LockedPackage { + ) -> mozart_registry::lockfile::LockedPackage { let support = support_source.map(|s| serde_json::json!({"source": s})); - let source = source_url.map(|url| crate::lockfile::LockedSource { + let source = source_url.map(|url| mozart_registry::lockfile::LockedSource { source_type: "git".to_string(), url: url.to_string(), reference: None, }); - crate::lockfile::LockedPackage { + mozart_registry::lockfile::LockedPackage { name: "vendor/package".to_string(), version: "1.0.0".to_string(), version_normalized: None, diff --git a/crates/mozart/src/commands/bump.rs b/crates/mozart/src/commands/bump.rs index 4c37dd6..af2809d 100644 --- a/crates/mozart/src/commands/bump.rs +++ b/crates/mozart/src/commands/bump.rs @@ -25,7 +25,7 @@ pub struct BumpArgs { pub fn execute( args: &BumpArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -44,7 +44,8 @@ pub fn execute( let composer_json_content = std::fs::read_to_string(&composer_json_path)?; // Parse composer.json - let mut root: crate::package::RawPackageData = serde_json::from_str(&composer_json_content)?; + let mut root: mozart_core::package::RawPackageData = + serde_json::from_str(&composer_json_content)?; // Warn if package is not a project (libraries shouldn't bump) if let Some(ref pkg_type) = root.package_type @@ -52,7 +53,7 @@ pub fn execute( { console.info(&format!( "{}", - crate::console::warning(&format!( + mozart_core::console::warning(&format!( "Warning: Bumping constraints for a non-project package (type=\"{pkg_type}\"). \ Libraries should not pin their dependencies." )) @@ -65,12 +66,12 @@ pub fn execute( } // Read and parse lock file - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; // Check lock file freshness if !lock.is_fresh(&composer_json_content) { - return Err(crate::exit_code::bail( - crate::exit_code::LOCK_FILE_INVALID, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::LOCK_FILE_INVALID, "composer.lock is not up to date with composer.json. \ Run `mozart install` or `mozart update` to refresh it.", )); @@ -107,7 +108,7 @@ pub fn execute( } if let Some((pretty_version, version_normalized)) = locked_versions.get(&pkg_name.to_lowercase()) - && let Some(new_constraint) = crate::version_bumper::bump_requirement( + && let Some(new_constraint) = mozart_core::version_bumper::bump_requirement( constraint, pretty_version, version_normalized.as_deref(), @@ -131,7 +132,7 @@ pub fn execute( } if let Some((pretty_version, version_normalized)) = locked_versions.get(&pkg_name.to_lowercase()) - && let Some(new_constraint) = crate::version_bumper::bump_requirement( + && let Some(new_constraint) = mozart_core::version_bumper::bump_requirement( constraint, pretty_version, version_normalized.as_deref(), @@ -154,16 +155,16 @@ pub fn execute( if args.dry_run { println!( "{}: {} → {}", - crate::console::info(name), + mozart_core::console::info(name), old, - crate::console::comment(new) + mozart_core::console::comment(new) ); } else { println!( "Bumping {} from {} to {}", - crate::console::info(name), + mozart_core::console::info(name), old, - crate::console::comment(new) + mozart_core::console::comment(new) ); } } @@ -182,18 +183,19 @@ pub fn execute( } // Write updated composer.json - crate::package::write_to_file(&root, &composer_json_path)?; + mozart_core::package::write_to_file(&root, &composer_json_path)?; // Update the lock file content-hash to match the new composer.json let new_composer_json_content = std::fs::read_to_string(&composer_json_path)?; - let new_hash = crate::lockfile::LockFile::compute_content_hash(&new_composer_json_content)?; + let new_hash = + mozart_registry::lockfile::LockFile::compute_content_hash(&new_composer_json_content)?; let mut updated_lock = lock; updated_lock.content_hash = new_hash; updated_lock.write_to_file(&lock_path)?; println!( "\n{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "{} constraint(s) bumped successfully.", total_changes )) @@ -206,7 +208,7 @@ pub fn execute( /// Build a map of lowercase package names to (pretty_version, version_normalized) from composer.lock. fn build_locked_versions_map( - lock: &crate::lockfile::LockFile, + lock: &mozart_registry::lockfile::LockFile, ) -> HashMap<String, (String, Option<String>)> { let mut map: HashMap<String, (String, Option<String>)> = HashMap::new(); @@ -242,7 +244,7 @@ fn is_platform_package(name: &str) -> bool { #[cfg(test)] mod tests { use super::*; - use crate::lockfile::{LockFile, LockedPackage}; + use mozart_registry::lockfile::{LockFile, LockedPackage}; use std::collections::BTreeMap; use tempfile::tempdir; @@ -344,9 +346,9 @@ mod tests { dry_run: false, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -380,9 +382,9 @@ mod tests { dry_run: true, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -417,9 +419,9 @@ mod tests { dry_run: false, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -460,9 +462,9 @@ mod tests { dry_run: false, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -505,9 +507,9 @@ mod tests { dry_run: false, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -589,9 +591,9 @@ mod tests { dry_run: false, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); @@ -636,9 +638,9 @@ mod tests { dry_run: false, }; let cli = make_cli(dir.path()); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; execute(&args, &cli, &console).unwrap(); diff --git a/crates/mozart/src/commands/check_platform_reqs.rs b/crates/mozart/src/commands/check_platform_reqs.rs index ad7b860..71728d3 100644 --- a/crates/mozart/src/commands/check_platform_reqs.rs +++ b/crates/mozart/src/commands/check_platform_reqs.rs @@ -55,7 +55,7 @@ struct CheckResult { pub fn execute( args: &CheckPlatformReqsArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -89,7 +89,7 @@ pub fn execute( } // Detect real platform - let platform = crate::platform::detect_platform(); + let platform = mozart_core::platform::detect_platform(); // Check requirements against detected platform let results = check_requirements(&requirements, &platform); @@ -146,7 +146,7 @@ fn collect_requirements( // Always include root composer.json requirements let composer_json_path = working_dir.join("composer.json"); - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; add_platform_requirements_from_map(&root.require, "root", &mut requirements); if !args.no_dev { @@ -161,7 +161,7 @@ fn collect_from_lock( no_dev: bool, requirements: &mut BTreeMap<String, Vec<PlatformRequirement>>, ) -> anyhow::Result<()> { - let lock = crate::lockfile::LockFile::read_from_file(lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(lock_path)?; for pkg in &lock.packages { add_platform_requirements_from_map(&pkg.require, &pkg.name, requirements); @@ -181,7 +181,7 @@ fn collect_from_installed( no_dev: bool, requirements: &mut BTreeMap<String, Vec<PlatformRequirement>>, ) -> anyhow::Result<()> { - let installed = crate::installed::InstalledPackages::read(vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(vendor_dir)?; let dev_names: std::collections::HashSet<String> = installed .dev_package_names @@ -200,7 +200,7 @@ fn collect_from_installed( { for (dep_name, dep_constraint_val) in require_obj { let dep_lower = dep_name.to_lowercase(); - if crate::platform::is_platform_package(&dep_lower) { + if mozart_core::platform::is_platform_package(&dep_lower) { let constraint = dep_constraint_val.as_str().unwrap_or("*").to_string(); requirements .entry(dep_lower) @@ -224,7 +224,7 @@ fn add_platform_requirements_from_map( ) { for (name, constraint) in require { let name_lower = name.to_lowercase(); - if crate::platform::is_platform_package(&name_lower) { + if mozart_core::platform::is_platform_package(&name_lower) { requirements .entry(name_lower) .or_default() @@ -240,7 +240,7 @@ fn add_platform_requirements_from_map( fn check_requirements( requirements: &BTreeMap<String, Vec<PlatformRequirement>>, - platform: &[crate::platform::PlatformPackage], + platform: &[mozart_core::platform::PlatformPackage], ) -> Vec<CheckResult> { let mut results: Vec<CheckResult> = Vec::new(); @@ -279,18 +279,18 @@ fn check_requirements( } Some(detected) => { // Check all constraints - let detected_version = match crate::constraint::Version::parse(&detected.version) { + let detected_version = match mozart_constraint::Version::parse(&detected.version) { Ok(v) => v, Err(_) => { // Unparseable version → treat as 0.0.0 - crate::constraint::Version::parse("0.0.0").unwrap() + mozart_constraint::Version::parse("0.0.0").unwrap() } }; let mut failed_req: Option<(String, String)> = None; for req in reqs { let constraint = - match crate::constraint::VersionConstraint::parse(&req.constraint) { + match mozart_constraint::VersionConstraint::parse(&req.constraint) { Ok(c) => c, Err(_) => continue, // skip unparseable constraints }; @@ -352,9 +352,9 @@ fn render_text(results: &[CheckResult]) { CheckStatus::Success => { println!( "{} {} {}", - crate::console::info(&padded_name), - crate::console::comment(&padded_version), - crate::console::info("success"), + mozart_core::console::info(&padded_name), + mozart_core::console::comment(&padded_version), + mozart_core::console::info("success"), ); } CheckStatus::Failed => { @@ -365,9 +365,9 @@ fn render_text(results: &[CheckResult]) { .unwrap_or(("", "")); println!( "{} {} {} requires {} ({})", - crate::console::comment(&padded_name), - crate::console::comment(&padded_version), - crate::console::error("failed"), + mozart_core::console::comment(&padded_name), + mozart_core::console::comment(&padded_version), + mozart_core::console::error("failed"), provider, constraint, ); @@ -380,9 +380,9 @@ fn render_text(results: &[CheckResult]) { .unwrap_or(("*", "")); println!( "{} {} {} requires {} ({})", - crate::console::comment(&padded_name), - crate::console::comment(&padded_version), - crate::console::error("missing"), + mozart_core::console::comment(&padded_name), + mozart_core::console::comment(&padded_version), + mozart_core::console::error("missing"), provider, constraint, ); @@ -426,7 +426,7 @@ fn render_json(results: &[CheckResult]) -> anyhow::Result<()> { #[cfg(test)] mod tests { use super::*; - use crate::platform::PlatformPackage; + use mozart_core::platform::PlatformPackage; use std::collections::BTreeMap; use tempfile::tempdir; @@ -501,17 +501,25 @@ mod tests { #[test] fn test_is_platform_package() { - assert!(crate::platform::is_platform_package("php")); - assert!(crate::platform::is_platform_package("ext-json")); - assert!(crate::platform::is_platform_package("ext-mbstring")); - assert!(crate::platform::is_platform_package("lib-pcre")); - assert!(crate::platform::is_platform_package("php-64bit")); - assert!(crate::platform::is_platform_package("composer-plugin-api")); - assert!(crate::platform::is_platform_package("composer-runtime-api")); + assert!(mozart_core::platform::is_platform_package("php")); + assert!(mozart_core::platform::is_platform_package("ext-json")); + assert!(mozart_core::platform::is_platform_package("ext-mbstring")); + assert!(mozart_core::platform::is_platform_package("lib-pcre")); + assert!(mozart_core::platform::is_platform_package("php-64bit")); + assert!(mozart_core::platform::is_platform_package( + "composer-plugin-api" + )); + assert!(mozart_core::platform::is_platform_package( + "composer-runtime-api" + )); - assert!(!crate::platform::is_platform_package("monolog/monolog")); - assert!(!crate::platform::is_platform_package("psr/log")); - assert!(!crate::platform::is_platform_package("symfony/console")); + assert!(!mozart_core::platform::is_platform_package( + "monolog/monolog" + )); + assert!(!mozart_core::platform::is_platform_package("psr/log")); + assert!(!mozart_core::platform::is_platform_package( + "symfony/console" + )); } // ── test_collect_requirements_from_lock ────────────────────────────────── diff --git a/crates/mozart/src/commands/clear_cache.rs b/crates/mozart/src/commands/clear_cache.rs index 59baff3..afab64d 100644 --- a/crates/mozart/src/commands/clear_cache.rs +++ b/crates/mozart/src/commands/clear_cache.rs @@ -1,5 +1,5 @@ -use crate::cache::{Cache, build_cache_config}; use clap::Args; +use mozart_registry::cache::{Cache, build_cache_config}; #[derive(Args)] pub struct ClearCacheArgs { @@ -11,9 +11,9 @@ pub struct ClearCacheArgs { pub fn execute( args: &ClearCacheArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { - let config = build_cache_config(cli); + let config = build_cache_config(cli.no_cache); if args.gc { // Run GC only (probabilistic under normal circumstances, but forced here) diff --git a/crates/mozart/src/commands/completion.rs b/crates/mozart/src/commands/completion.rs index 406749c..4c2f4a8 100644 --- a/crates/mozart/src/commands/completion.rs +++ b/crates/mozart/src/commands/completion.rs @@ -12,7 +12,7 @@ pub struct CompletionArgs { pub fn execute( args: &CompletionArgs, _cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let mut cmd = super::Cli::command(); clap_complete::aot::generate(args.shell, &mut cmd, "mozart", &mut std::io::stdout()); diff --git a/crates/mozart/src/commands/config.rs b/crates/mozart/src/commands/config.rs index e875a92..2a3ab85 100644 --- a/crates/mozart/src/commands/config.rs +++ b/crates/mozart/src/commands/config.rs @@ -514,7 +514,7 @@ fn write_json_file(path: &Path, value: &serde_json::Value) -> anyhow::Result<()> { std::fs::create_dir_all(parent)?; } - crate::package::write_to_file(value, path)?; + mozart_core::package::write_to_file(value, path)?; Ok(()) } @@ -606,7 +606,7 @@ fn render_value(v: &serde_json::Value) -> String { pub fn execute( args: &ConfigArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // 1. Handle --editor mode if args.editor { @@ -1025,7 +1025,7 @@ fn execute_read( None => { eprintln!( "{}", - crate::console::error( + mozart_core::console::error( "No command specified. Use --list to show all config values, \ or provide a setting key." ) diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index 654eb0e..e9a1911 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -1,12 +1,12 @@ -use crate::console; -use crate::downloader; -use crate::lockfile; -use crate::package::{self, Stability}; -use crate::packagist; -use crate::resolver::{self, PlatformConfig, ResolveRequest}; -use crate::validation; -use crate::version; use clap::Args; +use mozart_core::console; +use mozart_core::package::{self, Stability}; +use mozart_core::validation; +use mozart_registry::downloader; +use mozart_registry::lockfile; +use mozart_registry::packagist; +use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest}; +use mozart_registry::version; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -173,7 +173,7 @@ fn is_dir_non_empty(path: &Path) -> bool { pub fn execute( args: &CreateProjectArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // --- Handle deprecated / no-op flags --- if args.prefer_source { @@ -423,8 +423,8 @@ pub fn execute( console.info("Resolving dependencies..."); let resolved = resolver::resolve(&request).map_err(|e| { - crate::exit_code::bail( - crate::exit_code::DEPENDENCY_RESOLUTION_FAILED, + mozart_core::exit_code::bail( + mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, e.to_string(), ) })?; diff --git a/crates/mozart/src/commands/dependency.rs b/crates/mozart/src/commands/dependency.rs index 0ae8bb4..4714de2 100644 --- a/crates/mozart/src/commands/dependency.rs +++ b/crates/mozart/src/commands/dependency.rs @@ -67,7 +67,7 @@ pub fn load_packages(working_dir: &Path, locked: bool) -> Result<Vec<PackageInfo // Add the root package (composer.json) as a synthetic entry if composer_json_path.exists() - && let Ok(root) = crate::package::read_from_file(&composer_json_path) + && let Ok(root) = mozart_core::package::read_from_file(&composer_json_path) { // Extract conflict from extra_fields if present let conflict: BTreeMap<String, String> = root @@ -98,7 +98,7 @@ fn load_from_lockfile(lock_path: &Path) -> Result<Vec<PackageInfo>> { if !lock_path.exists() { anyhow::bail!("composer.lock not found — run `mozart install` first or omit --locked"); } - let lock = crate::lockfile::LockFile::read_from_file(lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(lock_path)?; let mut packages: Vec<PackageInfo> = Vec::new(); @@ -131,7 +131,7 @@ fn load_from_lockfile(lock_path: &Path) -> Result<Vec<PackageInfo>> { fn load_from_installed(working_dir: &Path) -> Result<Vec<PackageInfo>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; let packages = installed .packages @@ -192,7 +192,7 @@ fn load_from_installed(working_dir: &Path) -> Result<Vec<PackageInfo>> { pub fn get_dependents( packages: &[PackageInfo], needles: &[String], - constraint: Option<&crate::constraint::VersionConstraint>, + constraint: Option<&mozart_constraint::VersionConstraint>, inverted: bool, recursive: bool, ) -> Result<Vec<DependencyResult>> { @@ -317,7 +317,7 @@ fn recurse_dependents( fn get_prohibitors( packages: &[PackageInfo], needles: &[String], - constraint: Option<&crate::constraint::VersionConstraint>, + constraint: Option<&mozart_constraint::VersionConstraint>, _recursive: bool, ) -> Result<Vec<DependencyResult>> { let mut results: Vec<DependencyResult> = Vec::new(); @@ -333,7 +333,7 @@ fn get_prohibitors( .find(|(k, _)| k.to_lowercase() == needle_lower) && let Some(requested_version) = constraint && let Ok(pkg_constraint) = - crate::constraint::VersionConstraint::parse(req_constraint_str) + mozart_constraint::VersionConstraint::parse(req_constraint_str) { // The package requires `needle` but with a different // (incompatible) constraint — it blocks the requested version. @@ -359,7 +359,7 @@ fn get_prohibitors( .find(|(k, _)| k.to_lowercase() == needle_lower) && let Some(requested_version) = constraint && let Ok(pkg_constraint) = - crate::constraint::VersionConstraint::parse(req_constraint_str) + mozart_constraint::VersionConstraint::parse(req_constraint_str) && constraint_prohibits(requested_version, &pkg_constraint) { results.push(DependencyResult { @@ -380,7 +380,7 @@ fn get_prohibitors( .find(|(k, _)| k.to_lowercase() == needle_lower) && let Some(requested_version) = constraint && let Ok(conflict_constraint) = - crate::constraint::VersionConstraint::parse(conflict_constraint_str) + mozart_constraint::VersionConstraint::parse(conflict_constraint_str) { // If the conflict constraint overlaps with (matches) the // requested version range, this package conflicts with it. @@ -408,8 +408,8 @@ fn get_prohibitors( /// We sample a set of "representative versions" from the requested constraint /// and check whether none of them satisfy the package's constraint. fn constraint_prohibits( - requested: &crate::constraint::VersionConstraint, - pkg_constraint: &crate::constraint::VersionConstraint, + requested: &mozart_constraint::VersionConstraint, + pkg_constraint: &mozart_constraint::VersionConstraint, ) -> bool { // We try to determine if there is any version satisfying *requested* that // does NOT satisfy *pkg_constraint*. @@ -430,8 +430,8 @@ fn constraint_prohibits( /// That is, if the conflict constraint matches at least one version that the /// requested constraint also matches. fn constraint_overlaps( - requested: &crate::constraint::VersionConstraint, - conflict_constraint: &crate::constraint::VersionConstraint, + requested: &mozart_constraint::VersionConstraint, + conflict_constraint: &mozart_constraint::VersionConstraint, ) -> bool { let probes = sample_versions_from_constraint(requested); if probes.is_empty() { @@ -446,9 +446,9 @@ fn constraint_overlaps( /// constraint. These are used for the "does this constraint overlap/prohibit /// that constraint?" heuristic. fn sample_versions_from_constraint( - constraint: &crate::constraint::VersionConstraint, -) -> Vec<crate::constraint::Version> { - use crate::constraint::Version; + constraint: &mozart_constraint::VersionConstraint, +) -> Vec<mozart_constraint::Version> { + use mozart_constraint::Version; // Broad grid of versions to probe let candidates: &[&str] = &[ @@ -498,7 +498,7 @@ fn sample_versions_from_constraint( /// Columns: package name | version | link description | link constraint pub fn print_table(results: &[DependencyResult]) { if results.is_empty() { - println!("{}", crate::console::info("No relationships found.")); + println!("{}", mozart_core::console::info("No relationships found.")); return; } @@ -522,10 +522,10 @@ pub fn print_table(results: &[DependencyResult]) { for r in results { println!( "{:<name_w$} {:<ver_w$} {:<desc_w$} {}", - crate::console::info(&r.package_name), - crate::console::comment(&r.package_version), + mozart_core::console::info(&r.package_name), + mozart_core::console::comment(&r.package_version), r.link_description, - crate::console::comment(&r.link_constraint), + mozart_core::console::comment(&r.link_constraint), name_w = name_w, ver_w = ver_w, desc_w = desc_w, @@ -544,7 +544,7 @@ pub fn print_table(results: &[DependencyResult]) { /// ``` pub fn print_tree(results: &[DependencyResult], depth: usize) { if results.is_empty() && depth == 0 { - println!("{}", crate::console::info("No relationships found.")); + println!("{}", mozart_core::console::info("No relationships found.")); return; } @@ -556,10 +556,10 @@ pub fn print_tree(results: &[DependencyResult], depth: usize) { println!( "{}{:<} {} {} {}", prefix, - crate::console::info(&r.package_name), - crate::console::comment(&r.package_version), + mozart_core::console::info(&r.package_name), + mozart_core::console::comment(&r.package_version), r.link_description, - crate::console::comment(&r.link_constraint), + mozart_core::console::comment(&r.link_constraint), ); if !r.children.is_empty() { @@ -685,7 +685,7 @@ mod tests { make_pkg("root/project", "ROOT", &[("vendor/a", "^1.0")], &[], true), make_pkg("vendor/a", "1.0.0", &[], &[], false), ]; - let constraint = crate::constraint::VersionConstraint::parse("2.0.0").unwrap(); + let constraint = mozart_constraint::VersionConstraint::parse("2.0.0").unwrap(); let needles = vec!["vendor/a".to_string()]; let results = get_dependents(&packages, &needles, Some(&constraint), true, false).unwrap(); assert!(!results.is_empty(), "root should prohibit vendor/a 2.0"); @@ -713,7 +713,7 @@ mod tests { false, ), ]; - let constraint = crate::constraint::VersionConstraint::parse("2.0.0").unwrap(); + let constraint = mozart_constraint::VersionConstraint::parse("2.0.0").unwrap(); let needles = vec!["vendor/a".to_string()]; let results = get_dependents(&packages, &needles, Some(&constraint), true, false).unwrap(); // vendor/b conflicts with vendor/a ^2.0 which covers 2.0.0 @@ -733,7 +733,7 @@ mod tests { make_pkg("root/project", "ROOT", &[("vendor/a", "^2.0")], &[], true), make_pkg("vendor/a", "2.0.0", &[], &[], false), ]; - let constraint = crate::constraint::VersionConstraint::parse("2.5.0").unwrap(); + let constraint = mozart_constraint::VersionConstraint::parse("2.5.0").unwrap(); let needles = vec!["vendor/a".to_string()]; let results = get_dependents(&packages, &needles, Some(&constraint), true, false).unwrap(); assert!( diff --git a/crates/mozart/src/commands/depends.rs b/crates/mozart/src/commands/depends.rs index 80e70f1..91b3829 100644 --- a/crates/mozart/src/commands/depends.rs +++ b/crates/mozart/src/commands/depends.rs @@ -22,7 +22,7 @@ pub struct DependsArgs { pub fn execute( args: &DependsArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -34,7 +34,7 @@ pub fn execute( if packages.is_empty() { println!( "{}", - crate::console::info("No packages found. Run `mozart install` first.") + mozart_core::console::info("No packages found. Run `mozart install` first.") ); return Ok(()); } diff --git a/crates/mozart/src/commands/diagnose.rs b/crates/mozart/src/commands/diagnose.rs index 199ed60..606d00e 100644 --- a/crates/mozart/src/commands/diagnose.rs +++ b/crates/mozart/src/commands/diagnose.rs @@ -220,7 +220,7 @@ fn check_composer_lock(working_dir: &Path) -> CheckResult { } }; - let lock = match crate::lockfile::LockFile::read_from_file(&lock_path) { + let lock = match mozart_registry::lockfile::LockFile::read_from_file(&lock_path) { Ok(l) => l, Err(e) => return CheckResult::Fail(format!("composer.lock is invalid: {e}")), }; @@ -374,7 +374,7 @@ fn check_cache_dir(cache_dir: &Path) -> CheckResult { pub fn execute( _args: &DiagnoseArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -551,7 +551,7 @@ mod tests { #[test] fn test_check_composer_lock_fresh() { - use crate::lockfile::LockFile; + use mozart_registry::lockfile::LockFile; let dir = tempdir().unwrap(); @@ -587,7 +587,7 @@ mod tests { #[test] fn test_check_composer_lock_stale() { - use crate::lockfile::LockFile; + use mozart_registry::lockfile::LockFile; let dir = tempdir().unwrap(); diff --git a/crates/mozart/src/commands/dump_autoload.rs b/crates/mozart/src/commands/dump_autoload.rs index 6f38ab9..a920f5a 100644 --- a/crates/mozart/src/commands/dump_autoload.rs +++ b/crates/mozart/src/commands/dump_autoload.rs @@ -51,7 +51,7 @@ pub struct DumpAutoloadArgs { pub fn execute( args: &DumpAutoloadArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -62,14 +62,14 @@ pub fn execute( let dev_mode = !args.no_dev; // Determine suffix: read from existing autoload.php, or from lock file, or generate - let suffix = crate::autoload::determine_suffix(&working_dir, &vendor_dir)?; + let suffix = mozart_autoload::autoload::determine_suffix(&working_dir, &vendor_dir)?; if args.dry_run { console.info("Dry run: would generate autoload files"); return Ok(()); } - crate::autoload::generate(&crate::autoload::AutoloadConfig { + mozart_autoload::autoload::generate(&mozart_autoload::autoload::AutoloadConfig { project_dir: working_dir, vendor_dir, dev_mode, @@ -79,7 +79,7 @@ pub fn execute( apcu: args.apcu, apcu_prefix: args.apcu_prefix.clone(), strict_psr: args.strict_psr, - platform_check: crate::autoload::PlatformCheckMode::Full, + platform_check: mozart_autoload::autoload::PlatformCheckMode::Full, ignore_platform_reqs: args.ignore_platform_reqs, })?; diff --git a/crates/mozart/src/commands/exec.rs b/crates/mozart/src/commands/exec.rs index 32781a5..1e785cb 100644 --- a/crates/mozart/src/commands/exec.rs +++ b/crates/mozart/src/commands/exec.rs @@ -20,7 +20,7 @@ pub struct ExecArgs { pub fn execute( args: &ExecArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -58,7 +58,7 @@ pub fn execute( } else { // Check root composer.json bin entries let composer_json_path = working_dir.join("composer.json"); - if let Ok(root) = crate::package::read_from_file(&composer_json_path) { + if let Ok(root) = mozart_core::package::read_from_file(&composer_json_path) { root.bin.into_iter().find_map(|entry| { let p = working_dir.join(&entry); let stem = Path::new(&entry) @@ -159,7 +159,7 @@ fn get_binaries(working_dir: &Path, bin_dir: &Path) -> Vec<(String, bool)> { // Collect from root composer.json bin entries let composer_json_path = working_dir.join("composer.json"); - if let Ok(root) = crate::package::read_from_file(&composer_json_path) { + if let Ok(root) = mozart_core::package::read_from_file(&composer_json_path) { let existing: std::collections::HashSet<&str> = binaries.iter().map(|(n, _)| n.as_str()).collect(); let mut local: Vec<String> = root @@ -363,7 +363,7 @@ mod tests { assert!(!candidate.exists()); // Confirm root bin entries are also empty - let root = crate::package::read_from_file(&dir.path().join("composer.json")).unwrap(); + let root = mozart_core::package::read_from_file(&dir.path().join("composer.json")).unwrap(); assert!(root.bin.is_empty()); } } diff --git a/crates/mozart/src/commands/fund.rs b/crates/mozart/src/commands/fund.rs index e8a42f5..ad91d6b 100644 --- a/crates/mozart/src/commands/fund.rs +++ b/crates/mozart/src/commands/fund.rs @@ -26,7 +26,7 @@ struct FundingEntry { pub fn execute( args: &FundArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -64,9 +64,10 @@ pub fn execute( fn collect_funding_from_locked(working_dir: &Path) -> anyhow::Result<Vec<FundingEntry>> { let lock_path = working_dir.join("composer.lock"); - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if let Some(ref pkgs_dev) = lock.packages_dev { all_packages.extend(pkgs_dev.iter()); } @@ -94,7 +95,7 @@ fn collect_funding_from_locked(working_dir: &Path) -> anyhow::Result<Vec<Funding fn collect_funding_from_installed(working_dir: &Path) -> anyhow::Result<Vec<FundingEntry>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; let entries = installed .packages @@ -361,7 +362,7 @@ mod tests { #[test] fn test_fund_from_lockfile() { - use crate::lockfile::{LockFile, LockedPackage}; + use mozart_registry::lockfile::{LockFile, LockedPackage}; use tempfile::tempdir; let dir = tempdir().unwrap(); @@ -451,7 +452,7 @@ mod tests { let working_dir = dir.path(); let vendor_dir = working_dir.join("vendor"); - let mut installed = crate::installed::InstalledPackages::new(); + let mut installed = mozart_registry::installed::InstalledPackages::new(); let mut extra = BTreeMap::new(); extra.insert( @@ -461,7 +462,7 @@ mod tests { "url": "https://github.com/Seldaek" }]), ); - installed.upsert(crate::installed::InstalledPackageEntry { + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -475,7 +476,7 @@ mod tests { }); // Package without funding - installed.upsert(crate::installed::InstalledPackageEntry { + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "psr/log".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -498,7 +499,7 @@ mod tests { #[test] fn test_fund_no_funding_data() { - use crate::lockfile::{LockFile, LockedPackage}; + use mozart_registry::lockfile::{LockFile, LockedPackage}; use tempfile::tempdir; let dir = tempdir().unwrap(); diff --git a/crates/mozart/src/commands/global.rs b/crates/mozart/src/commands/global.rs index e6e7bc8..1cde2c1 100644 --- a/crates/mozart/src/commands/global.rs +++ b/crates/mozart/src/commands/global.rs @@ -16,7 +16,7 @@ pub struct GlobalArgs { pub fn execute( args: &GlobalArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { use clap::Parser as _; use std::fs; diff --git a/crates/mozart/src/commands/init.rs b/crates/mozart/src/commands/init.rs index be104c6..25cc70e 100644 --- a/crates/mozart/src/commands/init.rs +++ b/crates/mozart/src/commands/init.rs @@ -1,9 +1,9 @@ -use crate::console; -use crate::package::{self, RawAuthor, RawAutoload, RawPackageData, RawRepository}; -use crate::validation; use anyhow::{Context, bail}; use clap::Args; use colored::Colorize; +use mozart_core::console; +use mozart_core::package::{self, RawAuthor, RawAutoload, RawPackageData, RawRepository}; +use mozart_core::validation; use std::collections::BTreeMap; use std::path::{Path, PathBuf}; use std::process::Command; @@ -211,7 +211,7 @@ fn build_interactive( let name = console.ask_validated( &format!( "Package name (<vendor>/<name>) [{}]", - crate::console::comment(&default_name), + mozart_core::console::comment(&default_name), ), &default_name, |val| { @@ -229,7 +229,10 @@ fn build_interactive( // Description let default_desc = args.description.clone().unwrap_or_default(); let description = console.ask( - &format!("Description [{}]", crate::console::comment(&default_desc)), + &format!( + "Description [{}]", + mozart_core::console::comment(&default_desc) + ), &default_desc, ); let description = if description.is_empty() { @@ -248,7 +251,7 @@ fn build_interactive( &format!( "Author [{}n to skip]", if !default_author.is_empty() { - format!("{}, ", crate::console::comment(&default_author)) + format!("{}, ", mozart_core::console::comment(&default_author)) } else { String::new() } @@ -272,7 +275,7 @@ fn build_interactive( let stability_input = console.ask( &format!( "Minimum Stability [{}]", - crate::console::comment(&default_stability), + mozart_core::console::comment(&default_stability), ), &default_stability, ); @@ -292,7 +295,7 @@ fn build_interactive( let type_input = console.ask( &format!( "Package Type (e.g. library, project, metapackage, composer-plugin) [{}]", - crate::console::comment(&default_type), + mozart_core::console::comment(&default_type), ), &default_type, ); @@ -305,7 +308,10 @@ fn build_interactive( // License let default_license = args.license.clone().unwrap_or_default(); let license_input = console.ask( - &format!("License [{}]", crate::console::comment(&default_license),), + &format!( + "License [{}]", + mozart_core::console::comment(&default_license), + ), &default_license, ); let license = if license_input.is_empty() { @@ -319,7 +325,7 @@ fn build_interactive( console.info(""); console.info(&format!( "{}", - crate::console::info("Define your dependencies.") + mozart_core::console::info("Define your dependencies.") )); console.info(""); let require = parse_requirements(&args.require)?; @@ -335,7 +341,7 @@ fn build_interactive( &format!( "Add PSR-4 autoload mapping? Maps namespace \"{}\" to the entered relative path. [{}, n to skip]", namespace, - crate::console::comment(&default_autoload), + mozart_core::console::comment(&default_autoload), ), &default_autoload, ); diff --git a/crates/mozart/src/commands/install.rs b/crates/mozart/src/commands/install.rs index fb7335b..1094d99 100644 --- a/crates/mozart/src/commands/install.rs +++ b/crates/mozart/src/commands/install.rs @@ -1,8 +1,8 @@ -use crate::console; -use crate::downloader; -use crate::installed; -use crate::lockfile; use clap::Args; +use mozart_core::console; +use mozart_registry::downloader; +use mozart_registry::installed; +use mozart_registry::lockfile; use std::collections::{BTreeMap, HashSet}; use std::path::{Path, PathBuf}; @@ -475,7 +475,7 @@ pub fn install_from_lock( let suffix = lock.content_hash.clone(); - crate::autoload::generate(&crate::autoload::AutoloadConfig { + mozart_autoload::autoload::generate(&mozart_autoload::autoload::AutoloadConfig { project_dir: working_dir.to_path_buf(), vendor_dir: vendor_dir.to_path_buf(), dev_mode, @@ -485,7 +485,7 @@ pub fn install_from_lock( apcu: config.apcu_autoloader, apcu_prefix: config.apcu_autoloader_prefix.clone(), strict_psr: false, - platform_check: crate::autoload::PlatformCheckMode::Full, + platform_check: mozart_autoload::autoload::PlatformCheckMode::Full, ignore_platform_reqs: config.ignore_platform_reqs, })?; @@ -499,7 +499,7 @@ pub fn install_from_lock( pub fn execute( args: &InstallArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Step 1: Resolve the working directory let working_dir = resolve_working_dir(cli); @@ -507,8 +507,8 @@ pub fn execute( // Step 2: Validate arguments if !args.packages.is_empty() { let pkgs = args.packages.join(" "); - return Err(crate::exit_code::bail( - crate::exit_code::GENERAL_ERROR, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::GENERAL_ERROR, format!( "Invalid argument {pkgs}. Use \"mozart require {pkgs}\" instead to add packages to your composer.json." ), @@ -516,8 +516,8 @@ pub fn execute( } if args.no_install { - return Err(crate::exit_code::bail( - crate::exit_code::GENERAL_ERROR, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::GENERAL_ERROR, "Invalid option \"--no-install\". Use \"mozart update --no-install\" instead if you are trying to update the composer.lock file.", )); } @@ -537,8 +537,8 @@ pub fn execute( // Step 3: Read composer.lock let lock_path = working_dir.join("composer.lock"); if !lock_path.exists() { - return Err(crate::exit_code::bail( - crate::exit_code::LOCK_FILE_INVALID, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::LOCK_FILE_INVALID, "No composer.lock file present. Run \"mozart update\" to generate one.", )); } diff --git a/crates/mozart/src/commands/licenses.rs b/crates/mozart/src/commands/licenses.rs index 0703976..4ffd928 100644 --- a/crates/mozart/src/commands/licenses.rs +++ b/crates/mozart/src/commands/licenses.rs @@ -30,7 +30,7 @@ struct LicenseEntry { pub fn execute( args: &LicensesArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -51,7 +51,7 @@ pub fn execute( if !composer_json_path.exists() { anyhow::bail!("No composer.json found in {}", working_dir.display()); } - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; let root_name = root.name.clone(); let root_version = root @@ -98,7 +98,7 @@ pub fn execute( fn load_installed_licenses(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec<LicenseEntry>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; let dev_names: HashSet<String> = installed .dev_package_names @@ -134,9 +134,10 @@ fn load_locked_licenses(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec< ); } - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if !no_dev && let Some(ref pkgs_dev) = lock.packages_dev { all_packages.extend(pkgs_dev.iter()); @@ -157,7 +158,9 @@ fn load_locked_licenses(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec< // ─── License extraction ─────────────────────────────────────────────────────── -fn extract_installed_licenses(pkg: &crate::installed::InstalledPackageEntry) -> Vec<String> { +fn extract_installed_licenses( + pkg: &mozart_registry::installed::InstalledPackageEntry, +) -> Vec<String> { pkg.extra_fields .get("license") .and_then(|v| v.as_array()) @@ -205,9 +208,12 @@ fn render_text( root_licenses.join(", ") }; // Print root package header - println!("Name: {}", crate::console::comment(root_name)); - println!("Version: {}", crate::console::comment(root_version)); - println!("Licenses: {}", crate::console::comment(&license_display)); + println!("Name: {}", mozart_core::console::comment(root_name)); + println!("Version: {}", mozart_core::console::comment(root_version)); + println!( + "Licenses: {}", + mozart_core::console::comment(&license_display) + ); println!("Dependencies:"); println!(); @@ -312,8 +318,8 @@ mod tests { name: &str, version: &str, extra: BTreeMap<String, serde_json::Value>, - ) -> crate::installed::InstalledPackageEntry { - crate::installed::InstalledPackageEntry { + ) -> mozart_registry::installed::InstalledPackageEntry { + mozart_registry::installed::InstalledPackageEntry { name: name.to_string(), version: version.to_string(), version_normalized: None, @@ -429,10 +435,10 @@ mod tests { .unwrap(); // Build installed packages - let mut installed = crate::installed::InstalledPackages::new(); + let mut installed = mozart_registry::installed::InstalledPackages::new(); let mut extra = BTreeMap::new(); extra.insert("license".to_string(), serde_json::json!(["MIT"])); - installed.upsert(crate::installed::InstalledPackageEntry { + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -468,12 +474,12 @@ mod tests { ) .unwrap(); - let mut installed = crate::installed::InstalledPackages::new(); + let mut installed = mozart_registry::installed::InstalledPackages::new(); // Production package let mut extra_prod = BTreeMap::new(); extra_prod.insert("license".to_string(), serde_json::json!(["MIT"])); - installed.upsert(crate::installed::InstalledPackageEntry { + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -489,7 +495,7 @@ mod tests { // Dev package let mut extra_dev = BTreeMap::new(); extra_dev.insert("license".to_string(), serde_json::json!(["BSD-3-Clause"])); - installed.upsert(crate::installed::InstalledPackageEntry { + installed.upsert(mozart_registry::installed::InstalledPackageEntry { name: "phpunit/phpunit".to_string(), version: "10.0.0".to_string(), version_normalized: None, @@ -519,7 +525,7 @@ mod tests { #[test] fn test_load_locked_licenses_basic() { - use crate::lockfile::{LockFile, LockedPackage}; + use mozart_registry::lockfile::{LockFile, LockedPackage}; use tempfile::tempdir; let dir = tempdir().unwrap(); diff --git a/crates/mozart/src/commands/outdated.rs b/crates/mozart/src/commands/outdated.rs index b6672c4..49c541f 100644 --- a/crates/mozart/src/commands/outdated.rs +++ b/crates/mozart/src/commands/outdated.rs @@ -99,7 +99,7 @@ struct OutdatedEntry { pub fn execute( args: &OutdatedArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -120,7 +120,7 @@ pub fn execute( // Load root composer.json for --direct filtering and constraint lookup let composer_json_path = working_dir.join("composer.json"); let root_package = if composer_json_path.exists() { - crate::package::read_from_file(&composer_json_path).ok() + mozart_core::package::read_from_file(&composer_json_path).ok() } else { None }; @@ -247,7 +247,7 @@ pub fn execute( fn load_installed_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec<PackageInfo>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; let dev_names: HashSet<String> = installed .dev_package_names @@ -301,9 +301,10 @@ fn load_locked_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec< ); } - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if !no_dev && let Some(ref pkgs_dev) = lock.packages_dev { all_packages.extend(pkgs_dev.iter()); @@ -333,10 +334,10 @@ fn load_locked_packages(working_dir: &Path, no_dev: bool) -> anyhow::Result<Vec< // ─── Version fetching ──────────────────────────────────────────────────────── fn fetch_latest_version(name: &str) -> anyhow::Result<PackageInfo> { - use crate::package::Stability; - use crate::version::find_best_candidate; + use mozart_core::package::Stability; + use mozart_registry::version::find_best_candidate; - let versions = crate::packagist::fetch_package_versions(name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(name, None)?; let best = find_best_candidate(&versions, Stability::Stable) .ok_or_else(|| anyhow::anyhow!("No stable version found for {name}"))?; @@ -361,7 +362,7 @@ fn classify_update( latest_normalized: &str, root_constraint: Option<&str>, ) -> UpdateCategory { - use crate::version::compare_normalized_versions; + use mozart_registry::version::compare_normalized_versions; // If latest is not newer than current, it's up-to-date if compare_normalized_versions(latest_normalized, current_normalized) != Ordering::Greater { @@ -370,8 +371,8 @@ fn classify_update( // We have an update available — classify it if let Some(constraint_str) = root_constraint - && let Ok(constraint) = crate::constraint::VersionConstraint::parse(constraint_str) - && let Ok(latest_ver) = crate::constraint::Version::parse(latest_normalized) + && let Ok(constraint) = mozart_constraint::VersionConstraint::parse(constraint_str) + && let Ok(latest_ver) = mozart_constraint::Version::parse(latest_normalized) { if constraint.matches(&latest_ver) { return UpdateCategory::SemverCompatible; @@ -460,7 +461,10 @@ fn passes_level_filter(args: &OutdatedArgs, current: &str, latest: &str) -> bool fn render_text(entries: &[OutdatedEntry]) { if entries.is_empty() { - println!("{}", crate::console::info("All packages are up to date.")); + println!( + "{}", + mozart_core::console::info("All packages are up to date.") + ); return; } @@ -484,23 +488,23 @@ fn render_text(entries: &[OutdatedEntry]) { let (name_str, lat_str) = match entry.category { UpdateCategory::UpToDate => ( - crate::console::info(&name_col).to_string(), - crate::console::info(&lat_col).to_string(), + mozart_core::console::info(&name_col).to_string(), + mozart_core::console::info(&lat_col).to_string(), ), UpdateCategory::SemverCompatible => ( - crate::console::highlight(&name_col).to_string(), - crate::console::highlight(&lat_col).to_string(), + mozart_core::console::highlight(&name_col).to_string(), + mozart_core::console::highlight(&lat_col).to_string(), ), UpdateCategory::SemverIncompatible => ( - crate::console::comment(&name_col).to_string(), - crate::console::comment(&lat_col).to_string(), + mozart_core::console::comment(&name_col).to_string(), + mozart_core::console::comment(&lat_col).to_string(), ), }; println!( "{} {} {} {}", name_str, - crate::console::comment(&cur_col), + mozart_core::console::comment(&cur_col), lat_str, entry.description ); diff --git a/crates/mozart/src/commands/prohibits.rs b/crates/mozart/src/commands/prohibits.rs index f545bb2..3ec01a7 100644 --- a/crates/mozart/src/commands/prohibits.rs +++ b/crates/mozart/src/commands/prohibits.rs @@ -25,7 +25,7 @@ pub struct ProhibitsArgs { pub fn execute( args: &ProhibitsArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -37,13 +37,13 @@ pub fn execute( if packages.is_empty() { println!( "{}", - crate::console::info("No packages found. Run `mozart install` first.") + mozart_core::console::info("No packages found. Run `mozart install` first.") ); return Ok(()); } // Parse the version constraint the user is asking about - let version_constraint = crate::constraint::VersionConstraint::parse(&args.version) + let version_constraint = mozart_constraint::VersionConstraint::parse(&args.version) .map_err(|e| anyhow::anyhow!("Invalid version constraint '{}': {}", args.version, e))?; let recursive = args.tree || args.recursive; @@ -61,7 +61,7 @@ pub fn execute( if results.is_empty() { println!( "{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "{} {} can be installed.", args.package, args.version )) diff --git a/crates/mozart/src/commands/reinstall.rs b/crates/mozart/src/commands/reinstall.rs index 78611b4..d064136 100644 --- a/crates/mozart/src/commands/reinstall.rs +++ b/crates/mozart/src/commands/reinstall.rs @@ -68,7 +68,7 @@ pub struct ReinstallArgs { pub fn execute( args: &ReinstallArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Step 1: Resolve working directory let working_dir = match &cli.working_dir { @@ -79,7 +79,7 @@ pub fn execute( let vendor_dir = working_dir.join("vendor"); // Step 2: Read installed.json - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; // Step 3: Read composer.lock let lock_path = working_dir.join("composer.lock"); @@ -89,7 +89,7 @@ pub fn execute( working_dir.display() ); } - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; // Step 4: Validate — error if both --type and package names are provided; // error if neither is provided. @@ -116,7 +116,7 @@ pub fn execute( .map(|n| n.to_lowercase()) .collect(); - let candidates: Vec<&crate::installed::InstalledPackageEntry> = installed + let candidates: Vec<&mozart_registry::installed::InstalledPackageEntry> = installed .packages .iter() .filter(|pkg| { @@ -128,7 +128,7 @@ pub fn execute( }) .collect(); - let selected: Vec<&crate::installed::InstalledPackageEntry> = if has_type { + let selected: Vec<&mozart_registry::installed::InstalledPackageEntry> = if has_type { filter_by_type(&candidates, &args.r#type) } else { filter_by_names(&candidates, &args.packages) @@ -141,7 +141,7 @@ pub fn execute( // Step 6: For each selected package, find its locked metadata. // Build a lookup map: lowercase name -> LockedPackage - let all_locked: Vec<&crate::lockfile::LockedPackage> = lock + let all_locked: Vec<&mozart_registry::lockfile::LockedPackage> = lock .packages .iter() .chain(lock.packages_dev.as_deref().unwrap_or(&[])) @@ -161,8 +161,8 @@ pub fn execute( } // Step 8: For each package, remove vendor dir and re-download. - let cache_config = crate::cache::build_cache_config(cli); - let files_cache = crate::cache::Cache::files(&cache_config); + let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); + let files_cache = mozart_registry::cache::Cache::files(&cache_config); let mut reinstalled_count = 0usize; @@ -202,12 +202,12 @@ pub fn execute( } // Re-download and install - let mut progress = crate::downloader::DownloadProgress::new( + let mut progress = mozart_registry::downloader::DownloadProgress::new( !args.no_progress, format!("{} ({})", locked.name, locked.version), ); - crate::downloader::install_package( + mozart_registry::downloader::install_package( &dist.url, &dist.dist_type, dist.shasum.as_deref(), @@ -233,7 +233,7 @@ pub fn execute( let dev_mode = !args.no_dev && installed.dev; let suffix = lock.content_hash.clone(); - crate::autoload::generate(&crate::autoload::AutoloadConfig { + mozart_autoload::autoload::generate(&mozart_autoload::autoload::AutoloadConfig { project_dir: working_dir.to_path_buf(), vendor_dir: vendor_dir.to_path_buf(), dev_mode, @@ -243,7 +243,7 @@ pub fn execute( apcu: args.apcu_autoloader, apcu_prefix: args.apcu_autoloader_prefix.clone(), strict_psr: false, - platform_check: crate::autoload::PlatformCheckMode::Full, + platform_check: mozart_autoload::autoload::PlatformCheckMode::Full, ignore_platform_reqs: args.ignore_platform_reqs, })?; @@ -257,9 +257,9 @@ pub fn execute( /// Filter candidates by package type (case-insensitive). fn filter_by_type<'a>( - candidates: &[&'a crate::installed::InstalledPackageEntry], + candidates: &[&'a mozart_registry::installed::InstalledPackageEntry], types: &[String], -) -> Vec<&'a crate::installed::InstalledPackageEntry> { +) -> Vec<&'a mozart_registry::installed::InstalledPackageEntry> { let lower_types: Vec<String> = types.iter().map(|t| t.to_lowercase()).collect(); candidates .iter() @@ -280,9 +280,9 @@ fn filter_by_type<'a>( /// Patterns support `*` as a wildcard matching any sequence of characters /// (including `/`). fn filter_by_names<'a>( - candidates: &[&'a crate::installed::InstalledPackageEntry], + candidates: &[&'a mozart_registry::installed::InstalledPackageEntry], patterns: &[String], -) -> Vec<&'a crate::installed::InstalledPackageEntry> { +) -> Vec<&'a mozart_registry::installed::InstalledPackageEntry> { candidates .iter() .filter(|pkg| { @@ -339,9 +339,9 @@ fn glob_matches(pattern: &str, value: &str) -> bool { /// Find a locked package by name (case-insensitive). fn find_locked_package<'a>( - locked: &[&'a crate::lockfile::LockedPackage], + locked: &[&'a mozart_registry::lockfile::LockedPackage], name: &str, -) -> Option<&'a crate::lockfile::LockedPackage> { +) -> Option<&'a mozart_registry::lockfile::LockedPackage> { let name_lower = name.to_lowercase(); locked .iter() @@ -361,8 +361,8 @@ mod tests { fn make_installed_entry( name: &str, pkg_type: Option<&str>, - ) -> crate::installed::InstalledPackageEntry { - crate::installed::InstalledPackageEntry { + ) -> mozart_registry::installed::InstalledPackageEntry { + mozart_registry::installed::InstalledPackageEntry { name: name.to_string(), version: "1.0.0".to_string(), version_normalized: None, @@ -376,8 +376,8 @@ mod tests { } } - fn make_locked_package(name: &str, version: &str) -> crate::lockfile::LockedPackage { - crate::lockfile::LockedPackage { + fn make_locked_package(name: &str, version: &str) -> mozart_registry::lockfile::LockedPackage { + mozart_registry::lockfile::LockedPackage { name: name.to_string(), version: version.to_string(), version_normalized: None, @@ -453,7 +453,7 @@ mod tests { make_locked_package("psr/log", "3.0.0"), make_locked_package("monolog/monolog", "3.8.0"), ]; - let refs: Vec<&crate::lockfile::LockedPackage> = pkgs.iter().collect(); + let refs: Vec<&mozart_registry::lockfile::LockedPackage> = pkgs.iter().collect(); let result = find_locked_package(&refs, "psr/log"); assert!(result.is_some()); @@ -463,7 +463,7 @@ mod tests { #[test] fn test_find_locked_package_case_insensitive() { let pkgs = vec![make_locked_package("Monolog/Monolog", "3.8.0")]; - let refs: Vec<&crate::lockfile::LockedPackage> = pkgs.iter().collect(); + let refs: Vec<&mozart_registry::lockfile::LockedPackage> = pkgs.iter().collect(); let result = find_locked_package(&refs, "monolog/monolog"); assert!(result.is_some()); @@ -472,7 +472,7 @@ mod tests { #[test] fn test_find_locked_package_not_found() { let pkgs = vec![make_locked_package("psr/log", "3.0.0")]; - let refs: Vec<&crate::lockfile::LockedPackage> = pkgs.iter().collect(); + let refs: Vec<&mozart_registry::lockfile::LockedPackage> = pkgs.iter().collect(); let result = find_locked_package(&refs, "monolog/monolog"); assert!(result.is_none()); @@ -601,7 +601,7 @@ mod tests { let e1 = make_installed_entry("psr/log", Some("library")); let e2 = make_installed_entry("phpunit/phpunit", Some("library")); - let mut installed = crate::installed::InstalledPackages::new(); + let mut installed = mozart_registry::installed::InstalledPackages::new(); installed.packages.push(e1.clone()); installed.packages.push(e2.clone()); installed.dev_package_names = vec!["phpunit/phpunit".to_string()]; @@ -613,7 +613,7 @@ mod tests { .collect(); // Simulate --no-dev filtering - let candidates: Vec<&crate::installed::InstalledPackageEntry> = installed + let candidates: Vec<&mozart_registry::installed::InstalledPackageEntry> = installed .packages .iter() .filter(|pkg| !dev_package_names.contains(&pkg.name.to_lowercase())) @@ -628,13 +628,13 @@ mod tests { let e1 = make_installed_entry("psr/log", Some("library")); let e2 = make_installed_entry("phpunit/phpunit", Some("library")); - let mut installed = crate::installed::InstalledPackages::new(); + let mut installed = mozart_registry::installed::InstalledPackages::new(); installed.packages.push(e1.clone()); installed.packages.push(e2.clone()); installed.dev_package_names = vec!["phpunit/phpunit".to_string()]; // no_dev = false: include all - let candidates: Vec<&crate::installed::InstalledPackageEntry> = + let candidates: Vec<&mozart_registry::installed::InstalledPackageEntry> = installed.packages.iter().collect(); assert_eq!(candidates.len(), 2); diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index 6969745..de4b77b 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -1,9 +1,9 @@ -use crate::console; -use crate::lockfile; -use crate::package; -use crate::resolver::{self, PlatformConfig, ResolveRequest}; -use crate::validation; use clap::Args; +use mozart_core::console; +use mozart_core::package; +use mozart_core::validation; +use mozart_registry::lockfile; +use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest}; use std::collections::HashMap; #[derive(Args)] @@ -99,7 +99,7 @@ pub struct RemoveArgs { pub fn execute( args: &RemoveArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Step 1: Validate inputs if args.packages.is_empty() && !args.unused { @@ -286,8 +286,8 @@ pub fn execute( // Run resolver let mut resolved = resolver::resolve(&request).map_err(|e| { - crate::exit_code::bail( - crate::exit_code::DEPENDENCY_RESOLUTION_FAILED, + mozart_core::exit_code::bail( + mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, e.to_string(), ) })?; @@ -477,8 +477,8 @@ pub fn execute( #[cfg(test)] mod tests { use super::*; - use crate::lockfile; - use crate::package::RawPackageData; + use mozart_core::package::RawPackageData; + use mozart_registry::lockfile; use std::collections::BTreeMap; // ──────────── Helper constructors ──────────── @@ -684,8 +684,8 @@ mod tests { #[test] #[ignore] fn test_remove_full_e2e() { - use crate::lockfile::{LockFileGenerationRequest, generate_lock_file}; - use crate::resolver::{ResolveRequest, resolve}; + use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; + use mozart_registry::resolver::{ResolveRequest, resolve}; use std::collections::HashMap; use tempfile::tempdir; @@ -705,11 +705,11 @@ mod tests { require: vec![("psr/log".to_string(), "^3.0".to_string())], require_dev: vec![], include_dev: false, - minimum_stability: crate::package::Stability::Stable, + minimum_stability: mozart_core::package::Stability::Stable, stability_flags: HashMap::new(), prefer_stable: true, prefer_lowest: false, - platform: crate::resolver::PlatformConfig::new(), + platform: mozart_registry::resolver::PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, @@ -736,11 +736,11 @@ mod tests { require: vec![], require_dev: vec![], include_dev: false, - minimum_stability: crate::package::Stability::Stable, + minimum_stability: mozart_core::package::Stability::Stable, stability_flags: HashMap::new(), prefer_stable: true, prefer_lowest: false, - platform: crate::resolver::PlatformConfig::new(), + platform: mozart_registry::resolver::PlatformConfig::new(), ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, diff --git a/crates/mozart/src/commands/repository.rs b/crates/mozart/src/commands/repository.rs index c54601b..0974e29 100644 --- a/crates/mozart/src/commands/repository.rs +++ b/crates/mozart/src/commands/repository.rs @@ -38,7 +38,7 @@ pub struct RepositoryArgs { pub fn execute( _args: &RepositoryArgs, _cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { todo!() } diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index 6e76b6e..960182f 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -1,11 +1,11 @@ -use crate::console; -use crate::lockfile; -use crate::package::{self, Stability}; -use crate::packagist; -use crate::resolver::{self, PlatformConfig, ResolveRequest}; -use crate::validation; -use crate::version; use clap::Args; +use mozart_core::console; +use mozart_core::package::{self, Stability}; +use mozart_core::validation; +use mozart_registry::lockfile; +use mozart_registry::packagist; +use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest}; +use mozart_registry::version; use std::collections::HashMap; use std::io::{BufRead, IsTerminal, Write}; @@ -346,7 +346,7 @@ fn interactive_search_packages( pub fn execute( args: &RequireArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Collect the effective list of packages to add. // If none were provided on the CLI, try interactive search (unless --no-interaction). @@ -609,8 +609,8 @@ pub fn execute( let mut resolved = match resolver::resolve(&request) { Ok(packages) => packages, Err(e) => { - return Err(crate::exit_code::bail( - crate::exit_code::DEPENDENCY_RESOLUTION_FAILED, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, e.to_string(), )); } @@ -758,7 +758,7 @@ pub fn execute( .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); if prefer_source { - console.info(&crate::console::warning( + console.info(&mozart_core::console::warning( "Warning: Source installs are not yet supported. Falling back to dist.", )); } @@ -836,7 +836,7 @@ mod tests { /// Verify that --sort-packages sorts both require and require-dev maps. #[test] fn test_sort_packages_sorts_both_sections() { - use crate::package::RawPackageData; + use mozart_core::package::RawPackageData; let mut raw = RawPackageData::new("test/project".to_string()); raw.require @@ -915,8 +915,8 @@ mod tests { #[test] #[ignore] fn test_require_full_e2e() { - use crate::lockfile::{LockFileGenerationRequest, generate_lock_file}; - use crate::package::RawPackageData; + use mozart_core::package::RawPackageData; + use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; let composer_json_content = r#"{"name": "test/project", "require": {"psr/log": "^3.0"}}"#; let composer_json: RawPackageData = serde_json::from_str(composer_json_content).unwrap(); @@ -956,7 +956,7 @@ mod tests { #[test] #[ignore] fn test_require_no_install_writes_lock_only() { - use crate::package::RawPackageData; + use mozart_core::package::RawPackageData; use tempfile::tempdir; let dir = tempdir().unwrap(); diff --git a/crates/mozart/src/commands/run_script.rs b/crates/mozart/src/commands/run_script.rs index 8f2b5cd..acef421 100644 --- a/crates/mozart/src/commands/run_script.rs +++ b/crates/mozart/src/commands/run_script.rs @@ -49,7 +49,7 @@ const INTERNAL_ONLY_EVENTS: &[&str] = &[ pub fn execute( args: &RunScriptArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), diff --git a/crates/mozart/src/commands/search.rs b/crates/mozart/src/commands/search.rs index d172976..98189ff 100644 --- a/crates/mozart/src/commands/search.rs +++ b/crates/mozart/src/commands/search.rs @@ -1,5 +1,5 @@ -use crate::packagist::SearchResult; use clap::Args; +use mozart_registry::packagist::SearchResult; #[derive(Args)] pub struct SearchArgs { @@ -62,11 +62,12 @@ fn passes_only_vendor(result: &SearchResult, query: &str) -> bool { pub fn execute( args: &SearchArgs, _cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let query = args.tokens.join(" "); - let (all_results, total) = crate::packagist::search_packages(&query, args.r#type.as_deref())?; + let (all_results, total) = + mozart_registry::packagist::search_packages(&query, args.r#type.as_deref())?; // Apply client-side filters let mut results: Vec<&SearchResult> = all_results.iter().collect(); @@ -92,7 +93,7 @@ pub fn execute( if results.is_empty() { eprintln!( "{}", - crate::console::warning(&format!("No packages found for \"{query}\"")) + mozart_core::console::warning(&format!("No packages found for \"{query}\"")) ); return Ok(()); } @@ -115,9 +116,13 @@ pub fn execute( println!( "{} {} {}", - crate::console::info(&format!("{:<width$}", result.name, width = name_width)), - crate::console::comment(&dl_str), - crate::console::comment(&fav_str), + mozart_core::console::info(&format!( + "{:<width$}", + result.name, + width = name_width + )), + mozart_core::console::comment(&dl_str), + mozart_core::console::comment(&fav_str), ); if !result.description.is_empty() { println!(" {}", result.description); @@ -163,7 +168,7 @@ mod tests { #[test] fn test_parse_search_response() { - use crate::packagist::SearchResponse; + use mozart_registry::packagist::SearchResponse; let json = r#"{ "results": [ @@ -209,7 +214,7 @@ mod tests { #[test] fn test_parse_search_response_with_next() { - use crate::packagist::SearchResponse; + use mozart_registry::packagist::SearchResponse; let json = r#"{ "results": [], diff --git a/crates/mozart/src/commands/self_update.rs b/crates/mozart/src/commands/self_update.rs index 29c67ef..03d2643 100644 --- a/crates/mozart/src/commands/self_update.rs +++ b/crates/mozart/src/commands/self_update.rs @@ -53,7 +53,7 @@ const BACKUP_EXTENSION: &str = ".old"; pub fn execute( args: &SelfUpdateArgs, _cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let current_exe = std::env::current_exe() .map_err(|e| anyhow::anyhow!("Could not determine current executable path: {e}"))?; @@ -278,7 +278,7 @@ fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> anyhow: if args.version.is_none() && target_version == current_version { println!( "{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "Mozart is already at the latest version ({current_version})" )) ); @@ -329,14 +329,14 @@ fn update(args: &SelfUpdateArgs, current_exe: &Path, data_dir: &Path) -> anyhow: println!( "{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "Mozart updated successfully from {current_version} to {target_version}" )) ); if args.clean_backups { clean_backups(data_dir)?; - println!("{}", crate::console::comment("Old backups removed.")); + println!("{}", mozart_core::console::comment("Old backups removed.")); } Ok(()) @@ -367,7 +367,7 @@ fn rollback(current_exe: &Path, data_dir: &Path) -> anyhow::Result<()> { println!( "{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "Rollback successful. Restored from {}", backup.file_name().unwrap_or_default().to_string_lossy() )) diff --git a/crates/mozart/src/commands/show.rs b/crates/mozart/src/commands/show.rs index a8ae995..c6a446d 100644 --- a/crates/mozart/src/commands/show.rs +++ b/crates/mozart/src/commands/show.rs @@ -102,7 +102,7 @@ pub struct ShowArgs { pub fn execute( args: &ShowArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -142,17 +142,17 @@ pub fn execute( fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; if installed.packages.is_empty() { // Warn if composer.json has requirements but nothing is installed let composer_json_path = working_dir.join("composer.json"); if composer_json_path.exists() { - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; if !root.require.is_empty() || !root.require_dev.is_empty() { eprintln!( "{}", - crate::console::warning( + mozart_core::console::warning( "No dependencies installed. Try running mozart install or update." ) ); @@ -216,11 +216,11 @@ fn execute_installed(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> } fn filter_installed_packages<'a>( - installed: &'a crate::installed::InstalledPackages, + installed: &'a mozart_registry::installed::InstalledPackages, args: &ShowArgs, working_dir: &Path, -) -> anyhow::Result<Vec<&'a crate::installed::InstalledPackageEntry>> { - let mut packages: Vec<&crate::installed::InstalledPackageEntry> = +) -> anyhow::Result<Vec<&'a mozart_registry::installed::InstalledPackageEntry>> { + let mut packages: Vec<&mozart_registry::installed::InstalledPackageEntry> = installed.packages.iter().collect(); // --no-dev: exclude dev packages @@ -237,7 +237,7 @@ fn filter_installed_packages<'a>( if args.direct { let composer_json_path = working_dir.join("composer.json"); if composer_json_path.exists() { - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; let mut direct_names: HashSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect(); if !args.no_dev { @@ -254,7 +254,7 @@ fn filter_installed_packages<'a>( } fn show_installed_package_list( - packages: &[&crate::installed::InstalledPackageEntry], + packages: &[&mozart_registry::installed::InstalledPackageEntry], args: &ShowArgs, _vendor_dir: &Path, ) -> anyhow::Result<()> { @@ -297,7 +297,7 @@ fn show_installed_package_list( // --outdated: skip packages that are up-to-date if args.outdated { if let Some(ref li) = latest_info { - use crate::version::compare_normalized_versions; + use mozart_registry::version::compare_normalized_versions; use std::cmp::Ordering; if compare_normalized_versions(&li.version_normalized, &version_normalized) != Ordering::Greater @@ -362,20 +362,24 @@ fn show_installed_package_list( .map(|li| classify_update_category(&entry.version_normalized, &li.version_normalized)); let name_str = match category { - Some(ListUpdateKind::Compatible) => { - crate::console::highlight(&format!("{:<width$}", entry.name, width = name_width)) - .to_string() - } - Some(ListUpdateKind::Incompatible) => { - crate::console::comment(&format!("{:<width$}", entry.name, width = name_width)) - .to_string() - } - _ => crate::console::info(&format!("{:<width$}", entry.name, width = name_width)) + Some(ListUpdateKind::Compatible) => mozart_core::console::highlight(&format!( + "{:<width$}", + entry.name, + width = name_width + )) + .to_string(), + Some(ListUpdateKind::Incompatible) => mozart_core::console::comment(&format!( + "{:<width$}", + entry.name, + width = name_width + )) + .to_string(), + _ => mozart_core::console::info(&format!("{:<width$}", entry.name, width = name_width)) .to_string(), }; let version_str = - crate::console::comment(&format!("{:<width$}", version, width = version_width)) + mozart_core::console::comment(&format!("{:<width$}", version, width = version_width)) .to_string(); if show_latest { @@ -383,20 +387,20 @@ fn show_installed_package_list( Some(li) => { let lv = format_version(&li.version); match category { - Some(ListUpdateKind::Compatible) => crate::console::highlight(&format!( - "{:<width$}", - lv, - width = latest_width - )) + Some(ListUpdateKind::Compatible) => mozart_core::console::highlight( + &format!("{:<width$}", lv, width = latest_width), + ) .to_string(), - Some(ListUpdateKind::Incompatible) => crate::console::comment(&format!( + Some(ListUpdateKind::Incompatible) => mozart_core::console::comment( + &format!("{:<width$}", lv, width = latest_width), + ) + .to_string(), + _ => mozart_core::console::info(&format!( "{:<width$}", lv, width = latest_width )) .to_string(), - _ => crate::console::info(&format!("{:<width$}", lv, width = latest_width)) - .to_string(), } } None => format!("{:<width$}", "", width = latest_width), @@ -439,7 +443,7 @@ enum ListUpdateKind { } fn classify_update_category(current_normalized: &str, latest_normalized: &str) -> ListUpdateKind { - use crate::version::compare_normalized_versions; + use mozart_registry::version::compare_normalized_versions; use std::cmp::Ordering; if compare_normalized_versions(latest_normalized, current_normalized) != Ordering::Greater { @@ -469,10 +473,10 @@ fn extract_major(version_normalized: &str) -> u64 { } fn fetch_latest_for_package(name: &str) -> anyhow::Result<LatestInfo> { - use crate::package::Stability; - use crate::version::find_best_candidate; + use mozart_core::package::Stability; + use mozart_registry::version::find_best_candidate; - let versions = crate::packagist::fetch_package_versions(name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(name, None)?; let best = find_best_candidate(&versions, Stability::Stable) .ok_or_else(|| anyhow::anyhow!("No stable version found for {name}"))?; @@ -513,7 +517,7 @@ fn render_installed_json(entries: &[InstalledListEntry]) -> anyhow::Result<()> { } fn show_installed_package_detail( - installed: &crate::installed::InstalledPackages, + installed: &mozart_registry::installed::InstalledPackages, package_name: &str, working_dir: &Path, ) -> anyhow::Result<()> { @@ -535,36 +539,36 @@ fn show_installed_package_detail( let vendor_dir = working_dir.join("vendor"); - println!("{} : {}", crate::console::info("name"), pkg.name); + println!("{} : {}", mozart_core::console::info("name"), pkg.name); println!( "{} : {}", - crate::console::info("descrip."), + mozart_core::console::info("descrip."), get_installed_description(pkg) ); println!( "{} : {}", - crate::console::info("keywords"), + mozart_core::console::info("keywords"), get_installed_keywords(pkg) ); println!( "{} : {}", - crate::console::info("versions"), + mozart_core::console::info("versions"), format_version_highlight(&pkg.version) ); println!( "{} : {}", - crate::console::info("type"), + mozart_core::console::info("type"), pkg.package_type.as_deref().unwrap_or("library") ); // License if let Some(licenses) = get_installed_license(pkg) { - println!("{} : {}", crate::console::info("license"), licenses); + println!("{} : {}", mozart_core::console::info("license"), licenses); } // Homepage if let Some(homepage) = get_installed_homepage(pkg) { - println!("{} : {}", crate::console::info("homepage"), homepage); + println!("{} : {}", mozart_core::console::info("homepage"), homepage); } // Source @@ -577,9 +581,9 @@ fn show_installed_package_detail( .unwrap_or(""); println!( "{} : [{}] {} {}", - crate::console::info("source"), + mozart_core::console::info("source"), source_type, - crate::console::comment(source_url), + mozart_core::console::comment(source_url), source_ref ); } @@ -591,9 +595,9 @@ fn show_installed_package_detail( let dist_ref = dist.get("reference").and_then(|v| v.as_str()).unwrap_or(""); println!( "{} : [{}] {} {}", - crate::console::info("dist"), + mozart_core::console::info("dist"), dist_type, - crate::console::comment(dist_url), + mozart_core::console::comment(dist_url), dist_ref ); } @@ -603,7 +607,7 @@ fn show_installed_package_detail( if install_path.exists() { println!( "{} : {}", - crate::console::info("path"), + mozart_core::console::info("path"), install_path.display() ); } @@ -613,10 +617,10 @@ fn show_installed_package_detail( && !requires.is_empty() { println!(); - println!("{}", crate::console::info("requires")); + println!("{}", mozart_core::console::info("requires")); for (name, constraint) in requires { let c = constraint.as_str().unwrap_or(""); - println!("{} {}", name, crate::console::comment(c)); + println!("{} {}", name, mozart_core::console::comment(c)); } } @@ -628,10 +632,10 @@ fn show_installed_package_detail( && !requires_dev.is_empty() { println!(); - println!("{}", crate::console::info("requires (dev)")); + println!("{}", mozart_core::console::info("requires (dev)")); for (name, constraint) in requires_dev { let c = constraint.as_str().unwrap_or(""); - println!("{} {}", name, crate::console::comment(c)); + println!("{} {}", name, mozart_core::console::comment(c)); } } @@ -648,10 +652,11 @@ fn execute_locked(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { ); } - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; // Combine packages and packages-dev - let mut packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if let Some(ref pkgs_dev) = lock.packages_dev && !args.no_dev @@ -663,7 +668,7 @@ fn execute_locked(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if args.direct { let composer_json_path = working_dir.join("composer.json"); if composer_json_path.exists() { - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; let mut direct_names: HashSet<String> = root.require.keys().map(|k| k.to_lowercase()).collect(); if !args.no_dev { @@ -691,7 +696,7 @@ fn execute_locked(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { } fn show_locked_package_list( - packages: &[&crate::lockfile::LockedPackage], + packages: &[&mozart_registry::lockfile::LockedPackage], args: &ShowArgs, ) -> anyhow::Result<()> { let show_latest = args.latest || args.outdated; @@ -732,7 +737,7 @@ fn show_locked_package_list( // --outdated: skip packages that are up-to-date if args.outdated { if let Some(ref li) = latest_info { - use crate::version::compare_normalized_versions; + use mozart_registry::version::compare_normalized_versions; use std::cmp::Ordering; if compare_normalized_versions(&li.version_normalized, &version_normalized) != Ordering::Greater @@ -795,20 +800,24 @@ fn show_locked_package_list( .map(|li| classify_update_category(&entry.version_normalized, &li.version_normalized)); let name_str = match category { - Some(ListUpdateKind::Compatible) => { - crate::console::highlight(&format!("{:<width$}", entry.name, width = name_width)) - .to_string() - } - Some(ListUpdateKind::Incompatible) => { - crate::console::comment(&format!("{:<width$}", entry.name, width = name_width)) - .to_string() - } - _ => crate::console::info(&format!("{:<width$}", entry.name, width = name_width)) + Some(ListUpdateKind::Compatible) => mozart_core::console::highlight(&format!( + "{:<width$}", + entry.name, + width = name_width + )) + .to_string(), + Some(ListUpdateKind::Incompatible) => mozart_core::console::comment(&format!( + "{:<width$}", + entry.name, + width = name_width + )) + .to_string(), + _ => mozart_core::console::info(&format!("{:<width$}", entry.name, width = name_width)) .to_string(), }; let version_str = - crate::console::comment(&format!("{:<width$}", version, width = version_width)) + mozart_core::console::comment(&format!("{:<width$}", version, width = version_width)) .to_string(); if show_latest { @@ -816,20 +825,20 @@ fn show_locked_package_list( Some(li) => { let lv = format_version(&li.version); match category { - Some(ListUpdateKind::Compatible) => crate::console::highlight(&format!( - "{:<width$}", - lv, - width = latest_width - )) + Some(ListUpdateKind::Compatible) => mozart_core::console::highlight( + &format!("{:<width$}", lv, width = latest_width), + ) + .to_string(), + Some(ListUpdateKind::Incompatible) => mozart_core::console::comment( + &format!("{:<width$}", lv, width = latest_width), + ) .to_string(), - Some(ListUpdateKind::Incompatible) => crate::console::comment(&format!( + _ => mozart_core::console::info(&format!( "{:<width$}", lv, width = latest_width )) .to_string(), - _ => crate::console::info(&format!("{:<width$}", lv, width = latest_width)) - .to_string(), } } None => format!("{:<width$}", "", width = latest_width), @@ -889,7 +898,7 @@ fn render_locked_json(entries: &[LockedListEntry]) -> anyhow::Result<()> { } fn show_locked_package_detail( - lock: &crate::lockfile::LockFile, + lock: &mozart_registry::lockfile::LockFile, package_name: &str, ) -> anyhow::Result<()> { // Search in both packages and packages-dev @@ -906,10 +915,10 @@ fn show_locked_package_detail( } }; - println!("{} : {}", crate::console::info("name"), pkg.name); + println!("{} : {}", mozart_core::console::info("name"), pkg.name); println!( "{} : {}", - crate::console::info("descrip."), + mozart_core::console::info("descrip."), pkg.description.as_deref().unwrap_or("") ); @@ -919,16 +928,16 @@ fn show_locked_package_detail( .as_ref() .map(|kw| kw.join(", ")) .unwrap_or_default(); - println!("{} : {}", crate::console::info("keywords"), keywords); + println!("{} : {}", mozart_core::console::info("keywords"), keywords); println!( "{} : * {}", - crate::console::info("versions"), + mozart_core::console::info("versions"), format_version(&pkg.version) ); println!( "{} : {}", - crate::console::info("type"), + mozart_core::console::info("type"), pkg.package_type.as_deref().unwrap_or("library") ); @@ -936,23 +945,23 @@ fn show_locked_package_detail( if let Some(ref licenses) = pkg.license { println!( "{} : {}", - crate::console::info("license"), + mozart_core::console::info("license"), licenses.join(", ") ); } // Homepage if let Some(ref homepage) = pkg.homepage { - println!("{} : {}", crate::console::info("homepage"), homepage); + println!("{} : {}", mozart_core::console::info("homepage"), homepage); } // Source if let Some(ref source) = pkg.source { println!( "{} : [{}] {} {}", - crate::console::info("source"), + mozart_core::console::info("source"), source.source_type, - crate::console::comment(&source.url), + mozart_core::console::comment(&source.url), source.reference.as_deref().unwrap_or("") ); } @@ -961,9 +970,9 @@ fn show_locked_package_detail( if let Some(ref dist) = pkg.dist { println!( "{} : [{}] {} {}", - crate::console::info("dist"), + mozart_core::console::info("dist"), dist.dist_type, - crate::console::comment(&dist.url), + mozart_core::console::comment(&dist.url), dist.reference.as_deref().unwrap_or("") ); } @@ -971,18 +980,18 @@ fn show_locked_package_detail( // Requires if !pkg.require.is_empty() { println!(); - println!("{}", crate::console::info("requires")); + println!("{}", mozart_core::console::info("requires")); for (name, constraint) in &pkg.require { - println!("{} {}", name, crate::console::comment(constraint)); + println!("{} {}", name, mozart_core::console::comment(constraint)); } } // Requires (dev) if !pkg.require_dev.is_empty() { println!(); - println!("{}", crate::console::info("requires (dev)")); + println!("{}", mozart_core::console::info("requires (dev)")); for (name, constraint) in &pkg.require_dev { - println!("{} {}", name, crate::console::comment(constraint)); + println!("{} {}", name, mozart_core::console::comment(constraint)); } } @@ -991,9 +1000,9 @@ fn show_locked_package_detail( && !suggests.is_empty() { println!(); - println!("{}", crate::console::info("suggests")); + println!("{}", mozart_core::console::info("suggests")); for (name, reason) in suggests { - println!("{} {}", name, crate::console::comment(reason)); + println!("{} {}", name, mozart_core::console::comment(reason)); } } @@ -1007,46 +1016,46 @@ fn show_self(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if !composer_json_path.exists() { anyhow::bail!("No composer.json found in {}", working_dir.display()); } - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; if args.name_only { println!("{}", root.name); return Ok(()); } - println!("{} : {}", crate::console::info("name"), root.name); + println!("{} : {}", mozart_core::console::info("name"), root.name); println!( "{} : {}", - crate::console::info("descrip."), + mozart_core::console::info("descrip."), root.description.as_deref().unwrap_or("") ); println!( "{} : {}", - crate::console::info("type"), + mozart_core::console::info("type"), root.package_type.as_deref().unwrap_or("project") ); if let Some(ref license) = root.license { - println!("{} : {}", crate::console::info("license"), license); + println!("{} : {}", mozart_core::console::info("license"), license); } if let Some(ref homepage) = root.homepage { - println!("{} : {}", crate::console::info("homepage"), homepage); + println!("{} : {}", mozart_core::console::info("homepage"), homepage); } // Requires if !root.require.is_empty() { println!(); - println!("{}", crate::console::info("requires")); + println!("{}", mozart_core::console::info("requires")); for (name, constraint) in &root.require { - println!("{} {}", name, crate::console::comment(constraint)); + println!("{} {}", name, mozart_core::console::comment(constraint)); } } // Requires (dev) if !root.require_dev.is_empty() { println!(); - println!("{}", crate::console::info("requires (dev)")); + println!("{}", mozart_core::console::info("requires (dev)")); for (name, constraint) in &root.require_dev { - println!("{} {}", name, crate::console::comment(constraint)); + println!("{} {}", name, mozart_core::console::comment(constraint)); } } @@ -1063,13 +1072,13 @@ fn show_tree(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { anyhow::bail!("No composer.json found in {}", working_dir.display()); } - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; // Load all locked packages into a map for quick lookup - let pkg_map: HashMap<String, &crate::lockfile::LockedPackage>; + let pkg_map: HashMap<String, &mozart_registry::lockfile::LockedPackage>; let lock_storage; if lock_path.exists() { - lock_storage = crate::lockfile::LockFile::read_from_file(&lock_path)?; + lock_storage = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; pkg_map = lock_storage .packages .iter() @@ -1101,8 +1110,8 @@ fn show_tree(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { // Print root println!( "{} {}", - crate::console::info(&root.name), - crate::console::comment(root.description.as_deref().unwrap_or("")) + mozart_core::console::info(&root.name), + mozart_core::console::comment(root.description.as_deref().unwrap_or("")) ); // Render each root dependency as a tree @@ -1130,7 +1139,7 @@ fn show_tree(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { fn print_tree_node( pkg_name: &str, constraint: &str, - pkg_map: &HashMap<String, &crate::lockfile::LockedPackage>, + pkg_map: &HashMap<String, &mozart_registry::lockfile::LockedPackage>, prefix: &str, child_prefix: &str, visited: &mut HashSet<String>, @@ -1148,8 +1157,8 @@ fn print_tree_node( println!( "{} {} {} {}", prefix, - crate::console::info(pkg_name), - crate::console::comment(&version), + mozart_core::console::info(pkg_name), + mozart_core::console::comment(&version), description ); @@ -1206,7 +1215,7 @@ fn print_tree_node( println!( "{} {} {} (not installed)", prefix, - crate::console::comment(pkg_name), + mozart_core::console::comment(pkg_name), constraint ); } @@ -1233,12 +1242,12 @@ fn show_platform(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { let mut platform_packages: Vec<(String, String, String)> = Vec::new(); // (name, version, source) // Try to detect PHP from the system - let php_version = crate::platform::detect_php_version(); + let php_version = mozart_core::platform::detect_php_version(); // Load platform requirements from lock file if available let lock_path = working_dir.join("composer.lock"); if lock_path.exists() { - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; // Collect platform entries from lock's platform field if let Some(obj) = lock.platform.as_object() { @@ -1268,7 +1277,7 @@ fn show_platform(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { } // Detect PHP extensions if PHP is available - let extensions = crate::platform::detect_php_extensions(); + let extensions = mozart_core::platform::detect_php_extensions(); for ext in &extensions { let ext_name = format!("ext-{ext}"); if !platform_packages.iter().any(|(n, _, _)| *n == ext_name) { @@ -1327,8 +1336,8 @@ fn show_platform(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { for (name, version, _source) in &platform_packages { println!( "{} {}", - crate::console::info(&format!("{:<width$}", name, width = name_width)), - crate::console::comment(&format!("{:<width$}", version, width = version_width)), + mozart_core::console::info(&format!("{:<width$}", name, width = name_width)), + mozart_core::console::comment(&format!("{:<width$}", version, width = version_width)), ); } @@ -1346,7 +1355,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { // Otherwise, show all installed packages with their available (latest) versions // by querying Packagist for each installed package let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir); + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir); let installed = match installed { Ok(i) if !i.packages.is_empty() => i, @@ -1354,16 +1363,16 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { // Try lock file let lock_path = working_dir.join("composer.lock"); if lock_path.exists() { - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; println!( "{}", - crate::console::info( + mozart_core::console::info( "Available versions for locked packages (from Packagist):" ) ); println!(); - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = lock.packages.iter().collect(); if !args.no_dev && let Some(ref dev_pkgs) = lock.packages_dev @@ -1382,7 +1391,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { eprintln!( "{}", - crate::console::warning( + mozart_core::console::warning( "No dependencies installed. Try running mozart install or update." ) ); @@ -1392,7 +1401,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { println!( "{}", - crate::console::info("Available versions for installed packages (from Packagist):") + mozart_core::console::info("Available versions for installed packages (from Packagist):") ); println!(); @@ -1404,7 +1413,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { if is_platform_package(&pkg.name) { continue; } - match crate::packagist::fetch_package_versions(&pkg.name, None) { + match mozart_registry::packagist::fetch_package_versions(&pkg.name, None) { Ok(versions) => { let version_strings: Vec<String> = versions.iter().map(|v| v.version.clone()).collect(); @@ -1439,7 +1448,7 @@ fn show_available(args: &ShowArgs, working_dir: &Path) -> anyhow::Result<()> { } fn show_available_versions(pkg_name: &str, args: &ShowArgs) -> anyhow::Result<()> { - let versions = crate::packagist::fetch_package_versions(pkg_name, None)?; + let versions = mozart_registry::packagist::fetch_package_versions(pkg_name, None)?; if versions.is_empty() { println!("No versions found for {pkg_name}"); return Ok(()); @@ -1458,19 +1467,22 @@ fn show_available_versions(pkg_name: &str, args: &ShowArgs) -> anyhow::Result<() println!( "{}", - crate::console::info(&format!("Available versions for {pkg_name}:")) + mozart_core::console::info(&format!("Available versions for {pkg_name}:")) ); for v in &versions { - println!(" {}", crate::console::comment(&v.version)); + println!(" {}", mozart_core::console::comment(&v.version)); } Ok(()) } fn show_available_versions_inline(pkg_name: &str) { - match crate::packagist::fetch_package_versions(pkg_name, None) { + match mozart_registry::packagist::fetch_package_versions(pkg_name, None) { Ok(versions) => { if versions.is_empty() { - println!("{}: no versions found", crate::console::info(pkg_name)); + println!( + "{}: no versions found", + mozart_core::console::info(pkg_name) + ); return; } // Show up to 5 most recent versions @@ -1486,15 +1498,15 @@ fn show_available_versions_inline(pkg_name: &str) { }; println!( "{}: {}{}", - crate::console::info(pkg_name), - crate::console::comment(&shown.join(", ")), + mozart_core::console::info(pkg_name), + mozart_core::console::comment(&shown.join(", ")), rest ); } Err(_) => { println!( "{}: (could not fetch from Packagist)", - crate::console::comment(pkg_name) + mozart_core::console::comment(pkg_name) ); } } @@ -1513,7 +1525,7 @@ fn format_version_highlight(version: &str) -> String { } /// Extract description from an InstalledPackageEntry's extra_fields. -fn get_installed_description(pkg: &crate::installed::InstalledPackageEntry) -> String { +fn get_installed_description(pkg: &mozart_registry::installed::InstalledPackageEntry) -> String { pkg.extra_fields .get("description") .and_then(|v| v.as_str()) @@ -1522,7 +1534,7 @@ fn get_installed_description(pkg: &crate::installed::InstalledPackageEntry) -> S } /// Extract keywords from an InstalledPackageEntry's extra_fields. -fn get_installed_keywords(pkg: &crate::installed::InstalledPackageEntry) -> String { +fn get_installed_keywords(pkg: &mozart_registry::installed::InstalledPackageEntry) -> String { pkg.extra_fields .get("keywords") .and_then(|v| v.as_array()) @@ -1536,7 +1548,9 @@ fn get_installed_keywords(pkg: &crate::installed::InstalledPackageEntry) -> Stri } /// Extract license from an InstalledPackageEntry's extra_fields. -fn get_installed_license(pkg: &crate::installed::InstalledPackageEntry) -> Option<String> { +fn get_installed_license( + pkg: &mozart_registry::installed::InstalledPackageEntry, +) -> Option<String> { pkg.extra_fields.get("license").and_then(|v| { v.as_array().map(|arr| { arr.iter() @@ -1548,7 +1562,9 @@ fn get_installed_license(pkg: &crate::installed::InstalledPackageEntry) -> Optio } /// Extract homepage from an InstalledPackageEntry's extra_fields. -fn get_installed_homepage(pkg: &crate::installed::InstalledPackageEntry) -> Option<String> { +fn get_installed_homepage( + pkg: &mozart_registry::installed::InstalledPackageEntry, +) -> Option<String> { pkg.extra_fields .get("homepage") .and_then(|v| v.as_str()) @@ -1711,7 +1727,7 @@ mod tests { "description".to_string(), serde_json::Value::String("A logging library".to_string()), ); - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -1729,7 +1745,7 @@ mod tests { #[test] fn test_get_installed_description_absent() { use std::collections::BTreeMap; - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "psr/log".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -1754,7 +1770,7 @@ mod tests { "keywords".to_string(), serde_json::json!(["log", "psr3", "logging"]), ); - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "psr/log".to_string(), version: "3.0.0".to_string(), version_normalized: None, diff --git a/crates/mozart/src/commands/status.rs b/crates/mozart/src/commands/status.rs index dc26a5f..ad6dac1 100644 --- a/crates/mozart/src/commands/status.rs +++ b/crates/mozart/src/commands/status.rs @@ -47,7 +47,7 @@ struct PackageStatus { pub fn execute( args: &StatusArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -55,15 +55,15 @@ pub fn execute( }; let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; if installed.packages.is_empty() { println!("No packages installed."); return Ok(()); } - let cache_config = crate::cache::build_cache_config(cli); - let files_cache = crate::cache::Cache::files(&cache_config); + let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache); + let files_cache = mozart_registry::cache::Cache::files(&cache_config); let show_files = args.verbose || cli.verbose > 0; @@ -99,7 +99,7 @@ pub fn execute( // Download original archive to a temp dir let tmp_dir = make_temp_dir(&pkg.name)?; - let downloaded = crate::downloader::download_dist( + let downloaded = mozart_registry::downloader::download_dist( &dist.url, dist.shasum.as_deref(), None, @@ -117,8 +117,10 @@ pub fn execute( // Extract archive to temp dir let extract_result = match dist.dist_type.as_str() { - "zip" => crate::downloader::extract_zip(&bytes, &tmp_dir), - "tar" | "tar.gz" | "tgz" => crate::downloader::extract_tar_gz(&bytes, &tmp_dir), + "zip" => mozart_registry::downloader::extract_zip(&bytes, &tmp_dir), + "tar" | "tar.gz" | "tgz" => { + mozart_registry::downloader::extract_tar_gz(&bytes, &tmp_dir) + } other => { eprintln!( " Warning: unsupported dist type '{}' for {}", @@ -184,7 +186,7 @@ pub fn execute( // ─── Helpers ────────────────────────────────────────────────────────────────── /// Extract dist info from an installed package entry. -fn extract_dist_info(pkg: &crate::installed::InstalledPackageEntry) -> Option<DistInfo> { +fn extract_dist_info(pkg: &mozart_registry::installed::InstalledPackageEntry) -> Option<DistInfo> { // Try the strongly-typed `dist` field first let dist_val = pkg.dist.as_ref().or_else(|| pkg.extra_fields.get("dist"))?; @@ -213,7 +215,7 @@ fn extract_dist_info(pkg: &crate::installed::InstalledPackageEntry) -> Option<Di /// since it is a path relative to `vendor/composer/`. Falls back to /// `vendor/<package-name>`. fn resolve_install_path( - pkg: &crate::installed::InstalledPackageEntry, + pkg: &mozart_registry::installed::InstalledPackageEntry, vendor_dir: &Path, ) -> PathBuf { if let Some(ref rel) = pkg.install_path { @@ -484,7 +486,7 @@ mod tests { fn test_extract_dist_info_from_dist_field() { use std::collections::BTreeMap; - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "vendor/pkg".to_string(), version: "1.0.0".to_string(), version_normalized: None, @@ -512,7 +514,7 @@ mod tests { fn test_extract_dist_info_no_url() { use std::collections::BTreeMap; - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "vendor/pkg".to_string(), version: "1.0.0".to_string(), version_normalized: None, @@ -536,7 +538,7 @@ mod tests { fn test_extract_dist_info_absent() { use std::collections::BTreeMap; - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "vendor/pkg".to_string(), version: "1.0.0".to_string(), version_normalized: None, @@ -558,7 +560,7 @@ mod tests { fn test_resolve_install_path_default() { use std::collections::BTreeMap; - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "3.0.0".to_string(), version_normalized: None, @@ -580,7 +582,7 @@ mod tests { fn test_resolve_install_path_with_install_path() { use std::collections::BTreeMap; - let pkg = crate::installed::InstalledPackageEntry { + let pkg = mozart_registry::installed::InstalledPackageEntry { name: "monolog/monolog".to_string(), version: "3.0.0".to_string(), version_normalized: None, diff --git a/crates/mozart/src/commands/suggests.rs b/crates/mozart/src/commands/suggests.rs index df58e47..d528171 100644 --- a/crates/mozart/src/commands/suggests.rs +++ b/crates/mozart/src/commands/suggests.rs @@ -41,7 +41,7 @@ struct Suggestion { pub fn execute( args: &SuggestsArgs, cli: &super::Cli, - _console: &crate::console::Console, + _console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -160,9 +160,10 @@ fn collect_suggestions_from_locked( no_dev: bool, ) -> anyhow::Result<Vec<Suggestion>> { let lock_path = working_dir.join("composer.lock"); - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if !no_dev && let Some(ref pkgs_dev) = lock.packages_dev { all_packages.extend(pkgs_dev.iter()); } @@ -187,7 +188,7 @@ fn collect_suggestions_from_installed( no_dev: bool, ) -> anyhow::Result<Vec<Suggestion>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; if installed.packages.is_empty() { let installed_json = vendor_dir.join("composer/installed.json"); @@ -233,7 +234,7 @@ fn collect_suggestions_from_root(working_dir: &Path) -> anyhow::Result<Vec<Sugge return Ok(vec![]); } - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; // suggest is in extra_fields since RawPackageData doesn't model it explicitly let suggest_val = root.extra_fields.get("suggest"); @@ -264,11 +265,12 @@ fn collect_installed_names_from_lock( no_dev: bool, ) -> anyhow::Result<HashSet<String>> { let lock_path = working_dir.join("composer.lock"); - let lock = crate::lockfile::LockFile::read_from_file(&lock_path)?; + let lock = mozart_registry::lockfile::LockFile::read_from_file(&lock_path)?; let mut names: HashSet<String> = HashSet::new(); - let mut all_packages: Vec<&crate::lockfile::LockedPackage> = lock.packages.iter().collect(); + let mut all_packages: Vec<&mozart_registry::lockfile::LockedPackage> = + lock.packages.iter().collect(); if !no_dev && let Some(ref pkgs_dev) = lock.packages_dev { all_packages.extend(pkgs_dev.iter()); } @@ -299,7 +301,7 @@ fn collect_installed_names_from_installed( no_dev: bool, ) -> anyhow::Result<HashSet<String>> { let vendor_dir = working_dir.join("vendor"); - let installed = crate::installed::InstalledPackages::read(&vendor_dir)?; + let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?; let dev_names: HashSet<String> = installed .dev_package_names @@ -330,7 +332,7 @@ fn collect_installed_names_from_installed( // Add platform packages from require/require-dev in composer.json let composer_json_path = working_dir.join("composer.json"); if composer_json_path.exists() - && let Ok(root) = crate::package::read_from_file(&composer_json_path) + && let Ok(root) = mozart_core::package::read_from_file(&composer_json_path) { for name in root.require.keys().chain(root.require_dev.keys()) { if is_platform_package(name) { @@ -342,7 +344,10 @@ fn collect_installed_names_from_installed( Ok(names) } -fn add_platform_names_from_lock(lock: &crate::lockfile::LockFile, names: &mut HashSet<String>) { +fn add_platform_names_from_lock( + lock: &mozart_registry::lockfile::LockFile, + names: &mut HashSet<String>, +) { // Collect platform keys from the lock's platform and platform_dev objects if let Some(obj) = lock.platform.as_object() { for key in obj.keys() { @@ -372,7 +377,7 @@ fn compute_direct_deps(working_dir: &Path) -> anyhow::Result<HashSet<String>> { if !composer_json_path.exists() { return Ok(HashSet::new()); } - let root = crate::package::read_from_file(&composer_json_path)?; + let root = mozart_core::package::read_from_file(&composer_json_path)?; let mut deps: HashSet<String> = HashSet::new(); for name in root.require.keys().chain(root.require_dev.keys()) { deps.insert(name.to_lowercase()); @@ -447,8 +452,8 @@ mod tests { fn make_locked_package( name: &str, suggest: Option<BTreeMap<String, String>>, - ) -> crate::lockfile::LockedPackage { - crate::lockfile::LockedPackage { + ) -> mozart_registry::lockfile::LockedPackage { + mozart_registry::lockfile::LockedPackage { name: name.to_string(), version: "1.0.0".to_string(), version_normalized: None, @@ -476,7 +481,7 @@ mod tests { fn make_installed_entry( name: &str, suggest: Option<BTreeMap<String, String>>, - ) -> crate::installed::InstalledPackageEntry { + ) -> mozart_registry::installed::InstalledPackageEntry { let mut extra_fields: BTreeMap<String, serde_json::Value> = BTreeMap::new(); if let Some(s) = suggest { let map: serde_json::Map<String, serde_json::Value> = s @@ -485,7 +490,7 @@ mod tests { .collect(); extra_fields.insert("suggest".to_string(), serde_json::Value::Object(map)); } - crate::installed::InstalledPackageEntry { + mozart_registry::installed::InstalledPackageEntry { name: name.to_string(), version: "1.0.0".to_string(), version_normalized: None, @@ -500,11 +505,11 @@ mod tests { } fn minimal_lock( - packages: Vec<crate::lockfile::LockedPackage>, - packages_dev: Option<Vec<crate::lockfile::LockedPackage>>, - ) -> crate::lockfile::LockFile { - crate::lockfile::LockFile { - readme: crate::lockfile::LockFile::default_readme(), + packages: Vec<mozart_registry::lockfile::LockedPackage>, + packages_dev: Option<Vec<mozart_registry::lockfile::LockedPackage>>, + ) -> mozart_registry::lockfile::LockFile { + mozart_registry::lockfile::LockFile { + readme: mozart_registry::lockfile::LockFile::default_readme(), content_hash: "abc123".to_string(), packages, packages_dev, @@ -687,7 +692,7 @@ mod tests { let mut suggest = BTreeMap::new(); suggest.insert("ext-redis".to_string(), "For Redis caching".to_string()); - let mut installed = crate::installed::InstalledPackages::new(); + let mut installed = mozart_registry::installed::InstalledPackages::new(); installed.upsert(make_installed_entry("vendor/cache", Some(suggest))); installed.upsert(make_installed_entry("vendor/other", None)); installed.write(&vendor_dir).unwrap(); diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index d4056e4..3a7c423 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -1,8 +1,8 @@ -use crate::console; -use crate::lockfile; -use crate::package::{self, Stability}; -use crate::resolver::{self, PlatformConfig, ResolveRequest, ResolvedPackage}; use clap::Args; +use mozart_core::console; +use mozart_core::package::{self, Stability}; +use mozart_registry::lockfile; +use mozart_registry::resolver::{self, PlatformConfig, ResolveRequest, ResolvedPackage}; use std::collections::{HashMap, HashSet}; #[derive(Args)] @@ -633,7 +633,7 @@ pub fn apply_minimal_changes( pub fn execute( args: &UpdateArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { // Step 1: Resolve the working directory let working_dir = super::install::resolve_working_dir(cli); @@ -670,8 +670,8 @@ pub fn execute( // Step 3: Read composer.json let composer_json_path = working_dir.join("composer.json"); if !composer_json_path.exists() { - return Err(crate::exit_code::bail( - crate::exit_code::GENERAL_ERROR, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::GENERAL_ERROR, format!( "Composer could not find a composer.json file in {}", working_dir.display() @@ -746,8 +746,8 @@ pub fn execute( let mut resolved = match resolver::resolve(&request) { Ok(packages) => packages, Err(e) => { - return Err(crate::exit_code::bail( - crate::exit_code::DEPENDENCY_RESOLUTION_FAILED, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::DEPENDENCY_RESOLUTION_FAILED, e.to_string(), )); } @@ -777,8 +777,8 @@ pub fn execute( let update_packages: Vec<String> = if !args.packages.is_empty() { match &old_lock { None => { - return Err(crate::exit_code::bail( - crate::exit_code::NO_LOCK_FILE_FOR_PARTIAL_UPDATE, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::NO_LOCK_FILE_FOR_PARTIAL_UPDATE, "No lock file found. Cannot perform partial update. Run `mozart update` first.", )); } @@ -834,8 +834,8 @@ pub fn execute( if !update_packages.is_empty() { match &old_lock { None => { - return Err(crate::exit_code::bail( - crate::exit_code::NO_LOCK_FILE_FOR_PARTIAL_UPDATE, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::NO_LOCK_FILE_FOR_PARTIAL_UPDATE, "No lock file found. Cannot perform partial update. Run `mozart update` first.", )); } @@ -947,7 +947,7 @@ pub fn execute( .map(|s| s.eq_ignore_ascii_case("source")) .unwrap_or(false); if prefer_source { - console.info(&crate::console::warning( + console.info(&mozart_core::console::warning( "Warning: Source installs are not yet supported. Falling back to dist.", )); } @@ -986,11 +986,11 @@ fn handle_lock_mode( lock_path: &std::path::Path, composer_json_content: &str, dry_run: bool, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { if !lock_path.exists() { - return Err(crate::exit_code::bail( - crate::exit_code::LOCK_FILE_INVALID, + return Err(mozart_core::exit_code::bail( + mozart_core::exit_code::LOCK_FILE_INVALID, "No lock file found. Run `mozart update` to generate one.", )); } @@ -1360,9 +1360,9 @@ mod tests { // Composer.json content that will produce a different hash let composer_json_content = r#"{"name": "test/project", "require": {"psr/log": "^3.0"}}"#; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; let result = handle_lock_mode(&lock_path, composer_json_content, false, &console); @@ -1388,9 +1388,9 @@ mod tests { lock.content_hash = correct_hash.clone(); lock.write_to_file(&lock_path).unwrap(); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; let result = handle_lock_mode(&lock_path, composer_json_content, false, &console); @@ -1412,9 +1412,9 @@ mod tests { let composer_json_content = r#"{"name": "test/project", "require": {"psr/log": "^3.0"}}"#; - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; let result = handle_lock_mode(&lock_path, composer_json_content, true, &console); @@ -1660,9 +1660,9 @@ mod tests { #[test] #[ignore] fn test_update_full_e2e() { - use crate::lockfile::{LockFileGenerationRequest, generate_lock_file}; - use crate::package::RawPackageData; - use crate::resolver::{ResolveRequest, resolve}; + use mozart_core::package::RawPackageData; + use mozart_registry::lockfile::{LockFileGenerationRequest, generate_lock_file}; + use mozart_registry::resolver::{ResolveRequest, resolve}; let composer_json_content = r#"{"name": "test/project", "require": {"monolog/monolog": "^3.0"}}"#; @@ -1717,9 +1717,9 @@ mod tests { let expected_hash = lockfile::LockFile::compute_content_hash(composer_json_content).unwrap(); - let console = crate::console::Console { + let console = mozart_core::console::Console { interactive: false, - verbosity: crate::console::Verbosity::Normal, + verbosity: mozart_core::console::Verbosity::Normal, decorated: false, }; handle_lock_mode(&lock_path, composer_json_content, false, &console).unwrap(); diff --git a/crates/mozart/src/commands/validate.rs b/crates/mozart/src/commands/validate.rs index 1dec3fe..50e3cce 100644 --- a/crates/mozart/src/commands/validate.rs +++ b/crates/mozart/src/commands/validate.rs @@ -70,7 +70,7 @@ impl ValidationResult { pub fn execute( args: &ValidateArgs, cli: &super::Cli, - console: &crate::console::Console, + console: &mozart_core::console::Console, ) -> anyhow::Result<()> { let working_dir = match &cli.working_dir { Some(dir) => PathBuf::from(dir), @@ -91,7 +91,7 @@ pub fn execute( // Check file exists if !file.exists() { - return Err(crate::exit_code::bail( + return Err(mozart_core::exit_code::bail( VALIDATE_FILE_ERROR, format!("{} not found.", file.display()), )); @@ -101,7 +101,7 @@ pub fn execute( let content = match std::fs::read_to_string(&file) { Ok(c) => c, Err(_) => { - return Err(crate::exit_code::bail( + return Err(mozart_core::exit_code::bail( VALIDATE_FILE_ERROR, format!("{} is not readable.", file.display()), )); @@ -112,7 +112,7 @@ pub fn execute( let json_value: serde_json::Value = match serde_json::from_str(&content) { Ok(v) => v, Err(e) => { - return Err(crate::exit_code::bail( + return Err(mozart_core::exit_code::bail( VALIDATE_JSON_ERROR, format!("{} does not contain valid JSON: {e}", file.display()), )); @@ -147,7 +147,7 @@ pub fn execute( args.strict, ); if exit_code != 0 { - return Err(crate::exit_code::bail_silent(exit_code)); + return Err(mozart_core::exit_code::bail_silent(exit_code)); } Ok(()) @@ -208,7 +208,7 @@ fn check_name(obj: &serde_json::Map<String, serde_json::Value>, result: &mut Val // Must contain a slash (vendor/package format) if !name.is_empty() - && !crate::validation::validate_package_name(name) + && !mozart_core::validation::validate_package_name(name) && !name.contains('/') { result.errors.push(format!( @@ -365,7 +365,7 @@ fn check_minimum_stability( result: &mut ValidationResult, ) { if let Some(stability) = obj.get("minimum-stability").and_then(|v| v.as_str()) - && !crate::validation::validate_stability(stability) + && !mozart_core::validation::validate_stability(stability) { result.errors.push(format!( "The minimum-stability \"{stability}\" is invalid. \ @@ -391,7 +391,7 @@ fn check_lock_freshness( return; } - match crate::lockfile::LockFile::read_from_file(&lock_path) { + match mozart_registry::lockfile::LockFile::read_from_file(&lock_path) { Ok(lock) => { if !lock.is_fresh(composer_json_content) { lock_errors.push( @@ -422,35 +422,37 @@ fn output_result( if result.has_errors() { eprintln!( "{}", - crate::console::error(&format!( + mozart_core::console::error(&format!( "{name} is invalid, the following errors/warnings were found:" )) ); } else if result.has_publish_errors() && check_publish { eprintln!( "{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "{name} is valid for simple usage with Composer but has" )) ); eprintln!( "{}", - crate::console::info("strict errors that make it unable to be published as a package") + mozart_core::console::info( + "strict errors that make it unable to be published as a package" + ) ); eprintln!( "{}", - crate::console::warning( + mozart_core::console::warning( "See https://getcomposer.org/doc/04-schema.md for details on the schema" ) ); } else if result.has_warnings() { eprintln!( "{}", - crate::console::info(&format!("{name} is valid, but with a few warnings")) + mozart_core::console::info(&format!("{name} is valid, but with a few warnings")) ); eprintln!( "{}", - crate::console::warning( + mozart_core::console::warning( "See https://getcomposer.org/doc/04-schema.md for details on the schema" ) ); @@ -458,12 +460,15 @@ fn output_result( let kind = if check_lock { "errors" } else { "warnings" }; println!( "{}", - crate::console::info(&format!( + mozart_core::console::info(&format!( "{name} is valid but your composer.lock has some {kind}" )) ); } else { - println!("{}", crate::console::info(&format!("{name} is valid"))); + println!( + "{}", + mozart_core::console::info(&format!("{name} is valid")) + ); } // Collect error and warning message lines @@ -506,7 +511,7 @@ fn output_result( // Print errors for msg in &all_errors { if msg.starts_with('#') { - eprintln!("{}", crate::console::error(msg)); + eprintln!("{}", mozart_core::console::error(msg)); } else { eprintln!("{msg}"); } @@ -515,7 +520,7 @@ fn output_result( // Print warnings for msg in &all_warnings { if msg.starts_with('#') { - eprintln!("{}", crate::console::warning(msg)); + eprintln!("{}", mozart_core::console::warning(msg)); } else { eprintln!("{msg}"); } @@ -907,7 +912,7 @@ mod tests { #[test] fn test_check_lock_freshness_fresh_lock() { - use crate::lockfile::LockFile; + use mozart_registry::lockfile::LockFile; use tempfile::tempdir; let dir = tempdir().unwrap(); @@ -943,7 +948,7 @@ mod tests { #[test] fn test_check_lock_freshness_stale_lock() { - use crate::lockfile::LockFile; + use mozart_registry::lockfile::LockFile; use tempfile::tempdir; let dir = tempdir().unwrap(); diff --git a/crates/mozart/src/console.rs b/crates/mozart/src/console.rs index 5f108c0..e37ff23 100644 --- a/crates/mozart/src/console.rs +++ b/crates/mozart/src/console.rs @@ -94,17 +94,20 @@ pub struct Console { } impl Console { - /// Build a `Console` from the parsed CLI. + /// Build a `Console` from primitive arguments. /// - /// This is the primary constructor used in production. It reads - /// `cli.verbose`, `cli.quiet`, `cli.ansi`, `cli.no_ansi`, and - /// `cli.no_interaction` to configure all fields. - pub fn from_cli(cli: &crate::commands::Cli) -> Self { - let verbosity = Verbosity::from_flags(cli.verbose, cli.quiet); - let decorated = Self::resolve_decorated(cli.ansi, cli.no_ansi); + /// This is the primary constructor. Pass the relevant CLI flag values: + /// - `verbose`: the `-v` flag count (0, 1, 2, 3+) + /// - `quiet`: whether `--quiet` was passed + /// - `ansi`: whether `--ansi` was passed + /// - `no_ansi`: whether `--no-ansi` was passed + /// - `no_interaction`: whether `--no-interaction` / `-n` was passed + pub fn new(verbose: u8, quiet: bool, ansi: bool, no_ansi: bool, no_interaction: bool) -> Self { + let verbosity = Verbosity::from_flags(verbose, quiet); + let decorated = Self::resolve_decorated(ansi, no_ansi); colored::control::set_override(decorated); Self { - interactive: !cli.no_interaction, + interactive: !no_interaction, verbosity, decorated, } diff --git a/crates/mozart/src/constraint.rs b/crates/mozart/src/constraint.rs index e41818c..32dc84e 100644 --- a/crates/mozart/src/constraint.rs +++ b/crates/mozart/src/constraint.rs @@ -1,1972 +1,2 @@ -use std::cmp::Ordering; - -/// A parsed Composer version (always 4 numeric segments + optional stability suffix). -/// Composer normalizes all versions to `major.minor.patch.build[-stability[N]]`. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Version { - pub major: u64, - pub minor: u64, - pub patch: u64, - pub build: u64, - /// None = stable, Some("alpha1"), Some("beta2"), Some("RC1"), Some("dev") - pub pre_release: Option<String>, - /// true for "dev-master", "dev-feature/foo", etc. - pub is_dev_branch: bool, - /// The original branch name for dev branches (e.g. "master", "feature/foo") - pub dev_branch_name: Option<String>, -} - -/// Stability rank for ordering (lower = more stable). -fn stability_rank(pre: &str) -> u8 { - let lower = pre.to_lowercase(); - if lower.starts_with("dev") { - 50 - } else if lower.starts_with("alpha") || lower.starts_with("a") { - 40 - } else if lower.starts_with("beta") || lower.starts_with("b") { - 30 - } else if lower.starts_with("rc") { - 20 - } else if lower.starts_with("patch") || lower.starts_with("pl") || lower == "p" { - 5 - } else { - 0 - } -} - -/// Extract numeric suffix from a pre-release string like "alpha1" → 1, "beta" → 0 -fn pre_release_number(pre: &str) -> u64 { - let digits: String = pre.chars().skip_while(|c| c.is_alphabetic()).collect(); - digits.parse().unwrap_or(0) -} - -impl PartialOrd for Version { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for Version { - fn cmp(&self, other: &Self) -> Ordering { - // Dev branches are always lowest - match (self.is_dev_branch, other.is_dev_branch) { - (true, true) => { - // Compare branch names - return self.dev_branch_name.cmp(&other.dev_branch_name); - } - (true, false) => return Ordering::Less, - (false, true) => return Ordering::Greater, - (false, false) => {} - } - - // Compare numeric segments - let num_cmp = (self.major, self.minor, self.patch, self.build).cmp(&( - other.major, - other.minor, - other.patch, - other.build, - )); - if num_cmp != Ordering::Equal { - return num_cmp; - } - - // Compare pre-release: None (stable) > any pre-release - match (&self.pre_release, &other.pre_release) { - (None, None) => Ordering::Equal, - (None, Some(_)) => Ordering::Greater, - (Some(_), None) => Ordering::Less, - (Some(a), Some(b)) => { - let rank_a = stability_rank(a); - let rank_b = stability_rank(b); - match rank_a.cmp(&rank_b) { - Ordering::Equal => { - // Same stability: compare numeric suffix - pre_release_number(a).cmp(&pre_release_number(b)) - } - // Lower rank = more stable = greater version - Ordering::Less => Ordering::Greater, - Ordering::Greater => Ordering::Less, - } - } - } - } -} - -impl Version { - /// Parse a version string into a `Version` struct using Composer normalization rules. - /// - /// For inline aliases (`"1.0.x-dev as 1.0.0"`), the LEFT side (the real branch version) - /// is used. This is the correct behaviour for identifying *what* version a package provides. - pub fn parse(input: &str) -> Result<Version, String> { - let s = input.trim(); - - // Strip inline alias: "1.0.x-dev as 1.0.0" → "1.0.x-dev" - let s = if let Some(pos) = s.find(" as ") { - &s[..pos] - } else { - s - }; - - // Strip stability flag: "@dev", "@alpha", "@beta", "@RC", "@stable" - let s = if let Some(pos) = s.rfind('@') { - let after = &s[pos + 1..]; - let known = ["dev", "alpha", "beta", "rc", "stable"]; - if known.iter().any(|k| after.eq_ignore_ascii_case(k)) { - &s[..pos] - } else { - s - } - } else { - s - }; - - // Handle dev-* prefix branches - if s.to_lowercase().starts_with("dev-") { - let branch = &s[4..]; - return Ok(Version { - major: 0, - minor: 0, - patch: 0, - build: 0, - pre_release: Some("dev".to_string()), - is_dev_branch: true, - dev_branch_name: Some(branch.to_string()), - }); - } - - // Handle *-dev suffix (e.g., "2.1.x-dev" or "2.x-dev") - let s_lower = s.to_lowercase(); - if s_lower.ends_with("-dev") || s_lower.ends_with(".x-dev") { - let base = if s_lower.ends_with("-dev") { - &s[..s.len() - 4] - } else { - s - }; - // Replace any trailing .x with nothing, parse numeric parts - let base = base.trim_end_matches(".x").trim_end_matches("-dev"); - let parts: Vec<&str> = base.split('.').collect(); - let major = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0); - let minor = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0); - return Ok(Version { - major, - minor, - patch: 9999999, - build: 9999999, - pre_release: Some("dev".to_string()), - is_dev_branch: true, - dev_branch_name: None, - }); - } - - // Strip leading v/V - let s = s - .strip_prefix('v') - .or_else(|| s.strip_prefix('V')) - .unwrap_or(s); - - // Strip build metadata after + - let s = s.split('+').next().unwrap_or(s); - - // Parse the version using regex-like approach - parse_classical_version(s) - } - - /// Parse a version string for use inside a *constraint expression*. - /// - /// The difference from [`Version::parse`] is the treatment of inline aliases: - /// `"1.0.x-dev as 1.0.0"` → takes the **right** side (`1.0.0`). - /// - /// Inline aliases appear in `require` fields like: - /// ```text - /// "some/package": "1.0.x-dev as 1.0.0" - /// ``` - /// Here the author wants the constraint to be satisfied by the real version `1.0.0`, - /// while the left side (`1.0.x-dev`) indicates the branch that provides it. - pub fn parse_for_constraint(input: &str) -> Result<Version, String> { - let s = input.trim(); - // For inline aliases, take the RIGHT side (alias target) - let s = if let Some(pos) = s.find(" as ") { - s[pos + 4..].trim() - } else { - s - }; - Version::parse(s) - } - - /// Create a "dev boundary" version for constraint matching (major.minor.patch.build with dev pre-release). - pub fn dev_boundary(major: u64, minor: u64, patch: u64, build: u64) -> Version { - Version { - major, - minor, - patch, - build, - pre_release: Some("dev".to_string()), - is_dev_branch: false, - dev_branch_name: None, - } - } -} - -fn parse_classical_version(s: &str) -> Result<Version, String> { - // Split on '-' to separate version from pre-release - let (version_part, pre_part) = if let Some(pos) = s.find('-') { - (&s[..pos], Some(&s[pos + 1..])) - } else { - (s, None) - }; - - let segments: Vec<&str> = version_part.split('.').collect(); - if segments.is_empty() || segments[0].is_empty() { - return Err(format!("Invalid version: {s}")); - } - - let major: u64 = segments[0] - .parse() - .map_err(|_| format!("Invalid major version segment: {}", segments[0]))?; - let minor: u64 = segments.get(1).and_then(|p| p.parse().ok()).unwrap_or(0); - let patch: u64 = segments - .get(2) - .and_then(|p| { - // strip trailing .x - let p = p.trim_end_matches('x').trim_end_matches('.'); - if p.is_empty() { - Some(0) - } else { - p.parse().ok() - } - }) - .unwrap_or(0); - let build: u64 = segments.get(3).and_then(|p| p.parse().ok()).unwrap_or(0); - - let pre_release = pre_part.map(normalize_pre_release); - - Ok(Version { - major, - minor, - patch, - build, - pre_release, - is_dev_branch: false, - dev_branch_name: None, - }) -} - -fn normalize_pre_release(s: &str) -> String { - // Normalize aliases: b→beta, a→alpha, rc→RC, p/pl/patch→patch - let lower = s.to_lowercase(); - // Strip leading non-alpha characters (dots, underscores, dashes used as separators) - let normalized = lower - .trim_start_matches(|c: char| !c.is_alphabetic()) - .to_string(); - - // Extract the alphabetic prefix (stability name) - let alpha: String = normalized - .chars() - .take_while(|c| c.is_alphabetic()) - .collect(); - // Extract only digits from the rest (strip separators like dots) - let num: String = normalized - .chars() - .skip_while(|c| c.is_alphabetic()) - .filter(|c| c.is_ascii_digit()) - .collect(); - - if alpha.starts_with("beta") || alpha == "b" { - format!("beta{num}") - } else if alpha.starts_with("alpha") || alpha == "a" { - format!("alpha{num}") - } else if alpha == "rc" { - format!("RC{num}") - } else if alpha == "patch" || alpha == "pl" || alpha == "p" { - format!("patch{num}") - } else if alpha == "dev" { - "dev".to_string() - } else { - s.to_string() - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// Constraint types -// ───────────────────────────────────────────────────────────────────────────── - -/// A single atomic constraint. -#[derive(Debug, Clone)] -pub enum Constraint { - /// Exact version match - Exact(Version), - /// Greater than: `> 1.2.3` - GreaterThan(Version), - /// Greater than or equal: `>= 1.2.3` - GreaterThanOrEqual(Version), - /// Less than: `< 1.2.3` - LessThan(Version), - /// Less than or equal: `<= 1.2.3` - LessThanOrEqual(Version), - /// Not equal: `!= 1.2.3` - NotEqual(Version), - /// Matches any version - Any, -} - -impl Constraint { - pub fn matches(&self, v: &Version) -> bool { - match self { - Constraint::Exact(target) => v == target, - Constraint::GreaterThan(target) => v > target, - Constraint::GreaterThanOrEqual(target) => v >= target, - Constraint::LessThan(target) => v < target, - Constraint::LessThanOrEqual(target) => v <= target, - Constraint::NotEqual(target) => v != target, - Constraint::Any => true, - } - } -} - -/// A compound constraint with AND/OR combinators. -#[derive(Debug, Clone)] -pub enum VersionConstraint { - /// Single atomic constraint - Single(Constraint), - /// All must match (AND — space/comma separated) - And(Vec<VersionConstraint>), - /// At least one must match (OR — `||` separated) - Or(Vec<VersionConstraint>), -} - -impl VersionConstraint { - pub fn matches(&self, version: &Version) -> bool { - match self { - VersionConstraint::Single(c) => c.matches(version), - VersionConstraint::And(cs) => cs.iter().all(|c| c.matches(version)), - VersionConstraint::Or(cs) => cs.iter().any(|c| c.matches(version)), - } - } - - /// Parse a constraint string like `^1.2`, `>=1.0 <2.0`, `^1.0 || ^2.0`. - pub fn parse(input: &str) -> Result<VersionConstraint, String> { - let input = input.trim(); - - // Split on || (OR) - let or_parts: Vec<&str> = split_or(input); - - if or_parts.len() > 1 { - let constraints: Result<Vec<_>, _> = - or_parts.iter().map(|p| parse_and_group(p.trim())).collect(); - let mut cs = constraints?; - // Flatten single-element groups - if cs.len() == 1 { - return Ok(cs.remove(0)); - } - return Ok(VersionConstraint::Or(cs)); - } - - parse_and_group(input) - } -} - -/// Split on `||` (pipe-OR), but not inside version strings. -fn split_or(s: &str) -> Vec<&str> { - let mut parts = Vec::new(); - let mut start = 0; - let bytes = s.as_bytes(); - let mut i = 0; - while i < bytes.len() { - if i + 1 < bytes.len() && bytes[i] == b'|' && bytes[i + 1] == b'|' { - parts.push(s[start..i].trim()); - i += 2; - start = i; - } else { - i += 1; - } - } - parts.push(s[start..].trim()); - parts -} - -/// Parse an AND group (space or comma separated constraints). -fn parse_and_group(s: &str) -> Result<VersionConstraint, String> { - // Detect inline alias first: "1.0.x-dev as 1.0.0" - // The entire expression is a single atomic constraint; parse it directly. - if s.contains(" as ") { - return parse_single(s); - } - - // Detect hyphen range first: "1.0 - 2.0" where both sides start with a digit - if let Some(idx) = s.find(" - ") { - let before = s[..idx].trim(); - let after = s[idx + 3..].trim(); - let before_is_version = before - .chars() - .next() - .is_some_and(|c| c.is_ascii_digit() || c == 'v' || c == 'V'); - let after_is_version = after - .chars() - .next() - .is_some_and(|c| c.is_ascii_digit() || c == 'v' || c == 'V'); - if before_is_version && after_is_version { - return parse_hyphen_range(s); - } - } - - let parts = split_and(s); - - if parts.is_empty() { - return Err("Empty constraint".to_string()); - } - - let constraints: Result<Vec<_>, _> = parts.iter().map(|p| parse_single(p.trim())).collect(); - let mut cs = constraints?; - - if cs.len() == 1 { - return Ok(cs.remove(0)); - } - - // Flatten nested And - let flat: Vec<VersionConstraint> = cs - .into_iter() - .flat_map(|c| match c { - VersionConstraint::And(inner) => inner, - other => vec![other], - }) - .collect(); - - Ok(VersionConstraint::And(flat)) -} - -/// Split on spaces or commas (AND separator), respecting that version strings -/// can contain `-` (pre-release). -fn split_and(s: &str) -> Vec<String> { - // A constraint "part" is separated by space or comma when not part of - // operator prefixes like `>=`, `<=`, `!=`, or version like `1.2.3-beta`. - // Strategy: tokenize by whitespace/comma, then re-join multi-token ranges. - let tokens: Vec<&str> = s.split([' ', ',']).filter(|t| !t.is_empty()).collect(); - - let mut parts: Vec<String> = Vec::new(); - let mut current = String::new(); - - for token in tokens { - if current.is_empty() { - current = token.to_string(); - } else { - // If the token starts with an operator or a digit/^ ~/>, it's a new constraint - let starts_new = token.starts_with(|c: char| { - matches!(c, '>' | '<' | '!' | '=' | '^' | '~' | '*') || c.is_ascii_digit() - }); - if starts_new { - parts.push(current.trim().to_string()); - current = token.to_string(); - } else { - // Continuation (e.g. part of a version string with spaces) - current.push(' '); - current.push_str(token); - } - } - } - if !current.is_empty() { - parts.push(current.trim().to_string()); - } - - parts -} - -/// Parse a single constraint part. -fn parse_single(s: &str) -> Result<VersionConstraint, String> { - if s == "*" || s.is_empty() { - return Ok(VersionConstraint::Single(Constraint::Any)); - } - - // Caret: ^1.2.3 - if let Some(rest) = s.strip_prefix('^') { - return parse_caret(rest); - } - - // Tilde: ~1.2.3 - if let Some(rest) = s.strip_prefix('~') { - return parse_tilde(rest); - } - - // Hyphen range: "1.0 - 2.0" — handled at and-group level, but check here too - if s.contains(" - ") { - return parse_hyphen_range(s); - } - - // Comparison operators - // Use parse_for_constraint so that inline aliases like "1.0.x-dev as 1.0.0" - // resolve to the alias target (right-hand side) when used in constraint context. - if let Some(rest) = s.strip_prefix(">=") { - let v = Version::parse_for_constraint(rest.trim())?; - return Ok(VersionConstraint::Single(Constraint::GreaterThanOrEqual(v))); - } - if let Some(rest) = s.strip_prefix("<=") { - let v = Version::parse_for_constraint(rest.trim())?; - return Ok(VersionConstraint::Single(Constraint::LessThanOrEqual(v))); - } - if let Some(rest) = s.strip_prefix("!=") { - let v = Version::parse_for_constraint(rest.trim())?; - return Ok(VersionConstraint::Single(Constraint::NotEqual(v))); - } - if let Some(rest) = s.strip_prefix('>') { - let v = Version::parse_for_constraint(rest.trim())?; - return Ok(VersionConstraint::Single(Constraint::GreaterThan(v))); - } - if let Some(rest) = s.strip_prefix('<') { - let v = Version::parse_for_constraint(rest.trim())?; - return Ok(VersionConstraint::Single(Constraint::LessThan(v))); - } - if let Some(rest) = s.strip_prefix('=') { - let v = Version::parse_for_constraint(rest.trim())?; - return Ok(VersionConstraint::Single(Constraint::Exact(v))); - } - - // Wildcard: 1.2.* or 1.* - if s.ends_with(".*") || s.ends_with(".*.*") || s == "*" { - return parse_wildcard(s); - } - - // Exact version (may carry an inline alias; take the alias target for matching) - let v = Version::parse_for_constraint(s)?; - Ok(VersionConstraint::Single(Constraint::Exact(v))) -} - -/// Parse `^major.minor.patch` caret constraint. -/// First non-zero segment is the "locked" boundary. -fn parse_caret(s: &str) -> Result<VersionConstraint, String> { - let parts: Vec<&str> = s.split('.').collect(); - let major: u64 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0); - let minor: u64 = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0); - let patch: u64 = parts.get(2).and_then(|p| p.parse().ok()).unwrap_or(0); - let build: u64 = parts.get(3).and_then(|p| p.parse().ok()).unwrap_or(0); - - let lower = Version::dev_boundary(major, minor, patch, build); - - // Determine upper bound based on first non-zero segment - let upper = if major > 0 { - Version::dev_boundary(major + 1, 0, 0, 0) - } else if minor > 0 { - Version::dev_boundary(0, minor + 1, 0, 0) - } else if patch > 0 { - Version::dev_boundary(0, 0, patch + 1, 0) - } else { - Version::dev_boundary(0, 0, 1, 0) - }; - - Ok(VersionConstraint::And(vec![ - VersionConstraint::Single(Constraint::GreaterThanOrEqual(lower)), - VersionConstraint::Single(Constraint::LessThan(upper)), - ])) -} - -/// Parse `~major.minor.patch` tilde constraint. -fn parse_tilde(s: &str) -> Result<VersionConstraint, String> { - let parts: Vec<&str> = s.split('.').collect(); - let major: u64 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0); - let minor: u64 = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0); - let patch: u64 = parts.get(2).and_then(|p| p.parse().ok()).unwrap_or(0); - let build: u64 = parts.get(3).and_then(|p| p.parse().ok()).unwrap_or(0); - - let lower = Version::dev_boundary(major, minor, patch, build); - - // ~major.minor.patch → >=major.minor.patch <major.(minor+1).0 - // ~major.minor → >=major.minor.0 <(major+1).0.0 - // ~major → >=major.0.0 <(major+1).0.0 - let upper = if parts.len() >= 3 { - Version::dev_boundary(major, minor + 1, 0, 0) - } else { - Version::dev_boundary(major + 1, 0, 0, 0) - }; - - Ok(VersionConstraint::And(vec![ - VersionConstraint::Single(Constraint::GreaterThanOrEqual(lower)), - VersionConstraint::Single(Constraint::LessThan(upper)), - ])) -} - -/// Parse `1.2.*` wildcard constraint. -fn parse_wildcard(s: &str) -> Result<VersionConstraint, String> { - if s == "*" { - return Ok(VersionConstraint::Single(Constraint::Any)); - } - - // Strip trailing .* - let base = s.trim_end_matches(".*"); - if base.is_empty() { - return Ok(VersionConstraint::Single(Constraint::Any)); - } - - let parts: Vec<&str> = base.split('.').collect(); - let major: u64 = parts.first().and_then(|p| p.parse().ok()).unwrap_or(0); - let minor: u64 = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(0); - - let (lower, upper) = if parts.len() == 1 { - ( - Version::dev_boundary(major, 0, 0, 0), - Version::dev_boundary(major + 1, 0, 0, 0), - ) - } else { - ( - Version::dev_boundary(major, minor, 0, 0), - Version::dev_boundary(major, minor + 1, 0, 0), - ) - }; - - Ok(VersionConstraint::And(vec![ - VersionConstraint::Single(Constraint::GreaterThanOrEqual(lower)), - VersionConstraint::Single(Constraint::LessThan(upper)), - ])) -} - -/// Parse `1.0 - 2.0` hyphen range. -fn parse_hyphen_range(s: &str) -> Result<VersionConstraint, String> { - let parts: Vec<&str> = s.splitn(2, " - ").collect(); - if parts.len() != 2 { - return Err(format!("Invalid hyphen range: {s}")); - } - - let lower_v = Version::parse_for_constraint(parts[0].trim())?; - let upper_v = Version::parse_for_constraint(parts[1].trim())?; - - Ok(VersionConstraint::And(vec![ - VersionConstraint::Single(Constraint::GreaterThanOrEqual(lower_v)), - VersionConstraint::Single(Constraint::LessThanOrEqual(upper_v)), - ])) -} - -#[cfg(test)] -mod tests { - use super::*; - - // ──────────── Version parsing ──────────── - - #[test] - fn test_parse_simple() { - let v = Version::parse("1.2.3").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 3); - assert_eq!(v.build, 0); - assert_eq!(v.pre_release, None); - assert!(!v.is_dev_branch); - } - - #[test] - fn test_parse_with_v_prefix() { - let v = Version::parse("v1.2").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 0); - assert_eq!(v.build, 0); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_four_segments() { - let v = Version::parse("1.2.3.4").unwrap(); - assert_eq!((v.major, v.minor, v.patch, v.build), (1, 2, 3, 4)); - } - - #[test] - fn test_parse_beta() { - let v = Version::parse("1.0.0-beta.1").unwrap(); - assert_eq!(v.major, 1); - // "beta.1" normalizes to "beta1" (dot is stripped) - assert_eq!(v.pre_release, Some("beta1".to_string())); - } - - #[test] - fn test_parse_beta1() { - let v = Version::parse("1.0.0-beta1").unwrap(); - assert_eq!(v.pre_release, Some("beta1".to_string())); - } - - #[test] - fn test_parse_rc() { - let v = Version::parse("1.0.0-RC1").unwrap(); - assert_eq!(v.pre_release, Some("RC1".to_string())); - } - - #[test] - fn test_parse_alpha() { - let v = Version::parse("2.0.0-alpha3").unwrap(); - assert_eq!(v.pre_release, Some("alpha3".to_string())); - } - - #[test] - fn test_parse_dev_master() { - let v = Version::parse("dev-master").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.dev_branch_name, Some("master".to_string())); - assert_eq!(v.pre_release, Some("dev".to_string())); - } - - #[test] - fn test_parse_dev_feature() { - let v = Version::parse("dev-feature/foo").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.dev_branch_name, Some("feature/foo".to_string())); - } - - #[test] - fn test_parse_x_dev() { - let v = Version::parse("2.1.x-dev").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.major, 2); - assert_eq!(v.minor, 1); - assert_eq!(v.patch, 9999999); - assert_eq!(v.build, 9999999); - } - - #[test] - fn test_parse_strip_at_stability() { - let v = Version::parse("1.2.3@stable").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 3); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_inline_alias() { - let v = Version::parse("1.0.x-dev as 1.0.0").unwrap(); - // Takes left side: 1.0.x-dev - assert!(v.is_dev_branch); - } - - #[test] - fn test_parse_for_constraint_inline_alias() { - // parse_for_constraint takes the RIGHT side of an inline alias - let v = Version::parse_for_constraint("1.0.x-dev as 1.0.0").unwrap(); - assert!(!v.is_dev_branch); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 0); - assert_eq!(v.patch, 0); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_for_constraint_no_alias() { - // Without an alias, parse_for_constraint behaves like parse - let v = Version::parse_for_constraint("1.2.3").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 3); - assert!(!v.is_dev_branch); - } - - #[test] - fn test_constraint_inline_alias_exact_matches_target() { - // A constraint written as "1.0.x-dev as 1.0.0" should match 1.0.0 (the alias target) - let c = VersionConstraint::parse("1.0.x-dev as 1.0.0").unwrap(); - let target = Version::parse("1.0.0").unwrap(); - assert!(c.matches(&target)); - // But NOT a different version - let other = Version::parse("1.1.0").unwrap(); - assert!(!c.matches(&other)); - } - - // ──────────── Version ordering ──────────── - - #[test] - fn test_ordering_major() { - let a = Version::parse("2.0.0").unwrap(); - let b = Version::parse("1.0.0").unwrap(); - assert!(a > b); - } - - #[test] - fn test_ordering_minor() { - let a = Version::parse("1.2.0").unwrap(); - let b = Version::parse("1.1.0").unwrap(); - assert!(a > b); - } - - #[test] - fn test_ordering_stable_gt_rc() { - let stable = Version::parse("1.0.0").unwrap(); - let rc = Version::parse("1.0.0-RC1").unwrap(); - assert!(stable > rc); - } - - #[test] - fn test_ordering_rc_gt_beta() { - let rc = Version::parse("1.0.0-RC1").unwrap(); - let beta = Version::parse("1.0.0-beta1").unwrap(); - assert!(rc > beta); - } - - #[test] - fn test_ordering_beta_gt_alpha() { - let beta = Version::parse("1.0.0-beta1").unwrap(); - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - assert!(beta > alpha); - } - - #[test] - fn test_ordering_alpha_gt_dev_branch() { - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - let dev = Version::parse("dev-master").unwrap(); - assert!(alpha > dev); - } - - #[test] - fn test_ordering_pre_release_numbers() { - let beta2 = Version::parse("1.0.0-beta2").unwrap(); - let beta1 = Version::parse("1.0.0-beta1").unwrap(); - assert!(beta2 > beta1); - } - - // ──────────── Constraint parsing ──────────── - - #[test] - fn test_parse_any() { - let c = VersionConstraint::parse("*").unwrap(); - let v = Version::parse("1.2.3").unwrap(); - assert!(c.matches(&v)); - } - - #[test] - fn test_parse_exact() { - let c = VersionConstraint::parse("1.2.3").unwrap(); - let v = Version::parse("1.2.3").unwrap(); - assert!(c.matches(&v)); - let v2 = Version::parse("1.2.4").unwrap(); - assert!(!c.matches(&v2)); - } - - #[test] - fn test_parse_gte() { - let c = VersionConstraint::parse(">=1.0.0").unwrap(); - assert!(c.matches(&Version::parse("1.0.0").unwrap())); - assert!(c.matches(&Version::parse("2.0.0").unwrap())); - assert!(!c.matches(&Version::parse("0.9.0").unwrap())); - } - - #[test] - fn test_parse_caret_major() { - let c = VersionConstraint::parse("^1.2").unwrap(); - assert!(c.matches(&Version::parse("1.2.0").unwrap())); - assert!(c.matches(&Version::parse("1.3.0").unwrap())); - assert!(c.matches(&Version::parse("1.9.9").unwrap())); - assert!(!c.matches(&Version::parse("2.0.0").unwrap())); - assert!(!c.matches(&Version::parse("1.1.0").unwrap())); - } - - #[test] - fn test_parse_caret_zero_minor() { - // ^0.2.3 → >=0.2.3 <0.3.0 - let c = VersionConstraint::parse("^0.2.3").unwrap(); - assert!(c.matches(&Version::parse("0.2.3").unwrap())); - assert!(c.matches(&Version::parse("0.2.9").unwrap())); - assert!(!c.matches(&Version::parse("0.3.0").unwrap())); - assert!(!c.matches(&Version::parse("1.0.0").unwrap())); - } - - #[test] - fn test_parse_tilde_three_parts() { - // ~1.2.3 → >=1.2.3 <1.3.0 - let c = VersionConstraint::parse("~1.2.3").unwrap(); - assert!(c.matches(&Version::parse("1.2.3").unwrap())); - assert!(c.matches(&Version::parse("1.2.9").unwrap())); - assert!(!c.matches(&Version::parse("1.3.0").unwrap())); - } - - #[test] - fn test_parse_tilde_two_parts() { - // ~1.2 → >=1.2.0 <2.0.0 - let c = VersionConstraint::parse("~1.2").unwrap(); - assert!(c.matches(&Version::parse("1.2.0").unwrap())); - assert!(c.matches(&Version::parse("1.9.0").unwrap())); - assert!(!c.matches(&Version::parse("2.0.0").unwrap())); - } - - #[test] - fn test_parse_wildcard() { - let c = VersionConstraint::parse("1.2.*").unwrap(); - assert!(c.matches(&Version::parse("1.2.0").unwrap())); - assert!(c.matches(&Version::parse("1.2.9").unwrap())); - assert!(!c.matches(&Version::parse("1.3.0").unwrap())); - } - - #[test] - fn test_parse_and() { - let c = VersionConstraint::parse(">=1.0 <2.0").unwrap(); - assert!(c.matches(&Version::parse("1.0.0").unwrap())); - assert!(c.matches(&Version::parse("1.9.9").unwrap())); - assert!(!c.matches(&Version::parse("2.0.0").unwrap())); - assert!(!c.matches(&Version::parse("0.9.9").unwrap())); - } - - #[test] - fn test_parse_or() { - let c = VersionConstraint::parse("^1.0 || ^2.0").unwrap(); - assert!(c.matches(&Version::parse("1.5.0").unwrap())); - assert!(c.matches(&Version::parse("2.3.0").unwrap())); - assert!(!c.matches(&Version::parse("3.0.0").unwrap())); - } - - #[test] - fn test_parse_not_equal() { - let c = VersionConstraint::parse("!=1.5.0").unwrap(); - assert!(c.matches(&Version::parse("1.4.0").unwrap())); - assert!(!c.matches(&Version::parse("1.5.0").unwrap())); - assert!(c.matches(&Version::parse("1.6.0").unwrap())); - } - - #[test] - fn test_parse_hyphen_range() { - let c = VersionConstraint::parse("1.0 - 2.0").unwrap(); - assert!(c.matches(&Version::parse("1.0.0").unwrap())); - assert!(c.matches(&Version::parse("1.5.0").unwrap())); - assert!(c.matches(&Version::parse("2.0.0").unwrap())); - assert!(!c.matches(&Version::parse("0.9.0").unwrap())); - assert!(!c.matches(&Version::parse("2.1.0").unwrap())); - } - - // ──────────── Helper ──────────── - - fn satisfies(constraint: &str, version: &str) -> bool { - let c = VersionConstraint::parse(constraint).unwrap(); - let v = Version::parse(version).unwrap(); - c.matches(&v) - } - - // ══════════════════════════════════════════════════════════════════════════ - // 1. VERSION PARSING EDGE CASES - // ══════════════════════════════════════════════════════════════════════════ - - #[test] - fn test_parse_single_segment() { - let v = Version::parse("1").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 0); - assert_eq!(v.patch, 0); - assert_eq!(v.build, 0); - assert_eq!(v.pre_release, None); - assert!(!v.is_dev_branch); - } - - #[test] - fn test_parse_two_segments() { - let v = Version::parse("1.2").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 0); - assert_eq!(v.build, 0); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_zero_version() { - let v = Version::parse("0.0.0").unwrap(); - assert_eq!(v.major, 0); - assert_eq!(v.minor, 0); - assert_eq!(v.patch, 0); - assert_eq!(v.build, 0); - assert_eq!(v.pre_release, None); - assert!(!v.is_dev_branch); - } - - #[test] - fn test_parse_zero_zero_one() { - let v = Version::parse("0.0.1").unwrap(); - assert_eq!((v.major, v.minor, v.patch, v.build), (0, 0, 1, 0)); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_large_version_numbers() { - let v = Version::parse("99999.1.2.3").unwrap(); - assert_eq!(v.major, 99999); - assert_eq!(v.minor, 1); - assert_eq!(v.patch, 2); - assert_eq!(v.build, 3); - } - - #[test] - fn test_parse_uppercase_v_prefix() { - let v = Version::parse("V1.2.3").unwrap(); - assert_eq!(v.major, 1); - assert_eq!(v.minor, 2); - assert_eq!(v.patch, 3); - assert_eq!(v.pre_release, None); - assert!(!v.is_dev_branch); - } - - #[test] - fn test_parse_build_metadata_stripped() { - // Build metadata after '+' should be stripped - let v = Version::parse("1.2.3+build.456").unwrap(); - assert_eq!((v.major, v.minor, v.patch), (1, 2, 3)); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_shorthand_b_normalizes_to_beta() { - // "b2" suffix → beta2 - let v = Version::parse("1.0.0-b2").unwrap(); - assert_eq!(v.pre_release, Some("beta2".to_string())); - } - - #[test] - fn test_parse_shorthand_a_normalizes_to_alpha() { - // "a1" suffix → alpha1 - let v = Version::parse("1.0.0-a1").unwrap(); - assert_eq!(v.pre_release, Some("alpha1".to_string())); - } - - #[test] - fn test_parse_shorthand_p_normalizes_to_patch() { - // "p1" suffix → patch1 - let v = Version::parse("1.0.0-p1").unwrap(); - assert_eq!(v.pre_release, Some("patch1".to_string())); - } - - #[test] - fn test_parse_shorthand_pl_normalizes_to_patch() { - // "pl2" suffix → patch2 - let v = Version::parse("1.0.0-pl2").unwrap(); - assert_eq!(v.pre_release, Some("patch2".to_string())); - } - - #[test] - fn test_parse_shorthand_rc_lowercase_normalizes_to_rc() { - // "rc2" suffix → RC2 - let v = Version::parse("1.0.0-rc2").unwrap(); - assert_eq!(v.pre_release, Some("RC2".to_string())); - } - - #[test] - fn test_parse_stability_beta_no_number() { - // "1.0.0-beta" with no number - let v = Version::parse("1.0.0-beta").unwrap(); - assert_eq!(v.pre_release, Some("beta".to_string())); - } - - #[test] - fn test_parse_dev_release_branch() { - // "dev-release-1.0" is a dev branch named "release-1.0" - let v = Version::parse("dev-release-1.0").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.dev_branch_name, Some("release-1.0".to_string())); - assert_eq!(v.pre_release, Some("dev".to_string())); - } - - #[test] - fn test_parse_dev_master_uppercase() { - // "DEV-master" — case-insensitive dev- prefix - let v = Version::parse("DEV-master").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.dev_branch_name, Some("master".to_string())); - } - - #[test] - fn test_parse_x_dev_two_segment() { - // "2.x-dev" → major=2, minor=0, patch=9999999, build=9999999 - let v = Version::parse("2.x-dev").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.major, 2); - assert_eq!(v.minor, 0); - assert_eq!(v.patch, 9999999); - assert_eq!(v.build, 9999999); - } - - #[test] - fn test_parse_numeric_dev_suffix() { - // "2.1-dev" — ends with -dev, treated as *-dev suffix branch - let v = Version::parse("2.1-dev").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.major, 2); - assert_eq!(v.minor, 1); - } - - #[test] - fn test_parse_stability_flag_dev() { - // "1.0.0@dev" → strip @dev suffix, parse 1.0.0 as stable - let v = Version::parse("1.0.0@dev").unwrap(); - assert_eq!((v.major, v.minor, v.patch), (1, 0, 0)); - assert!(!v.is_dev_branch); - // After stripping @dev, no pre-release suffix remains - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_stability_flag_alpha() { - let v = Version::parse("1.0.0@alpha").unwrap(); - assert_eq!((v.major, v.minor, v.patch), (1, 0, 0)); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_stability_flag_beta() { - let v = Version::parse("1.0.0@beta").unwrap(); - assert_eq!((v.major, v.minor, v.patch), (1, 0, 0)); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_stability_flag_rc() { - let v = Version::parse("1.0.0@rc").unwrap(); - assert_eq!((v.major, v.minor, v.patch), (1, 0, 0)); - assert_eq!(v.pre_release, None); - } - - #[test] - fn test_parse_inline_alias_left_side() { - // "dev-main as 1.0.x-dev" → left side is "dev-main" - let v = Version::parse("dev-main as 1.0.x-dev").unwrap(); - assert!(v.is_dev_branch); - assert_eq!(v.dev_branch_name, Some("main".to_string())); - } - - #[test] - fn test_parse_error_empty_string() { - let result = Version::parse(""); - assert!(result.is_err(), "Expected error for empty string"); - } - - #[test] - fn test_parse_error_not_a_version() { - // Strings with no numeric start should fail - let result = Version::parse("not-a-version"); - assert!( - result.is_err(), - "Expected error for 'not-a-version', got: {:?}", - result - ); - } - - #[test] - fn test_parse_error_only_dots() { - let result = Version::parse("...."); - assert!(result.is_err(), "Expected error for '....'"); - } - - #[test] - fn test_parse_error_non_numeric_segment() { - // "1.abc.3" — minor segment is non-numeric; parse degrades minor to 0 - // The implementation uses `and_then(|p| p.parse().ok()).unwrap_or(0)`, - // so non-numeric segments silently become 0. This is intentional behavior. - let v = Version::parse("1.abc.3").unwrap(); - assert_eq!(v.major, 1); - // minor "abc" fails to parse as u64, so falls back to 0 - assert_eq!(v.minor, 0); - } - - // ══════════════════════════════════════════════════════════════════════════ - // 2. VERSION ORDERING - // ══════════════════════════════════════════════════════════════════════════ - - #[test] - fn test_ordering_equal_versions() { - let a = Version::parse("1.2.3").unwrap(); - let b = Version::parse("1.2.3").unwrap(); - assert_eq!(a.cmp(&b), std::cmp::Ordering::Equal); - } - - #[test] - fn test_ordering_patch_difference() { - let a = Version::parse("1.2.4").unwrap(); - let b = Version::parse("1.2.3").unwrap(); - assert!(a > b); - } - - #[test] - fn test_ordering_build_segment_difference() { - let a = Version::parse("1.2.3.2").unwrap(); - let b = Version::parse("1.2.3.1").unwrap(); - assert!(a > b); - } - - #[test] - fn test_ordering_dev_branch_lt_dev_prerelease() { - // "1.0.0-dev" ends with "-dev", so the parser treats it as a *-dev suffix branch - // (is_dev_branch=true, dev_branch_name=None, major=1, minor=0, patch=9999999). - // "dev-master" is also is_dev_branch=true with dev_branch_name=Some("master"). - // When both are dev branches, they compare by dev_branch_name: - // Some("master") vs None → Some > None, so dev-master > 1.0.0-dev (x-dev form). - let dev_branch = Version::parse("dev-master").unwrap(); - let dev_prerelease = Version::parse("1.0.0-dev").unwrap(); - // Both are dev branches; "master" branch name > None → dev-master is Greater - assert!(dev_branch > dev_prerelease); - } - - #[test] - fn test_ordering_dev_prerelease_lt_alpha() { - let dev = Version::parse("1.0.0-dev").unwrap(); - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - assert!(dev < alpha); - } - - #[test] - fn test_ordering_alpha_lt_beta() { - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - let beta = Version::parse("1.0.0-beta1").unwrap(); - assert!(alpha < beta); - } - - #[test] - fn test_ordering_beta_lt_rc() { - let beta = Version::parse("1.0.0-beta1").unwrap(); - let rc = Version::parse("1.0.0-RC1").unwrap(); - assert!(beta < rc); - } - - #[test] - fn test_ordering_rc_lt_stable() { - let rc = Version::parse("1.0.0-RC1").unwrap(); - let stable = Version::parse("1.0.0").unwrap(); - assert!(rc < stable); - } - - #[test] - fn test_ordering_stable_lt_patch() { - // The Ord impl: (None, Some(_)) => Greater — stable (pre_release=None) beats any - // pre_release including "patch1". Even though stability_rank("patch")=5 which is - // higher than stable's implicit 0, that path is only reached when both sides are - // Some(_). Since stable has pre_release=None, stable > patch version. - let stable = Version::parse("1.0.0").unwrap(); - let patch = Version::parse("1.0.0-patch1").unwrap(); - assert!(stable > patch); - } - - #[test] - fn test_ordering_rc3_gt_rc2() { - let rc3 = Version::parse("1.0.0-RC3").unwrap(); - let rc2 = Version::parse("1.0.0-RC2").unwrap(); - assert!(rc3 > rc2); - } - - #[test] - fn test_ordering_alpha5_gt_alpha3() { - let a5 = Version::parse("1.0.0-alpha5").unwrap(); - let a3 = Version::parse("1.0.0-alpha3").unwrap(); - assert!(a5 > a3); - } - - #[test] - fn test_ordering_dev_branches_alphabetical() { - // Between two dev branches, compare branch names alphabetically - let dev_foo = Version::parse("dev-foo").unwrap(); - let dev_bar = Version::parse("dev-bar").unwrap(); - // "bar" < "foo" alphabetically - assert!(dev_foo > dev_bar); - } - - #[test] - fn test_ordering_zero_versions() { - let a = Version::parse("0.0.2").unwrap(); - let b = Version::parse("0.0.1").unwrap(); - assert!(a > b); - } - - #[test] - fn test_ordering_four_vs_three_segment_equal() { - // 1.2.3.0 and 1.2.3 should be equal (build defaults to 0) - let a = Version::parse("1.2.3.0").unwrap(); - let b = Version::parse("1.2.3").unwrap(); - assert_eq!(a, b); - } - - #[test] - fn test_ordering_comprehensive_chain() { - // Note: "1.0.0-dev" is parsed as a *-dev suffix branch (is_dev_branch=true, - // dev_branch_name=None) due to the "-dev" suffix rule in Version::parse. - // "dev-foo" is also a dev branch (is_dev_branch=true, dev_branch_name=Some("foo")). - // Comparing two dev branches uses dev_branch_name: None < Some("foo"), so - // the *-dev form (None) < "dev-foo" (Some("foo")). - // For "1.0.0-alpha1", "1.0.0-beta1", "1.0.0-RC1", "1.0.0": normal numeric ordering. - let dev_x_dev = Version::parse("1.0.0-dev").unwrap(); // *-dev branch, name=None - let dev_branch = Version::parse("dev-foo").unwrap(); // named branch, name=Some("foo") - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - let beta = Version::parse("1.0.0-beta1").unwrap(); - let rc = Version::parse("1.0.0-RC1").unwrap(); - let stable = Version::parse("1.0.0").unwrap(); - - // Both dev branches; dev_branch_name None < Some("foo") - assert!(dev_x_dev < dev_branch); - // dev_branch (is_dev_branch=true) < alpha (is_dev_branch=false) - assert!(dev_branch < alpha); - assert!(alpha < beta); - assert!(beta < rc); - assert!(rc < stable); - } - - // ══════════════════════════════════════════════════════════════════════════ - // 3. CONSTRAINT PARSING EDGE CASES - // ══════════════════════════════════════════════════════════════════════════ - - // ── Caret ── - - #[test] - fn test_caret_zero_zero_three() { - // ^0.0.3 → >=0.0.3 <0.0.4 - assert!(satisfies("^0.0.3", "0.0.3")); - assert!(!satisfies("^0.0.3", "0.0.4")); - assert!(!satisfies("^0.0.3", "0.0.2")); - } - - #[test] - fn test_caret_zero_zero_zero() { - // ^0.0.0 → first non-zero is none, upper = 0.0.1 - assert!(satisfies("^0.0.0", "0.0.0")); - assert!(!satisfies("^0.0.0", "0.0.1")); - } - - #[test] - fn test_caret_single_major() { - // ^1 → >=1.0.0 <2.0.0 - assert!(satisfies("^1", "1.0.0")); - assert!(satisfies("^1", "1.99.99")); - assert!(!satisfies("^1", "2.0.0")); - assert!(!satisfies("^1", "0.9.9")); - } - - #[test] - fn test_caret_four_segments() { - // ^1.2.3.4 → >=1.2.3.4 <2.0.0.0 - assert!(satisfies("^1.2.3.4", "1.2.3.4")); - assert!(satisfies("^1.2.3.4", "1.9.0.0")); - assert!(!satisfies("^1.2.3.4", "2.0.0.0")); - assert!(!satisfies("^1.2.3.4", "1.2.3.3")); - } - - #[test] - fn test_caret_lower_boundary() { - // ^1.2.3 lower boundary: 1.2.3 matches but 1.2.2 does not - assert!(satisfies("^1.2.3", "1.2.3")); - assert!(!satisfies("^1.2.3", "1.2.2")); - } - - #[test] - fn test_caret_upper_boundary() { - // ^1.2.3 upper boundary: 1.9.9 matches, 2.0.0 does not - assert!(satisfies("^1.2.3", "1.9.9")); - assert!(!satisfies("^1.2.3", "2.0.0")); - } - - // ── Tilde ── - - #[test] - fn test_tilde_single_major() { - // ~1 → >=1.0.0 <2.0.0 - assert!(satisfies("~1", "1.0.0")); - assert!(satisfies("~1", "1.99.0")); - assert!(!satisfies("~1", "2.0.0")); - assert!(!satisfies("~1", "0.9.9")); - } - - #[test] - fn test_tilde_four_segments() { - // ~1.2.3.4 → >=1.2.3.4 <1.3.0.0 - assert!(satisfies("~1.2.3.4", "1.2.3.4")); - assert!(satisfies("~1.2.9.0", "1.2.9.0")); - assert!(!satisfies("~1.2.3.4", "1.3.0.0")); - assert!(!satisfies("~1.2.3.4", "1.2.3.3")); - } - - #[test] - fn test_tilde_lower_boundary() { - // ~1.2.3: 1.2.3 matches, 1.2.2 does not - assert!(satisfies("~1.2.3", "1.2.3")); - assert!(!satisfies("~1.2.3", "1.2.2")); - } - - #[test] - fn test_tilde_upper_boundary() { - // ~1.2.3: 1.2.9 matches, 1.3.0 does not - assert!(satisfies("~1.2.3", "1.2.9")); - assert!(!satisfies("~1.2.3", "1.3.0")); - } - - // ── Wildcard ── - - #[test] - fn test_wildcard_major_only() { - // 1.* → >=1.0.0 <2.0.0 - assert!(satisfies("1.*", "1.0.0")); - assert!(satisfies("1.*", "1.99.0")); - assert!(!satisfies("1.*", "2.0.0")); - assert!(!satisfies("1.*", "0.9.9")); - } - - #[test] - fn test_wildcard_double_star() { - // 1.*.* is treated like 1.* - assert!(satisfies("1.*.*", "1.5.0")); - assert!(!satisfies("1.*.*", "2.0.0")); - } - - #[test] - fn test_wildcard_three_segment() { - // 1.2.3.* — the implementation strips trailing .*; base is "1.2.3" - // parse_wildcard strips .* and splits on '.'; parts.len()=3 → minor constraint - assert!(satisfies("1.2.3.*", "1.2.3")); - assert!(satisfies("1.2.3.*", "1.2.9")); - assert!(!satisfies("1.2.3.*", "1.3.0")); - } - - #[test] - fn test_wildcard_zero_major() { - // 0.* → >=0.0.0 <1.0.0 - assert!(satisfies("0.*", "0.0.0")); - assert!(satisfies("0.*", "0.99.0")); - assert!(!satisfies("0.*", "1.0.0")); - } - - #[test] - fn test_wildcard_v_prefix() { - // v1.* — the wildcard parser strips the trailing .*; base becomes "v1" - // parse_wildcard's base.split('.') on "v1" → single part "v1" - // v1 fails to parse as u64, falls back to 0 — so this is like 0.* - // Mark as ignore since the behavior diverges from the expected semantic - #[allow(unused)] - let _ = VersionConstraint::parse("v1.*"); // just verify it doesn't panic - } - - // ── Hyphen ranges ── - - #[test] - fn test_hyphen_range_partial_from() { - // "1.0 - 2.0": 1.0 is a partial "from", lower = >=1.0.0 - assert!(satisfies("1.0 - 2.0", "1.0.0")); - assert!(satisfies("1.0 - 2.0", "1.5.0")); - } - - #[test] - fn test_hyphen_range_partial_to() { - // "1.0 - 2.0": upper = <=2.0.0 (inclusive) - assert!(satisfies("1.0 - 2.0", "2.0.0")); - assert!(!satisfies("1.0 - 2.0", "2.0.1")); - } - - #[test] - fn test_hyphen_range_same_version() { - // "1.0.0 - 1.0.0" → >=1.0.0 <=1.0.0, matches only 1.0.0 - assert!(satisfies("1.0.0 - 1.0.0", "1.0.0")); - assert!(!satisfies("1.0.0 - 1.0.0", "1.0.1")); - assert!(!satisfies("1.0.0 - 1.0.0", "0.9.9")); - } - - #[test] - fn test_hyphen_range_with_prerelease() { - // "1.0.0-alpha1 - 1.0.0-RC1" - assert!(satisfies("1.0.0-alpha1 - 1.0.0-RC1", "1.0.0-alpha1")); - assert!(satisfies("1.0.0-alpha1 - 1.0.0-RC1", "1.0.0-beta1")); - assert!(satisfies("1.0.0-alpha1 - 1.0.0-RC1", "1.0.0-RC1")); - assert!(!satisfies("1.0.0-alpha1 - 1.0.0-RC1", "1.0.0")); - } - - // ── Comparison operators ── - - #[test] - fn test_gt_boundary() { - assert!(!satisfies(">1.0.0", "1.0.0")); - assert!(satisfies(">1.0.0", "1.0.1")); - } - - #[test] - fn test_lt_boundary() { - assert!(!satisfies("<1.0.0", "1.0.0")); - assert!(satisfies("<1.0.0", "0.9.9")); - } - - #[test] - fn test_lte_boundary() { - assert!(satisfies("<=1.0.0", "1.0.0")); - assert!(!satisfies("<=1.0.0", "1.0.1")); - } - - #[test] - fn test_exact_equals_sign() { - // "=1.2.3" is exact match - assert!(satisfies("=1.2.3", "1.2.3")); - assert!(!satisfies("=1.2.3", "1.2.4")); - } - - #[test] - #[ignore = "== (double equals) is not supported: the '=' handler passes '=1.2.3' to \ - Version::parse_for_constraint which fails to parse '=1' as a major number"] - fn test_double_equals_sign() { - // "==1.2.3" — the '=' branch strips one '=', leaving "=1.2.3", which is then - // passed to Version::parse_for_constraint. That function tries to parse "=1" as - // a major version number and fails. Double-equals is not a supported syntax. - assert!(satisfies("==1.2.3", "1.2.3")); - assert!(!satisfies("==1.2.3", "1.2.4")); - } - - #[test] - fn test_not_equal_boundary() { - assert!(!satisfies("!=1.5.0", "1.5.0")); - assert!(satisfies("!=1.5.0", "1.4.9")); - assert!(satisfies("!=1.5.0", "1.5.1")); - } - - #[test] - fn test_gte_with_spaces() { - // Spaces after operator should be handled - assert!(satisfies(">=1.0.0", "1.0.0")); - } - - // ── AND constraints ── - - #[test] - fn test_and_comma_separated() { - // Comma-separated constraints act as AND - assert!(satisfies(">=1.0,<2.0", "1.5.0")); - assert!(!satisfies(">=1.0,<2.0", "2.0.0")); - assert!(!satisfies(">=1.0,<2.0", "0.9.0")); - } - - #[test] - fn test_and_three_way() { - assert!(satisfies(">=1.0 !=1.5.0 <2.0", "1.3.0")); - assert!(!satisfies(">=1.0 !=1.5.0 <2.0", "1.5.0")); - assert!(!satisfies(">=1.0 !=1.5.0 <2.0", "2.0.0")); - } - - #[test] - fn test_and_impossible_range() { - // >=2.0 <1.0 — impossible range, nothing should match - assert!(!satisfies(">=2.0 <1.0", "1.5.0")); - assert!(!satisfies(">=2.0 <1.0", "2.0.0")); - assert!(!satisfies(">=2.0 <1.0", "0.5.0")); - } - - #[test] - fn test_and_tight_range() { - // >=1.2.3 <=1.2.3 — only exactly 1.2.3 - assert!(satisfies(">=1.2.3 <=1.2.3", "1.2.3")); - assert!(!satisfies(">=1.2.3 <=1.2.3", "1.2.4")); - assert!(!satisfies(">=1.2.3 <=1.2.3", "1.2.2")); - } - - // ── OR constraints ── - - #[test] - fn test_or_double_pipe() { - assert!(satisfies("^1.0 || ^2.0", "1.5.0")); - assert!(satisfies("^1.0 || ^2.0", "2.3.0")); - assert!(!satisfies("^1.0 || ^2.0", "3.0.0")); - } - - #[test] - fn test_or_three_branches() { - assert!(satisfies("^1.0 || ^2.0 || ^3.0", "1.0.0")); - assert!(satisfies("^1.0 || ^2.0 || ^3.0", "2.5.0")); - assert!(satisfies("^1.0 || ^2.0 || ^3.0", "3.9.9")); - assert!(!satisfies("^1.0 || ^2.0 || ^3.0", "4.0.0")); - } - - #[test] - fn test_or_with_wildcard() { - assert!(satisfies("1.* || 3.*", "1.5.0")); - assert!(satisfies("1.* || 3.*", "3.0.0")); - assert!(!satisfies("1.* || 3.*", "2.0.0")); - } - - #[test] - fn test_or_overlapping_ranges() { - // Overlapping ranges are fine — union semantics - assert!(satisfies(">=1.0 <3.0 || >=2.0 <4.0", "1.5.0")); - assert!(satisfies(">=1.0 <3.0 || >=2.0 <4.0", "2.5.0")); - assert!(satisfies(">=1.0 <3.0 || >=2.0 <4.0", "3.5.0")); - assert!(!satisfies(">=1.0 <3.0 || >=2.0 <4.0", "0.9.0")); - assert!(!satisfies(">=1.0 <3.0 || >=2.0 <4.0", "4.0.0")); - } - - #[test] - fn test_or_exact_versions() { - assert!(satisfies("1.0.0 || 2.0.0 || 3.0.0", "1.0.0")); - assert!(satisfies("1.0.0 || 2.0.0 || 3.0.0", "2.0.0")); - assert!(satisfies("1.0.0 || 2.0.0 || 3.0.0", "3.0.0")); - assert!(!satisfies("1.0.0 || 2.0.0 || 3.0.0", "1.0.1")); - } - - // ── Complex combined ── - - #[test] - fn test_combined_and_within_or() { - // ">=1.0 <2.0 || >=3.0 <4.0" - assert!(satisfies(">=1.0 <2.0 || >=3.0 <4.0", "1.5.0")); - assert!(satisfies(">=1.0 <2.0 || >=3.0 <4.0", "3.5.0")); - assert!(!satisfies(">=1.0 <2.0 || >=3.0 <4.0", "2.5.0")); - assert!(!satisfies(">=1.0 <2.0 || >=3.0 <4.0", "4.0.0")); - } - - #[test] - fn test_combined_real_world_laravel_pattern() { - // "^8.0||^9.0||^10.0||^11.0" — real Laravel constraint - assert!(satisfies("^8.0||^9.0||^10.0||^11.0", "8.5.0")); - assert!(satisfies("^8.0||^9.0||^10.0||^11.0", "9.0.0")); - assert!(satisfies("^8.0||^9.0||^10.0||^11.0", "10.48.22")); - assert!(satisfies("^8.0||^9.0||^10.0||^11.0", "11.0.1")); - assert!(!satisfies("^8.0||^9.0||^10.0||^11.0", "7.9.9")); - assert!(!satisfies("^8.0||^9.0||^10.0||^11.0", "12.0.0")); - } - - #[test] - fn test_combined_real_world_symfony_pattern() { - // ">=5.4 <7.0" — typical Symfony range - assert!(satisfies(">=5.4 <7.0", "5.4.0")); - assert!(satisfies(">=5.4 <7.0", "6.4.5")); - assert!(!satisfies(">=5.4 <7.0", "5.3.9")); - assert!(!satisfies(">=5.4 <7.0", "7.0.0")); - } - - // ── Edge cases ── - - #[test] - fn test_constraint_empty_string_is_any() { - // Empty string → Any constraint - let c = VersionConstraint::parse("*").unwrap(); - let v = Version::parse("9.9.9").unwrap(); - assert!(c.matches(&v)); - } - - #[test] - fn test_constraint_v_prefix_in_exact() { - // "v1.2.3" exact constraint — strip v prefix - assert!(satisfies("v1.2.3", "1.2.3")); - assert!(!satisfies("v1.2.3", "1.2.4")); - } - - #[test] - fn test_constraint_extra_whitespace_and() { - // Extra spaces around operators in AND groups - assert!(satisfies(">=1.0.0 <2.0.0", "1.5.0")); - assert!(!satisfies(">=1.0.0 <2.0.0", "2.0.0")); - } - - // ══════════════════════════════════════════════════════════════════════════ - // 4. CONSTRAINT MATCHING - // ══════════════════════════════════════════════════════════════════════════ - - #[test] - fn test_dev_branch_exact_match() { - // dev-master matches dev-master constraint exactly - let c = VersionConstraint::parse("dev-master").unwrap(); - let v = Version::parse("dev-master").unwrap(); - assert!(c.matches(&v)); - } - - #[test] - fn test_dev_branch_different_branch_no_match() { - let c = VersionConstraint::parse("dev-master").unwrap(); - let v = Version::parse("dev-develop").unwrap(); - assert!(!c.matches(&v)); - } - - #[test] - fn test_dev_branch_against_caret_no_match() { - // dev-master does not satisfy ^1.0 (it is a dev branch, always lowest) - let c = VersionConstraint::parse("^1.0").unwrap(); - let v = Version::parse("dev-master").unwrap(); - assert!(!c.matches(&v)); - } - - #[test] - fn test_any_constraint_matches_dev_branch() { - // "*" matches any version including dev branches - let c = VersionConstraint::parse("*").unwrap(); - let v = Version::parse("dev-master").unwrap(); - assert!(c.matches(&v)); - } - - #[test] - fn test_prerelease_within_caret_range() { - // Pre-release of a version within ^1.0 should match - // e.g. 1.5.0-beta1 — it is >= dev boundary 1.0.0 and < dev boundary 2.0.0 - assert!(satisfies("^1.0", "1.5.0-beta1")); - } - - #[test] - fn test_caret_lower_minus_one_no_match() { - // ^1.2.3 lower-1 = 1.2.2 → should NOT match - assert!(!satisfies("^1.2.3", "1.2.2")); - } - - #[test] - fn test_caret_upper_minus_one_matches() { - // ^1.2.3 upper-1 patch: 1.9.9 should still match (below 2.0.0) - assert!(satisfies("^1.2.3", "1.9.9")); - } - - #[test] - fn test_tilde_lower_minus_one_no_match() { - assert!(!satisfies("~1.2.3", "1.2.2")); - } - - #[test] - fn test_tilde_upper_minus_one_matches() { - assert!(satisfies("~1.2.3", "1.2.9")); - } - - // ══════════════════════════════════════════════════════════════════════════ - // 5. INTERNAL FUNCTION TESTS (via public API) - // ══════════════════════════════════════════════════════════════════════════ - - // stability_rank() — tested via ordering since the function is private - - #[test] - fn test_stability_rank_dev_via_ordering() { - // dev rank=50 (highest number = least stable), alpha rank=40 - // So dev < alpha in version ordering terms - let dev = Version::parse("1.0.0-dev").unwrap(); - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - assert!(dev < alpha, "dev should be less stable than alpha1"); - } - - #[test] - fn test_stability_rank_alpha_via_ordering() { - // alpha rank=40, beta rank=30 - let alpha = Version::parse("1.0.0-alpha1").unwrap(); - let beta = Version::parse("1.0.0-beta1").unwrap(); - assert!(alpha < beta, "alpha should be less stable than beta"); - } - - #[test] - fn test_stability_rank_beta_via_ordering() { - // beta rank=30, RC rank=20 - let beta = Version::parse("1.0.0-beta1").unwrap(); - let rc = Version::parse("1.0.0-RC1").unwrap(); - assert!(beta < rc, "beta should be less stable than RC"); - } - - #[test] - fn test_stability_rank_rc_via_ordering() { - // RC rank=20, stable rank=0 - let rc = Version::parse("1.0.0-RC1").unwrap(); - let stable = Version::parse("1.0.0").unwrap(); - assert!(rc < stable, "RC should be less stable than stable"); - } - - #[test] - fn test_stability_rank_patch_via_ordering() { - // The Ord impl: (None, Some(_)) => Greater. - // stable has pre_release=None; patch version has pre_release=Some("patch1"). - // The None arm wins unconditionally: stable is always Greater than any pre_release. - // This means "patch" releases (post-release fixes) sort BELOW stable in this impl. - let patch_ver = Version::parse("1.0.0-patch1").unwrap(); - let stable = Version::parse("1.0.0").unwrap(); - assert!( - stable > patch_ver, - "stable (None pre_release) beats patch pre-release" - ); - } - - // normalize_pre_release() — tested via Version::parse pre_release field - - #[test] - fn test_normalize_pre_release_b_to_beta() { - let v = Version::parse("1.0.0-b3").unwrap(); - assert_eq!(v.pre_release, Some("beta3".to_string())); - } - - #[test] - fn test_normalize_pre_release_a_to_alpha() { - let v = Version::parse("1.0.0-a1").unwrap(); - assert_eq!(v.pre_release, Some("alpha1".to_string())); - } - - #[test] - fn test_normalize_pre_release_rc_to_rc_uppercase() { - let v = Version::parse("1.0.0-rc").unwrap(); - assert_eq!(v.pre_release, Some("RC".to_string())); - } - - #[test] - fn test_normalize_pre_release_pl_to_patch() { - let v = Version::parse("1.0.0-pl2").unwrap(); - assert_eq!(v.pre_release, Some("patch2".to_string())); - } - - #[test] - fn test_normalize_pre_release_patch_explicit() { - let v = Version::parse("1.0.0-patch3").unwrap(); - assert_eq!(v.pre_release, Some("patch3".to_string())); - } - - // pre_release_number() — tested via ordering of numbered pre-releases - - #[test] - fn test_pre_release_number_ordering_beta() { - // beta10 > beta2 if pre_release_number extracts correctly - let b10 = Version::parse("1.0.0-beta10").unwrap(); - let b2 = Version::parse("1.0.0-beta2").unwrap(); - assert!(b10 > b2); - } - - #[test] - fn test_pre_release_number_ordering_rc() { - let rc5 = Version::parse("1.0.0-RC5").unwrap(); - let rc1 = Version::parse("1.0.0-RC1").unwrap(); - assert!(rc5 > rc1); - } - - #[test] - fn test_pre_release_number_zero_when_missing() { - // "alpha" with no number → 0; "alpha1" → 1; alpha1 > alpha - let alpha1 = Version::parse("1.0.0-alpha1").unwrap(); - let alpha = Version::parse("1.0.0-alpha").unwrap(); - assert!(alpha1 > alpha); - } - - // ══════════════════════════════════════════════════════════════════════════ - // 6. COMPOSER BEHAVIORAL COMPATIBILITY - // ══════════════════════════════════════════════════════════════════════════ - - #[test] - fn test_composer_caret_four_matches_minor_bump() { - // ^4.0 matches 4.5.3 - assert!(satisfies("^4.0", "4.5.3")); - } - - #[test] - fn test_composer_caret_four_does_not_match_next_major() { - assert!(!satisfies("^4.0", "5.0.0")); - } - - #[test] - fn test_composer_caret_zero_three_matches_patch() { - // ^0.3 matches 0.3.5 (same minor family) - assert!(satisfies("^0.3", "0.3.5")); - } - - #[test] - fn test_composer_caret_zero_three_does_not_match_next_minor() { - // ^0.3 does NOT match 0.4.0 - assert!(!satisfies("^0.3", "0.4.0")); - } - - #[test] - fn test_composer_tilde_four_one_matches_within_major() { - // ~4.1 → >=4.1.0 <5.0.0 — matches 4.9.0 - assert!(satisfies("~4.1", "4.9.0")); - } - - #[test] - fn test_composer_tilde_four_one_does_not_match_next_major() { - // ~4.1 does NOT match 5.0.0 - assert!(!satisfies("~4.1", "5.0.0")); - } - - #[test] - fn test_composer_range_gap_matches_second_range() { - // ">=1.0 <1.1 || >=1.2" — gap at 1.1.x; 1.2.0 matches - assert!(satisfies(">=1.0 <1.1 || >=1.2", "1.2.0")); - } - - #[test] - fn test_composer_range_gap_does_not_match_in_gap() { - // 1.1.5 is in the gap — should NOT match - assert!(!satisfies(">=1.0 <1.1 || >=1.2", "1.1.5")); - } - - #[test] - fn test_composer_laravel_constraint_matches_v10() { - // "^8.0||^9.0||^10.0||^11.0" — Laravel-style; 10.48.22 matches - assert!(satisfies("^8.0||^9.0||^10.0||^11.0", "10.48.22")); - } - - #[test] - fn test_composer_laravel_constraint_does_not_match_v7() { - assert!(!satisfies("^8.0||^9.0||^10.0||^11.0", "7.9.9")); - } - - #[test] - fn test_composer_symfony_range_matches_6_4() { - // ">=5.4 <7.0" — Symfony; 6.4.5 matches - assert!(satisfies(">=5.4 <7.0", "6.4.5")); - } - - #[test] - fn test_composer_symfony_range_does_not_match_7_0() { - assert!(!satisfies(">=5.4 <7.0", "7.0.0")); - } - - #[test] - fn test_composer_not_equal_in_range() { - // ">=1.0 !=1.5.0 <2.0" — typical blacklist constraint - assert!(satisfies(">=1.0 !=1.5.0 <2.0", "1.4.9")); - assert!(!satisfies(">=1.0 !=1.5.0 <2.0", "1.5.0")); - assert!(satisfies(">=1.0 !=1.5.0 <2.0", "1.5.1")); - assert!(!satisfies(">=1.0 !=1.5.0 <2.0", "2.0.0")); - } - - #[test] - fn test_composer_exact_major_minor_match() { - // exact "1.5.0" only matches 1.5.0 - assert!(satisfies("1.5.0", "1.5.0")); - assert!(!satisfies("1.5.0", "1.5.1")); - } - - // ══════════════════════════════════════════════════════════════════════════ - // 7. DIVERGENCE INVESTIGATION - // ══════════════════════════════════════════════════════════════════════════ - - #[test] - fn test_hyphen_range_partial_upper_two_segment() { - // "1.0 - 2": upper is <=2.0.0 (parse "2" → 2.0.0.0, inclusive) - assert!(satisfies("1.0 - 2", "2.0.0")); - assert!(!satisfies("1.0 - 2", "2.0.1")); - assert!(!satisfies("1.0 - 2", "2.1.0")); - } - - #[test] - fn test_caret_with_prerelease_suffix() { - // ^1.2.3-beta1 — the caret parser ignores pre-release in its bounds calculation - // because parse_caret works on the numeric parts only. - // Lower: dev_boundary(1,2,3,0). Upper: dev_boundary(2,0,0,0). - // 1.2.3-beta1 (pre_release=Some("beta1")) is >= lower boundary? - // dev_boundary uses pre_release=Some("dev"), so lower is (1,2,3,0,dev) - // Version 1.2.3-beta1 has same numeric, but beta > dev in stability terms - // so 1.2.3-beta1 >= lower (1.2.3-dev) is true. - assert!(satisfies("^1.2.3-beta1", "1.2.3-beta1")); - assert!(satisfies("^1.2.3-beta1", "1.5.0")); - assert!(!satisfies("^1.2.3-beta1", "2.0.0")); - } - - #[test] - fn test_tilde_with_prerelease_suffix() { - // ~1.2.3-alpha1: lower = dev_boundary(1,2,3,0), upper = dev_boundary(1,3,0,0) - // 1.2.3-alpha1 has numeric (1,2,3,0); pre_release "alpha1" > "dev" - assert!(satisfies("~1.2.3-alpha1", "1.2.3-alpha1")); - assert!(satisfies("~1.2.3-alpha1", "1.2.9")); - assert!(!satisfies("~1.2.3-alpha1", "1.3.0")); - } - - #[test] - fn test_dev_boundary_comparison() { - // Version::dev_boundary creates a version with pre_release=Some("dev") and - // is_dev_branch=false. These should sort correctly against real versions. - let lower = Version::dev_boundary(1, 0, 0, 0); - let v = Version::parse("1.0.0").unwrap(); - // 1.0.0 (stable) > 1.0.0-dev (lower boundary) - assert!(v > lower); - } - - #[test] - fn test_x_dev_ordering_within_range() { - // "2.x-dev" version has patch=9999999, build=9999999 and is a dev branch. - // Dev branches are always lowest. So "2.x-dev" < "2.0.0" < "3.0.0". - let x_dev = Version::parse("2.x-dev").unwrap(); - let stable = Version::parse("2.0.0").unwrap(); - assert!(x_dev < stable); - } - - #[test] - fn test_four_segment_vs_three_segment_constraint() { - // "1.2.3.4" exact constraint — matches only 1.2.3.4, not 1.2.3 - assert!(satisfies("1.2.3.4", "1.2.3.4")); - assert!(!satisfies("1.2.3.4", "1.2.3")); - assert!(!satisfies("1.2.3.4", "1.2.3.5")); - } - - #[test] - fn test_date_style_version_ordering() { - // Date-based versioning: 20230101 > 20220101 - let a = Version::parse("20230101.0.0").unwrap(); - let b = Version::parse("20220101.0.0").unwrap(); - assert!(a > b); - } -} +// This module has been moved to the `mozart-constraint` crate. +// This file is intentionally left empty and the module declaration removed from lib.rs. diff --git a/crates/mozart/src/downloader.rs b/crates/mozart/src/downloader.rs index cfed951..86fd677 100644 --- a/crates/mozart/src/downloader.rs +++ b/crates/mozart/src/downloader.rs @@ -1,4 +1,4 @@ -use crate::cache::Cache; +use mozart_registry::cache::Cache; use sha1::{Digest, Sha1}; use std::collections::HashSet; use std::fs; diff --git a/crates/mozart/src/installed.rs b/crates/mozart/src/installed.rs index 8ed4721..7543b0e 100644 --- a/crates/mozart/src/installed.rs +++ b/crates/mozart/src/installed.rs @@ -1,4 +1,4 @@ -use crate::package::to_json_pretty; +use mozart_core::package::to_json_pretty; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::fs; diff --git a/crates/mozart/src/lib.rs b/crates/mozart/src/lib.rs index 4275833..82b6da3 100644 --- a/crates/mozart/src/lib.rs +++ b/crates/mozart/src/lib.rs @@ -1,19 +1 @@ -pub mod archiver; -pub mod autoload; -pub mod cache; pub mod commands; -pub mod console; -pub mod constraint; -pub mod downloader; -pub mod exit_code; -pub mod installed; -pub mod lockfile; -pub mod package; -pub mod packagist; -pub mod php_scanner; -pub mod platform; -pub mod resolver; -pub mod suggest; -pub mod validation; -pub mod version; -pub mod version_bumper; diff --git a/crates/mozart/src/lockfile.rs b/crates/mozart/src/lockfile.rs index 4742772..3a13778 100644 --- a/crates/mozart/src/lockfile.rs +++ b/crates/mozart/src/lockfile.rs @@ -1,7 +1,7 @@ -use crate::cache::Cache; -use crate::package::{RawPackageData, to_json_pretty}; -use crate::packagist::{self, PackagistDist, PackagistSource, PackagistVersion}; -use crate::resolver::ResolvedPackage; +use mozart_registry::cache::Cache; +use mozart_core::package::{RawPackageData, to_json_pretty}; +use mozart_registry::packagist::{self, PackagistDist, PackagistSource, PackagistVersion}; +use mozart_registry::resolver::ResolvedPackage; use serde::{Deserialize, Serialize}; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; use std::fs; @@ -613,13 +613,13 @@ mod tests { replace: BTreeMap::new(), provide: BTreeMap::new(), conflict: BTreeMap::new(), - dist: Some(crate::packagist::PackagistDist { + dist: Some(mozart_registry::packagist::PackagistDist { dist_type: "zip".to_string(), url: format!("https://example.com/{version}.zip"), reference: Some("deadbeef".to_string()), shasum: Some("abc123".to_string()), }), - source: Some(crate::packagist::PackagistSource { + source: Some(mozart_registry::packagist::PackagistSource { source_type: "git".to_string(), url: "https://github.com/example/pkg.git".to_string(), reference: Some("deadbeef".to_string()), @@ -1012,9 +1012,9 @@ mod tests { #[test] #[ignore] fn test_generate_lock_file_monolog() { - use crate::package::Stability; - use crate::resolver::PlatformConfig; - use crate::resolver::{ResolveRequest, resolve}; + use mozart_core::package::Stability; + use mozart_registry::resolver::PlatformConfig; + use mozart_registry::resolver::{ResolveRequest, resolve}; // Resolve monolog/monolog ^3.0 let resolve_request = ResolveRequest { diff --git a/crates/mozart/src/main.rs b/crates/mozart/src/main.rs index dd85279..59ad392 100644 --- a/crates/mozart/src/main.rs +++ b/crates/mozart/src/main.rs @@ -1,6 +1,6 @@ use clap::Parser; use mozart::commands; -use mozart::exit_code; +use mozart_core::exit_code; fn main() { let cli = commands::Cli::parse(); @@ -11,13 +11,13 @@ fn main() { if let Some(mozart_err) = e.downcast_ref::<exit_code::MozartError>() { // Only print a message when there is one (bail_silent produces empty message). if !mozart_err.message.is_empty() { - eprintln!("{}", mozart::console::error(&mozart_err.message)); + eprintln!("{}", mozart_core::console::error(&mozart_err.message)); } std::process::exit(mozart_err.exit_code); } // Generic anyhow error — print and exit with GENERAL_ERROR. - eprintln!("{}", mozart::console::error(&format!("{e:#}"))); + eprintln!("{}", mozart_core::console::error(&format!("{e:#}"))); std::process::exit(exit_code::GENERAL_ERROR); } } diff --git a/crates/mozart/src/packagist.rs b/crates/mozart/src/packagist.rs index ba80e7e..2503255 100644 --- a/crates/mozart/src/packagist.rs +++ b/crates/mozart/src/packagist.rs @@ -1,4 +1,4 @@ -use crate::cache::Cache; +use mozart_registry::cache::Cache; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; diff --git a/crates/mozart/src/resolver.rs b/crates/mozart/src/resolver.rs index 3243a44..cb4561e 100644 --- a/crates/mozart/src/resolver.rs +++ b/crates/mozart/src/resolver.rs @@ -13,10 +13,10 @@ use pubgrub::{ PackageResolutionStatistics, PubGrubError, Ranges, Reporter, }; -use crate::cache::Cache; -use crate::constraint::{Constraint, VersionConstraint}; -use crate::package::Stability; -use crate::packagist; +use mozart_registry::cache::Cache; +use mozart_constraint::{Constraint, VersionConstraint}; +use mozart_core::package::Stability; +use mozart_registry::packagist; // ───────────────────────────────────────────────────────────────────────────── // Stability constants @@ -372,7 +372,7 @@ fn single_constraint_to_ranges(c: &Constraint) -> Result<ComposerVS, String> { } /// Convert a `constraint::Version` to a `ComposerVersion`. -fn version_to_composer(v: &crate::constraint::Version) -> Result<ComposerVersion, String> { +fn version_to_composer(v: &mozart_constraint::Version) -> Result<ComposerVersion, String> { // Dev branches cannot be represented as ComposerVersion if v.is_dev_branch { return Err(format!( diff --git a/crates/mozart/src/version.rs b/crates/mozart/src/version.rs index 7520464..d71be2c 100644 --- a/crates/mozart/src/version.rs +++ b/crates/mozart/src/version.rs @@ -1,5 +1,5 @@ -use crate::package::Stability; -use crate::packagist::PackagistVersion; +use mozart_core::package::Stability; +use mozart_registry::packagist::PackagistVersion; use std::cmp::Ordering; /// Determine the stability of a normalized version string. |
