diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-05-15 23:58:52 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-05-16 10:00:40 +0900 |
| commit | 76ecec162a54b46b408b94820cbb84304fbdd316 (patch) | |
| tree | 4243ff6dee5ad9de74370cc158d9d6bf0e1a1b4e /crates | |
| parent | 02e3c8170ee6fedc1d7c4129736e38881b768d7d (diff) | |
| download | php-shirabe-76ecec162a54b46b408b94820cbb84304fbdd316.tar.gz php-shirabe-76ecec162a54b46b408b94820cbb84304fbdd316.tar.zst php-shirabe-76ecec162a54b46b408b94820cbb84304fbdd316.zip | |
feat(port): port ReinstallCommand.php
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe/src/command/reinstall_command.rs | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/crates/shirabe/src/command/reinstall_command.rs b/crates/shirabe/src/command/reinstall_command.rs index 397fad4..59bb2ec 100644 --- a/crates/shirabe/src/command/reinstall_command.rs +++ b/crates/shirabe/src/command/reinstall_command.rs @@ -1 +1,220 @@ //! ref: composer/src/Composer/Command/ReinstallCommand.php + +use std::any::Any; + +use anyhow::Result; +use shirabe_external_packages::composer::pcre::preg::Preg; +use shirabe_external_packages::symfony::console::input::input_interface::InputInterface; +use shirabe_external_packages::symfony::console::output::output_interface::OutputInterface; +use shirabe_php_shim::InvalidArgumentException; + +use crate::command::base_command::BaseCommand; +use crate::command::completion_trait::CompletionTrait; +use crate::console::input::input_argument::InputArgument; +use crate::console::input::input_option::InputOption; +use crate::dependency_resolver::operation::install_operation::InstallOperation; +use crate::dependency_resolver::operation::uninstall_operation::UninstallOperation; +use crate::dependency_resolver::transaction::Transaction; +use crate::package::alias_package::AliasPackage; +use crate::package::base_package::BasePackage; +use crate::plugin::command_event::CommandEvent; +use crate::plugin::plugin_events::PluginEvents; +use crate::script::script_events::ScriptEvents; +use crate::util::platform::Platform; + +#[derive(Debug)] +pub struct ReinstallCommand { + inner: BaseCommand, +} + +impl CompletionTrait for ReinstallCommand {} + +impl ReinstallCommand { + pub fn configure(&mut self) { + let suggest_prefer_install = self.suggest_prefer_install(); + let suggest_installed_package_types = self.suggest_installed_package_types(false); + let suggest_installed_package = self.suggest_installed_package(false); + self.inner + .set_name("reinstall") + .set_description("Uninstalls and reinstalls the given package names") + .set_definition(vec![ + InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None, vec![]), + InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None, vec![]), + InputOption::new("prefer-install", None, Some(InputOption::VALUE_REQUIRED), "Forces installation from package dist|source|auto (auto chooses source for dev versions, dist for the rest).", None, suggest_prefer_install), + InputOption::new("no-autoloader", None, Some(InputOption::VALUE_NONE), "Skips autoloader generation", None, vec![]), + InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None, vec![]), + InputOption::new("optimize-autoloader", Some(shirabe_php_shim::PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None, vec![]), + InputOption::new("classmap-authoritative", Some(shirabe_php_shim::PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None, vec![]), + InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None, vec![]), + InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None, vec![]), + InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None, vec![]), + InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None, vec![]), + InputOption::new("type", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Filter packages to reinstall by type(s)", None, suggest_installed_package_types), + InputArgument::new("packages", Some(InputArgument::IS_ARRAY), "List of package names to reinstall, can include a wildcard (*) to match any substring.", None, suggest_installed_package), + ]) + .set_help( + "The <info>reinstall</info> command looks up installed packages by name,\n\ + uninstalls them and reinstalls them. This lets you do a clean install\n\ + of a package if you messed with its files, or if you wish to change\n\ + the installation type using --prefer-install.\n\n\ + <info>php composer.phar reinstall acme/foo \"acme/bar-*\"</info>\n\n\ + Read more at https://getcomposer.org/doc/03-cli.md#reinstall" + ); + } + + pub fn execute(&self, input: &dyn InputInterface, output: &dyn OutputInterface) -> Result<i64> { + let io = self.inner.get_io(); + + let composer = self.inner.require_composer()?; + + let local_repo = composer.get_repository_manager().get_local_repository(); + let mut packages_to_reinstall: Vec<Box<dyn crate::package::package_interface::PackageInterface>> = vec![]; + let mut package_names_to_reinstall: Vec<String> = vec![]; + + let type_option = input.get_option("type"); + let type_count = type_option.as_list().map_or(0, |l| l.len()); + let packages_arg = input.get_argument("packages"); + let packages_count = packages_arg.as_list().map_or(0, |l| l.len()); + + if type_count > 0 { + if packages_count > 0 { + return Err(InvalidArgumentException { + message: "You cannot specify package names and filter by type at the same time.".to_string(), + code: 0, + }.into()); + } + let filter_types: Vec<String> = type_option.as_list() + .map(|l| l.iter().filter_map(|v| v.as_string().map(|s| s.to_string())).collect()) + .unwrap_or_default(); + for package in local_repo.get_canonical_packages() { + if filter_types.contains(&package.get_type().to_string()) { + package_names_to_reinstall.push(package.get_name().to_string()); + packages_to_reinstall.push(package); + } + } + } else { + if packages_count == 0 { + return Err(InvalidArgumentException { + message: "You must pass one or more package names to be reinstalled.".to_string(), + code: 0, + }.into()); + } + let patterns: Vec<String> = packages_arg.as_list() + .map(|l| l.iter().filter_map(|v| v.as_string().map(|s| s.to_string())).collect()) + .unwrap_or_default(); + for pattern in &patterns { + let pattern_regexp = BasePackage::package_name_to_regexp(pattern); + let mut matched = false; + for package in local_repo.get_canonical_packages() { + if Preg::is_match(&pattern_regexp, package.get_name()).unwrap_or(false) { + matched = true; + package_names_to_reinstall.push(package.get_name().to_string()); + packages_to_reinstall.push(package); + } + } + if !matched { + io.write_error(&format!("<warning>Pattern \"{}\" does not match any currently installed packages.</warning>", pattern)); + } + } + } + + if packages_to_reinstall.is_empty() { + io.write_error("<warning>Found no packages to reinstall, aborting.</warning>"); + return Ok(1); + } + + let mut uninstall_operations: Vec<UninstallOperation> = vec![]; + for package in packages_to_reinstall { + uninstall_operations.push(UninstallOperation::new(package)); + } + + let present_packages = local_repo.get_packages(); + let result_packages = present_packages.clone(); + let present_packages: Vec<_> = present_packages.into_iter() + .filter(|package| !package_names_to_reinstall.contains(&package.get_name().to_string())) + .collect(); + + let transaction = Transaction::new(present_packages, result_packages); + let install_operations = transaction.get_operations(); + + let mut install_order = indexmap::IndexMap::new(); + for (index, op) in install_operations.iter().enumerate() { + if let Some(install_op) = (op.as_any() as &dyn Any).downcast_ref::<InstallOperation>() { + if (install_op.get_package().as_any() as &dyn Any).downcast_ref::<AliasPackage>().is_none() { + install_order.insert(install_op.get_package().get_name().to_string(), index); + } + } + } + + uninstall_operations.sort_by(|a, b| { + let a_order = install_order.get(a.get_package().get_name()).copied().unwrap_or(0); + let b_order = install_order.get(b.get_package().get_name()).copied().unwrap_or(0); + b_order.cmp(&a_order) + }); + + // TODO(plugin): dispatch CommandEvent + let command_event = CommandEvent::new( + PluginEvents::COMMAND.to_string(), + "reinstall".to_string(), + Box::new(input), + Box::new(output), + vec![], + vec![], + ); + let event_dispatcher = composer.get_event_dispatcher(); + event_dispatcher.dispatch(command_event.get_name(), &command_event); + + let config = composer.get_config(); + let (prefer_source, prefer_dist) = self.inner.get_preferred_install_options(config, input)?; + + let installation_manager = composer.get_installation_manager(); + let download_manager = composer.get_download_manager(); + let package = composer.get_package(); + + installation_manager.set_output_progress(!input.get_option("no-progress").as_bool().unwrap_or(false)); + if input.get_option("no-plugins").as_bool().unwrap_or(false) { + installation_manager.disable_plugins(); + } + + download_manager.set_prefer_source(prefer_source); + download_manager.set_prefer_dist(prefer_dist); + + let dev_mode = local_repo.get_dev_mode().unwrap_or(true); + + Platform::put_env("COMPOSER_DEV_MODE", if dev_mode { "1" } else { "0" }); + event_dispatcher.dispatch_script(ScriptEvents::PRE_INSTALL_CMD, dev_mode); + + installation_manager.execute(local_repo, uninstall_operations, dev_mode); + installation_manager.execute(local_repo, install_operations, dev_mode); + + if !input.get_option("no-autoloader").as_bool().unwrap_or(false) { + let optimize = input.get_option("optimize-autoloader").as_bool().unwrap_or(false) + || config.get("optimize-autoloader").as_bool().unwrap_or(false); + let authoritative = input.get_option("classmap-authoritative").as_bool().unwrap_or(false) + || config.get("classmap-authoritative").as_bool().unwrap_or(false); + let apcu_prefix = input.get_option("apcu-autoloader-prefix").as_string_opt().map(|s| s.to_string()); + let apcu = apcu_prefix.is_some() + || input.get_option("apcu-autoloader").as_bool().unwrap_or(false) + || config.get("apcu-autoloader").as_bool().unwrap_or(false); + + let generator = composer.get_autoload_generator(); + generator.set_class_map_authoritative(authoritative); + generator.set_apcu(apcu, apcu_prefix.as_deref()); + generator.set_platform_requirement_filter(self.inner.get_platform_requirement_filter(input)?); + generator.dump( + config, + local_repo, + package, + installation_manager, + "composer", + optimize, + None, + composer.get_locker(), + ); + } + + event_dispatcher.dispatch_script(ScriptEvents::POST_INSTALL_CMD, dev_mode); + + Ok(0) + } +} |
