From 1c5342e61f3714a5bc4cb66b1d31d12aca2c3c0f Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Thu, 3 Sep 2026 12:06:45 +0200 Subject: [PATCH 1/6] refactor: rename include-vendor option to include-core in compatibility check command --- CHANGELOG.md | 2 +- docs/commands_reference.md | 2 +- .../Hyva/CompatibilityCheckCommand.php | 24 +++++++++---------- .../Hyva/CompatibilityCheckCommandTest.php | 4 ++-- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5e15678..2d798959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -488,7 +488,7 @@ All notable changes to this project will be documented in this file. - Scans Magento modules for Hyvä theme compatibility issues - Detects RequireJS, Knockout.js, jQuery, and UI Components usage - Interactive menu with Laravel Prompts for scan options - - Options: `--show-all`, `--third-party-only`, `--include-vendor`, `--detailed` + - Options: `--show-all`, `--third-party-only`, `--include-core`, `--detailed` - Color-coded output (✓ Compatible, ⚠ Warnings, ✗ Incompatible) - Detailed file-level issues with line numbers - Exit code 1 for critical issues, 0 for success diff --git a/docs/commands_reference.md b/docs/commands_reference.md index 443633f6..aaa09cd7 100644 --- a/docs/commands_reference.md +++ b/docs/commands_reference.md @@ -245,7 +245,7 @@ bin/magento hyva:check - `-a, --show-all` — Show all modules including compatible ones. - `-t, --third-party-only` — Check only third-party modules (exclude Magento\_\*). -- `--include-vendor` — Include Magento core modules in the check. +- `--include-core` — Include Magento core modules in the check. - `--detailed` — Show detailed compatibility information. **Output:** Displays a table with compatibility status per module. diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 498738c2..3928bac3 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -28,7 +28,7 @@ class CompatibilityCheckCommand extends AbstractCommand { private const OPTION_SHOW_ALL = 'show-all'; private const OPTION_THIRD_PARTY_ONLY = 'third-party-only'; - private const OPTION_INCLUDE_VENDOR = 'include-vendor'; + private const OPTION_INCLUDE_CORE = 'include-core'; private const OPTION_DETAILED = 'detailed'; private const DISPLAY_MODE_ISSUES = 'issues'; @@ -71,7 +71,7 @@ protected function configure(): void 'Check only third-party modules (exclude Magento_* modules)', ) ->addOption( - self::OPTION_INCLUDE_VENDOR, + self::OPTION_INCLUDE_CORE, null, InputOption::VALUE_NONE, 'Include Magento core modules (default: third-party modules only)', @@ -97,7 +97,7 @@ protected function executeCommand(InputInterface $input, OutputInterface $output $hasOptions = (bool) $input->getOption(self::OPTION_SHOW_ALL) || (bool) $input->getOption(self::OPTION_THIRD_PARTY_ONLY) - || (bool) $input->getOption(self::OPTION_INCLUDE_VENDOR) + || (bool) $input->getOption(self::OPTION_INCLUDE_CORE) || (bool) $input->getOption(self::OPTION_DETAILED); if (!$hasOptions && $this->isInteractiveTerminal($output)) { @@ -155,7 +155,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp // Map selected options to flags $showAll = $displayMode === self::DISPLAY_MODE_SHOW_ALL; $incompatibleOnly = $displayMode === self::DISPLAY_MODE_INCOMPATIBLE_ONLY; - $includeVendor = $scope === self::SCOPE_ALL; + $includeCore = $scope === self::SCOPE_ALL; $thirdPartyOnly = false; // Not needed in interactive mode // Show selected configuration @@ -168,7 +168,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp } else { $config[] = 'Show modules with issues'; } - $config[] = $includeVendor ? 'Include Magento core' : 'Third-party modules only'; + $config[] = $includeCore ? 'Include Magento core' : 'Third-party modules only'; if ($detailed) { $config[] = 'Detailed issues'; } @@ -176,7 +176,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $this->io->newLine(); // Run scan with selected options - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $incompatibleOnly); + return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $detailed, $incompatibleOnly); } catch (\Throwable $e) { $this->io->error('Interactive mode failed: ' . $e->getMessage()); $this->io->info('Falling back to default scan (third-party modules only)...'); @@ -199,12 +199,12 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): { $showAll = (bool) $input->getOption(self::OPTION_SHOW_ALL); $thirdPartyOnly = (bool) $input->getOption(self::OPTION_THIRD_PARTY_ONLY); - $includeVendor = (bool) $input->getOption(self::OPTION_INCLUDE_VENDOR); + $includeCore = (bool) $input->getOption(self::OPTION_INCLUDE_CORE); $detailed = (bool) $input->getOption(self::OPTION_DETAILED); $this->io->title('Hyvä Theme Compatibility Check'); - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, false); + return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $detailed, false); } /** @@ -212,7 +212,7 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): * * @param bool $showAll * @param bool $thirdPartyOnly - * @param bool $includeVendor + * @param bool $includeCore * @param bool $detailed * @param bool $incompatibleOnly * @return int @@ -220,15 +220,15 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): private function runScan( bool $showAll, bool $thirdPartyOnly, - bool $includeVendor, + bool $includeCore, bool $detailed, bool $incompatibleOnly, ): int { // Determine filter logic: // - thirdPartyOnly: Only scan non-Magento_* modules (default behavior) - // - includeVendor: Also scan Magento_* core modules + // - includeCore: Also scan Magento_* core modules // - excludeVendor: Whether to exclude vendor/ directory (always false for now) - $scanThirdPartyOnly = !$includeVendor; + $scanThirdPartyOnly = !$includeCore; $excludeVendor = false; // Run the compatibility check diff --git a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php index 164c7cc8..9da58529 100644 --- a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php +++ b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php @@ -365,7 +365,7 @@ public function testDetailedIssuesIncludesCompatibleModulesThatHaveWarnings(): v $this->assertStringContainsString('Vendor_Warned', $tester->getDisplay()); } - public function testIncludeVendorOptionIsPassedToChecker(): void + public function testIncludeCoreOptionIsPassedToChecker(): void { $this->compatibilityChecker->expects($this->once()) ->method('check') @@ -374,7 +374,7 @@ public function testIncludeVendorOptionIsPassedToChecker(): void $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); $tester = new CommandTester($this->command); - $tester->execute(['--include-vendor' => true]); + $tester->execute(['--include-core' => true]); } public function testCommandNameAndAliases(): void From e50e63a9011c5b7b8ee3b70f468cc46379a8159b Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Thu, 3 Sep 2026 12:13:08 +0200 Subject: [PATCH 2/6] feat: add validation for conflicting options in compatibility check command --- .../Command/Hyva/CompatibilityCheckCommand.php | 9 +++++++++ .../Hyva/CompatibilityCheckCommandTest.php | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 3928bac3..58581583 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -93,6 +93,15 @@ protected function configure(): void */ protected function executeCommand(InputInterface $input, OutputInterface $output): int { + // Validate conflicting options early + if ($input->getOption(self::OPTION_THIRD_PARTY_ONLY) + && $input->getOption(self::OPTION_INCLUDE_CORE) + ) { + $this->io->error('The options --third-party-only and --include-core cannot be used together.'); + + return Cli::RETURN_FAILURE; + } + // Check if we're in interactive mode (no options provided) $hasOptions = (bool) $input->getOption(self::OPTION_SHOW_ALL) diff --git a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php index 9da58529..d079537e 100644 --- a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php +++ b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php @@ -377,6 +377,23 @@ public function testIncludeCoreOptionIsPassedToChecker(): void $tester->execute(['--include-core' => true]); } + public function testConflictingThirdPartyOnlyAndIncludeCoreOptionsReturnError(): void + { + $this->compatibilityChecker->expects($this->never())->method('check'); + + $tester = new CommandTester($this->command); + $exitCode = $tester->execute([ + '--third-party-only' => true, + '--include-core' => true, + ]); + + $this->assertSame(Cli::RETURN_FAILURE, $exitCode); + $this->assertStringContainsString( + 'cannot be used together', + $tester->getDisplay(), + ); + } + public function testCommandNameAndAliases(): void { $this->assertSame('mageforge:hyva:compatibility:check', $this->command->getName()); From caf881a1d46fb4b4b19697e4cd44ee1ebcb2b9bb Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Thu, 3 Sep 2026 13:50:11 +0200 Subject: [PATCH 3/6] feat: add --exclude-vendor option to compatibility check command --- CHANGELOG.md | 2 +- docs/commands_reference.md | 1 + .../Hyva/CompatibilityCheckCommand.php | 26 ++++++++++++++++--- .../Hyva/CompatibilityCheckCommandTest.php | 12 +++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d798959..2766f3c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -488,7 +488,7 @@ All notable changes to this project will be documented in this file. - Scans Magento modules for Hyvä theme compatibility issues - Detects RequireJS, Knockout.js, jQuery, and UI Components usage - Interactive menu with Laravel Prompts for scan options - - Options: `--show-all`, `--third-party-only`, `--include-core`, `--detailed` + - Options: `--show-all`, `--third-party-only`, `--include-core`, `--exclude-vendor`, `--detailed` - Color-coded output (✓ Compatible, ⚠ Warnings, ✗ Incompatible) - Detailed file-level issues with line numbers - Exit code 1 for critical issues, 0 for success diff --git a/docs/commands_reference.md b/docs/commands_reference.md index aaa09cd7..7e5655d6 100644 --- a/docs/commands_reference.md +++ b/docs/commands_reference.md @@ -246,6 +246,7 @@ bin/magento hyva:check - `-a, --show-all` — Show all modules including compatible ones. - `-t, --third-party-only` — Check only third-party modules (exclude Magento\_\*). - `--include-core` — Include Magento core modules in the check. +- `--exclude-vendor` — Exclude modules installed in the vendor directory. - `--detailed` — Show detailed compatibility information. **Output:** Displays a table with compatibility status per module. diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 58581583..e6b4f648 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -29,6 +29,7 @@ class CompatibilityCheckCommand extends AbstractCommand private const OPTION_SHOW_ALL = 'show-all'; private const OPTION_THIRD_PARTY_ONLY = 'third-party-only'; private const OPTION_INCLUDE_CORE = 'include-core'; + private const OPTION_EXCLUDE_VENDOR = 'exclude-vendor'; private const OPTION_DETAILED = 'detailed'; private const DISPLAY_MODE_ISSUES = 'issues'; @@ -76,6 +77,12 @@ protected function configure(): void InputOption::VALUE_NONE, 'Include Magento core modules (default: third-party modules only)', ) + ->addOption( + self::OPTION_EXCLUDE_VENDOR, + null, + InputOption::VALUE_NONE, + 'Exclude modules installed in the vendor directory', + ) ->addOption( self::OPTION_DETAILED, 'd', @@ -107,6 +114,7 @@ protected function executeCommand(InputInterface $input, OutputInterface $output (bool) $input->getOption(self::OPTION_SHOW_ALL) || (bool) $input->getOption(self::OPTION_THIRD_PARTY_ONLY) || (bool) $input->getOption(self::OPTION_INCLUDE_CORE) + || (bool) $input->getOption(self::OPTION_EXCLUDE_VENDOR) || (bool) $input->getOption(self::OPTION_DETAILED); if (!$hasOptions && $this->isInteractiveTerminal($output)) { @@ -166,6 +174,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $incompatibleOnly = $displayMode === self::DISPLAY_MODE_INCOMPATIBLE_ONLY; $includeCore = $scope === self::SCOPE_ALL; $thirdPartyOnly = false; // Not needed in interactive mode + $excludeVendor = false; // Not configurable in interactive mode // Show selected configuration $this->io->newLine(); @@ -185,7 +194,14 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $this->io->newLine(); // Run scan with selected options - return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $detailed, $incompatibleOnly); + return $this->runScan( + $showAll, + $thirdPartyOnly, + $includeCore, + $excludeVendor, + $detailed, + $incompatibleOnly, + ); } catch (\Throwable $e) { $this->io->error('Interactive mode failed: ' . $e->getMessage()); $this->io->info('Falling back to default scan (third-party modules only)...'); @@ -209,11 +225,12 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): $showAll = (bool) $input->getOption(self::OPTION_SHOW_ALL); $thirdPartyOnly = (bool) $input->getOption(self::OPTION_THIRD_PARTY_ONLY); $includeCore = (bool) $input->getOption(self::OPTION_INCLUDE_CORE); + $excludeVendor = (bool) $input->getOption(self::OPTION_EXCLUDE_VENDOR); $detailed = (bool) $input->getOption(self::OPTION_DETAILED); $this->io->title('Hyvä Theme Compatibility Check'); - return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $detailed, false); + return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $excludeVendor, $detailed, false); } /** @@ -222,6 +239,7 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): * @param bool $showAll * @param bool $thirdPartyOnly * @param bool $includeCore + * @param bool $excludeVendor * @param bool $detailed * @param bool $incompatibleOnly * @return int @@ -230,15 +248,15 @@ private function runScan( bool $showAll, bool $thirdPartyOnly, bool $includeCore, + bool $excludeVendor, bool $detailed, bool $incompatibleOnly, ): int { // Determine filter logic: // - thirdPartyOnly: Only scan non-Magento_* modules (default behavior) // - includeCore: Also scan Magento_* core modules - // - excludeVendor: Whether to exclude vendor/ directory (always false for now) + // - excludeVendor: Whether to exclude modules installed in vendor/ $scanThirdPartyOnly = !$includeCore; - $excludeVendor = false; // Run the compatibility check $results = $this->compatibilityChecker->check($this->io, $showAll, $scanThirdPartyOnly, $excludeVendor); diff --git a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php index d079537e..ed16ada7 100644 --- a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php +++ b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php @@ -377,6 +377,18 @@ public function testIncludeCoreOptionIsPassedToChecker(): void $tester->execute(['--include-core' => true]); } + public function testExcludeVendorOptionIsPassedToChecker(): void + { + $this->compatibilityChecker->expects($this->once()) + ->method('check') + ->with($this->anything(), false, true, true) + ->willReturn($this->makeResults()); + $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); + + $tester = new CommandTester($this->command); + $tester->execute(['--exclude-vendor' => true]); + } + public function testConflictingThirdPartyOnlyAndIncludeCoreOptionsReturnError(): void { $this->compatibilityChecker->expects($this->never())->method('check'); From c29779ca3642e76f57944baf541ffa8376fedb47 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Thu, 3 Sep 2026 13:59:59 +0200 Subject: [PATCH 4/6] refactor: rename hasIncompatibilities to hasIssues in compatibility check logic --- .../Hyva/CompatibilityCheckCommand.php | 4 ++-- src/Service/Hyva/CompatibilityChecker.php | 10 +++++----- .../Hyva/CompatibilityCheckCommandTest.php | 20 +++++++++---------- .../Service/Hyva/CompatibilityCheckerTest.php | 20 +++++++++---------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index e6b4f648..61c1c609 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -271,7 +271,7 @@ private function runScan( $this->displayResults($results, $displayShowAll); // Display detailed issues if requested - if ($detailed && $results['hasIncompatibilities']) { + if ($detailed && $results['hasIssues']) { $this->displayDetailedIssues($results); } @@ -279,7 +279,7 @@ private function runScan( $this->displaySummary($results['summary']); // Display recommendations if there are issues - if ($results['hasIncompatibilities']) { + if ($results['hasIssues']) { $this->displayRecommendations(); } diff --git a/src/Service/Hyva/CompatibilityChecker.php b/src/Service/Hyva/CompatibilityChecker.php index c06f3364..9590c0a6 100644 --- a/src/Service/Hyva/CompatibilityChecker.php +++ b/src/Service/Hyva/CompatibilityChecker.php @@ -34,7 +34,7 @@ * @phpstan-type CheckResults array{ * modules: array, * summary: CheckSummary, - * hasIncompatibilities: bool + * hasIssues: bool * } */ class CompatibilityChecker @@ -57,7 +57,7 @@ public function __construct( * @param bool $thirdPartyOnly Whether to scan only third-party modules (excludes Magento_* modules) * @param bool $excludeVendor Whether to exclude modules from the vendor/ directory * @return array Results with structure: ['modules' => [], 'summary' => [], - * 'hasIncompatibilities' => bool] + * 'hasIssues' => bool] * @phpstan-return CheckResults */ public function check( @@ -78,7 +78,7 @@ public function check( 'criticalIssues' => 0, 'warningIssues' => 0, ], - 'hasIncompatibilities' => false, + 'hasIssues' => false, ]; $io->text(sprintf('Scanning %d modules for Hyvä compatibility...', count($modules))); @@ -118,12 +118,12 @@ public function check( $results['summary']['compatible']++; } else { $results['summary']['incompatible']++; - $results['hasIncompatibilities'] = true; + $results['hasIssues'] = true; } // Warnings alone still trigger the detail/recommendation display if ($hasWarnings) { - $results['hasIncompatibilities'] = true; + $results['hasIssues'] = true; } if ($moduleInfo['isHyvaAware']) { diff --git a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php index ed16ada7..4f9cdee8 100644 --- a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php +++ b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php @@ -44,7 +44,7 @@ private function makeResults(array $overrides = []): array 'criticalIssues' => 0, 'warningIssues' => 0, ], - 'hasIncompatibilities' => false, + 'hasIssues' => false, ], $overrides); } @@ -77,7 +77,7 @@ public function testReturnsFailureWhenCriticalIssuesFound(): void 'criticalIssues' => 2, 'warningIssues' => 1, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay') @@ -113,7 +113,7 @@ public function testReturnsSuccessWithWarningsOnly(): void 'criticalIssues' => 0, 'warningIssues' => 2, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay') @@ -140,7 +140,7 @@ public function testDisplaySummaryRendersEachDistinctFigure(): void 'criticalIssues' => 2, 'warningIssues' => 5, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); @@ -175,7 +175,7 @@ public function testExactlyZeroCriticalIssuesWithWarningsShowsWarningMessageNotC 'criticalIssues' => 0, 'warningIssues' => 3, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); @@ -202,7 +202,7 @@ public function testOneCriticalIssueShowsCriticalMessageWithExactCounts(): void 'criticalIssues' => 1, 'warningIssues' => 0, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); @@ -242,7 +242,7 @@ public function testWithoutShowAllOptionFormatResultsForDisplayReceivesFalse(): public function testDetailedFlagWithoutIncompatibilitiesSkipsDetailedIssues(): void { - $this->compatibilityChecker->method('check')->willReturn($this->makeResults(['hasIncompatibilities' => false])); + $this->compatibilityChecker->method('check')->willReturn($this->makeResults(['hasIssues' => false])); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); $this->compatibilityChecker->expects($this->never())->method('getDetailedIssues'); @@ -263,7 +263,7 @@ public function testIncompatibilitiesWithoutDetailedFlagSkipsDetailedIssues(): v 'criticalIssues' => 1, 'warningIssues' => 0, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); @@ -301,7 +301,7 @@ public function testDetailedOptionDisplaysFileLevelIssues(): void 'criticalIssues' => 1, 'warningIssues' => 0, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay') @@ -350,7 +350,7 @@ public function testDetailedIssuesIncludesCompatibleModulesThatHaveWarnings(): v 'criticalIssues' => 0, 'warningIssues' => 1, ], - 'hasIncompatibilities' => true, + 'hasIssues' => true, ]); $this->compatibilityChecker->method('check')->willReturn($results); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); diff --git a/tests/Unit/Service/Hyva/CompatibilityCheckerTest.php b/tests/Unit/Service/Hyva/CompatibilityCheckerTest.php index 0a9f50af..15053dfe 100644 --- a/tests/Unit/Service/Hyva/CompatibilityCheckerTest.php +++ b/tests/Unit/Service/Hyva/CompatibilityCheckerTest.php @@ -80,7 +80,7 @@ public function testAggregatesSummaryAcrossModules(): void ], $results['summary'], ); - $this->assertTrue($results['hasIncompatibilities']); + $this->assertTrue($results['hasIssues']); $this->assertTrue($results['modules']['Vendor_Clean']['compatible']); $this->assertFalse($results['modules']['Vendor_Broken']['compatible']); } @@ -93,7 +93,7 @@ public function testCriticalIssuesAloneMarkResultsAsIncompatible(): void $results = $this->checker->check($this->io); - $this->assertTrue($results['hasIncompatibilities']); + $this->assertTrue($results['hasIssues']); $this->assertFalse($results['modules']['Vendor_Broken']['hasWarnings']); } @@ -105,7 +105,7 @@ public function testFullyCompatibleModulesProduceNoIncompatibilities(): void $results = $this->checker->check($this->io); - $this->assertFalse($results['hasIncompatibilities']); + $this->assertFalse($results['hasIssues']); $this->assertSame(0, $results['summary']['incompatible']); } @@ -117,7 +117,7 @@ public function testWarningsAloneMarkResultsAsIncompatible(): void $results = $this->checker->check($this->io); - $this->assertTrue($results['hasIncompatibilities']); + $this->assertTrue($results['hasIssues']); $this->assertTrue($results['modules']['Vendor_Warned']['compatible']); $this->assertTrue($results['modules']['Vendor_Warned']['hasWarnings']); $this->assertSame(2, $results['summary']['warningIssues']); @@ -201,7 +201,7 @@ public function testDisplaysOnlyProblematicModulesByDefault(): void 'Vendor_Clean' => $this->moduleEntry(compatible: true, hasWarnings: false, critical: 0, total: 0), 'Vendor_Broken' => $this->moduleEntry(compatible: false, hasWarnings: false, critical: 2, total: 2), 'Vendor_Warned' => $this->moduleEntry(compatible: true, hasWarnings: true, critical: 0, total: 1), - ], hasIncompatibilities: true); + ], hasIssues: true); $tableData = $this->checker->formatResultsForDisplay($results); @@ -215,7 +215,7 @@ public function testDisplaysAllModulesWhenRequested(): void $results = $this->checkResults([ 'Vendor_Clean' => $this->moduleEntry(compatible: true, hasWarnings: false, critical: 0, total: 0), 'Vendor_Broken' => $this->moduleEntry(compatible: false, hasWarnings: false, critical: 2, total: 2), - ], hasIncompatibilities: true); + ], hasIssues: true); $tableData = $this->checker->formatResultsForDisplay($results, true); @@ -236,7 +236,7 @@ public function testFormatsMixedIssuesAndHyvaAwareStatus(): void total: 3, hyvaAware: true, ), - ], hasIncompatibilities: true); + ], hasIssues: true); $tableData = $this->checker->formatResultsForDisplay($results); @@ -254,7 +254,7 @@ public function testHyvaAwareCompatibleModuleGetsDedicatedStatus(): void total: 0, hyvaAware: true, ), - ], hasIncompatibilities: false); + ], hasIssues: false); $tableData = $this->checker->formatResultsForDisplay($results, true); @@ -333,7 +333,7 @@ private function givenModuleInfo(bool $hyvaAware): void * @param array $modules * @return CheckResults */ - private function checkResults(array $modules, bool $hasIncompatibilities): array + private function checkResults(array $modules, bool $hasIssues): array { return [ 'modules' => $modules, @@ -345,7 +345,7 @@ private function checkResults(array $modules, bool $hasIncompatibilities): array 'criticalIssues' => 0, 'warningIssues' => 0, ], - 'hasIncompatibilities' => $hasIncompatibilities, + 'hasIssues' => $hasIssues, ]; } From 1b7410d0f4461466294726b853b27f0515e932f4 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Thu, 3 Sep 2026 14:10:49 +0200 Subject: [PATCH 5/6] feat: enhance compatibility check with verbose output and add isHyvaAware determination logic --- .../Hyva/CompatibilityCheckCommand.php | 15 +++++++++++ src/Service/Hyva/ModuleScanner.php | 21 ++++++++++++++- tests/Unit/Service/Hyva/ModuleScannerTest.php | 27 ++++++++++++++++++- 3 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 61c1c609..a08caac3 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -135,6 +135,10 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp { $this->io->title('Hyvä Theme Compatibility Check'); + if ($this->isVerbose($output)) { + $this->io->info('Running in interactive mode'); + } + // Set environment variables for Laravel Prompts $this->setPromptEnvironment(); @@ -230,6 +234,17 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): $this->io->title('Hyvä Theme Compatibility Check'); + if ($this->isVerbose($output)) { + $this->io->info(sprintf( + 'Direct mode: showAll=%s, thirdPartyOnly=%s, includeCore=%s, excludeVendor=%s, detailed=%s', + $showAll ? 'true' : 'false', + $thirdPartyOnly ? 'true' : 'false', + $includeCore ? 'true' : 'false', + $excludeVendor ? 'true' : 'false', + $detailed ? 'true' : 'false', + )); + } + return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $excludeVendor, $detailed, false); } diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php index 80cc771d..bb9ed496 100644 --- a/src/Service/Hyva/ModuleScanner.php +++ b/src/Service/Hyva/ModuleScanner.php @@ -174,13 +174,32 @@ public function getModuleInfo(string $modulePath): array return [ 'name' => is_string($composerData['name'] ?? null) ? $composerData['name'] : 'Unknown', 'version' => is_string($composerData['version'] ?? null) ? $composerData['version'] : 'Unknown', - 'isHyvaAware' => $this->isHyvaCompatibilityPackage($composerData), + 'isHyvaAware' => $this->isHyvaAware($modulePath, $composerData), ]; } catch (\Throwable $e) { return ['name' => 'Unknown', 'version' => 'Unknown', 'isHyvaAware' => false]; } } + /** + * Determine whether a module is Hyvä-aware. + * + * A module is considered Hyvä-aware when it either declares a Hyvä dependency, + * is a Hyvä compatibility package, or ships a hyva-themes.json config. + * + * @param string $modulePath + * @param array $composerData + * @return bool + */ + private function isHyvaAware(string $modulePath, array $composerData): bool + { + if ($this->isHyvaCompatibilityPackage($composerData)) { + return true; + } + + return $this->fileDriver->isExists($modulePath . '/hyva-themes.json'); + } + /** * Get basename without using basename(). * diff --git a/tests/Unit/Service/Hyva/ModuleScannerTest.php b/tests/Unit/Service/Hyva/ModuleScannerTest.php index c891e0cd..816d9bdc 100644 --- a/tests/Unit/Service/Hyva/ModuleScannerTest.php +++ b/tests/Unit/Service/Hyva/ModuleScannerTest.php @@ -226,7 +226,9 @@ private function givenDirectories(array $directories): void */ private function givenComposerJson(array $composerData): void { - $this->fileDriver->method('isExists')->with('/module/composer.json')->willReturn(true); + $this->fileDriver->method('isExists')->willReturnCallback( + static fn(string $path): bool => $path === '/module/composer.json', + ); $this->fileDriver->method('fileGetContents')->willReturn(json_encode($composerData)); } @@ -239,4 +241,27 @@ public function testNonHyvaRequirementsAreNotHyvaAware(): void $this->assertFalse($this->scanner->getModuleInfo('/module')['isHyvaAware']); } + + public function testHyvaThemesJsonMakesModuleHyvaAware(): void + { + $this->fileDriver->method('isExists')->willReturnMap([ + ['/module/composer.json', true], + ['/module/hyva-themes.json', true], + ]); + $this->fileDriver->method('fileGetContents')->with('/module/composer.json')->willReturn( + json_encode(['name' => 'vendor/module']), + ); + + $this->assertTrue($this->scanner->getModuleInfo('/module')['isHyvaAware']); + } + + public function testComposerHyvaDependencyTakesPrecedenceOverMissingHyvaThemesJson(): void + { + $this->givenComposerJson([ + 'name' => 'vendor/module', + 'require' => ['hyva-themes/magento2-default-theme' => '*'], + ]); + + $this->assertTrue($this->scanner->getModuleInfo('/module')['isHyvaAware']); + } } From 1232d5e03be0be6280087425ec8d2cc797930826 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Tue, 8 Sep 2026 10:48:55 +0200 Subject: [PATCH 6/6] refactor: rename exclude-vendor option to include-vendor in compatibility check command --- CHANGELOG.md | 2 +- docs/commands_reference.md | 2 +- .../Hyva/CompatibilityCheckCommand.php | 36 +++++++++---------- src/Service/Hyva/ModuleScanner.php | 3 +- .../Hyva/CompatibilityCheckCommandTest.php | 20 ++++++++--- 5 files changed, 37 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2766f3c9..687d2a03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -488,7 +488,7 @@ All notable changes to this project will be documented in this file. - Scans Magento modules for Hyvä theme compatibility issues - Detects RequireJS, Knockout.js, jQuery, and UI Components usage - Interactive menu with Laravel Prompts for scan options - - Options: `--show-all`, `--third-party-only`, `--include-core`, `--exclude-vendor`, `--detailed` + - Options: `--show-all`, `--third-party-only`, `--include-core`, `--include-vendor`, `--detailed` - Color-coded output (✓ Compatible, ⚠ Warnings, ✗ Incompatible) - Detailed file-level issues with line numbers - Exit code 1 for critical issues, 0 for success diff --git a/docs/commands_reference.md b/docs/commands_reference.md index 7e5655d6..889638ab 100644 --- a/docs/commands_reference.md +++ b/docs/commands_reference.md @@ -246,7 +246,7 @@ bin/magento hyva:check - `-a, --show-all` — Show all modules including compatible ones. - `-t, --third-party-only` — Check only third-party modules (exclude Magento\_\*). - `--include-core` — Include Magento core modules in the check. -- `--exclude-vendor` — Exclude modules installed in the vendor directory. +- `--include-vendor` — Include modules installed in the vendor directory (default: excluded). - `--detailed` — Show detailed compatibility information. **Output:** Displays a table with compatibility status per module. diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index a08caac3..911cfe70 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -29,7 +29,7 @@ class CompatibilityCheckCommand extends AbstractCommand private const OPTION_SHOW_ALL = 'show-all'; private const OPTION_THIRD_PARTY_ONLY = 'third-party-only'; private const OPTION_INCLUDE_CORE = 'include-core'; - private const OPTION_EXCLUDE_VENDOR = 'exclude-vendor'; + private const OPTION_INCLUDE_VENDOR = 'include-vendor'; private const OPTION_DETAILED = 'detailed'; private const DISPLAY_MODE_ISSUES = 'issues'; @@ -78,10 +78,10 @@ protected function configure(): void 'Include Magento core modules (default: third-party modules only)', ) ->addOption( - self::OPTION_EXCLUDE_VENDOR, + self::OPTION_INCLUDE_VENDOR, null, InputOption::VALUE_NONE, - 'Exclude modules installed in the vendor directory', + 'Include modules installed in the vendor directory (default: excluded)', ) ->addOption( self::OPTION_DETAILED, @@ -101,9 +101,7 @@ protected function configure(): void protected function executeCommand(InputInterface $input, OutputInterface $output): int { // Validate conflicting options early - if ($input->getOption(self::OPTION_THIRD_PARTY_ONLY) - && $input->getOption(self::OPTION_INCLUDE_CORE) - ) { + if ($input->getOption(self::OPTION_THIRD_PARTY_ONLY) && $input->getOption(self::OPTION_INCLUDE_CORE)) { $this->io->error('The options --third-party-only and --include-core cannot be used together.'); return Cli::RETURN_FAILURE; @@ -114,7 +112,7 @@ protected function executeCommand(InputInterface $input, OutputInterface $output (bool) $input->getOption(self::OPTION_SHOW_ALL) || (bool) $input->getOption(self::OPTION_THIRD_PARTY_ONLY) || (bool) $input->getOption(self::OPTION_INCLUDE_CORE) - || (bool) $input->getOption(self::OPTION_EXCLUDE_VENDOR) + || (bool) $input->getOption(self::OPTION_INCLUDE_VENDOR) || (bool) $input->getOption(self::OPTION_DETAILED); if (!$hasOptions && $this->isInteractiveTerminal($output)) { @@ -178,7 +176,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $incompatibleOnly = $displayMode === self::DISPLAY_MODE_INCOMPATIBLE_ONLY; $includeCore = $scope === self::SCOPE_ALL; $thirdPartyOnly = false; // Not needed in interactive mode - $excludeVendor = false; // Not configurable in interactive mode + $includeVendor = false; // Vendor modules excluded by default in interactive mode // Show selected configuration $this->io->newLine(); @@ -202,7 +200,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $showAll, $thirdPartyOnly, $includeCore, - $excludeVendor, + $includeVendor, $detailed, $incompatibleOnly, ); @@ -229,23 +227,23 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): $showAll = (bool) $input->getOption(self::OPTION_SHOW_ALL); $thirdPartyOnly = (bool) $input->getOption(self::OPTION_THIRD_PARTY_ONLY); $includeCore = (bool) $input->getOption(self::OPTION_INCLUDE_CORE); - $excludeVendor = (bool) $input->getOption(self::OPTION_EXCLUDE_VENDOR); + $includeVendor = (bool) $input->getOption(self::OPTION_INCLUDE_VENDOR); $detailed = (bool) $input->getOption(self::OPTION_DETAILED); $this->io->title('Hyvä Theme Compatibility Check'); if ($this->isVerbose($output)) { $this->io->info(sprintf( - 'Direct mode: showAll=%s, thirdPartyOnly=%s, includeCore=%s, excludeVendor=%s, detailed=%s', + 'Direct mode: showAll=%s, thirdPartyOnly=%s, includeCore=%s, includeVendor=%s, detailed=%s', $showAll ? 'true' : 'false', $thirdPartyOnly ? 'true' : 'false', $includeCore ? 'true' : 'false', - $excludeVendor ? 'true' : 'false', + $includeVendor ? 'true' : 'false', $detailed ? 'true' : 'false', )); } - return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $excludeVendor, $detailed, false); + return $this->runScan($showAll, $thirdPartyOnly, $includeCore, $includeVendor, $detailed, false); } /** @@ -254,7 +252,7 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): * @param bool $showAll * @param bool $thirdPartyOnly * @param bool $includeCore - * @param bool $excludeVendor + * @param bool $includeVendor * @param bool $detailed * @param bool $incompatibleOnly * @return int @@ -263,18 +261,18 @@ private function runScan( bool $showAll, bool $thirdPartyOnly, bool $includeCore, - bool $excludeVendor, + bool $includeVendor, bool $detailed, bool $incompatibleOnly, ): int { // Determine filter logic: - // - thirdPartyOnly: Only scan non-Magento_* modules (default behavior) + // - thirdPartyOnly: Only scan non-Magento_* modules // - includeCore: Also scan Magento_* core modules - // - excludeVendor: Whether to exclude modules installed in vendor/ - $scanThirdPartyOnly = !$includeCore; + // - includeVendor: Whether to include modules installed in vendor/ + $scanThirdPartyOnly = $thirdPartyOnly || !$includeCore; // Run the compatibility check - $results = $this->compatibilityChecker->check($this->io, $showAll, $scanThirdPartyOnly, $excludeVendor); + $results = $this->compatibilityChecker->check($this->io, $showAll, $scanThirdPartyOnly, !$includeVendor); // Determine display mode: // showAll = show all modules including compatible ones diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php index bb9ed496..7e0af4a7 100644 --- a/src/Service/Hyva/ModuleScanner.php +++ b/src/Service/Hyva/ModuleScanner.php @@ -188,7 +188,8 @@ public function getModuleInfo(string $modulePath): array * is a Hyvä compatibility package, or ships a hyva-themes.json config. * * @param string $modulePath - * @param array $composerData + * @param array $composerData + * @phpstan-param array $composerData * @return bool */ private function isHyvaAware(string $modulePath, array $composerData): bool diff --git a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php index 4f9cdee8..7a5fdee2 100644 --- a/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php +++ b/tests/Unit/Console/Command/Hyva/CompatibilityCheckCommandTest.php @@ -365,11 +365,23 @@ public function testDetailedIssuesIncludesCompatibleModulesThatHaveWarnings(): v $this->assertStringContainsString('Vendor_Warned', $tester->getDisplay()); } + public function testDefaultExcludesVendorModules(): void + { + $this->compatibilityChecker->expects($this->once()) + ->method('check') + ->with($this->anything(), false, true, true) + ->willReturn($this->makeResults()); + $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); + + $tester = new CommandTester($this->command); + $tester->execute([]); + } + public function testIncludeCoreOptionIsPassedToChecker(): void { $this->compatibilityChecker->expects($this->once()) ->method('check') - ->with($this->anything(), false, false, false) + ->with($this->anything(), false, false, true) ->willReturn($this->makeResults()); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); @@ -377,16 +389,16 @@ public function testIncludeCoreOptionIsPassedToChecker(): void $tester->execute(['--include-core' => true]); } - public function testExcludeVendorOptionIsPassedToChecker(): void + public function testIncludeVendorOptionIsPassedToChecker(): void { $this->compatibilityChecker->expects($this->once()) ->method('check') - ->with($this->anything(), false, true, true) + ->with($this->anything(), false, true, false) ->willReturn($this->makeResults()); $this->compatibilityChecker->method('formatResultsForDisplay')->willReturn([]); $tester = new CommandTester($this->command); - $tester->execute(['--exclude-vendor' => true]); + $tester->execute(['--include-vendor' => true]); } public function testConflictingThirdPartyOnlyAndIncludeCoreOptionsReturnError(): void