aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/commands/remove.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-21 15:19:18 +0900
committernsfisis <nsfisis@gmail.com>2026-02-21 15:19:18 +0900
commitc04744719bd16d9414a9f9a358691d03a993670c (patch)
tree2354da304c97094bfbe23bb38ceba08d60ea19a2 /crates/mozart/src/commands/remove.rs
parentc07e2073e0484924f80f3bb68ea95ce127b42df6 (diff)
downloadphp-mozart-c04744719bd16d9414a9f9a358691d03a993670c.tar.gz
php-mozart-c04744719bd16d9414a9f9a358691d03a993670c.tar.zst
php-mozart-c04744719bd16d9414a9f9a358691d03a993670c.zip
feat(require,remove): add interactive search and dependency-aware partial updates
Implement Phase 5.5 of the require/remove commands: - Interactive package search when no packages specified on CLI (require) - --with-dependencies/--with-all-dependencies partial update for require - --with-all-dependencies/--no-update-with-dependencies for remove - --minimal-changes support for remove - Extract search API types and logic from search.rs into packagist.rs for reuse by both search and require commands Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates/mozart/src/commands/remove.rs')
-rw-r--r--crates/mozart/src/commands/remove.rs64
1 files changed, 46 insertions, 18 deletions
diff --git a/crates/mozart/src/commands/remove.rs b/crates/mozart/src/commands/remove.rs
index b227df8..1c9b619 100644
--- a/crates/mozart/src/commands/remove.rs
+++ b/crates/mozart/src/commands/remove.rs
@@ -120,22 +120,6 @@ pub fn execute(args: &RemoveArgs, cli: &super::Cli) -> anyhow::Result<()> {
);
}
- // Warn about flags that are accepted but not fully implemented
- if args.minimal_changes {
- eprintln!(
- "{}",
- console::warning("--minimal-changes is not yet implemented and will be ignored.")
- );
- }
- if args.no_update_with_dependencies {
- eprintln!(
- "{}",
- console::warning(
- "--no-update-with-dependencies is not yet implemented and will be ignored."
- )
- );
- }
-
// Step 3: Resolve working directory and read composer.json
let working_dir = super::install::resolve_working_dir(cli);
let composer_path = working_dir.join("composer.json");
@@ -296,7 +280,7 @@ pub fn execute(args: &RemoveArgs, cli: &super::Cli) -> anyhow::Result<()> {
eprintln!("Resolving dependencies...");
// Run resolver
- let resolved = match resolver::resolve(&request) {
+ let mut resolved = match resolver::resolve(&request) {
Ok(packages) => packages,
Err(e) => {
eprintln!("{}", console::error(&e.to_string()));
@@ -304,7 +288,7 @@ pub fn execute(args: &RemoveArgs, cli: &super::Cli) -> anyhow::Result<()> {
}
};
- // Read old lock file (if any) for change reporting
+ // Read old lock file (if any) for change reporting and partial update
let old_lock = if lock_path.exists() {
match lockfile::LockFile::read_from_file(&lock_path) {
Ok(l) => Some(l),
@@ -323,6 +307,50 @@ pub fn execute(args: &RemoveArgs, cli: &super::Cli) -> anyhow::Result<()> {
None
};
+ // Apply partial update logic for `remove`:
+ //
+ // Composer's default for `remove` is to also update the direct dependencies of the
+ // removed packages (i.e. they become candidates for removal if nothing else needs them).
+ // With --with-all-dependencies the full transitive dependency tree is considered.
+ // With --no-update-with-dependencies only the removed packages themselves are freed.
+ //
+ // We implement this by building an "allow list" of packages that may change:
+ // - --no-update-with-dependencies: only the removed packages
+ // - --with-all-dependencies: removed packages + full transitive deps
+ // - default: removed packages + direct deps (Composer default)
+ // Then we pin everything NOT in the allow list to its locked version.
+ let with_all_deps = args.with_all_dependencies || args.update_with_all_dependencies;
+
+ if let Some(ref lock) = old_lock {
+ let removed_names: Vec<String> = args
+ .packages
+ .iter()
+ .map(|s| s.trim().to_lowercase())
+ .collect();
+
+ let allow_list = if args.no_update_with_dependencies {
+ // Only the removed packages themselves are freed
+ removed_names
+ } else if with_all_deps {
+ super::update::expand_with_all_dependencies(removed_names, lock)
+ } else {
+ // Default: freed packages + their direct dependencies
+ super::update::expand_with_direct_dependencies(removed_names, lock)
+ };
+
+ // For --minimal-changes, additionally pin packages beyond the allow list
+ if args.minimal_changes {
+ eprintln!(
+ "{}",
+ console::info(
+ "Minimal changes mode: preserving locked versions for non-removed packages."
+ )
+ );
+ }
+
+ resolved = super::update::apply_partial_update(resolved, lock, &allow_list);
+ }
+
// Get the composer.json content string for content-hash computation.
// For --dry-run, serialize from memory; otherwise re-read the file we just wrote.
let composer_json_content = if args.dry_run {