aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/command/require_command.rs
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-08-07 07:26:48 +0900
committernsfisis <nsfisis@gmail.com>2026-08-07 07:26:48 +0900
commitf749a47804cd296a3059cd3f8079c62dbaa5fdc0 (patch)
treea84d5d40f6f9eea2a83355a273d0fb57214a864a /crates/shirabe/src/command/require_command.rs
parente7f83b74e8f8c12b4a1b0f9f613387b03858dbdd (diff)
downloadphp-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.tar.gz
php-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.tar.zst
php-shirabe-f749a47804cd296a3059cd3f8079c62dbaa5fdc0.zip
refactor: merge split inherent impl blocks into one per type
Enable clippy::multiple_inherent_impl and fix the 21 sites it reports. Types whose inherent methods were spread across two or three impl blocks now keep them in a single block; only the impl headers move, no method bodies change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates/shirabe/src/command/require_command.rs')
-rw-r--r--crates/shirabe/src/command/require_command.rs1164
1 files changed, 581 insertions, 583 deletions
diff --git a/crates/shirabe/src/command/require_command.rs b/crates/shirabe/src/command/require_command.rs
index d2b8360d..f4410c19 100644
--- a/crates/shirabe/src/command/require_command.rs
+++ b/crates/shirabe/src/command/require_command.rs
@@ -89,590 +89,7 @@ impl RequireCommand {
.expect("RequireCommand::configure uses static, valid metadata");
command
}
-}
-
-impl PackageDiscoveryTrait for RequireCommand {
- fn get_repos_mut(
- &self,
- ) -> std::cell::RefMut<'_, Option<crate::repository::RepositoryInterfaceHandle>> {
- self.repos.borrow_mut()
- }
-
- fn get_repository_sets_mut(
- &self,
- ) -> std::cell::RefMut<'_, IndexMap<String, std::rc::Rc<std::cell::RefCell<RepositorySet>>>>
- {
- self.repository_sets.borrow_mut()
- }
-}
-
-impl Command for RequireCommand {
- fn configure(&self) -> anyhow::Result<()> {
- self.set_name("require")?;
- self.set_aliases(vec!["r".to_string()])?;
- self.set_description("Adds required packages to your composer.json and installs them");
- self.set_definition(&[
- InputArgument::new5("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, self.suggest_available_package_incl_platform()).unwrap().into(),
- InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Add requirement to require-dev.", None).unwrap().into(),
- InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None).unwrap().into(),
- InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None).unwrap().into(),
- InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None).unwrap().into(),
- InputOption::new6("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, self.suggest_prefer_install()).unwrap().into(),
- InputOption::new("fixed", None, Some(InputOption::VALUE_NONE), "Write fixed version to the composer.json.", None).unwrap().into(),
- InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None).unwrap().into(),
- InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None).unwrap().into(),
- InputOption::new("no-update", None, Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None).unwrap().into(),
- InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None).unwrap().into(),
- InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None).unwrap().into(),
- InputOption::new6("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), SuggestedValues::List(Auditor::FORMATS.iter().map(|s| s.to_string()).collect())).unwrap().into(),
- InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None).unwrap().into(),
- InputOption::new("update-no-dev", None, Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None).unwrap().into(),
- InputOption::new("update-with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None).unwrap().into(),
- InputOption::new("update-with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None).unwrap().into(),
- InputOption::new("with-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-dependencies", None).unwrap().into(),
- InputOption::new("with-all-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None).unwrap().into(),
- InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None).unwrap().into(),
- InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None).unwrap().into(),
- InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None).unwrap().into(),
- InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None).unwrap().into(),
- InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None).unwrap().into(),
- InputOption::new("sort-packages", None, Some(InputOption::VALUE_NONE), "Sorts packages when adding/updating a new dependency", None).unwrap().into(),
- InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None).unwrap().into(),
- InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None).unwrap().into(),
- InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None).unwrap().into(),
- InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None).unwrap().into(),
- ]);
- self.set_help(
- "The require command adds required packages to your composer.json and installs them.\n\
- \n\
- If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of\n\
- matches to require.\n\
- \n\
- If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.\n\
- \n\
- If you do not want to install the new dependencies immediately you can call it with --no-update\n\
- \n\
- Read more at https://getcomposer.org/doc/03-cli.md#require-r"
- );
- Ok(())
- }
-
- /// @throws \Seld\JsonLint\ParsingException
- fn execute(
- &self,
- input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> anyhow::Result<i64> {
- *self.file.borrow_mut() = Factory::get_composer_file()?;
-
- if input
- .borrow()
- .get_option("no-suggest")?
- .as_bool()
- .unwrap_or(false)
- {
- self.get_io().write_error3("<warning>You are using the deprecated option \"--no-suggest\". It has no effect and will break in Composer 3.</warning>", true, io_interface::NORMAL);
- }
-
- let file = self.file.borrow().clone();
- self.newly_created.set(!file_exists(&file));
- let write_failed =
- self.newly_created.get() && file_put_contents(&file, b"{\n}\n").is_none();
- if write_failed {
- let msg = format!("<error>{} could not be created.</error>", file);
- self.get_io().write_error3(&msg, true, io_interface::NORMAL);
-
- return Ok(1);
- }
- if !Filesystem::is_readable(&file) {
- let msg = format!("<error>{} is not readable.</error>", file);
- self.get_io().write_error3(&msg, true, io_interface::NORMAL);
-
- return Ok(1);
- }
- if filesize(&file) == Some(0) {
- file_put_contents(&file, b"{\n}\n");
- }
-
- *self.json.borrow_mut() = Some(std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new(
- file.clone(),
- None,
- None,
- )?)));
- *self.lock.borrow_mut() = Factory::get_lock_file(&file);
- let json = self.json.borrow().as_ref().unwrap().clone();
- *self.composer_backup.borrow_mut() =
- file_get_contents(json.borrow().get_path()).unwrap_or_default();
- let lock = self.lock.borrow().clone();
- *self.lock_backup.borrow_mut() = if file_exists(&lock) {
- file_get_contents(&lock)
- } else {
- None
- };
-
- // PHP: function ($signal, $handler) use ($io, $self) {
- // $io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
- // $self->revertComposerFile(); $handler->exitWithLastSignal(); }
- // TODO(phase-c): SignalHandler::create takes a `Box<dyn Fn> + 'static` handler that cannot
- // borrow &self, but the body must call self.revert_composer_file() (which mutates the
- // command's composer.json backup state) and self.get_io(). Faithfully wiring this needs the
- // revert state + io shared into the closure (Rc<RefCell<...>>), i.e. the shared-ownership
- // rework of the command — the same pattern as InstallationManager::execute's signal handler.
- let signal_handler = SignalHandler::create(
- vec![
- SignalHandler::SIGINT.to_string(),
- SignalHandler::SIGTERM.to_string(),
- SignalHandler::SIGHUP.to_string(),
- ],
- Box::new(move |signal: String, handler: &SignalHandler| {
- let _ = signal;
- handler.exit_with_last_signal();
- }),
- );
-
- // check for writability by writing to the file as is_writable can not be trusted on network-mounts
- // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
- let file_path = file.clone();
- let backup_contents = self.composer_backup.borrow().clone();
- if !is_writable(&file)
- && Silencer::call(|| {
- shirabe_php_shim::file_put_contents(&file_path, backup_contents.as_bytes());
- Ok::<bool, anyhow::Error>(false)
- })
- .ok()
- == Some(false)
- {
- let msg = format!("<error>{} is not writable.</error>", file);
- self.get_io().write_error3(&msg, true, io_interface::NORMAL);
-
- return Ok(1);
- }
-
- if input.borrow().get_option("fixed")?.as_bool() == Some(true) {
- let config = json.borrow_mut().read()?;
-
- let package_type = if empty(&config.get("type").cloned().unwrap_or(PhpMixed::Null)) {
- "library".to_string()
- } else {
- config
- .get("type")
- .and_then(|v| v.as_string())
- .unwrap_or("")
- .to_string()
- };
-
- // @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
- if package_type != "project"
- && !input.borrow().get_option("dev")?.as_bool().unwrap_or(false)
- {
- self.get_io().write_error3("<error>The \"--fixed\" option is only allowed for packages with a \"project\" type or for dev dependencies to prevent possible misuses.</error>", true, io_interface::NORMAL);
-
- if config.get("type").is_none() {
- self.get_io().write_error3("<error>If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".</error>", true, io_interface::NORMAL);
- }
-
- return Ok(1);
- }
- }
-
- let composer = self.require_composer(None, None)?;
- let composer = crate::composer::composer_full(&composer);
- let repository_manager = composer.get_repository_manager().clone();
- let repository_manager = repository_manager.borrow();
- let repos = repository_manager.get_repositories();
-
- let platform_overrides = composer.get_config().borrow_mut().get("platform");
- let platform_overrides_map: IndexMap<String, PhpMixed> = platform_overrides
- .as_array()
- .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
- .unwrap_or_default();
- // initialize self.repos as it is used by the PackageDiscoveryTrait
- let platform_repo =
- PlatformRepositoryHandle::new(PlatformRepository::new(vec![], platform_overrides_map)?);
- let mut combined: Vec<crate::repository::RepositoryInterfaceHandle> =
- vec![platform_repo.clone().into()];
- for repo in repos {
- combined.push(repo.clone());
- }
- *self.get_repos_mut() = Some(crate::repository::RepositoryInterfaceHandle::new(
- CompositeRepository::new(combined),
- ));
-
- let preferred_stability = if composer.get_package().get_prefer_stable() {
- "stable".to_string()
- } else {
- composer.get_package().get_minimum_stability()
- };
-
- // Hoist argument computations into locals so no borrow of `input` is held across the
- // call: `determine_requirements` may prompt via ConsoleIO, which mutably borrows the
- // same input RefCell.
- let packages: Vec<String> = input
- .borrow()
- .get_argument("packages")?
- .as_list()
- .map(|l| {
- l.iter()
- .filter_map(|v| v.as_string().map(|s| s.to_string()))
- .collect()
- })
- .unwrap_or_default();
- // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint
- let no_update = input
- .borrow()
- .get_option("no-update")?
- .as_bool()
- .unwrap_or(false);
- let fixed = input
- .borrow()
- .get_option("fixed")?
- .as_bool()
- .unwrap_or(false);
- let requirements_result = self.determine_requirements(
- input.clone(),
- output.clone(),
- packages,
- Some(&platform_repo),
- &preferred_stability,
- no_update,
- fixed,
- );
-
- let requirements = match requirements_result {
- Ok(r) => r,
- Err(e) => {
- if self.newly_created.get() {
- self.revert_composer_file();
-
- return Err(RuntimeException {
- message: format!(
- "No composer.json present in the current directory ({}), this may be the cause of the following exception.",
- self.file.borrow()
- ),
- code: 0,
- }
- .into());
- }
-
- return Err(e);
- }
- };
-
- let mut requirements = self.format_requirements(requirements)?;
-
- if !input.borrow().get_option("dev")?.as_bool().unwrap_or(false)
- && self.get_io().is_interactive()
- && !composer.is_global()
- {
- let mut dev_packages: Vec<Vec<String>> = vec![];
- let dev_tags: Vec<String> = vec![
- "dev".to_string(),
- "testing".to_string(),
- "static analysis".to_string(),
- ];
- let current_requires_by_key = self.get_packages_by_require_key();
- for (name, _version) in &requirements {
- // skip packages which are already in the composer.json as those have already been decided
- if current_requires_by_key.contains_key(name) {
- continue;
- }
-
- let found_packages: Vec<crate::package::PackageInterfaceHandle> = self
- .get_repos()
- .find_packages(name, None)?
- .into_iter()
- .collect();
- let pkg: Option<crate::package::PackageInterfaceHandle> =
- PackageSorter::get_most_current_version(found_packages);
- let pkg_as_complete: Option<crate::package::CompletePackageInterfaceHandle> =
- pkg.as_ref().and_then(|p| p.as_complete());
- if let Some(pkg_complete) = pkg_as_complete {
- let lowered: Vec<String> =
- array_map(|s: &String| strtolower(s), &pkg_complete.get_keywords());
- let pkg_dev_tags: Vec<String> = array_intersect(&dev_tags, &lowered);
- if (pkg_dev_tags.len() as i64) > 0 {
- dev_packages.push(pkg_dev_tags);
- }
- }
- let _ = pkg;
- }
-
- if (dev_packages.len() as i64) == (requirements.len() as i64) {
- let plural = if (requirements.len() as i64) > 1 {
- "s"
- } else {
- ""
- };
- let plural2 = if (requirements.len() as i64) > 1 {
- "are"
- } else {
- "is"
- };
- let plural3 = if (requirements.len() as i64) > 1 {
- "they are"
- } else {
- "it is"
- };
- let merged: Vec<String> = dev_packages.iter().flatten().cloned().collect();
- let pkg_dev_tags: Vec<String> = array_unique(&merged);
- let warn_msg = format!(
- "The package{} you required {} recommended to be placed in require-dev (because {} tagged as \"{}\") but you did not use --dev.",
- plural,
- plural2,
- plural3,
- implode("\", \"", &pkg_dev_tags),
- );
- self.get_io().warning(&warn_msg, &[]);
- if self.get_io().ask_confirmation(
- "<info>Do you want to re-run the command with --dev?</> [<comment>yes</>]? "
- .to_string(),
- true,
- ) {
- input.borrow_mut().set_option("dev", PhpMixed::Bool(true))?;
- }
- }
-
- // unset($devPackages, $pkgDevTags);
- }
-
- let mut require_key = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
- "require-dev"
- } else {
- "require"
- };
- let mut remove_key = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
- "require"
- } else {
- "require-dev"
- };
-
- // check which requirements need the version guessed
- let mut requirements_to_guess: Vec<String> = vec![];
- for (package, constraint) in requirements.clone().iter() {
- if constraint == "guess" {
- requirements.insert(package.clone(), "*".to_string());
- requirements_to_guess.push(package.clone());
- }
- }
-
- // validate requirements format
- let version_parser = VersionParser::new();
- for (package, constraint) in &requirements {
- if strtolower(package) == composer.get_package().get_name() {
- let msg = format!(
- "<error>Root package '{}' cannot require itself in its composer.json</error>",
- package.clone(),
- );
- self.get_io().write_error3(&msg, true, io_interface::NORMAL);
-
- return Ok(1);
- }
- if constraint == "self.version" {
- continue;
- }
- version_parser.parse_constraints(constraint)?;
- }
-
- let inconsistent_require_keys =
- self.get_inconsistent_require_keys(&requirements, require_key);
- if (inconsistent_require_keys.len() as i64) > 0 {
- for package in &inconsistent_require_keys {
- let warn_msg = format!(
- "{} is currently present in the {} key and you ran the command {} the --dev flag, which will move it to the {} key.",
- package.clone(),
- remove_key,
- if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
- "with"
- } else {
- "without"
- },
- require_key,
- );
- self.get_io().warning(&warn_msg, &[]);
- }
-
- if self.get_io().is_interactive() {
- let q1 = format!(
- "<info>Do you want to move {}?</info> [<comment>no</comment>]? ",
- if (inconsistent_require_keys.len() as i64) > 1 {
- "these requirements"
- } else {
- "this requirement"
- },
- );
- if !self.get_io().ask_confirmation(q1, false) {
- let q2 = format!(
- "<info>Do you want to re-run the command {} --dev?</info> [<comment>yes</comment>]? ",
- if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
- "without"
- } else {
- "with"
- },
- );
- if !self.get_io().ask_confirmation(q2, true) {
- return Ok(0);
- }
-
- input.borrow_mut().set_option("dev", PhpMixed::Bool(true))?;
- std::mem::swap(&mut require_key, &mut remove_key);
- }
- }
- }
-
- let sort_packages = input
- .borrow()
- .get_option("sort-packages")?
- .as_bool()
- .unwrap_or(false)
- || composer
- .get_config()
- .borrow()
- .get("sort-packages")
- .as_bool()
- .unwrap_or(false);
-
- self.first_require.set(self.newly_created.get());
- if !self.first_require.get() {
- let composer_definition = json.borrow_mut().read()?;
- let require_count = composer_definition
- .get("require")
- .and_then(|v| v.as_array())
- .map(|m| m.len() as i64)
- .unwrap_or(0);
- let require_dev_count = composer_definition
- .get("require-dev")
- .and_then(|v| v.as_array())
- .map(|m| m.len() as i64)
- .unwrap_or(0);
- if require_count == 0 && require_dev_count == 0 {
- self.first_require.set(true);
- }
- }
-
- if !input
- .borrow()
- .get_option("dry-run")?
- .as_bool()
- .unwrap_or(false)
- {
- self.update_file(&json, &requirements, require_key, remove_key, sort_packages);
- }
-
- let updated_msg = format!(
- "<info>{} has been {}</info>",
- file,
- if self.newly_created.get() {
- "created"
- } else {
- "updated"
- }
- );
- self.get_io()
- .write_error3(&updated_msg, true, io_interface::NORMAL);
-
- if input
- .borrow()
- .get_option("no-update")?
- .as_bool()
- .unwrap_or(false)
- {
- return Ok(0);
- }
-
- composer
- .get_plugin_manager()
- .borrow_mut()
- .deactivate_installed_plugins()?;
-
- let io = self.get_io().clone();
- let do_update_result = self.do_update(
- input.clone(),
- output,
- io,
- &requirements,
- require_key,
- remove_key,
- );
- let dry_run = input
- .borrow()
- .get_option("dry-run")?
- .as_bool()
- .unwrap_or(false);
-
- let result = match do_update_result {
- Ok(result) => {
- let final_result = if result == 0 && (requirements_to_guess.len() as i64) > 0 {
- let fixed = input
- .borrow()
- .get_option("fixed")?
- .as_bool()
- .unwrap_or(false);
- self.update_requirements_after_resolution(
- &requirements_to_guess,
- require_key,
- remove_key,
- sort_packages,
- dry_run,
- fixed,
- )?
- } else {
- result
- };
- Ok(final_result)
- }
- Err(e) => {
- if !self.dependency_resolution_completed.get() {
- self.revert_composer_file();
- }
- Err(e)
- }
- };
-
- // finally
- if dry_run && self.newly_created.get() {
- // @unlink($this->json->getPath());
- unlink(json.borrow().get_path());
- }
- signal_handler.unregister();
-
- result
- }
-
- fn interact(
- &self,
- _input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
- _output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) {
- }
-
- fn initialize(
- &self,
- input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
- output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
- ) -> anyhow::Result<()> {
- base_command_initialize(self, input, output)
- }
-
- fn complete(
- &self,
- input: &shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput,
- suggestions: &mut shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions,
- ) -> anyhow::Result<()> {
- crate::command::base_command::base_command_complete(self, input, suggestions)
- }
-
- shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data);
-}
-
-impl BaseCommand for RequireCommand {
- fn base_command_data(&self) -> &crate::command::BaseCommandData {
- &self.base_command_data
- }
-
- crate::delegate_base_command_trait_impls_to_inner!(base_command_data);
-}
-impl RequireCommand {
fn get_inconsistent_require_keys(
&self,
new_requirements: &IndexMap<String, String>,
@@ -1302,3 +719,584 @@ impl RequireCommand {
}
}
}
+
+impl PackageDiscoveryTrait for RequireCommand {
+ fn get_repos_mut(
+ &self,
+ ) -> std::cell::RefMut<'_, Option<crate::repository::RepositoryInterfaceHandle>> {
+ self.repos.borrow_mut()
+ }
+
+ fn get_repository_sets_mut(
+ &self,
+ ) -> std::cell::RefMut<'_, IndexMap<String, std::rc::Rc<std::cell::RefCell<RepositorySet>>>>
+ {
+ self.repository_sets.borrow_mut()
+ }
+}
+
+impl Command for RequireCommand {
+ fn configure(&self) -> anyhow::Result<()> {
+ self.set_name("require")?;
+ self.set_aliases(vec!["r".to_string()])?;
+ self.set_description("Adds required packages to your composer.json and installs them");
+ self.set_definition(&[
+ InputArgument::new5("packages", Some(InputArgument::IS_ARRAY | InputArgument::OPTIONAL), "Optional package name can also include a version constraint, e.g. foo/bar or foo/bar:1.0.0 or foo/bar=1.0.0 or \"foo/bar 1.0.0\"", None, self.suggest_available_package_incl_platform()).unwrap().into(),
+ InputOption::new("dev", None, Some(InputOption::VALUE_NONE), "Add requirement to require-dev.", None).unwrap().into(),
+ InputOption::new("dry-run", None, Some(InputOption::VALUE_NONE), "Outputs the operations but will not execute anything (implicitly enables --verbose).", None).unwrap().into(),
+ InputOption::new("prefer-source", None, Some(InputOption::VALUE_NONE), "Forces installation from package sources when possible, including VCS information.", None).unwrap().into(),
+ InputOption::new("prefer-dist", None, Some(InputOption::VALUE_NONE), "Forces installation from package dist (default behavior).", None).unwrap().into(),
+ InputOption::new6("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, self.suggest_prefer_install()).unwrap().into(),
+ InputOption::new("fixed", None, Some(InputOption::VALUE_NONE), "Write fixed version to the composer.json.", None).unwrap().into(),
+ InputOption::new("no-suggest", None, Some(InputOption::VALUE_NONE), "DEPRECATED: This flag does not exist anymore.", None).unwrap().into(),
+ InputOption::new("no-progress", None, Some(InputOption::VALUE_NONE), "Do not output download progress.", None).unwrap().into(),
+ InputOption::new("no-update", None, Some(InputOption::VALUE_NONE), "Disables the automatic update of the dependencies (implies --no-install).", None).unwrap().into(),
+ InputOption::new("no-install", None, Some(InputOption::VALUE_NONE), "Skip the install step after updating the composer.lock file.", None).unwrap().into(),
+ InputOption::new("no-audit", None, Some(InputOption::VALUE_NONE), "Skip the audit step after updating the composer.lock file (can also be set via the COMPOSER_NO_AUDIT=1 env var).", None).unwrap().into(),
+ InputOption::new6("audit-format", None, Some(InputOption::VALUE_REQUIRED), "Audit output format. Must be \"table\", \"plain\", \"json\", or \"summary\".", Some(PhpMixed::String(Auditor::FORMAT_SUMMARY.to_string())), SuggestedValues::List(Auditor::FORMATS.iter().map(|s| s.to_string()).collect())).unwrap().into(),
+ InputOption::new("no-security-blocking", None, Some(InputOption::VALUE_NONE), "Allows installing packages with security advisories or that are abandoned (can also be set via the COMPOSER_NO_SECURITY_BLOCKING=1 env var).", None).unwrap().into(),
+ InputOption::new("update-no-dev", None, Some(InputOption::VALUE_NONE), "Run the dependency update with the --no-dev option.", None).unwrap().into(),
+ InputOption::new("update-with-dependencies", Some(PhpMixed::String("w".to_string())), Some(InputOption::VALUE_NONE), "Allows inherited dependencies to be updated, except those that are root requirements (can also be set via the COMPOSER_WITH_DEPENDENCIES=1 env var).", None).unwrap().into(),
+ InputOption::new("update-with-all-dependencies", Some(PhpMixed::String("W".to_string())), Some(InputOption::VALUE_NONE), "Allows all inherited dependencies to be updated, including those that are root requirements (can also be set via the COMPOSER_WITH_ALL_DEPENDENCIES=1 env var).", None).unwrap().into(),
+ InputOption::new("with-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-dependencies", None).unwrap().into(),
+ InputOption::new("with-all-dependencies", None, Some(InputOption::VALUE_NONE), "Alias for --update-with-all-dependencies", None).unwrap().into(),
+ InputOption::new("ignore-platform-req", None, Some(InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY), "Ignore a specific platform requirement (php & ext- packages).", None).unwrap().into(),
+ InputOption::new("ignore-platform-reqs", None, Some(InputOption::VALUE_NONE), "Ignore all platform requirements (php & ext- packages).", None).unwrap().into(),
+ InputOption::new("prefer-stable", None, Some(InputOption::VALUE_NONE), "Prefer stable versions of dependencies (can also be set via the COMPOSER_PREFER_STABLE=1 env var).", None).unwrap().into(),
+ InputOption::new("prefer-lowest", None, Some(InputOption::VALUE_NONE), "Prefer lowest versions of dependencies (can also be set via the COMPOSER_PREFER_LOWEST=1 env var).", None).unwrap().into(),
+ InputOption::new("minimal-changes", Some(PhpMixed::String("m".to_string())), Some(InputOption::VALUE_NONE), "During an update with -w/-W, only perform absolutely necessary changes to transitive dependencies (can also be set via the COMPOSER_MINIMAL_CHANGES=1 env var).", None).unwrap().into(),
+ InputOption::new("sort-packages", None, Some(InputOption::VALUE_NONE), "Sorts packages when adding/updating a new dependency", None).unwrap().into(),
+ InputOption::new("optimize-autoloader", Some(PhpMixed::String("o".to_string())), Some(InputOption::VALUE_NONE), "Optimize autoloader during autoloader dump", None).unwrap().into(),
+ InputOption::new("classmap-authoritative", Some(PhpMixed::String("a".to_string())), Some(InputOption::VALUE_NONE), "Autoload classes from the classmap only. Implicitly enables `--optimize-autoloader`.", None).unwrap().into(),
+ InputOption::new("apcu-autoloader", None, Some(InputOption::VALUE_NONE), "Use APCu to cache found/not-found classes.", None).unwrap().into(),
+ InputOption::new("apcu-autoloader-prefix", None, Some(InputOption::VALUE_REQUIRED), "Use a custom prefix for the APCu autoloader cache. Implicitly enables --apcu-autoloader", None).unwrap().into(),
+ ]);
+ self.set_help(
+ "The require command adds required packages to your composer.json and installs them.\n\
+ \n\
+ If you do not specify a package, composer will prompt you to search for a package, and given results, provide a list of\n\
+ matches to require.\n\
+ \n\
+ If you do not specify a version constraint, composer will choose a suitable one based on the available package versions.\n\
+ \n\
+ If you do not want to install the new dependencies immediately you can call it with --no-update\n\
+ \n\
+ Read more at https://getcomposer.org/doc/03-cli.md#require-r"
+ );
+ Ok(())
+ }
+
+ /// @throws \Seld\JsonLint\ParsingException
+ fn execute(
+ &self,
+ input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
+ output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
+ ) -> anyhow::Result<i64> {
+ *self.file.borrow_mut() = Factory::get_composer_file()?;
+
+ if input
+ .borrow()
+ .get_option("no-suggest")?
+ .as_bool()
+ .unwrap_or(false)
+ {
+ self.get_io().write_error3("<warning>You are using the deprecated option \"--no-suggest\". It has no effect and will break in Composer 3.</warning>", true, io_interface::NORMAL);
+ }
+
+ let file = self.file.borrow().clone();
+ self.newly_created.set(!file_exists(&file));
+ let write_failed =
+ self.newly_created.get() && file_put_contents(&file, b"{\n}\n").is_none();
+ if write_failed {
+ let msg = format!("<error>{} could not be created.</error>", file);
+ self.get_io().write_error3(&msg, true, io_interface::NORMAL);
+
+ return Ok(1);
+ }
+ if !Filesystem::is_readable(&file) {
+ let msg = format!("<error>{} is not readable.</error>", file);
+ self.get_io().write_error3(&msg, true, io_interface::NORMAL);
+
+ return Ok(1);
+ }
+ if filesize(&file) == Some(0) {
+ file_put_contents(&file, b"{\n}\n");
+ }
+
+ *self.json.borrow_mut() = Some(std::rc::Rc::new(std::cell::RefCell::new(JsonFile::new(
+ file.clone(),
+ None,
+ None,
+ )?)));
+ *self.lock.borrow_mut() = Factory::get_lock_file(&file);
+ let json = self.json.borrow().as_ref().unwrap().clone();
+ *self.composer_backup.borrow_mut() =
+ file_get_contents(json.borrow().get_path()).unwrap_or_default();
+ let lock = self.lock.borrow().clone();
+ *self.lock_backup.borrow_mut() = if file_exists(&lock) {
+ file_get_contents(&lock)
+ } else {
+ None
+ };
+
+ // PHP: function ($signal, $handler) use ($io, $self) {
+ // $io->writeError('Received '.$signal.', aborting', true, IOInterface::DEBUG);
+ // $self->revertComposerFile(); $handler->exitWithLastSignal(); }
+ // TODO(phase-c): SignalHandler::create takes a `Box<dyn Fn> + 'static` handler that cannot
+ // borrow &self, but the body must call self.revert_composer_file() (which mutates the
+ // command's composer.json backup state) and self.get_io(). Faithfully wiring this needs the
+ // revert state + io shared into the closure (Rc<RefCell<...>>), i.e. the shared-ownership
+ // rework of the command — the same pattern as InstallationManager::execute's signal handler.
+ let signal_handler = SignalHandler::create(
+ vec![
+ SignalHandler::SIGINT.to_string(),
+ SignalHandler::SIGTERM.to_string(),
+ SignalHandler::SIGHUP.to_string(),
+ ],
+ Box::new(move |signal: String, handler: &SignalHandler| {
+ let _ = signal;
+ handler.exit_with_last_signal();
+ }),
+ );
+
+ // check for writability by writing to the file as is_writable can not be trusted on network-mounts
+ // see https://github.com/composer/composer/issues/8231 and https://bugs.php.net/bug.php?id=68926
+ let file_path = file.clone();
+ let backup_contents = self.composer_backup.borrow().clone();
+ if !is_writable(&file)
+ && Silencer::call(|| {
+ shirabe_php_shim::file_put_contents(&file_path, backup_contents.as_bytes());
+ Ok::<bool, anyhow::Error>(false)
+ })
+ .ok()
+ == Some(false)
+ {
+ let msg = format!("<error>{} is not writable.</error>", file);
+ self.get_io().write_error3(&msg, true, io_interface::NORMAL);
+
+ return Ok(1);
+ }
+
+ if input.borrow().get_option("fixed")?.as_bool() == Some(true) {
+ let config = json.borrow_mut().read()?;
+
+ let package_type = if empty(&config.get("type").cloned().unwrap_or(PhpMixed::Null)) {
+ "library".to_string()
+ } else {
+ config
+ .get("type")
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string()
+ };
+
+ // @see https://github.com/composer/composer/pull/8313#issuecomment-532637955
+ if package_type != "project"
+ && !input.borrow().get_option("dev")?.as_bool().unwrap_or(false)
+ {
+ self.get_io().write_error3("<error>The \"--fixed\" option is only allowed for packages with a \"project\" type or for dev dependencies to prevent possible misuses.</error>", true, io_interface::NORMAL);
+
+ if config.get("type").is_none() {
+ self.get_io().write_error3("<error>If your package is not a library, you can explicitly specify the \"type\" by using \"composer config type project\".</error>", true, io_interface::NORMAL);
+ }
+
+ return Ok(1);
+ }
+ }
+
+ let composer = self.require_composer(None, None)?;
+ let composer = crate::composer::composer_full(&composer);
+ let repository_manager = composer.get_repository_manager().clone();
+ let repository_manager = repository_manager.borrow();
+ let repos = repository_manager.get_repositories();
+
+ let platform_overrides = composer.get_config().borrow_mut().get("platform");
+ let platform_overrides_map: IndexMap<String, PhpMixed> = platform_overrides
+ .as_array()
+ .map(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
+ .unwrap_or_default();
+ // initialize self.repos as it is used by the PackageDiscoveryTrait
+ let platform_repo =
+ PlatformRepositoryHandle::new(PlatformRepository::new(vec![], platform_overrides_map)?);
+ let mut combined: Vec<crate::repository::RepositoryInterfaceHandle> =
+ vec![platform_repo.clone().into()];
+ for repo in repos {
+ combined.push(repo.clone());
+ }
+ *self.get_repos_mut() = Some(crate::repository::RepositoryInterfaceHandle::new(
+ CompositeRepository::new(combined),
+ ));
+
+ let preferred_stability = if composer.get_package().get_prefer_stable() {
+ "stable".to_string()
+ } else {
+ composer.get_package().get_minimum_stability()
+ };
+
+ // Hoist argument computations into locals so no borrow of `input` is held across the
+ // call: `determine_requirements` may prompt via ConsoleIO, which mutably borrows the
+ // same input RefCell.
+ let packages: Vec<String> = input
+ .borrow()
+ .get_argument("packages")?
+ .as_list()
+ .map(|l| {
+ l.iter()
+ .filter_map(|v| v.as_string().map(|s| s.to_string()))
+ .collect()
+ })
+ .unwrap_or_default();
+ // if there is no update, we need to use the best possible version constraint directly as we cannot rely on the solver to guess the best constraint
+ let no_update = input
+ .borrow()
+ .get_option("no-update")?
+ .as_bool()
+ .unwrap_or(false);
+ let fixed = input
+ .borrow()
+ .get_option("fixed")?
+ .as_bool()
+ .unwrap_or(false);
+ let requirements_result = self.determine_requirements(
+ input.clone(),
+ output.clone(),
+ packages,
+ Some(&platform_repo),
+ &preferred_stability,
+ no_update,
+ fixed,
+ );
+
+ let requirements = match requirements_result {
+ Ok(r) => r,
+ Err(e) => {
+ if self.newly_created.get() {
+ self.revert_composer_file();
+
+ return Err(RuntimeException {
+ message: format!(
+ "No composer.json present in the current directory ({}), this may be the cause of the following exception.",
+ self.file.borrow()
+ ),
+ code: 0,
+ }
+ .into());
+ }
+
+ return Err(e);
+ }
+ };
+
+ let mut requirements = self.format_requirements(requirements)?;
+
+ if !input.borrow().get_option("dev")?.as_bool().unwrap_or(false)
+ && self.get_io().is_interactive()
+ && !composer.is_global()
+ {
+ let mut dev_packages: Vec<Vec<String>> = vec![];
+ let dev_tags: Vec<String> = vec![
+ "dev".to_string(),
+ "testing".to_string(),
+ "static analysis".to_string(),
+ ];
+ let current_requires_by_key = self.get_packages_by_require_key();
+ for (name, _version) in &requirements {
+ // skip packages which are already in the composer.json as those have already been decided
+ if current_requires_by_key.contains_key(name) {
+ continue;
+ }
+
+ let found_packages: Vec<crate::package::PackageInterfaceHandle> = self
+ .get_repos()
+ .find_packages(name, None)?
+ .into_iter()
+ .collect();
+ let pkg: Option<crate::package::PackageInterfaceHandle> =
+ PackageSorter::get_most_current_version(found_packages);
+ let pkg_as_complete: Option<crate::package::CompletePackageInterfaceHandle> =
+ pkg.as_ref().and_then(|p| p.as_complete());
+ if let Some(pkg_complete) = pkg_as_complete {
+ let lowered: Vec<String> =
+ array_map(|s: &String| strtolower(s), &pkg_complete.get_keywords());
+ let pkg_dev_tags: Vec<String> = array_intersect(&dev_tags, &lowered);
+ if (pkg_dev_tags.len() as i64) > 0 {
+ dev_packages.push(pkg_dev_tags);
+ }
+ }
+ let _ = pkg;
+ }
+
+ if (dev_packages.len() as i64) == (requirements.len() as i64) {
+ let plural = if (requirements.len() as i64) > 1 {
+ "s"
+ } else {
+ ""
+ };
+ let plural2 = if (requirements.len() as i64) > 1 {
+ "are"
+ } else {
+ "is"
+ };
+ let plural3 = if (requirements.len() as i64) > 1 {
+ "they are"
+ } else {
+ "it is"
+ };
+ let merged: Vec<String> = dev_packages.iter().flatten().cloned().collect();
+ let pkg_dev_tags: Vec<String> = array_unique(&merged);
+ let warn_msg = format!(
+ "The package{} you required {} recommended to be placed in require-dev (because {} tagged as \"{}\") but you did not use --dev.",
+ plural,
+ plural2,
+ plural3,
+ implode("\", \"", &pkg_dev_tags),
+ );
+ self.get_io().warning(&warn_msg, &[]);
+ if self.get_io().ask_confirmation(
+ "<info>Do you want to re-run the command with --dev?</> [<comment>yes</>]? "
+ .to_string(),
+ true,
+ ) {
+ input.borrow_mut().set_option("dev", PhpMixed::Bool(true))?;
+ }
+ }
+
+ // unset($devPackages, $pkgDevTags);
+ }
+
+ let mut require_key = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
+ "require-dev"
+ } else {
+ "require"
+ };
+ let mut remove_key = if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
+ "require"
+ } else {
+ "require-dev"
+ };
+
+ // check which requirements need the version guessed
+ let mut requirements_to_guess: Vec<String> = vec![];
+ for (package, constraint) in requirements.clone().iter() {
+ if constraint == "guess" {
+ requirements.insert(package.clone(), "*".to_string());
+ requirements_to_guess.push(package.clone());
+ }
+ }
+
+ // validate requirements format
+ let version_parser = VersionParser::new();
+ for (package, constraint) in &requirements {
+ if strtolower(package) == composer.get_package().get_name() {
+ let msg = format!(
+ "<error>Root package '{}' cannot require itself in its composer.json</error>",
+ package.clone(),
+ );
+ self.get_io().write_error3(&msg, true, io_interface::NORMAL);
+
+ return Ok(1);
+ }
+ if constraint == "self.version" {
+ continue;
+ }
+ version_parser.parse_constraints(constraint)?;
+ }
+
+ let inconsistent_require_keys =
+ self.get_inconsistent_require_keys(&requirements, require_key);
+ if (inconsistent_require_keys.len() as i64) > 0 {
+ for package in &inconsistent_require_keys {
+ let warn_msg = format!(
+ "{} is currently present in the {} key and you ran the command {} the --dev flag, which will move it to the {} key.",
+ package.clone(),
+ remove_key,
+ if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
+ "with"
+ } else {
+ "without"
+ },
+ require_key,
+ );
+ self.get_io().warning(&warn_msg, &[]);
+ }
+
+ if self.get_io().is_interactive() {
+ let q1 = format!(
+ "<info>Do you want to move {}?</info> [<comment>no</comment>]? ",
+ if (inconsistent_require_keys.len() as i64) > 1 {
+ "these requirements"
+ } else {
+ "this requirement"
+ },
+ );
+ if !self.get_io().ask_confirmation(q1, false) {
+ let q2 = format!(
+ "<info>Do you want to re-run the command {} --dev?</info> [<comment>yes</comment>]? ",
+ if input.borrow().get_option("dev")?.as_bool().unwrap_or(false) {
+ "without"
+ } else {
+ "with"
+ },
+ );
+ if !self.get_io().ask_confirmation(q2, true) {
+ return Ok(0);
+ }
+
+ input.borrow_mut().set_option("dev", PhpMixed::Bool(true))?;
+ std::mem::swap(&mut require_key, &mut remove_key);
+ }
+ }
+ }
+
+ let sort_packages = input
+ .borrow()
+ .get_option("sort-packages")?
+ .as_bool()
+ .unwrap_or(false)
+ || composer
+ .get_config()
+ .borrow()
+ .get("sort-packages")
+ .as_bool()
+ .unwrap_or(false);
+
+ self.first_require.set(self.newly_created.get());
+ if !self.first_require.get() {
+ let composer_definition = json.borrow_mut().read()?;
+ let require_count = composer_definition
+ .get("require")
+ .and_then(|v| v.as_array())
+ .map(|m| m.len() as i64)
+ .unwrap_or(0);
+ let require_dev_count = composer_definition
+ .get("require-dev")
+ .and_then(|v| v.as_array())
+ .map(|m| m.len() as i64)
+ .unwrap_or(0);
+ if require_count == 0 && require_dev_count == 0 {
+ self.first_require.set(true);
+ }
+ }
+
+ if !input
+ .borrow()
+ .get_option("dry-run")?
+ .as_bool()
+ .unwrap_or(false)
+ {
+ self.update_file(&json, &requirements, require_key, remove_key, sort_packages);
+ }
+
+ let updated_msg = format!(
+ "<info>{} has been {}</info>",
+ file,
+ if self.newly_created.get() {
+ "created"
+ } else {
+ "updated"
+ }
+ );
+ self.get_io()
+ .write_error3(&updated_msg, true, io_interface::NORMAL);
+
+ if input
+ .borrow()
+ .get_option("no-update")?
+ .as_bool()
+ .unwrap_or(false)
+ {
+ return Ok(0);
+ }
+
+ composer
+ .get_plugin_manager()
+ .borrow_mut()
+ .deactivate_installed_plugins()?;
+
+ let io = self.get_io().clone();
+ let do_update_result = self.do_update(
+ input.clone(),
+ output,
+ io,
+ &requirements,
+ require_key,
+ remove_key,
+ );
+ let dry_run = input
+ .borrow()
+ .get_option("dry-run")?
+ .as_bool()
+ .unwrap_or(false);
+
+ let result = match do_update_result {
+ Ok(result) => {
+ let final_result = if result == 0 && (requirements_to_guess.len() as i64) > 0 {
+ let fixed = input
+ .borrow()
+ .get_option("fixed")?
+ .as_bool()
+ .unwrap_or(false);
+ self.update_requirements_after_resolution(
+ &requirements_to_guess,
+ require_key,
+ remove_key,
+ sort_packages,
+ dry_run,
+ fixed,
+ )?
+ } else {
+ result
+ };
+ Ok(final_result)
+ }
+ Err(e) => {
+ if !self.dependency_resolution_completed.get() {
+ self.revert_composer_file();
+ }
+ Err(e)
+ }
+ };
+
+ // finally
+ if dry_run && self.newly_created.get() {
+ // @unlink($this->json->getPath());
+ unlink(json.borrow().get_path());
+ }
+ signal_handler.unregister();
+
+ result
+ }
+
+ fn interact(
+ &self,
+ _input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
+ _output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
+ ) {
+ }
+
+ fn initialize(
+ &self,
+ input: std::rc::Rc<std::cell::RefCell<dyn InputInterface>>,
+ output: std::rc::Rc<std::cell::RefCell<dyn OutputInterface>>,
+ ) -> anyhow::Result<()> {
+ base_command_initialize(self, input, output)
+ }
+
+ fn complete(
+ &self,
+ input: &shirabe_external_packages::symfony::console::completion::completion_input::CompletionInput,
+ suggestions: &mut shirabe_external_packages::symfony::console::completion::completion_suggestions::CompletionSuggestions,
+ ) -> anyhow::Result<()> {
+ crate::command::base_command::base_command_complete(self, input, suggestions)
+ }
+
+ shirabe_external_packages::delegate_command_trait_impls_to_inner!(base_command_data);
+}
+
+impl BaseCommand for RequireCommand {
+ fn base_command_data(&self) -> &crate::command::BaseCommandData {
+ &self.base_command_data
+ }
+
+ crate::delegate_base_command_trait_impls_to_inner!(base_command_data);
+}