aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/mozart/src/commands/reinstall.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/mozart/src/commands/reinstall.rs')
-rw-r--r--crates/mozart/src/commands/reinstall.rs665
1 files changed, 115 insertions, 550 deletions
diff --git a/crates/mozart/src/commands/reinstall.rs b/crates/mozart/src/commands/reinstall.rs
index 45f44f5..206d2bf 100644
--- a/crates/mozart/src/commands/reinstall.rs
+++ b/crates/mozart/src/commands/reinstall.rs
@@ -1,11 +1,14 @@
use clap::Args;
+use mozart_autoload::AutoloadGeneratorExt;
+use mozart_core::composer::{
+ AutoloadDumpOptions, Composer, LocalPackage, PlatformRequirementFilter,
+};
use mozart_core::console_format;
-use mozart_core::console_writeln;
-use mozart_core::package;
+use mozart_core::validation::package_name_to_regexp;
#[derive(Args)]
pub struct ReinstallArgs {
- /// Package(s) to reinstall
+ /// Package(s) to reinstall, can include a wildcard (*) to match any substring
pub packages: Vec<String>,
/// Forces installation from package sources when possible
@@ -20,14 +23,6 @@ pub struct ReinstallArgs {
#[arg(long)]
pub prefer_install: Option<String>,
- /// Only output what would be changed, do not modify files
- #[arg(long)]
- pub dry_run: bool,
-
- /// Disables installation of require-dev packages
- #[arg(long)]
- pub no_dev: bool,
-
/// Skips autoloader generation
#[arg(long)]
pub no_autoloader: bool,
@@ -71,73 +66,43 @@ pub async fn execute(
console: &mozart_core::console::Console,
) -> anyhow::Result<()> {
let working_dir = cli.working_dir()?;
+ let composer = Composer::require(&working_dir)?;
+ let local_repo = composer.repository_manager().local_repository();
- let vendor_dir = working_dir.join("vendor");
-
- // Step 2: Read installed.json
- let installed = mozart_registry::installed::InstalledPackages::read(&vendor_dir)?;
-
- // Step 3: Read composer.lock
- let lock_path = working_dir.join("composer.lock");
- if !lock_path.exists() {
- anyhow::bail!(
- "No composer.lock found in {}. Run `mozart install` first.",
- working_dir.display()
- );
- }
- 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.
- let has_packages = !args.packages.is_empty();
- let has_type = !args.r#type.is_empty();
+ // Selection: mirrors `ReinstallCommand::execute` lines 79-110.
+ let mut packages_to_reinstall: Vec<&LocalPackage> = Vec::new();
+ let mut package_names_to_reinstall: Vec<String> = Vec::new();
- if has_packages && has_type {
- anyhow::bail!(
- "You cannot use --type together with explicit package names. \
- Use one or the other."
- );
- }
- if !has_packages && !has_type {
- anyhow::bail!(
- "You must specify at least one package name or use --type to filter by package type."
- );
- }
-
- // Step 5: Determine packages to reinstall.
- // Build the full set of installed packages (prod + dev unless --no-dev).
- let dev_package_names: indexmap::IndexSet<String> = installed
- .dev_package_names
- .iter()
- .map(|n| n.to_lowercase())
- .collect();
-
- let candidates: Vec<&mozart_registry::installed::InstalledPackageEntry> = installed
- .packages
- .iter()
- .filter(|pkg| {
- // Apply --no-dev filter
- if args.no_dev && dev_package_names.contains(&pkg.name.to_lowercase()) {
- return false;
+ if !args.r#type.is_empty() {
+ if !args.packages.is_empty() {
+ anyhow::bail!("You cannot specify package names and filter by type at the same time.");
+ }
+ let lower_types: Vec<String> = args.r#type.iter().map(|t| t.to_lowercase()).collect();
+ for package in local_repo.canonical_packages() {
+ // Composer compares against `getType()` — packages without a
+ // `type` are normalised to `library` by the package loader.
+ let pt = package.package_type().unwrap_or("library").to_lowercase();
+ if lower_types.contains(&pt) {
+ packages_to_reinstall.push(package);
+ package_names_to_reinstall.push(package.pretty_name().to_string());
}
- true
- })
- .collect();
-
- let selected: Vec<&mozart_registry::installed::InstalledPackageEntry> = if has_type {
- filter_by_type(&candidates, &args.r#type)
+ }
} else {
- filter_by_names(&candidates, &args.packages)
- };
-
- // Emit per-pattern warnings for patterns that matched no installed packages.
- if has_packages {
+ if args.packages.is_empty() {
+ anyhow::bail!("You must pass one or more package names to be reinstalled.");
+ }
for pattern in &args.packages {
- let matched = candidates
- .iter()
- .any(|pkg| glob_matches(&pattern.to_lowercase(), &pkg.name.to_lowercase()));
+ let pattern_regexp = package_name_to_regexp(pattern);
+ let mut matched = false;
+ for package in local_repo.canonical_packages() {
+ if pattern_regexp.is_match(package.pretty_name()) {
+ matched = true;
+ packages_to_reinstall.push(package);
+ package_names_to_reinstall.push(package.pretty_name().to_string());
+ }
+ }
if !matched {
- console.info(&console_format!(
+ console.error(&console_format!(
"<warning>Pattern \"{}\" does not match any currently installed packages.</warning>",
pattern
));
@@ -145,65 +110,46 @@ pub async fn execute(
}
}
- if selected.is_empty() {
- console.info("Found no packages to reinstall, aborting.");
+ if packages_to_reinstall.is_empty() {
+ console.error(&console_format!(
+ "<warning>Found no packages to reinstall, aborting.</warning>"
+ ));
return Err(mozart_core::exit_code::bail_silent(
mozart_core::exit_code::GENERAL_ERROR,
));
}
- // Step 6: For each selected package, find its locked metadata.
- // Build a lookup map: lowercase name -> LockedPackage
- let all_locked: Vec<&mozart_registry::lockfile::LockedPackage> = lock
- .packages
- .iter()
- .chain(lock.packages_dev.as_deref().unwrap_or(&[]))
- .collect();
+ // TODO(plugins): build `uninstall_operations` + `Transaction(present, result)`
+ // and reverse-sort uninstalls by install order, mirroring PHP L118-143.
+ // TODO(plugins): dispatch CommandEvent for `reinstall`.
+ // TODO(plugins): apply prefer-source/prefer-dist via DownloadManager;
+ // today the flags are accepted but not propagated.
- // Step 7: Dry-run mode — just print what would be done.
- if args.dry_run {
- for pkg in &selected {
- let locked = find_locked_package(&all_locked, &pkg.name);
- if let Some(lp) = locked {
- console_writeln!(
- console,
- &format!(" - Would reinstall {} ({})", lp.name, lp.version),
- );
- } else {
- console_writeln!(
- console,
- &format!(" - Would reinstall {} (not found in lock file)", pkg.name),
- );
- }
- }
- return Ok(());
+ let dev_mode = local_repo.dev_mode().unwrap_or(true);
+
+ // SAFETY: single-threaded at this point; no concurrent env access.
+ unsafe {
+ std::env::set_var("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" });
}
- // Step 8: For each package, remove vendor dir and re-download.
+ // TODO(plugins): dispatchScript(PRE_INSTALL_CMD, dev_mode).
+
+ // Reinstall loop. Composer delegates this to
+ // `InstallationManager::execute(localRepo, ops, devMode)` twice; until
+ // `mozart-registry::installer_executor` exposes the same shape, we
+ // remove the install dir and re-download in place using each package's
+ // recorded `dist` info.
let cache_config = mozart_registry::cache::build_cache_config(cli.no_cache);
let files_cache = mozart_registry::cache::Cache::files(&cache_config);
+ let installation_manager = composer.installation_manager();
- let mut reinstalled_count = 0usize;
-
- for pkg in &selected {
- let locked = find_locked_package(&all_locked, &pkg.name);
- let locked = match locked {
- Some(lp) => lp,
- None => {
- console.info(&format!(
- " Warning: {} is not in the lock file; skipping.",
- pkg.name
- ));
- continue;
- }
- };
-
- let dist = match &locked.dist {
+ for package in &packages_to_reinstall {
+ let dist = match package.dist() {
Some(d) => d,
None => {
console.info(&format!(
" Warning: {} has no dist information; skipping.",
- locked.name
+ package.pretty_name()
));
continue;
}
@@ -211,467 +157,86 @@ pub async fn execute(
console.info(&format!(
" - Reinstalling {} ({})",
- locked.name, locked.version
+ package.pretty_name(),
+ package.pretty_version()
));
- // Remove vendor directory for this package
- let pkg_dir = vendor_dir.join(&locked.name);
- if pkg_dir.exists() {
- std::fs::remove_dir_all(&pkg_dir)?;
+ if let Some(install_path) = installation_manager.get_install_path(package)
+ && install_path.exists()
+ {
+ std::fs::remove_dir_all(&install_path)?;
}
- // Re-download and install
let mut progress = mozart_registry::downloader::DownloadProgress::new(
!args.no_progress,
- format!("{} ({})", locked.name, locked.version),
+ format!("{} ({})", package.pretty_name(), package.pretty_version()),
);
mozart_registry::downloader::install_package(
&dist.url,
- &dist.dist_type,
+ &dist.kind,
dist.shasum.as_deref(),
- &vendor_dir,
- &locked.name,
+ installation_manager.vendor_dir(),
+ package.pretty_name(),
Some(&mut progress),
&files_cache,
)
.await?;
progress.finish();
- reinstalled_count += 1;
- }
-
- if reinstalled_count == 0 {
- console_writeln!(console, "Nothing was reinstalled.",);
- return Ok(());
}
- // Step 9: Regenerate autoloader unless --no-autoloader.
if !args.no_autoloader {
- console.info("Generating autoload files");
-
- let dev_mode = !args.no_dev && installed.dev;
-
- // SAFETY: single-threaded at this point; no concurrent env access
- unsafe {
- std::env::set_var("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" });
- }
-
- let suffix = lock.content_hash.clone();
-
- // Read composer.json config section for autoloader defaults.
- let composer_path = working_dir.join("composer.json");
- let raw = package::read_from_file(&composer_path)?;
- let project_config = raw.extra_fields.get("config");
- let config_optimize = project_config
- .and_then(|c| c.get("optimize-autoloader"))
- .and_then(|v| v.as_bool())
- .unwrap_or(false);
- let config_classmap_auth = project_config
- .and_then(|c| c.get("classmap-authoritative"))
- .and_then(|v| v.as_bool())
- .unwrap_or(false);
- let config_apcu = project_config
- .and_then(|c| c.get("apcu-autoloader"))
- .and_then(|v| v.as_bool())
- .unwrap_or(false);
-
- let optimize = args.optimize_autoloader || config_optimize;
- let classmap_auth = args.classmap_authoritative || config_classmap_auth;
- let apcu = args.apcu_autoloader || args.apcu_autoloader_prefix.is_some() || config_apcu;
+ let optimize = args.optimize_autoloader || composer.config().optimize_autoloader;
+ let class_map_authoritative =
+ args.classmap_authoritative || composer.config().classmap_authoritative;
+ let apcu_prefix = args.apcu_autoloader_prefix.clone();
+ let apcu =
+ apcu_prefix.is_some() || args.apcu_autoloader || composer.config().apcu_autoloader;
- let _result =
- mozart_autoload::autoload::generate(&mozart_autoload::autoload::AutoloadConfig {
- project_dir: working_dir.to_path_buf(),
- vendor_dir: vendor_dir.to_path_buf(),
- dev_mode,
- suffix,
- classmap_authoritative: classmap_auth,
- optimize,
- apcu,
- apcu_prefix: args.apcu_autoloader_prefix.clone(),
- strict_psr: false,
- strict_ambiguous: false,
- platform_check: mozart_autoload::autoload::PlatformCheckMode::Full,
- ignore_platform_reqs: args.ignore_platform_reqs,
- })?;
-
- console.info("Generated autoload files");
- }
-
- Ok(())
-}
-
-/// Filter candidates by package type (case-insensitive).
-fn filter_by_type<'a>(
- candidates: &[&'a mozart_registry::installed::InstalledPackageEntry],
- types: &[String],
-) -> Vec<&'a mozart_registry::installed::InstalledPackageEntry> {
- let lower_types: Vec<String> = types.iter().map(|t| t.to_lowercase()).collect();
- candidates
- .iter()
- .filter(|pkg| {
- if let Some(ref pt) = pkg.package_type {
- lower_types.contains(&pt.to_lowercase())
- } else {
- // Packages without a type are treated as "library"
- lower_types.contains(&"library".to_string())
- }
- })
- .copied()
- .collect()
-}
-
-/// Filter candidates by package name patterns (glob/wildcard, case-insensitive).
-///
-/// Patterns support `*` as a wildcard matching any sequence of characters
-/// (including `/`).
-fn filter_by_names<'a>(
- candidates: &[&'a mozart_registry::installed::InstalledPackageEntry],
- patterns: &[String],
-) -> Vec<&'a mozart_registry::installed::InstalledPackageEntry> {
- candidates
- .iter()
- .filter(|pkg| {
- let name_lower = pkg.name.to_lowercase();
- patterns
- .iter()
- .any(|pat| glob_matches(&pat.to_lowercase(), &name_lower))
- })
- .copied()
- .collect()
-}
-
-/// Simple glob matching where `*` matches any sequence of characters.
-///
-/// The match is always performed on already-lowercased strings.
-fn glob_matches(pattern: &str, value: &str) -> bool {
- // If there is no wildcard, fall back to exact equality.
- if !pattern.contains('*') {
- return pattern == value;
- }
-
- // Split on `*` and match greedily left-to-right.
- let parts: Vec<&str> = pattern.split('*').collect();
- let mut remaining = value;
+ let options = AutoloadDumpOptions {
+ dev_mode: Some(dev_mode),
+ class_map_authoritative,
+ apcu,
+ apcu_prefix,
+ run_scripts: false,
+ dry_run: false,
+ platform_requirement_filter: get_platform_requirement_filter(args)?,
+ };
- for (i, part) in parts.iter().enumerate() {
- if part.is_empty() {
- continue;
- }
- if i == 0 {
- // First segment must match at the start
- if !remaining.starts_with(part) {
- return false;
- }
- remaining = &remaining[part.len()..];
- } else {
- // Subsequent segments must be found somewhere in `remaining`
- match remaining.find(part) {
- Some(pos) => {
- remaining = &remaining[pos + part.len()..];
- }
- None => return false,
- }
- }
+ let _class_map = composer.autoload_generator().dump(
+ &options,
+ composer.config(),
+ local_repo,
+ composer.package(),
+ installation_manager,
+ "composer",
+ optimize,
+ None,
+ composer.locker(),
+ false,
+ )?;
}
- // If the pattern ends with `*`, anything can trail; otherwise `remaining` must be empty.
- if pattern.ends_with('*') {
- true
- } else {
- remaining.is_empty()
- }
-}
+ // TODO(plugins): dispatchScript(POST_INSTALL_CMD, dev_mode).
-/// Find a locked package by name (case-insensitive).
-fn find_locked_package<'a>(
- locked: &[&'a mozart_registry::lockfile::LockedPackage],
- name: &str,
-) -> Option<&'a mozart_registry::lockfile::LockedPackage> {
- let name_lower = name.to_lowercase();
- locked
- .iter()
- .find(|lp| lp.name.to_lowercase() == name_lower)
- .copied()
+ Ok(())
}
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::collections::BTreeMap;
-
- fn make_installed_entry(
- name: &str,
- pkg_type: Option<&str>,
- ) -> mozart_registry::installed::InstalledPackageEntry {
- mozart_registry::installed::InstalledPackageEntry {
- name: name.to_string(),
- version: "1.0.0".to_string(),
- version_normalized: None,
- source: None,
- dist: None,
- package_type: pkg_type.map(|t| t.to_string()),
- install_path: None,
- autoload: None,
- aliases: vec![],
- homepage: None,
- support: None,
- extra_fields: BTreeMap::new(),
- }
- }
-
- 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,
- source: None,
- dist: None,
- require: BTreeMap::new(),
- require_dev: BTreeMap::new(),
- conflict: BTreeMap::new(),
- provide: BTreeMap::new(),
- replace: BTreeMap::new(),
- suggest: None,
- package_type: Some("library".to_string()),
- autoload: None,
- autoload_dev: None,
- license: None,
- description: None,
- homepage: None,
- keywords: None,
- authors: None,
- support: None,
- funding: None,
- time: None,
- extra_fields: BTreeMap::new(),
- }
- }
-
- #[test]
- fn test_glob_exact_match() {
- assert!(glob_matches("monolog/monolog", "monolog/monolog"));
- assert!(!glob_matches("monolog/monolog", "psr/log"));
- }
-
- #[test]
- fn test_glob_wildcard_suffix() {
- assert!(glob_matches("monolog/*", "monolog/monolog"));
- assert!(glob_matches("psr/*", "psr/log"));
- assert!(!glob_matches("psr/*", "monolog/monolog"));
- }
-
- #[test]
- fn test_glob_wildcard_prefix() {
- assert!(glob_matches("*/log", "monolog/log"));
- assert!(glob_matches("*/log", "psr/log"));
- assert!(!glob_matches("*/log", "psr/container"));
- }
-
- #[test]
- fn test_glob_wildcard_middle() {
- assert!(glob_matches("symfony/*/bridge", "symfony/http/bridge"));
- assert!(!glob_matches("symfony/*/bridge", "monolog/monolog"));
- }
-
- #[test]
- fn test_glob_star_only() {
- assert!(glob_matches("*", "anything/at/all"));
- assert!(glob_matches("*", "psr/log"));
- }
-
- #[test]
- fn test_glob_case_insensitive_by_caller() {
- // glob_matches operates on pre-lowercased strings;
- // confirm exact match fails if caller did not lowercase
- assert!(!glob_matches("Monolog/Monolog", "monolog/monolog"));
- // But lowercased match works
- assert!(glob_matches("monolog/monolog", "monolog/monolog"));
- }
-
- #[test]
- fn test_find_locked_package_found() {
- let pkgs = [
- make_locked_package("psr/log", "3.0.0"),
- make_locked_package("monolog/monolog", "3.8.0"),
- ];
- let refs: Vec<&mozart_registry::lockfile::LockedPackage> = pkgs.iter().collect();
-
- let result = find_locked_package(&refs, "psr/log");
- assert!(result.is_some());
- assert_eq!(result.unwrap().name, "psr/log");
- }
-
- #[test]
- fn test_find_locked_package_case_insensitive() {
- let pkgs = [make_locked_package("Monolog/Monolog", "3.8.0")];
- let refs: Vec<&mozart_registry::lockfile::LockedPackage> = pkgs.iter().collect();
-
- let result = find_locked_package(&refs, "monolog/monolog");
- assert!(result.is_some());
- }
-
- #[test]
- fn test_find_locked_package_not_found() {
- let pkgs = [make_locked_package("psr/log", "3.0.0")];
- let refs: Vec<&mozart_registry::lockfile::LockedPackage> = pkgs.iter().collect();
-
- let result = find_locked_package(&refs, "monolog/monolog");
- assert!(result.is_none());
- }
-
- #[test]
- fn test_filter_by_type_library() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let e2 = make_installed_entry("symfony/console", Some("library"));
- let e3 = make_installed_entry("my/plugin", Some("composer-plugin"));
- let candidates = vec![&e1, &e2, &e3];
-
- let result = filter_by_type(&candidates, &["library".to_string()]);
- assert_eq!(result.len(), 2);
- assert!(result.iter().any(|p| p.name == "psr/log"));
- assert!(result.iter().any(|p| p.name == "symfony/console"));
- }
-
- #[test]
- fn test_filter_by_type_case_insensitive() {
- let e1 = make_installed_entry("my/plugin", Some("Composer-Plugin"));
- let candidates = vec![&e1];
-
- let result = filter_by_type(&candidates, &["composer-plugin".to_string()]);
- assert_eq!(result.len(), 1);
- }
-
- #[test]
- fn test_filter_by_type_no_type_field_treated_as_library() {
- let e1 = make_installed_entry("psr/log", None); // no type
- let candidates = vec![&e1];
-
- let result = filter_by_type(&candidates, &["library".to_string()]);
- assert_eq!(result.len(), 1);
- assert_eq!(result[0].name, "psr/log");
- }
-
- #[test]
- fn test_filter_by_type_multiple_types() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let e2 = make_installed_entry("my/plugin", Some("composer-plugin"));
- let e3 = make_installed_entry("my/project", Some("project"));
- let candidates = vec![&e1, &e2, &e3];
-
- let result = filter_by_type(
- &candidates,
- &["library".to_string(), "composer-plugin".to_string()],
- );
- assert_eq!(result.len(), 2);
- }
-
- #[test]
- fn test_filter_by_names_exact() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let e2 = make_installed_entry("monolog/monolog", Some("library"));
- let candidates = vec![&e1, &e2];
-
- let result = filter_by_names(&candidates, &["psr/log".to_string()]);
- assert_eq!(result.len(), 1);
- assert_eq!(result[0].name, "psr/log");
- }
-
- #[test]
- fn test_filter_by_names_wildcard() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let e2 = make_installed_entry("psr/container", Some("library"));
- let e3 = make_installed_entry("monolog/monolog", Some("library"));
- let candidates = vec![&e1, &e2, &e3];
-
- let result = filter_by_names(&candidates, &["psr/*".to_string()]);
- assert_eq!(result.len(), 2);
- assert!(result.iter().any(|p| p.name == "psr/log"));
- assert!(result.iter().any(|p| p.name == "psr/container"));
- }
-
- #[test]
- fn test_filter_by_names_case_insensitive() {
- let e1 = make_installed_entry("Monolog/Monolog", Some("library"));
- let candidates = vec![&e1];
-
- let result = filter_by_names(&candidates, &["monolog/monolog".to_string()]);
- assert_eq!(result.len(), 1);
- }
-
- #[test]
- fn test_filter_by_names_no_match() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let candidates = vec![&e1];
-
- let result = filter_by_names(&candidates, &["nonexistent/package".to_string()]);
- assert!(result.is_empty());
- }
-
- /// Verify that the validation logic (both --type and names) is reflected in arg combinations.
- /// We can't call execute() without a full environment, but we can test the logic directly.
- #[test]
- fn test_mutual_exclusion_both_type_and_names() {
- let has_packages = true;
- let has_type = true;
- assert!(
- has_packages && has_type,
- "Both packages and type provided — should be rejected"
- );
- }
-
- #[test]
- fn test_mutual_exclusion_neither_type_nor_names() {
- let has_packages = false;
- let has_type = false;
- assert!(
- !has_packages && !has_type,
- "Neither packages nor type provided — should be rejected"
- );
- }
-
- #[test]
- fn test_dev_filtering_excludes_dev_packages() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let e2 = make_installed_entry("phpunit/phpunit", Some("library"));
-
- 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()];
-
- let dev_package_names: indexmap::IndexSet<String> = installed
- .dev_package_names
- .iter()
- .map(|n| n.to_lowercase())
- .collect();
-
- // Simulate --no-dev filtering
- let candidates: Vec<&mozart_registry::installed::InstalledPackageEntry> = installed
- .packages
- .iter()
- .filter(|pkg| !dev_package_names.contains(&pkg.name.to_lowercase()))
- .collect();
-
- assert_eq!(candidates.len(), 1);
- assert_eq!(candidates[0].name, "psr/log");
+/// Mirror of `BaseCommand::getPlatformRequirementFilter` for the
+/// `reinstall` command. Priority:
+/// 1. `--ignore-platform-reqs` → ignore every platform requirement
+/// 2. `--ignore-platform-req <name>...` (non-empty) → ignore the listed
+/// names (with `*` glob support)
+/// 3. neither → ignore nothing
+fn get_platform_requirement_filter(
+ args: &ReinstallArgs,
+) -> anyhow::Result<PlatformRequirementFilter> {
+ if args.ignore_platform_reqs {
+ return Ok(PlatformRequirementFilter::ignore_all());
}
-
- #[test]
- fn test_dev_filtering_includes_all_without_no_dev() {
- let e1 = make_installed_entry("psr/log", Some("library"));
- let e2 = make_installed_entry("phpunit/phpunit", Some("library"));
-
- 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<&mozart_registry::installed::InstalledPackageEntry> =
- installed.packages.iter().collect();
-
- assert_eq!(candidates.len(), 2);
+ if !args.ignore_platform_req.is_empty() {
+ return PlatformRequirementFilter::from_list(&args.ignore_platform_req);
}
+ Ok(PlatformRequirementFilter::ignore_nothing())
}