aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-22 00:37:54 +0900
committernsfisis <nsfisis@gmail.com>2026-02-22 00:37:54 +0900
commit0a8e5935e6305819bb02d8c69e2f046ff397913a (patch)
treee5a288e679477b1603d7989e986ca22bbe590aa4 /crates/mozart/src
parentb5af594fec7da72b15c9a202c641af0494db6355 (diff)
downloadphp-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')
-rw-r--r--crates/mozart/src/archiver.rs32
-rw-r--r--crates/mozart/src/autoload.rs10
-rw-r--r--crates/mozart/src/cache.rs4
-rw-r--r--crates/mozart/src/commands.rs8
-rw-r--r--crates/mozart/src/commands/about.rs2
-rw-r--r--crates/mozart/src/commands/archive.rs19
-rw-r--r--crates/mozart/src/commands/audit.rs39
-rw-r--r--crates/mozart/src/commands/browse.rs24
-rw-r--r--crates/mozart/src/commands/bump.rs64
-rw-r--r--crates/mozart/src/commands/check_platform_reqs.rs70
-rw-r--r--crates/mozart/src/commands/clear_cache.rs6
-rw-r--r--crates/mozart/src/commands/completion.rs2
-rw-r--r--crates/mozart/src/commands/config.rs6
-rw-r--r--crates/mozart/src/commands/create_project.rs22
-rw-r--r--crates/mozart/src/commands/dependency.rs52
-rw-r--r--crates/mozart/src/commands/depends.rs4
-rw-r--r--crates/mozart/src/commands/diagnose.rs8
-rw-r--r--crates/mozart/src/commands/dump_autoload.rs8
-rw-r--r--crates/mozart/src/commands/exec.rs8
-rw-r--r--crates/mozart/src/commands/fund.rs19
-rw-r--r--crates/mozart/src/commands/global.rs2
-rw-r--r--crates/mozart/src/commands/init.rs28
-rw-r--r--crates/mozart/src/commands/install.rs26
-rw-r--r--crates/mozart/src/commands/licenses.rs40
-rw-r--r--crates/mozart/src/commands/outdated.rs42
-rw-r--r--crates/mozart/src/commands/prohibits.rs8
-rw-r--r--crates/mozart/src/commands/reinstall.rs58
-rw-r--r--crates/mozart/src/commands/remove.rs32
-rw-r--r--crates/mozart/src/commands/repository.rs2
-rw-r--r--crates/mozart/src/commands/require.rs30
-rw-r--r--crates/mozart/src/commands/run_script.rs2
-rw-r--r--crates/mozart/src/commands/search.rs23
-rw-r--r--crates/mozart/src/commands/self_update.rs10
-rw-r--r--crates/mozart/src/commands/show.rs288
-rw-r--r--crates/mozart/src/commands/status.rs30
-rw-r--r--crates/mozart/src/commands/suggests.rs47
-rw-r--r--crates/mozart/src/commands/update.rs56
-rw-r--r--crates/mozart/src/commands/validate.rs45
-rw-r--r--crates/mozart/src/console.rs19
-rw-r--r--crates/mozart/src/constraint.rs1974
-rw-r--r--crates/mozart/src/downloader.rs2
-rw-r--r--crates/mozart/src/installed.rs2
-rw-r--r--crates/mozart/src/lib.rs18
-rw-r--r--crates/mozart/src/lockfile.rs18
-rw-r--r--crates/mozart/src/main.rs6
-rw-r--r--crates/mozart/src/packagist.rs2
-rw-r--r--crates/mozart/src/resolver.rs10
-rw-r--r--crates/mozart/src/version.rs4
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.