diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-23 01:52:22 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-23 01:59:22 +0900 |
| commit | 612af0aaacda404b8e177d0c1a6d3bd937e8d39a (patch) | |
| tree | e7a74752686e3216a533f7437d3838dc10c58328 | |
| parent | cc8f22ff7f1d9ed32e14f5b59a5498f8aa653091 (diff) | |
| download | php-mozart-612af0aaacda404b8e177d0c1a6d3bd937e8d39a.tar.gz php-mozart-612af0aaacda404b8e177d0c1a6d3bd937e8d39a.tar.zst php-mozart-612af0aaacda404b8e177d0c1a6d3bd937e8d39a.zip | |
fix(update): implement --with constraints, inline shorthand, and APCu passthrough
- Parse and apply --with temporary constraints to the resolver
- Support inline constraint shorthand (vendor/pkg:1.0.*)
- Reject --lock combined with specific package names
- Filter magic keywords (lock/nothing/mirrors) from package list
- Pass APCu CLI flags through to InstallConfig instead of hardcoding
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| -rw-r--r-- | crates/mozart-registry/src/lockfile.rs | 1 | ||||
| -rw-r--r-- | crates/mozart-registry/src/resolver.rs | 10 | ||||
| -rw-r--r-- | crates/mozart/src/commands/create_project.rs | 1 | ||||
| -rw-r--r-- | crates/mozart/src/commands/remove.rs | 4 | ||||
| -rw-r--r-- | crates/mozart/src/commands/require.rs | 3 | ||||
| -rw-r--r-- | crates/mozart/src/commands/show.rs | 14 | ||||
| -rw-r--r-- | crates/mozart/src/commands/update.rs | 59 |
7 files changed, 82 insertions, 10 deletions
diff --git a/crates/mozart-registry/src/lockfile.rs b/crates/mozart-registry/src/lockfile.rs index 682dbb1..bfae4ee 100644 --- a/crates/mozart-registry/src/lockfile.rs +++ b/crates/mozart-registry/src/lockfile.rs @@ -1031,6 +1031,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let resolved = resolve(&resolve_request) diff --git a/crates/mozart-registry/src/resolver.rs b/crates/mozart-registry/src/resolver.rs index 3e0936f..898a91c 100644 --- a/crates/mozart-registry/src/resolver.rs +++ b/crates/mozart-registry/src/resolver.rs @@ -345,6 +345,9 @@ pub struct ResolveRequest { pub ignore_platform_req_list: Vec<String>, /// Optional on-disk repo cache for Packagist API responses. pub repo_cache: Option<Cache>, + /// Temporary version constraint overrides (from --with flag). + /// Maps package name (lowercase) to constraint string. + pub temporary_constraints: HashMap<String, String>, } /// A single package in the resolution output. @@ -394,6 +397,12 @@ pub async fn resolve(request: &ResolveRequest) -> Result<Vec<ResolvedPackage>, R } } + // Apply temporary constraints (from --with flag or inline shorthand). + // These override existing root constraints or add new ones for transitive deps. + for (name, constraint) in &request.temporary_constraints { + root_requires.insert(name.clone(), Some(constraint.clone())); + } + // Capture data needed by spawn_blocking let handle = tokio::runtime::Handle::current(); let repo_cache = request.repo_cache.clone(); @@ -928,6 +937,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let result = resolve(&request).await; diff --git a/crates/mozart/src/commands/create_project.rs b/crates/mozart/src/commands/create_project.rs index b010a75..6a5b815 100644 --- a/crates/mozart/src/commands/create_project.rs +++ b/crates/mozart/src/commands/create_project.rs @@ -410,6 +410,7 @@ pub async fn execute( ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, + temporary_constraints: HashMap::new(), }; console.info("Resolving dependencies..."); diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs index acf878c..88fa4da 100644 --- a/crates/mozart/src/commands/remove.rs +++ b/crates/mozart/src/commands/remove.rs @@ -250,6 +250,7 @@ pub async fn execute( ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, + temporary_constraints: HashMap::new(), }; // Print header messages @@ -495,6 +496,7 @@ async fn remove_unused( ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, + temporary_constraints: HashMap::new(), }; console.info("Resolving dependencies to detect unused packages..."); @@ -823,6 +825,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let resolved = resolve(&request) .await @@ -858,6 +861,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let resolved2 = resolve(&request2) .await diff --git a/crates/mozart/src/commands/require.rs b/crates/mozart/src/commands/require.rs index ef5ff04..4ea739d 100644 --- a/crates/mozart/src/commands/require.rs +++ b/crates/mozart/src/commands/require.rs @@ -655,6 +655,7 @@ pub async fn execute( ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, + temporary_constraints: HashMap::new(), }; // Print header messages @@ -1026,6 +1027,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let resolved = resolver::resolve(&request) @@ -1078,6 +1080,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let resolved = resolver::resolve(&request) diff --git a/crates/mozart/src/commands/show.rs b/crates/mozart/src/commands/show.rs index 9eae36e..f194bce 100644 --- a/crates/mozart/src/commands/show.rs +++ b/crates/mozart/src/commands/show.rs @@ -138,12 +138,14 @@ pub async fn execute( // Fix 5: --format with invalid value if let Some(ref fmt) = args.format - && fmt != "text" && fmt != "json" { - anyhow::bail!( - "Unsupported format \"{}\". See help for supported formats.", - fmt - ); - } + && fmt != "text" + && fmt != "json" + { + anyhow::bail!( + "Unsupported format \"{}\". See help for supported formats.", + fmt + ); + } // Fix 6: --self with a package argument if args.self_info && args.package.is_some() { diff --git a/crates/mozart/src/commands/update.rs b/crates/mozart/src/commands/update.rs index f33f1b0..06e6b22 100644 --- a/crates/mozart/src/commands/update.rs +++ b/crates/mozart/src/commands/update.rs @@ -737,12 +737,61 @@ pub async fn execute( let vendor_dir = working_dir.join("vendor"); // Step 4: Handle --lock mode (early return) + // Fix 4: Reject combining --lock with specific package names if args.lock { + let non_magic: Vec<_> = args + .packages + .iter() + .filter(|p| !matches!(p.to_lowercase().as_str(), "lock" | "nothing" | "mirrors")) + .collect(); + if !non_magic.is_empty() { + anyhow::bail!( + "You cannot simultaneously update only a selection of packages and regenerate the lock file metadata." + ); + } return handle_lock_mode(&lock_path, &composer_json_content, args.dry_run, console); } let dev_mode = !args.no_dev; + // Fix 1C + Fix 2: Parse --with constraints and inline constraint shorthand. + let mut temporary_constraints: HashMap<String, String> = HashMap::new(); + + // Parse --with constraints (format: "vendor/package:constraint") + for with_entry in &args.with { + if let Some((name, constraint)) = with_entry.split_once(':') { + let name = name.trim().to_lowercase(); + let constraint = constraint.trim().to_string(); + if !name.is_empty() && !constraint.is_empty() { + temporary_constraints.insert(name, constraint); + } + } + } + + // Fix 2: Parse inline constraint shorthand from package arguments + // (e.g. "vendor/package:1.0.*" -> name="vendor/package", constraint="1.0.*") + let mut raw_packages: Vec<String> = Vec::new(); + for pkg in &args.packages { + if let Some((name, constraint)) = pkg.split_once(':') { + let name = name.trim().to_string(); + let constraint = constraint.trim().to_string(); + if !name.is_empty() && !constraint.is_empty() { + temporary_constraints.insert(name.to_lowercase(), constraint); + raw_packages.push(name); + } else { + raw_packages.push(pkg.clone()); + } + } else { + raw_packages.push(pkg.clone()); + } + } + + // Fix 5: Filter magic keywords from package list + let raw_packages: Vec<String> = raw_packages + .into_iter() + .filter(|p| !matches!(p.to_lowercase().as_str(), "lock" | "nothing" | "mirrors")) + .collect(); + // Step 5: Build the resolve request from composer.json // Filter out platform packages from require list for the resolver (they're handled separately) let require: Vec<(String, String)> = composer_json @@ -785,6 +834,7 @@ pub async fn execute( ignore_platform_reqs: args.ignore_platform_reqs, ignore_platform_req_list: args.ignore_platform_req.clone(), repo_cache: None, + temporary_constraints, }; // Step 6: Print header and run resolver @@ -828,7 +878,7 @@ pub async fn execute( // Note: wildcard expansion and dependency traversal both require a lock file. // If --minimal-changes is requested without specific packages, we pin all packages. // --root-reqs: treat root requirements as the package list - let effective_packages: Vec<String> = if args.root_reqs && args.packages.is_empty() { + let effective_packages: Vec<String> = if args.root_reqs && raw_packages.is_empty() { let mut root_pkgs: Vec<String> = composer_json .require .keys() @@ -846,7 +896,7 @@ pub async fn execute( } root_pkgs } else { - args.packages.clone() + raw_packages }; let update_packages: Vec<String> = if !effective_packages.is_empty() { @@ -1140,8 +1190,8 @@ pub async fn execute( ignore_platform_req: args.ignore_platform_req.clone(), optimize_autoloader: args.optimize_autoloader, classmap_authoritative: args.classmap_authoritative, - apcu_autoloader: false, - apcu_autoloader_prefix: None, + apcu_autoloader: args.apcu_autoloader || args.apcu_autoloader_prefix.is_some(), + apcu_autoloader_prefix: args.apcu_autoloader_prefix.clone(), download_only: false, }, ) @@ -1858,6 +1908,7 @@ mod tests { ignore_platform_reqs: false, ignore_platform_req_list: vec![], repo_cache: None, + temporary_constraints: HashMap::new(), }; let resolved = resolve(&request).await.expect("Resolution should succeed"); |
