diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6633c2..2fecab7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+# [2.4.0] = 21 June 2024
+### Added
+- New CLI `yireo_extensionchecker:list:modules` (module name, enabled/disabled, composer version)
+
# [2.3.4] = 8 May 2024
### Fixed
- Do not pick up on nodes that are functions #54 @sprankhub
diff --git a/Composer/ComposerProvider.php b/Composer/ComposerProvider.php
index 202fe78..50e45a7 100644
--- a/Composer/ComposerProvider.php
+++ b/Composer/ComposerProvider.php
@@ -22,7 +22,6 @@ public function __construct(
public function getVersionByComposerName(string $composerName): string
{
$composerPackages = $this->getComposerPackages();
-
foreach ($composerPackages as $composerPackage) {
if ($composerPackage['name'] === $composerName) {
return $composerPackage['version'];
diff --git a/Console/Command/ListModulesCommand.php b/Console/Command/ListModulesCommand.php
new file mode 100644
index 0000000..b0f9356
--- /dev/null
+++ b/Console/Command/ListModulesCommand.php
@@ -0,0 +1,99 @@
+setName('yireo_extensionchecker:list:modules');
+ $this->setDescription('List all Magento modules');
+ }
+
+ /**
+ * @param Input $input
+ * @param Output $output
+ *
+ * @return int
+ */
+ protected function execute(Input $input, Output $output): int
+ {
+ $table = new Table($output);
+ $table->setHeaders([
+ 'Module',
+ 'Status',
+ 'Setup Version',
+ 'Composer Version'
+ ]);
+
+ $componentPaths = $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE);
+ $moduleNames = array_keys($componentPaths);
+
+ foreach ($moduleNames as $moduleName) {
+ $moduleInfo = $this->moduleList->getOne($moduleName);
+ $status = $moduleInfo ? 'enabled' : 'disabled';
+ $setupVersion = isset($moduleInfo['setup_version']) ? $moduleInfo['setup_version'] : '-';
+
+ $table->addRow([
+ $moduleName,
+ $status,
+ $setupVersion,
+ $this->getComposerVersion($moduleName)
+ ]);
+ }
+
+ $table->render();
+
+ return Command::SUCCESS;
+ }
+
+ private function getComposerVersion(string $moduleName): string
+ {
+ $path = $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, $moduleName);
+ $composerJsonFile = $path. '/composer.json';
+ if (false === file_exists($composerJsonFile)) {
+ $composerJsonFile = dirname($path).'/composer.json';
+ }
+
+ if (false === file_exists($composerJsonFile)) {
+ return '';
+ }
+
+ $composerFile = $this->composerFileFactory->create($composerJsonFile);
+
+ try {
+ $composerVersion = $composerFile->get('version');
+ if (!empty($composerVersion)) {
+ return $composerVersion;
+ }
+ } catch(RuntimeException) {}
+
+
+ $composerName = $composerFile->get('name');
+ return $this->composerProvider->getVersionByComposerName($composerName);
+ }
+}
diff --git a/composer.json b/composer.json
index e36354b..72e4fa2 100644
--- a/composer.json
+++ b/composer.json
@@ -2,7 +2,7 @@
"name": "yireo/magento2-extensionchecker",
"license": "OSL-3.0",
"type": "magento2-module",
- "version": "2.3.4",
+ "version": "2.4.0",
"homepage": "https://github.com/yireo/Yireo_ExtensionChecker",
"description": "Scan the code of a Magento module",
"keywords": [
diff --git a/etc/di.xml b/etc/di.xml
index fd9ef5b..e4d51ba 100644
--- a/etc/di.xml
+++ b/etc/di.xml
@@ -8,6 +8,7 @@
- Yireo\ExtensionChecker\Console\Command\ListClassesCommand
- Yireo\ExtensionChecker\Console\Command\CreatePlantUmlDiagramCommand
- Yireo\ExtensionChecker\Console\Command\CheckMagentoVersionCommand
+ - Yireo\ExtensionChecker\Console\Command\ListModulesCommand