From 11634f279387bca5f0c6981e5f52922a9039c6dd Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 00:52:26 +0100 Subject: [PATCH 01/16] =?UTF-8?q?Add=20Hyv=C3=A4=20compatibility=20checker?= =?UTF-8?q?=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - New command: mageforge:hyva:compatibility:check (aliases: m:h:c:c, hyva:check) - Scans Magento modules for Hyvä theme incompatibilities - Detects RequireJS, Knockout.js, jQuery, UI Components usage - Options: --show-all, --third-party-only, --include-vendor, --detailed - Service layer: CompatibilityChecker, ModuleScanner, IncompatibilityDetector - Exit code 1 for critical issues, 0 for success - Updated CI/CD workflow and documentation --- .github/workflows/magento-compatibility.yml | 8 + docs/commands.md | 101 ++++++++ .../Hyva/CompatibilityCheckCommand.php | 219 ++++++++++++++++++ src/Service/Hyva/CompatibilityChecker.php | 200 ++++++++++++++++ src/Service/Hyva/IncompatibilityDetector.php | 183 +++++++++++++++ src/Service/Hyva/ModuleScanner.php | 159 +++++++++++++ src/etc/di.xml | 3 + 7 files changed, 873 insertions(+) create mode 100644 src/Console/Command/Hyva/CompatibilityCheckCommand.php create mode 100644 src/Service/Hyva/CompatibilityChecker.php create mode 100644 src/Service/Hyva/IncompatibilityDetector.php create mode 100644 src/Service/Hyva/ModuleScanner.php diff --git a/.github/workflows/magento-compatibility.yml b/.github/workflows/magento-compatibility.yml index c4edaf9..1e32e4b 100644 --- a/.github/workflows/magento-compatibility.yml +++ b/.github/workflows/magento-compatibility.yml @@ -134,6 +134,10 @@ jobs: echo "Test MageForge Theme List command:" bin/magento mageforge:theme:list + echo "Test MageForge Hyvä Compatibility Check command:" + bin/magento mageforge:hyva:compatibility:check --help + bin/magento m:h:c:c --help + - name: Test Summary run: | echo "MageForge module compatibility test with Magento ${{ matrix.magento-version }} completed" @@ -255,6 +259,10 @@ jobs: echo "Test MageForge Theme List command:" bin/magento mageforge:theme:list + echo "Test MageForge Hyvä Compatibility Check command:" + bin/magento mageforge:hyva:compatibility:check --help + bin/magento m:h:c:c --help + - name: Test Summary run: | echo "MageForge module compatibility test with Magento 2.4.8 completed" diff --git a/docs/commands.md b/docs/commands.md index c4301e7..32d5376 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -130,10 +130,111 @@ bin/magento mageforge:version - Fetches the latest version from GitHub API - Displays both versions for comparison +--- + +### 6. CompatibilityCheckCommand (`mageforge:hyva:compatibility:check`) + +**Purpose**: Scans all Magento modules for Hyvä theme compatibility issues such as RequireJS, Knockout.js, jQuery, and UI Components usage. + +**File**: `/src/Console/Command/Hyva/CompatibilityCheckCommand.php` + +**Dependencies**: +- `CompatibilityChecker` - Main orchestrator service for scanning modules + +**Usage**: +```bash +bin/magento mageforge:hyva:compatibility:check [options] +``` + +**Aliases**: +- `m:h:c:c` +- `hyva:check` + +**Options**: +- `--show-all` / `-a` - Show all modules including compatible ones +- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento_* modules) +- `--include-vendor` - Include vendor modules in scan (default: excluded) +- `--detailed` / `-d` - Show detailed file-level issues for incompatible modules + +**Examples**: +```bash +# Basic compatibility check +bin/magento m:h:c:c + +# Check only third-party modules with details +bin/magento m:h:c:c -t -d + +# Show all modules including compatible ones +bin/magento m:h:c:c -a --include-vendor + +# Using full command name +bin/magento mageforge:hyva:compatibility:check --detailed +``` + +**Implementation Details**: +- Scans module directories for JS, XML, and PHTML files +- Detects incompatibility patterns: + - **Critical Issues**: + - RequireJS `define()` and `require()` usage + - Knockout.js observables and computed properties + - Magento UI Components in XML + - `data-mage-init` and `x-magento-init` in templates + - **Warnings**: + - jQuery AJAX direct usage + - jQuery DOM manipulation + - Block removal in layout XML (review needed) +- Displays results in formatted tables with color-coded status: + - ✓ Green: Compatible modules + - ⚠ Yellow: Warnings (non-critical issues) + - ✗ Red: Incompatible (critical issues) + - ✓ Hyvä-Aware: Modules with Hyvä compatibility packages +- Provides summary statistics: + - Total modules scanned + - Compatible vs. incompatible count + - Hyvä-aware modules count + - Critical issues and warnings count +- Shows detailed file paths and line numbers with `--detailed` flag +- Provides helpful recommendations for resolving issues +- Returns exit code 1 if critical issues found, 0 otherwise + +**Detected Patterns**: + +*JavaScript Files (.js)*: +- `define([` - RequireJS module definition +- `require([` - RequireJS dependency loading +- `ko.observable` / `ko.observableArray` / `ko.computed` - Knockout.js +- `$.ajax` / `jQuery.ajax` - jQuery AJAX +- `mage/` - Magento RequireJS module references + +*XML Files (.xml)*: +- `setName($this->getCommandName('hyva', 'compatibility:check')) + ->setDescription('Check modules for Hyvä theme compatibility issues') + ->setAliases(['m:h:c:c', 'hyva:check']) + ->addOption( + self::OPTION_SHOW_ALL, + 'a', + InputOption::VALUE_NONE, + 'Show all modules including compatible ones' + ) + ->addOption( + self::OPTION_THIRD_PARTY_ONLY, + 't', + InputOption::VALUE_NONE, + 'Check only third-party modules (exclude Magento_* modules)' + ) + ->addOption( + self::OPTION_INCLUDE_VENDOR, + null, + InputOption::VALUE_NONE, + 'Include vendor modules in scan (default: excluded)' + ) + ->addOption( + self::OPTION_DETAILED, + 'd', + InputOption::VALUE_NONE, + 'Show detailed file-level issues for incompatible modules' + ); + } + + protected function executeCommand(InputInterface $input, OutputInterface $output): int + { + $showAll = $input->getOption(self::OPTION_SHOW_ALL); + $thirdPartyOnly = $input->getOption(self::OPTION_THIRD_PARTY_ONLY); + $includeVendor = $input->getOption(self::OPTION_INCLUDE_VENDOR); + $detailed = $input->getOption(self::OPTION_DETAILED); + + $this->io->title('Hyvä Theme Compatibility Check'); + + // Determine vendor filter logic + // If third-party-only OR include-vendor is set, don't exclude vendor modules + $excludeVendor = !$includeVendor && !$thirdPartyOnly; + + // Run the compatibility check + $results = $this->compatibilityChecker->check( + $this->io, + $output, + $showAll, + $thirdPartyOnly, + $excludeVendor + ); + + // Display results + $this->displayResults($results, $showAll || $detailed); + + // Display detailed issues if requested + if ($detailed && $results['hasIncompatibilities']) { + $this->displayDetailedIssues($results); + } + + // Display summary + $this->displaySummary($results); + + // Return appropriate exit code + return $results['summary']['criticalIssues'] > 0 + ? Cli::RETURN_FAILURE + : Cli::RETURN_SUCCESS; + } + + /** + * Display compatibility check results + */ + private function displayResults(array $results, bool $showAll): void + { + $this->io->section('Compatibility Results'); + + $tableData = $this->compatibilityChecker->formatResultsForDisplay($results, $showAll); + + if (empty($tableData)) { + $this->io->success('All scanned modules are compatible with Hyvä!'); + return; + } + + $this->io->table( + ['Module', 'Status', 'Issues'], + $tableData + ); + } + + /** + * Display detailed file-level issues + */ + private function displayDetailedIssues(array $results): void + { + $this->io->section('Detailed Issues'); + + foreach ($results['modules'] as $moduleName => $moduleData) { + // Only show modules with issues + if ($moduleData['compatible'] && !$moduleData['hasWarnings']) { + continue; + } + + $this->io->text(sprintf('%s', $moduleName)); + + $detailedIssues = $this->compatibilityChecker->getDetailedIssues($moduleName, $moduleData); + + foreach ($detailedIssues as $fileData) { + $this->io->text(sprintf(' %s', $fileData['file'])); + + foreach ($fileData['issues'] as $issue) { + $color = $issue['severity'] === 'critical' ? 'red' : 'yellow'; + $symbol = $issue['severity'] === 'critical' ? '✗' : '⚠'; + + $this->io->text(sprintf( + ' %s Line %d: %s', + $color, + $symbol, + $issue['line'], + $issue['description'] + )); + } + } + + $this->io->newLine(); + } + } + + /** + * Display summary statistics + */ + private function displaySummary(array $results): void + { + $summary = $results['summary']; + + $this->io->section('Summary'); + + $summaryData = [ + ['Total Modules Scanned', $summary['total']], + new TableSeparator(), + ['Compatible', sprintf('%d', $summary['compatible'])], + ['Incompatible', sprintf('%d', $summary['incompatible'])], + ['Hyvä-Aware Modules', sprintf('%d', $summary['hyvaAware'])], + new TableSeparator(), + ['Critical Issues', sprintf('%d', $summary['criticalIssues'])], + ['Warnings', sprintf('%d', $summary['warningIssues'])], + ]; + + $this->io->table([], $summaryData); + + // Final message + if ($summary['criticalIssues'] > 0) { + $this->io->error(sprintf( + 'Found %d critical compatibility issue(s) that need attention.', + $summary['criticalIssues'] + )); + } elseif ($summary['warningIssues'] > 0) { + $this->io->warning(sprintf( + 'Found %d warning(s). Review these for potential issues.', + $summary['warningIssues'] + )); + } else { + $this->io->success('All scanned modules are Hyvä compatible!'); + } + + // Provide helpful recommendations + $this->displayRecommendations($results); + } + + /** + * Display helpful recommendations + */ + private function displayRecommendations(array $results): void + { + if ($results['summary']['incompatible'] === 0) { + return; + } + + $this->io->section('Recommendations'); + + $recommendations = [ + '• Check if Hyvä compatibility packages exist for incompatible modules', + '• Review https://hyva.io/compatibility for known solutions', + '• Consider refactoring RequireJS/Knockout code to Alpine.js', + '• Contact module vendors for Hyvä-compatible versions', + ]; + + foreach ($recommendations as $recommendation) { + $this->io->text($recommendation); + } + } +} diff --git a/src/Service/Hyva/CompatibilityChecker.php b/src/Service/Hyva/CompatibilityChecker.php new file mode 100644 index 0000000..0b23735 --- /dev/null +++ b/src/Service/Hyva/CompatibilityChecker.php @@ -0,0 +1,200 @@ + [], 'summary' => [], 'hasIncompatibilities' => bool] + */ + public function check( + SymfonyStyle $io, + OutputInterface $output, + bool $showAll = false, + bool $thirdPartyOnly = false, + bool $excludeVendor = true + ): array { + $modules = $this->componentRegistrar->getPaths(ComponentRegistrar::MODULE); + $results = [ + 'modules' => [], + 'summary' => [ + 'total' => 0, + 'compatible' => 0, + 'incompatible' => 0, + 'hyvaAware' => 0, + 'criticalIssues' => 0, + 'warningIssues' => 0, + ], + 'hasIncompatibilities' => false, + ]; + + $io->text(sprintf('Scanning %d modules for Hyvä compatibility...', count($modules))); + $io->newLine(); + + foreach ($modules as $moduleName => $modulePath) { + // Filter by options + if ($excludeVendor && $this->isVendorModule($modulePath)) { + continue; + } + + if ($thirdPartyOnly && $this->isMagentoModule($moduleName)) { + continue; + } + + $results['summary']['total']++; + + if ($showAll) { + $io->text(sprintf(' Scanning: %s', $moduleName)); + } + + $scanResult = $this->moduleScanner->scanModule($modulePath); + $moduleInfo = $this->moduleScanner->getModuleInfo($modulePath); + + $isCompatible = $scanResult['criticalIssues'] === 0; + $hasWarnings = $scanResult['totalIssues'] > $scanResult['criticalIssues']; + + $results['modules'][$moduleName] = [ + 'path' => $modulePath, + 'compatible' => $isCompatible, + 'hasWarnings' => $hasWarnings, + 'scanResult' => $scanResult, + 'moduleInfo' => $moduleInfo, + ]; + + // Update summary + if ($isCompatible && !$hasWarnings) { + $results['summary']['compatible']++; + } else { + $results['summary']['incompatible']++; + $results['hasIncompatibilities'] = true; + } + + if ($moduleInfo['isHyvaAware']) { + $results['summary']['hyvaAware']++; + } + + $results['summary']['criticalIssues'] += $scanResult['criticalIssues']; + $results['summary']['warningIssues'] += ($scanResult['totalIssues'] - $scanResult['criticalIssues']); + } + + return $results; + } + + /** + * Check if module is a vendor module + */ + private function isVendorModule(string $modulePath): bool + { + return str_contains($modulePath, '/vendor/'); + } + + /** + * Check if module is a core Magento module + */ + private function isMagentoModule(string $moduleName): bool + { + return str_starts_with($moduleName, 'Magento_'); + } + + /** + * Format results for display + */ + public function formatResultsForDisplay(array $results, bool $showAll = false): array + { + $tableData = []; + + foreach ($results['modules'] as $moduleName => $data) { + $status = $this->getStatusDisplay($data); + $issues = $this->getIssuesDisplay($data); + + if ($showAll || !$data['compatible'] || $data['hasWarnings']) { + $tableData[] = [ + $moduleName, + $status, + $issues, + ]; + } + } + + return $tableData; + } + + /** + * Get status display string with colors + */ + private function getStatusDisplay(array $moduleData): string + { + if ($moduleData['moduleInfo']['isHyvaAware']) { + return '✓ Hyvä-Aware'; + } + + if ($moduleData['compatible'] && !$moduleData['hasWarnings']) { + return '✓ Compatible'; + } + + if ($moduleData['compatible'] && $moduleData['hasWarnings']) { + return '⚠ Warnings'; + } + + return '✗ Incompatible'; + } + + /** + * Get issues display string + */ + private function getIssuesDisplay(array $moduleData): string + { + $scanResult = $moduleData['scanResult']; + + if ($scanResult['totalIssues'] === 0) { + return 'None'; + } + + $parts = []; + + if ($scanResult['criticalIssues'] > 0) { + $parts[] = sprintf('%d critical', $scanResult['criticalIssues']); + } + + $warnings = $scanResult['totalIssues'] - $scanResult['criticalIssues']; + if ($warnings > 0) { + $parts[] = sprintf('%d warning(s)', $warnings); + } + + return implode(', ', $parts); + } + + /** + * Get detailed file issues for a module + */ + public function getDetailedIssues(string $moduleName, array $moduleData): array + { + $files = $moduleData['scanResult']['files'] ?? []; + $details = []; + + foreach ($files as $filePath => $issues) { + $details[] = [ + 'file' => $filePath, + 'issues' => $issues, + ]; + } + + return $details; + } +} diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php new file mode 100644 index 0000000..c666bf2 --- /dev/null +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -0,0 +1,183 @@ + [ + [ + 'pattern' => '/define\s*\(\s*\[/', + 'description' => 'RequireJS define() usage', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/require\s*\(\s*\[/', + 'description' => 'RequireJS require() usage', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/ko\.observable|ko\.observableArray|ko\.computed/', + 'description' => 'Knockout.js usage', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/\$\.ajax|jQuery\.ajax/', + 'description' => 'jQuery AJAX direct usage', + 'severity' => self::SEVERITY_WARNING, + ], + [ + 'pattern' => '/mage\//', + 'description' => 'Magento RequireJS module reference', + 'severity' => self::SEVERITY_CRITICAL, + ], + ], + 'xml' => [ + [ + 'pattern' => '/ 'UI Component usage', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/component="uiComponent"/', + 'description' => 'uiComponent reference', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/component="Magento_Ui\/js\//', + 'description' => 'Magento UI JS component', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/ 'Block removal (review for Hyvä compatibility)', + 'severity' => self::SEVERITY_WARNING, + ], + ], + 'phtml' => [ + [ + 'pattern' => '/data-mage-init\s*=/', + 'description' => 'data-mage-init JavaScript initialization', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/x-magento-init/', + 'description' => 'x-magento-init JavaScript initialization', + 'severity' => self::SEVERITY_CRITICAL, + ], + [ + 'pattern' => '/\$\(.*\)\..*\(/', + 'description' => 'jQuery DOM manipulation', + 'severity' => self::SEVERITY_WARNING, + ], + [ + 'pattern' => '/require\s*\(\s*\[/', + 'description' => 'RequireJS in template', + 'severity' => self::SEVERITY_CRITICAL, + ], + ], + ]; + + public function __construct( + private readonly File $fileDriver + ) { + } + + /** + * Detect incompatibilities in a file + * + * @return array Array of issues with keys: pattern, description, severity, line + */ + public function detectInFile(string $filePath): array + { + if (!$this->fileDriver->isExists($filePath)) { + return []; + } + + $extension = pathinfo($filePath, PATHINFO_EXTENSION); + $fileType = $this->mapExtensionToType($extension); + + if (!isset(self::INCOMPATIBLE_PATTERNS[$fileType])) { + return []; + } + + try { + $content = $this->fileDriver->fileGetContents($filePath); + $lines = explode("\n", $content); + + return $this->scanContentForPatterns($lines, self::INCOMPATIBLE_PATTERNS[$fileType]); + } catch (\Exception $e) { + return []; + } + } + + /** + * Map file extension to pattern type + */ + private function mapExtensionToType(string $extension): string + { + return match ($extension) { + 'js' => 'js', + 'xml' => 'xml', + 'phtml' => 'phtml', + default => 'unknown', + }; + } + + /** + * Scan content lines for pattern matches + */ + private function scanContentForPatterns(array $lines, array $patterns): array + { + $issues = []; + + foreach ($patterns as $patternConfig) { + foreach ($lines as $lineNumber => $lineContent) { + if (preg_match($patternConfig['pattern'], $lineContent)) { + $issues[] = [ + 'description' => $patternConfig['description'], + 'severity' => $patternConfig['severity'], + 'line' => $lineNumber + 1, // Convert to 1-based line numbers + 'pattern' => $patternConfig['pattern'], + ]; + } + } + } + + return $issues; + } + + /** + * Get severity color for console output + */ + public function getSeverityColor(string $severity): string + { + return match ($severity) { + self::SEVERITY_CRITICAL => 'red', + self::SEVERITY_WARNING => 'yellow', + default => 'white', + }; + } + + /** + * Get severity symbol + */ + public function getSeveritySymbol(string $severity): string + { + return match ($severity) { + self::SEVERITY_CRITICAL => '✗', + self::SEVERITY_WARNING => '⚠', + default => 'ℹ', + }; + } +} diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php new file mode 100644 index 0000000..795f643 --- /dev/null +++ b/src/Service/Hyva/ModuleScanner.php @@ -0,0 +1,159 @@ + [], 'totalIssues' => int, 'criticalIssues' => int] + */ + public function scanModule(string $modulePath): array + { + if (!$this->fileDriver->isDirectory($modulePath)) { + return ['files' => [], 'totalIssues' => 0, 'criticalIssues' => 0]; + } + + $filesWithIssues = []; + $totalIssues = 0; + $criticalIssues = 0; + + $files = $this->findRelevantFiles($modulePath); + + foreach ($files as $file) { + $issues = $this->incompatibilityDetector->detectInFile($file); + + if (!empty($issues)) { + $relativePath = str_replace($modulePath . '/', '', $file); + $filesWithIssues[$relativePath] = $issues; + $totalIssues += count($issues); + + foreach ($issues as $issue) { + if ($issue['severity'] === 'critical') { + $criticalIssues++; + } + } + } + } + + return [ + 'files' => $filesWithIssues, + 'totalIssues' => $totalIssues, + 'criticalIssues' => $criticalIssues, + ]; + } + + /** + * Recursively find all relevant files in a directory + */ + private function findRelevantFiles(string $directory): array + { + $relevantFiles = []; + + try { + $items = $this->fileDriver->readDirectory($directory); + + foreach ($items as $item) { + $basename = basename($item); + + // Skip excluded directories + if ($this->fileDriver->isDirectory($item)) { + if (in_array($basename, self::EXCLUDE_DIRECTORIES, true)) { + continue; + } + // Recursively scan subdirectories + $relevantFiles = array_merge($relevantFiles, $this->findRelevantFiles($item)); + continue; + } + + // Check if file has relevant extension + $extension = pathinfo($item, PATHINFO_EXTENSION); + if (in_array($extension, self::SCAN_EXTENSIONS, true)) { + $relevantFiles[] = $item; + } + } + } catch (\Exception $e) { + // Silently skip directories that can't be read + } + + return $relevantFiles; + } + + /** + * Check if module has Hyvä compatibility package + */ + public function hasHyvaCompatibilityPackage(string $modulePath): bool + { + $composerPath = $modulePath . '/composer.json'; + + if (!$this->fileDriver->isExists($composerPath)) { + return false; + } + + try { + $content = $this->fileDriver->fileGetContents($composerPath); + $composerData = json_decode($content, true); + + if (!is_array($composerData)) { + return false; + } + + // Check if this IS a Hyvä compatibility package + $packageName = $composerData['name'] ?? ''; + if (str_contains($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { + return true; + } + + // Check dependencies for Hyvä packages + $requires = $composerData['require'] ?? []; + foreach ($requires as $package => $version) { + if (str_contains($package, 'hyva-themes/')) { + return true; + } + } + } catch (\Exception $e) { + return false; + } + + return false; + } + + /** + * Get module info from composer.json + */ + public function getModuleInfo(string $modulePath): array + { + $composerPath = $modulePath . '/composer.json'; + + if (!$this->fileDriver->isExists($composerPath)) { + return ['name' => 'Unknown', 'version' => 'Unknown', 'isHyvaAware' => false]; + } + + try { + $content = $this->fileDriver->fileGetContents($composerPath); + $composerData = json_decode($content, true); + + return [ + 'name' => $composerData['name'] ?? 'Unknown', + 'version' => $composerData['version'] ?? 'Unknown', + 'isHyvaAware' => $this->hasHyvaCompatibilityPackage($modulePath), + ]; + } catch (\Exception $e) { + return ['name' => 'Unknown', 'version' => 'Unknown', 'isHyvaAware' => false]; + } + } +} diff --git a/src/etc/di.xml b/src/etc/di.xml index a30e947..15448b7 100644 --- a/src/etc/di.xml +++ b/src/etc/di.xml @@ -24,6 +24,9 @@ OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand + OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand From 44d98e1903e25f8184f467f401257b1ec7242b54 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 00:57:19 +0100 Subject: [PATCH 02/16] Fix: Default scan now includes third-party modules - Changed default behavior from 0 modules to third-party modules (18) - Without flags: Scans third-party only (excludes Magento_*) - With --include-vendor: Scans all 394 modules including Magento core - Updated documentation to reflect new default behavior --- docs/commands.md | 16 +++++++++++----- .../Command/Hyva/CompatibilityCheckCommand.php | 11 +++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 32d5376..16e796e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -153,19 +153,25 @@ bin/magento mageforge:hyva:compatibility:check [options] **Options**: - `--show-all` / `-a` - Show all modules including compatible ones - `--third-party-only` / `-t` - Check only third-party modules (exclude Magento_* modules) -- `--include-vendor` - Include vendor modules in scan (default: excluded) +- `--include-vendor` - Include Magento core modules in scan (default: third-party only) - `--detailed` / `-d` - Show detailed file-level issues for incompatible modules +**Default Behavior**: +Without any flags, the command scans **third-party modules only** (excludes `Magento_*` modules but includes vendor third-party like Hyva, PayPal, Mollie, etc.). + **Examples**: ```bash -# Basic compatibility check +# Basic scan (third-party modules only - DEFAULT) bin/magento m:h:c:c -# Check only third-party modules with details -bin/magento m:h:c:c -t -d +# Include Magento core modules +bin/magento m:h:c:c --include-vendor # Show all modules including compatible ones -bin/magento m:h:c:c -a --include-vendor +bin/magento m:h:c:c -a + +# Show detailed file-level issues +bin/magento m:h:c:c -d # Using full command name bin/magento mageforge:hyva:compatibility:check --detailed diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 781e443..9586b74 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -65,16 +65,19 @@ protected function executeCommand(InputInterface $input, OutputInterface $output $this->io->title('Hyvä Theme Compatibility Check'); - // Determine vendor filter logic - // If third-party-only OR include-vendor is set, don't exclude vendor modules - $excludeVendor = !$includeVendor && !$thirdPartyOnly; + // Determine filter logic: + // - Default (no flags): Scan third-party only (exclude Magento_* but include vendor third-party) + // - With --include-vendor: Scan everything including Magento_* + // - With --third-party-only: Explicitly scan only third-party + $scanThirdPartyOnly = $thirdPartyOnly || (!$includeVendor && !$thirdPartyOnly); + $excludeVendor = false; // Always include vendor for third-party scanning // Run the compatibility check $results = $this->compatibilityChecker->check( $this->io, $output, $showAll, - $thirdPartyOnly, + $scanThirdPartyOnly, $excludeVendor ); From c959f74d682a19b690847252ff48214dbe0afb2e Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:02:24 +0100 Subject: [PATCH 03/16] Add interactive menu with Laravel Prompts - Interactive mode activated when no options provided - Multi-select menu for scan options: * Show all modules * Include Magento core modules * Detailed file-level issues - Displays selected configuration before scan - Falls back to direct mode if interactive fails - Consistent with theme:build command UX --- .../Hyva/CompatibilityCheckCommand.php | 237 +++++++++++++++++- 1 file changed, 230 insertions(+), 7 deletions(-) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 9586b74..794d30b 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -4,6 +4,7 @@ namespace OpenForgeProject\MageForge\Console\Command\Hyva; +use Laravel\Prompts\MultiSelectPrompt; use Magento\Framework\Console\Cli; use OpenForgeProject\MageForge\Console\Command\AbstractCommand; use OpenForgeProject\MageForge\Service\Hyva\CompatibilityChecker; @@ -19,6 +20,9 @@ class CompatibilityCheckCommand extends AbstractCommand private const OPTION_INCLUDE_VENDOR = 'include-vendor'; private const OPTION_DETAILED = 'detailed'; + private array $originalEnv = []; + private array $secureEnvStorage = []; + public function __construct( private readonly CompatibilityChecker $compatibilityChecker ) { @@ -57,6 +61,85 @@ protected function configure(): void } protected function executeCommand(InputInterface $input, OutputInterface $output): int + { + // Check if we're in interactive mode (no options provided) + $hasOptions = $input->getOption(self::OPTION_SHOW_ALL) + || $input->getOption(self::OPTION_THIRD_PARTY_ONLY) + || $input->getOption(self::OPTION_INCLUDE_VENDOR) + || $input->getOption(self::OPTION_DETAILED); + + if (!$hasOptions && $this->isInteractiveTerminal($output)) { + return $this->runInteractiveMode($input, $output); + } + + return $this->runDirectMode($input, $output); + } + + /** + * Run interactive mode with Laravel Prompts + */ + private function runInteractiveMode(InputInterface $input, OutputInterface $output): int + { + $this->io->title('Hyvä Theme Compatibility Check'); + + // Set environment variables for Laravel Prompts + $this->setPromptEnvironment(); + + try { + $scanOptionsPrompt = new MultiSelectPrompt( + label: 'Select scan options', + options: [ + 'show-all' => 'Show all modules including compatible ones', + 'include-vendor' => 'Include Magento core modules (default: third-party only)', + 'detailed' => 'Show detailed file-level issues with line numbers', + ], + default: [], + hint: 'Space to toggle, Enter to confirm. Default: third-party modules only', + required: false, + ); + + $selectedOptions = $scanOptionsPrompt->prompt(); + \Laravel\Prompts\Prompt::terminal()->restoreTty(); + $this->resetPromptEnvironment(); + + // Apply selected options to input + $showAll = in_array('show-all', $selectedOptions); + $includeVendor = in_array('include-vendor', $selectedOptions); + $detailed = in_array('detailed', $selectedOptions); + $thirdPartyOnly = false; // Not needed in interactive mode + + // Show selected configuration + $this->io->newLine(); + $config = []; + if ($showAll) { + $config[] = 'Show all modules'; + } + if ($includeVendor) { + $config[] = 'Include Magento core'; + } else { + $config[] = 'Third-party modules only'; + } + if ($detailed) { + $config[] = 'Detailed issues'; + } + $this->io->comment('Configuration: ' . implode(', ', $config)); + $this->io->newLine(); + + // Run scan with selected options + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $output); + } catch (\Exception $e) { + $this->resetPromptEnvironment(); + $this->io->error('Interactive mode failed: ' . $e->getMessage()); + $this->io->info('Falling back to default scan (third-party modules only)...'); + $this->io->newLine(); + return $this->runDirectMode($input, $output); + } + } + + /** + * Run direct mode with command line options + */ + private function runDirectMode(InputInterface $input, OutputInterface $output): int { $showAll = $input->getOption(self::OPTION_SHOW_ALL); $thirdPartyOnly = $input->getOption(self::OPTION_THIRD_PARTY_ONLY); @@ -65,6 +148,20 @@ protected function executeCommand(InputInterface $input, OutputInterface $output $this->io->title('Hyvä Theme Compatibility Check'); + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $output); + } + + /** + * Run the actual compatibility scan + */ + private function runScan( + bool $showAll, + bool $thirdPartyOnly, + bool $includeVendor, + bool $detailed, + OutputInterface $output + ): int { + // Determine filter logic: // - Default (no flags): Scan third-party only (exclude Magento_* but include vendor third-party) // - With --include-vendor: Scan everything including Magento_* @@ -92,6 +189,11 @@ protected function executeCommand(InputInterface $input, OutputInterface $output // Display summary $this->displaySummary($results); + // Display recommendations if there are issues + if ($results['hasIncompatibilities']) { + $this->displayRecommendations(); + } + // Return appropriate exit code return $results['summary']['criticalIssues'] > 0 ? Cli::RETURN_FAILURE @@ -192,19 +294,13 @@ private function displaySummary(array $results): void } else { $this->io->success('All scanned modules are Hyvä compatible!'); } - - // Provide helpful recommendations - $this->displayRecommendations($results); } /** * Display helpful recommendations */ - private function displayRecommendations(array $results): void + private function displayRecommendations(): void { - if ($results['summary']['incompatible'] === 0) { - return; - } $this->io->section('Recommendations'); @@ -219,4 +315,131 @@ private function displayRecommendations(array $results): void $this->io->text($recommendation); } } + + /** + * Check if running in an interactive terminal + */ + private function isInteractiveTerminal(OutputInterface $output): bool + { + // Check if output is decorated (supports ANSI codes) + if (!$output->isDecorated()) { + return false; + } + + // Check if STDIN is available and readable + if (!defined('STDIN') || !is_resource(STDIN)) { + return false; + } + + // Check for common non-interactive environments + $nonInteractiveEnvs = [ + 'CI', + 'GITHUB_ACTIONS', + 'GITLAB_CI', + 'JENKINS_URL', + 'TEAMCITY_VERSION', + ]; + + foreach ($nonInteractiveEnvs as $env) { + if ($this->getEnvVar($env) || $this->getServerVar($env)) { + return false; + } + } + + // Additional check: try to detect if running in a proper TTY + $sttyOutput = shell_exec('stty -g 2>/dev/null'); + return !empty($sttyOutput); + } + + /** + * Set environment for Laravel Prompts to work properly in Docker/DDEV + */ + private function setPromptEnvironment(): void + { + // Store original values for reset + $this->originalEnv = [ + 'COLUMNS' => $this->getEnvVar('COLUMNS'), + 'LINES' => $this->getEnvVar('LINES'), + 'TERM' => $this->getEnvVar('TERM'), + ]; + + // Set terminal environment variables using safe method + $this->setEnvVar('COLUMNS', '100'); + $this->setEnvVar('LINES', '40'); + $this->setEnvVar('TERM', 'xterm-256color'); + } + + /** + * Reset terminal environment after prompts + */ + private function resetPromptEnvironment(): void + { + // Reset environment variables to original state using secure methods + foreach ($this->originalEnv as $key => $value) { + if ($value === null) { + // Remove from our secure cache + $this->removeSecureEnvironmentValue($key); + } else { + // Restore original value using secure method + $this->setEnvVar($key, $value); + } + } + } + + /** + * Securely remove environment variable from cache + */ + private function removeSecureEnvironmentValue(string $name): void + { + // Remove the specific variable from our secure storage + unset($this->secureEnvStorage[$name]); + + // Clear the static cache to force refresh on next access + $this->clearEnvironmentCache(); + } + + /** + * Simplified environment variable getter + */ + private function getEnvVar(string $name): ?string + { + // Check secure storage first + if (isset($this->secureEnvStorage[$name])) { + return $this->secureEnvStorage[$name]; + } + + // Fall back to system environment + $value = getenv($name); + return $value !== false ? $value : null; + } + + /** + * Simplified server variable getter + */ + private function getServerVar(string $name): ?string + { + return $_SERVER[$name] ?? null; + } + + /** + * Simplified environment variable setter + */ + private function setEnvVar(string $name, string $value): void + { + $this->secureEnvStorage[$name] = $value; + putenv("$name=$value"); + } + + /** + * Clear environment cache + */ + private function clearEnvironmentCache(): void + { + // Force refresh on next access by clearing our storage + $this->secureEnvStorage = array_filter( + $this->secureEnvStorage, + fn($key) => in_array($key, ['COLUMNS', 'LINES', 'TERM']), + ARRAY_FILTER_USE_KEY + ); + } } From 0b999fe004c52be0f2c5ab0784f260f4372b2e6f Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:03:22 +0100 Subject: [PATCH 04/16] Docs: Add interactive mode documentation --- docs/commands.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/commands.md b/docs/commands.md index 16e796e..5c36c7e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -156,6 +156,21 @@ bin/magento mageforge:hyva:compatibility:check [options] - `--include-vendor` - Include Magento core modules in scan (default: third-party only) - `--detailed` / `-d` - Show detailed file-level issues for incompatible modules +**Interactive Mode**: +When running **without any options**, the command launches an interactive menu (using Laravel Prompts): + +```bash +# Launch interactive menu +bin/magento m:h:c:c +``` + +The menu allows you to select: +- ☐ Show all modules including compatible ones +- ☐ Include Magento core modules (default: third-party only) +- ☐ Show detailed file-level issues with line numbers + +Use **Space** to toggle options, **Enter** to confirm and start the scan. + **Default Behavior**: Without any flags, the command scans **third-party modules only** (excludes `Magento_*` modules but includes vendor third-party like Hyva, PayPal, Mollie, etc.). From 73f63f8b1f30322f36feb92029e9ec2d0b47d38e Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:07:13 +0100 Subject: [PATCH 05/16] Add 'incompatible only' option to interactive menu - New option: Show only incompatible modules (pre-selected by default) - Improves UX by filtering out compatible modules - Users can still choose 'Show all' for complete overview - Configuration display updated to reflect selection --- docs/commands.md | 3 +++ .../Hyva/CompatibilityCheckCommand.php | 26 +++++++++++++++---- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 5c36c7e..f269d35 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -165,12 +165,15 @@ bin/magento m:h:c:c ``` The menu allows you to select: +- ☑ Show only incompatible modules (default behavior) - ☐ Show all modules including compatible ones - ☐ Include Magento core modules (default: third-party only) - ☐ Show detailed file-level issues with line numbers Use **Space** to toggle options, **Enter** to confirm and start the scan. +**Note**: "Show only incompatible" is pre-selected by default, ensuring you only see modules with issues. + **Default Behavior**: Without any flags, the command scans **third-party modules only** (excludes `Magento_*` modules but includes vendor third-party like Hyva, PayPal, Mollie, etc.). diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 794d30b..ab97f59 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -90,10 +90,11 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp label: 'Select scan options', options: [ 'show-all' => 'Show all modules including compatible ones', + 'incompatible-only' => 'Show only incompatible modules (default behavior)', 'include-vendor' => 'Include Magento core modules (default: third-party only)', 'detailed' => 'Show detailed file-level issues with line numbers', ], - default: [], + default: ['incompatible-only'], hint: 'Space to toggle, Enter to confirm. Default: third-party modules only', required: false, ); @@ -104,6 +105,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp // Apply selected options to input $showAll = in_array('show-all', $selectedOptions); + $incompatibleOnly = in_array('incompatible-only', $selectedOptions); $includeVendor = in_array('include-vendor', $selectedOptions); $detailed = in_array('detailed', $selectedOptions); $thirdPartyOnly = false; // Not needed in interactive mode @@ -113,6 +115,10 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $config = []; if ($showAll) { $config[] = 'Show all modules'; + } elseif ($incompatibleOnly) { + $config[] = 'Show incompatible only'; + } else { + $config[] = 'Show modules with issues'; } if ($includeVendor) { $config[] = 'Include Magento core'; @@ -125,8 +131,8 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $this->io->comment('Configuration: ' . implode(', ', $config)); $this->io->newLine(); - // Run scan with selected options - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $output); + // Run scan with selected options (pass incompatibleOnly flag) + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $incompatibleOnly, $output); } catch (\Exception $e) { $this->resetPromptEnvironment(); $this->io->error('Interactive mode failed: ' . $e->getMessage()); @@ -148,7 +154,7 @@ private function runDirectMode(InputInterface $input, OutputInterface $output): $this->io->title('Hyvä Theme Compatibility Check'); - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $output); + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, false, $output); } /** @@ -159,6 +165,7 @@ private function runScan( bool $thirdPartyOnly, bool $includeVendor, bool $detailed, + bool $incompatibleOnly, OutputInterface $output ): int { @@ -178,8 +185,17 @@ private function runScan( $excludeVendor ); + // Determine display mode based on flags + // If incompatibleOnly is set, only show modules with issues + // If showAll is set, show everything + // Otherwise, show default (incompatible only) + $displayShowAll = $showAll; + if ($incompatibleOnly && !$showAll) { + $displayShowAll = false; // Only show incompatible + } + // Display results - $this->displayResults($results, $showAll || $detailed); + $this->displayResults($results, $displayShowAll || $detailed); // Display detailed issues if requested if ($detailed && $results['hasIncompatibilities']) { From a6a3769134508b8d5cc3387b36b544a0557e6019 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:09:06 +0100 Subject: [PATCH 06/16] Remove pre-selection from 'incompatible only' option --- docs/commands.md | 4 +--- src/Console/Command/Hyva/CompatibilityCheckCommand.php | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index f269d35..61d309a 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -165,15 +165,13 @@ bin/magento m:h:c:c ``` The menu allows you to select: -- ☑ Show only incompatible modules (default behavior) - ☐ Show all modules including compatible ones +- ☐ Show only incompatible modules (default behavior) - ☐ Include Magento core modules (default: third-party only) - ☐ Show detailed file-level issues with line numbers Use **Space** to toggle options, **Enter** to confirm and start the scan. -**Note**: "Show only incompatible" is pre-selected by default, ensuring you only see modules with issues. - **Default Behavior**: Without any flags, the command scans **third-party modules only** (excludes `Magento_*` modules but includes vendor third-party like Hyva, PayPal, Mollie, etc.). diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index ab97f59..1ec1100 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -94,7 +94,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp 'include-vendor' => 'Include Magento core modules (default: third-party only)', 'detailed' => 'Show detailed file-level issues with line numbers', ], - default: ['incompatible-only'], + default: [], hint: 'Space to toggle, Enter to confirm. Default: third-party modules only', required: false, ); From 73850e040eb315015d4c69111f12e89733929cda Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:13:09 +0100 Subject: [PATCH 07/16] Update src/Service/Hyva/ModuleScanner.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Service/Hyva/ModuleScanner.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php index 795f643..a39ae80 100644 --- a/src/Service/Hyva/ModuleScanner.php +++ b/src/Service/Hyva/ModuleScanner.php @@ -114,14 +114,14 @@ public function hasHyvaCompatibilityPackage(string $modulePath): bool // Check if this IS a Hyvä compatibility package $packageName = $composerData['name'] ?? ''; - if (str_contains($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { + if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { return true; } // Check dependencies for Hyvä packages $requires = $composerData['require'] ?? []; foreach ($requires as $package => $version) { - if (str_contains($package, 'hyva-themes/')) { + if (str_starts_with($package, 'hyva-themes/')) { return true; } } From ee13cc1c61fb192b9e732a073fb46aa606dc85b6 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:14:34 +0100 Subject: [PATCH 08/16] =?UTF-8?q?Update=20CHANGELOG=20for=20Hyv=C3=A4=20co?= =?UTF-8?q?mpatibility=20checker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8795ee..a332a5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ All notable changes to this project will be documented in this file. ## UNRELEASED +### Added +- feat: add Hyvä compatibility checker command (`mageforge:hyva:compatibility:check`) + - 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` + - Color-coded output (✓ Compatible, ⚠ Warnings, ✗ Incompatible) + - Detailed file-level issues with line numbers + - Exit code 1 for critical issues, 0 for success + - Command aliases: `m:h:c:c`, `hyva:check` + ## Latest Release ### [0.2.2] - 2025-06-05 From 3520dc82b97a2bbdf1de7d61eb3cf10d9518ec8a Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:16:57 +0100 Subject: [PATCH 09/16] =?UTF-8?q?=E2=9C=A8=20feat:=20update=20CHANGELOG=20?= =?UTF-8?q?and=20improve=20di.xml=20formatting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + docs/commands.md | 38 +++++++++++++++-- src/etc/di.xml | 103 ++++++++++++++++++++++++++--------------------- 3 files changed, 91 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a332a5b..427dcdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ## UNRELEASED ### Added + - feat: add Hyvä compatibility checker command (`mageforge:hyva:compatibility:check`) - Scans Magento modules for Hyvä theme compatibility issues - Detects RequireJS, Knockout.js, jQuery, and UI Components usage diff --git a/docs/commands.md b/docs/commands.md index 61d309a..5cd420d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -19,14 +19,17 @@ All commands in MageForge follow a consistent structure based on Symfony's Conso **File**: `/src/Console/Command/ListThemeCommand.php` **Dependencies**: + - `ThemeList` - Service to retrieve theme information **Usage**: + ```bash bin/magento mageforge:theme:list ``` **Implementation Details**: + - Retrieves all themes from the `ThemeList` service - Displays a formatted table with theme information (code, title, path) - Returns success status code @@ -40,16 +43,19 @@ bin/magento mageforge:theme:list **File**: `/src/Console/Command/BuildThemeCommand.php` **Dependencies**: + - `ThemePath` - Service to resolve theme paths - `ThemeList` - Service to retrieve theme information - `BuilderPool` - Service to get appropriate builders for themes **Usage**: + ```bash bin/magento mageforge:theme:build [...] ``` **Implementation Details**: + - If no theme codes are provided, displays an interactive prompt to select themes - For each selected theme: 1. Resolves the theme path @@ -67,16 +73,19 @@ bin/magento mageforge:theme:build [...] **File**: `/src/Console/Command/ThemeWatchCommand.php` **Dependencies**: + - `BuilderPool` - Service to get appropriate builders for themes - `ThemeList` - Service to retrieve theme information - `ThemePath` - Service to resolve theme paths **Usage**: + ```bash bin/magento mageforge:theme:watch [--theme=THEME] ``` **Implementation Details**: + - If no theme code is provided, displays an interactive prompt to select a theme - Resolves the theme path - Determines the appropriate builder for the theme type @@ -92,15 +101,18 @@ bin/magento mageforge:theme:watch [--theme=THEME] **File**: `/src/Console/Command/SystemCheckCommand.php` **Dependencies**: + - `ProductMetadataInterface` - For retrieving Magento version - `Escaper` - For HTML escaping output **Usage**: + ```bash bin/magento mageforge:system:check ``` **Implementation Details**: + - Retrieves and displays: - PHP version - Node.js version (with comparison to latest LTS) @@ -118,14 +130,17 @@ bin/magento mageforge:system:check **File**: `/src/Console/Command/VersionCommand.php` **Dependencies**: + - `File` - Filesystem driver for reading files **Usage**: + ```bash bin/magento mageforge:version ``` **Implementation Details**: + - Reads the current module version from `composer.lock` - Fetches the latest version from GitHub API - Displays both versions for comparison @@ -139,20 +154,24 @@ bin/magento mageforge:version **File**: `/src/Console/Command/Hyva/CompatibilityCheckCommand.php` **Dependencies**: + - `CompatibilityChecker` - Main orchestrator service for scanning modules **Usage**: + ```bash bin/magento mageforge:hyva:compatibility:check [options] ``` **Aliases**: + - `m:h:c:c` - `hyva:check` **Options**: + - `--show-all` / `-a` - Show all modules including compatible ones -- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento_* modules) +- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento\_\* modules) - `--include-vendor` - Include Magento core modules in scan (default: third-party only) - `--detailed` / `-d` - Show detailed file-level issues for incompatible modules @@ -165,6 +184,7 @@ bin/magento m:h:c:c ``` The menu allows you to select: + - ☐ Show all modules including compatible ones - ☐ Show only incompatible modules (default behavior) - ☐ Include Magento core modules (default: third-party only) @@ -176,6 +196,7 @@ Use **Space** to toggle options, **Enter** to confirm and start the scan. Without any flags, the command scans **third-party modules only** (excludes `Magento_*` modules but includes vendor third-party like Hyva, PayPal, Mollie, etc.). **Examples**: + ```bash # Basic scan (third-party modules only - DEFAULT) bin/magento m:h:c:c @@ -194,6 +215,7 @@ bin/magento mageforge:hyva:compatibility:check --detailed ``` **Implementation Details**: + - Scans module directories for JS, XML, and PHTML files - Detects incompatibility patterns: - **Critical Issues**: @@ -221,26 +243,30 @@ bin/magento mageforge:hyva:compatibility:check --detailed **Detected Patterns**: -*JavaScript Files (.js)*: +_JavaScript Files (.js)_: + - `define([` - RequireJS module definition - `require([` - RequireJS dependency loading - `ko.observable` / `ko.observableArray` / `ko.computed` - Knockout.js - `$.ajax` / `jQuery.ajax` - jQuery AJAX - `mage/` - Magento RequireJS module references -*XML Files (.xml)*: +_XML Files (.xml)_: + - ` - - - - OpenForgeProject\MageForge\Console\Command\System\VersionCommand - OpenForgeProject\MageForge\Console\Command\System\CheckCommand - OpenForgeProject\MageForge\Console\Command\Theme\ListCommand - OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand - OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand - OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand - - - + + + + + OpenForgeProject\MageForge\Console\Command\System\VersionCommand + + OpenForgeProject\MageForge\Console\Command\System\CheckCommand + + OpenForgeProject\MageForge\Console\Command\Theme\ListCommand + + OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand + + OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand + + OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand + + + - - - - - OpenForgeProject\MageForge\Service\ThemeBuilder\HyvaThemes\Builder - OpenForgeProject\MageForge\Service\ThemeBuilder\MagentoStandard\Builder - OpenForgeProject\MageForge\Service\ThemeBuilder\TailwindCSS\Builder - - - + + + + + + OpenForgeProject\MageForge\Service\ThemeBuilder\HyvaThemes\Builder + + OpenForgeProject\MageForge\Service\ThemeBuilder\MagentoStandard\Builder + + OpenForgeProject\MageForge\Service\ThemeBuilder\TailwindCSS\Builder + + + From 262968b6d3cbc0a1a69b315e1b1d1a14a333b472 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Sat, 10 Jan 2026 01:18:22 +0100 Subject: [PATCH 10/16] Update src/Service/Hyva/IncompatibilityDetector.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Service/Hyva/IncompatibilityDetector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index c666bf2..3ee906a 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -59,7 +59,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/ '//', 'description' => 'Block removal (review for Hyvä compatibility)', 'severity' => self::SEVERITY_WARNING, ], From d8952bd7a685bd548442a4b5191b66fc17776354 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 01:19:26 +0100 Subject: [PATCH 11/16] =?UTF-8?q?Add=20actual=20execution=20tests=20for=20?= =?UTF-8?q?Hyv=C3=A4=20compatibility=20checker=20in=20CI=20(#60)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * Add actual execution tests for Hyvä compatibility checker command Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> Co-authored-by: Mathias Elle --- .github/workflows/magento-compatibility.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/magento-compatibility.yml b/.github/workflows/magento-compatibility.yml index 1e32e4b..f695098 100644 --- a/.github/workflows/magento-compatibility.yml +++ b/.github/workflows/magento-compatibility.yml @@ -138,6 +138,15 @@ jobs: bin/magento mageforge:hyva:compatibility:check --help bin/magento m:h:c:c --help + echo "Test MageForge Hyvä Compatibility Check - Show all modules:" + bin/magento mageforge:hyva:compatibility:check --show-all + + echo "Test MageForge Hyvä Compatibility Check - Third party only:" + bin/magento m:h:c:c --third-party-only + + echo "Test MageForge Hyvä Compatibility Check - Detailed output:" + bin/magento m:h:c:c --show-all --detailed + - name: Test Summary run: | echo "MageForge module compatibility test with Magento ${{ matrix.magento-version }} completed" @@ -263,6 +272,15 @@ jobs: bin/magento mageforge:hyva:compatibility:check --help bin/magento m:h:c:c --help + echo "Test MageForge Hyvä Compatibility Check - Show all modules:" + bin/magento mageforge:hyva:compatibility:check --show-all + + echo "Test MageForge Hyvä Compatibility Check - Third party only:" + bin/magento m:h:c:c --third-party-only + + echo "Test MageForge Hyvä Compatibility Check - Detailed output:" + bin/magento m:h:c:c --show-all --detailed + - name: Test Summary run: | echo "MageForge module compatibility test with Magento 2.4.8 completed" From 96ceecf07c670f3a5c3841247d9dface2fe7bf73 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 01:19:46 +0100 Subject: [PATCH 12/16] =?UTF-8?q?Clarify=20exit=20code=20behavior=20in=20H?= =?UTF-8?q?yv=C3=A4=20compatibility=20checker=20documentation=20(#61)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Initial plan * Clarify exit code documentation for Hyvä compatibility checker Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> Co-authored-by: Mathias Elle --- docs/commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/commands.md b/docs/commands.md index 5cd420d..3ce7099 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -239,7 +239,7 @@ bin/magento mageforge:hyva:compatibility:check --detailed - Critical issues and warnings count - Shows detailed file paths and line numbers with `--detailed` flag - Provides helpful recommendations for resolving issues -- Returns exit code 1 if critical issues found, 0 otherwise +- Returns exit code 1 if critical issues are found, 0 otherwise (even if warnings exist) **Detected Patterns**: From 7e3ba242438d930384dfd4187535c6b5c3ebbd0a Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Mon, 12 Jan 2026 09:07:45 +0100 Subject: [PATCH 13/16] Update src/Console/Command/Hyva/CompatibilityCheckCommand.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Console/Command/Hyva/CompatibilityCheckCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 1ec1100..68b4c34 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -50,7 +50,7 @@ protected function configure(): void self::OPTION_INCLUDE_VENDOR, null, InputOption::VALUE_NONE, - 'Include vendor modules in scan (default: excluded)' + 'Include vendor modules in scan (default: excluded, but included with --third-party-only)' ) ->addOption( self::OPTION_DETAILED, From 20d92d6fb9fce59c548c633417d9f0d3c0f3c5ed Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 09:15:52 +0100 Subject: [PATCH 14/16] Initial plan (#64) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mathias Elle --- .../Hyva/CompatibilityCheckCommand.php | 15 +++- src/Service/Hyva/CompatibilityChecker.php | 30 ++++++- src/Service/Hyva/IncompatibilityDetector.php | 6 ++ src/Service/Hyva/ModuleScanner.php | 79 ++++++++++++++----- 4 files changed, 105 insertions(+), 25 deletions(-) diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 68b4c34..7575ace 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -13,6 +13,12 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +/** + * Command to check Magento modules for Hyvä theme compatibility issues + * + * Scans modules for RequireJS, Knockout.js, jQuery, and UI Components usage + * that would be incompatible with Hyvä themes. + */ class CompatibilityCheckCommand extends AbstractCommand { private const OPTION_SHOW_ALL = 'show-all'; @@ -169,10 +175,11 @@ private function runScan( OutputInterface $output ): int { - // Determine filter logic: - // - Default (no flags): Scan third-party only (exclude Magento_* but include vendor third-party) - // - With --include-vendor: Scan everything including Magento_* - // - With --third-party-only: Explicitly scan only third-party + // Determine filter logic for vendor and third-party modules: + // - excludeVendor controls whether to scan modules in the vendor/ directory + // - By default (no flags): scan third-party modules including those in vendor/ + // - With --include-vendor: scan everything including Magento_* core modules + // - With --third-party-only: explicitly scan only third-party modules $scanThirdPartyOnly = $thirdPartyOnly || (!$includeVendor && !$thirdPartyOnly); $excludeVendor = false; // Always include vendor for third-party scanning diff --git a/src/Service/Hyva/CompatibilityChecker.php b/src/Service/Hyva/CompatibilityChecker.php index 0b23735..219475c 100644 --- a/src/Service/Hyva/CompatibilityChecker.php +++ b/src/Service/Hyva/CompatibilityChecker.php @@ -9,6 +9,12 @@ use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\SymfonyStyle; +/** + * Service that orchestrates Hyvä compatibility checking across Magento modules + * + * This service scans modules, detects incompatibilities with Hyvä theme framework, + * and provides formatted results with summary statistics. + */ class CompatibilityChecker { public function __construct( @@ -21,6 +27,11 @@ public function __construct( /** * Check all modules for Hyvä compatibility * + * @param SymfonyStyle $io Symfony Style IO for output + * @param OutputInterface $output Console output interface + * @param bool $showAll Whether to show all modules (including compatible ones) + * @param bool $thirdPartyOnly Whether to scan only third-party modules (excludes Magento_* modules) + * @param bool $excludeVendor Whether to exclude modules from the vendor/ directory (true = exclude, false = include) * @return array Results with structure: ['modules' => [], 'summary' => [], 'hasIncompatibilities' => bool] */ public function check( @@ -90,7 +101,9 @@ public function check( } $results['summary']['criticalIssues'] += $scanResult['criticalIssues']; - $results['summary']['warningIssues'] += ($scanResult['totalIssues'] - $scanResult['criticalIssues']); + // Calculate warnings explicitly to support future severity levels + $warningCount = max(0, $scanResult['totalIssues'] - $scanResult['criticalIssues']); + $results['summary']['warningIssues'] += $warningCount; } return $results; @@ -172,7 +185,8 @@ private function getIssuesDisplay(array $moduleData): string $parts[] = sprintf('%d critical', $scanResult['criticalIssues']); } - $warnings = $scanResult['totalIssues'] - $scanResult['criticalIssues']; + // Calculate warnings explicitly to ensure non-negative values + $warnings = max(0, $scanResult['totalIssues'] - $scanResult['criticalIssues']); if ($warnings > 0) { $parts[] = sprintf('%d warning(s)', $warnings); } @@ -185,7 +199,17 @@ private function getIssuesDisplay(array $moduleData): string */ public function getDetailedIssues(string $moduleName, array $moduleData): array { - $files = $moduleData['scanResult']['files'] ?? []; + // Safely access nested array structure + $scanResult = $moduleData['scanResult'] ?? []; + if (!is_array($scanResult)) { + return []; + } + + $files = $scanResult['files'] ?? []; + if (!is_array($files)) { + return []; + } + $details = []; foreach ($files as $filePath => $issues) { diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index 3ee906a..3c8e145 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -6,6 +6,12 @@ use Magento\Framework\Filesystem\Driver\File; +/** + * Service that detects Hyvä incompatibility patterns in JavaScript, XML, and PHTML files + * + * Uses pattern matching to identify RequireJS, Knockout.js, jQuery, and UI Components + * usage that would be problematic in a Hyvä environment. + */ class IncompatibilityDetector { private const SEVERITY_CRITICAL = 'critical'; diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php index a39ae80..9a956a4 100644 --- a/src/Service/Hyva/ModuleScanner.php +++ b/src/Service/Hyva/ModuleScanner.php @@ -6,6 +6,12 @@ use Magento\Framework\Filesystem\Driver\File; +/** + * Service that scans module directories for files containing Hyvä compatibility issues + * + * Recursively scans JavaScript, XML, and PHTML files within module directories + * to identify patterns that may be incompatible with Hyvä themes. + */ class ModuleScanner { private const SCAN_EXTENSIONS = ['js', 'xml', 'phtml']; @@ -87,14 +93,45 @@ private function findRelevantFiles(string $directory): array } } } catch (\Exception $e) { - // Silently skip directories that can't be read + // Skip directories that can't be read, but log details when debugging + if (getenv('MAGEFORGE_DEBUG')) { + error_log(sprintf( + '[MageForge][ModuleScanner] Failed to read directory "%s": %s', + $directory, + $e->getMessage() + )); + } } return $relevantFiles; } /** - * Check if module has Hyvä compatibility package + * Check if module has Hyvä compatibility package based on composer data + * + * @param array $composerData Parsed composer.json data + */ + private function isHyvaCompatibilityPackage(array $composerData): bool + { + // Check if this IS a Hyvä compatibility package + $packageName = $composerData['name'] ?? ''; + if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { + return true; + } + + // Check dependencies for Hyvä packages + $requires = $composerData['require'] ?? []; + foreach ($requires as $package => $version) { + if (str_starts_with($package, 'hyva-themes/')) { + return true; + } + } + + return false; + } + + /** + * Check if module has Hyvä compatibility package (public wrapper) */ public function hasHyvaCompatibilityPackage(string $modulePath): bool { @@ -112,24 +149,18 @@ public function hasHyvaCompatibilityPackage(string $modulePath): bool return false; } - // Check if this IS a Hyvä compatibility package - $packageName = $composerData['name'] ?? ''; - if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { - return true; - } - - // Check dependencies for Hyvä packages - $requires = $composerData['require'] ?? []; - foreach ($requires as $package => $version) { - if (str_starts_with($package, 'hyva-themes/')) { - return true; - } - } + return $this->isHyvaCompatibilityPackage($composerData); } catch (\Exception $e) { + // Log error when debugging to help identify JSON or file read issues + if (getenv('MAGEFORGE_DEBUG')) { + error_log(sprintf( + '[MageForge][ModuleScanner] Failed to read composer.json at "%s": %s', + $composerPath, + $e->getMessage() + )); + } return false; } - - return false; } /** @@ -147,12 +178,24 @@ public function getModuleInfo(string $modulePath): array $content = $this->fileDriver->fileGetContents($composerPath); $composerData = json_decode($content, true); + if (!is_array($composerData)) { + return ['name' => 'Unknown', 'version' => 'Unknown', 'isHyvaAware' => false]; + } + return [ 'name' => $composerData['name'] ?? 'Unknown', 'version' => $composerData['version'] ?? 'Unknown', - 'isHyvaAware' => $this->hasHyvaCompatibilityPackage($modulePath), + 'isHyvaAware' => $this->isHyvaCompatibilityPackage($composerData), ]; } catch (\Exception $e) { + // Log error when debugging to help identify JSON parsing or file read issues + if (getenv('MAGEFORGE_DEBUG')) { + error_log(sprintf( + '[MageForge][ModuleScanner] Failed to read module info at "%s": %s', + $composerPath, + $e->getMessage() + )); + } return ['name' => 'Unknown', 'version' => 'Unknown', 'isHyvaAware' => false]; } } From 2f4a1c18ac8e16b99f9ede5edc0aab948995cd74 Mon Sep 17 00:00:00 2001 From: Mathias Elle Date: Mon, 12 Jan 2026 09:24:54 +0100 Subject: [PATCH 15/16] Update src/Service/Hyva/IncompatibilityDetector.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Service/Hyva/IncompatibilityDetector.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index 3c8e145..038e111 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -43,7 +43,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_WARNING, ], [ - 'pattern' => '/mage\//', + 'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']/', 'description' => 'Magento RequireJS module reference', 'severity' => self::SEVERITY_CRITICAL, ], From f77c7acf7994a58275d0752844e427787e02cdce Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 09:54:25 +0100 Subject: [PATCH 16/16] Address code review feedback: fix duplicate config, improve regex patterns, enhance security (#65) * Initial plan * Apply code review feedback: fix duplicate di.xml, improve regex patterns, safer environment handling Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> * Replace error suppression with explicit exception handling in TTY detection Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> * Fix RequireJS pattern regex to properly match module references Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: dermatz <6103201+dermatz@users.noreply.github.com> --- docs/commands.md | 2 +- .../Hyva/CompatibilityCheckCommand.php | 43 ++++++++++++------- src/Service/Hyva/IncompatibilityDetector.php | 6 +-- src/Service/Hyva/ModuleScanner.php | 42 +++++++----------- src/etc/di.xml | 36 ++-------------- 5 files changed, 50 insertions(+), 79 deletions(-) diff --git a/docs/commands.md b/docs/commands.md index 3579f64..8b4391d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -205,7 +205,7 @@ bin/magento mageforge:hyva:compatibility:check [options] **Options**: - `--show-all` / `-a` - Show all modules including compatible ones -- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento\_\* modules) +- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento_* modules) - `--include-vendor` - Include Magento core modules in scan (default: third-party only) - `--detailed` / `-d` - Show detailed file-level issues for incompatible modules diff --git a/src/Console/Command/Hyva/CompatibilityCheckCommand.php b/src/Console/Command/Hyva/CompatibilityCheckCommand.php index 7575ace..e3f3fc6 100644 --- a/src/Console/Command/Hyva/CompatibilityCheckCommand.php +++ b/src/Console/Command/Hyva/CompatibilityCheckCommand.php @@ -96,8 +96,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp label: 'Select scan options', options: [ 'show-all' => 'Show all modules including compatible ones', - 'incompatible-only' => 'Show only incompatible modules (default behavior)', - 'include-vendor' => 'Include Magento core modules (default: third-party only)', + 'include-vendor' => 'Include vendor modules (default: excluded)', 'detailed' => 'Show detailed file-level issues with line numbers', ], default: [], @@ -111,7 +110,6 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp // Apply selected options to input $showAll = in_array('show-all', $selectedOptions); - $incompatibleOnly = in_array('incompatible-only', $selectedOptions); $includeVendor = in_array('include-vendor', $selectedOptions); $detailed = in_array('detailed', $selectedOptions); $thirdPartyOnly = false; // Not needed in interactive mode @@ -121,13 +119,11 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $config = []; if ($showAll) { $config[] = 'Show all modules'; - } elseif ($incompatibleOnly) { - $config[] = 'Show incompatible only'; } else { $config[] = 'Show modules with issues'; } if ($includeVendor) { - $config[] = 'Include Magento core'; + $config[] = 'Include vendor modules'; } else { $config[] = 'Third-party modules only'; } @@ -137,8 +133,8 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp $this->io->comment('Configuration: ' . implode(', ', $config)); $this->io->newLine(); - // Run scan with selected options (pass incompatibleOnly flag) - return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $incompatibleOnly, $output); + // Run scan with selected options + return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, false, $output); } catch (\Exception $e) { $this->resetPromptEnvironment(); $this->io->error('Interactive mode failed: ' . $e->getMessage()); @@ -176,12 +172,11 @@ private function runScan( ): int { // Determine filter logic for vendor and third-party modules: - // - excludeVendor controls whether to scan modules in the vendor/ directory - // - By default (no flags): scan third-party modules including those in vendor/ - // - With --include-vendor: scan everything including Magento_* core modules + // - By default (no flags): scan third-party modules excluding those in vendor/ + // - With --include-vendor: include vendor modules in the scan // - With --third-party-only: explicitly scan only third-party modules $scanThirdPartyOnly = $thirdPartyOnly || (!$includeVendor && !$thirdPartyOnly); - $excludeVendor = false; // Always include vendor for third-party scanning + $excludeVendor = !$includeVendor; // Run the compatibility check $results = $this->compatibilityChecker->check( @@ -369,9 +364,25 @@ private function isInteractiveTerminal(OutputInterface $output): bool } } - // Additional check: try to detect if running in a proper TTY - $sttyOutput = shell_exec('stty -g 2>/dev/null'); - return !empty($sttyOutput); + // Additional check: detect if running in a proper TTY using safer methods + if (\function_exists('stream_isatty') && \defined('STDIN')) { + try { + return \stream_isatty(\STDIN); + } catch (\Throwable $e) { + // Fall through to next check + } + } + + if (\function_exists('posix_isatty') && \defined('STDIN')) { + try { + return \posix_isatty(\STDIN); + } catch (\Throwable $e) { + // Fall through to default + } + } + + // Conservative default if no TTY-detection functions are available + return false; } /** @@ -450,7 +461,7 @@ private function getServerVar(string $name): ?string private function setEnvVar(string $name, string $value): void { $this->secureEnvStorage[$name] = $value; - putenv("$name=$value"); + $_SERVER[$name] = $value; } /** diff --git a/src/Service/Hyva/IncompatibilityDetector.php b/src/Service/Hyva/IncompatibilityDetector.php index 038e111..b3141b2 100644 --- a/src/Service/Hyva/IncompatibilityDetector.php +++ b/src/Service/Hyva/IncompatibilityDetector.php @@ -43,7 +43,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_WARNING, ], [ - 'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']/', + 'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']\s*[^\]]*\]/', 'description' => 'Magento RequireJS module reference', 'severity' => self::SEVERITY_CRITICAL, ], @@ -65,7 +65,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '//', + 'pattern' => '/]*\bremove\s*=\s*"true"[^>]*>/s', 'description' => 'Block removal (review for Hyvä compatibility)', 'severity' => self::SEVERITY_WARNING, ], @@ -82,7 +82,7 @@ class IncompatibilityDetector 'severity' => self::SEVERITY_CRITICAL, ], [ - 'pattern' => '/\$\(.*\)\..*\(/', + 'pattern' => '/\$\([^)]*\)\s*\.(on|click|ready|change|keyup|keydown|submit|ajax|each|css|hide|show|addClass|removeClass|toggleClass|append|prepend|html|text|val|attr|prop|data|trigger|find|parent|children)\s*\(/', 'description' => 'jQuery DOM manipulation', 'severity' => self::SEVERITY_WARNING, ], diff --git a/src/Service/Hyva/ModuleScanner.php b/src/Service/Hyva/ModuleScanner.php index 9a956a4..c695eb3 100644 --- a/src/Service/Hyva/ModuleScanner.php +++ b/src/Service/Hyva/ModuleScanner.php @@ -107,31 +107,7 @@ private function findRelevantFiles(string $directory): array } /** - * Check if module has Hyvä compatibility package based on composer data - * - * @param array $composerData Parsed composer.json data - */ - private function isHyvaCompatibilityPackage(array $composerData): bool - { - // Check if this IS a Hyvä compatibility package - $packageName = $composerData['name'] ?? ''; - if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { - return true; - } - - // Check dependencies for Hyvä packages - $requires = $composerData['require'] ?? []; - foreach ($requires as $package => $version) { - if (str_starts_with($package, 'hyva-themes/')) { - return true; - } - } - - return false; - } - - /** - * Check if module has Hyvä compatibility package (public wrapper) + * Check if module has Hyvä compatibility package */ public function hasHyvaCompatibilityPackage(string $modulePath): bool { @@ -149,7 +125,21 @@ public function hasHyvaCompatibilityPackage(string $modulePath): bool return false; } - return $this->isHyvaCompatibilityPackage($composerData); + // Check if this IS a Hyvä compatibility package + $packageName = $composerData['name'] ?? ''; + if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) { + return true; + } + + // Check dependencies for Hyvä packages + $requires = $composerData['require'] ?? []; + foreach ($requires as $package => $version) { + if (str_starts_with($package, 'hyva-themes/')) { + return true; + } + } + + return false; } catch (\Exception $e) { // Log error when debugging to help identify JSON or file read issues if (getenv('MAGEFORGE_DEBUG')) { diff --git a/src/etc/di.xml b/src/etc/di.xml index 10f7306..beb8235 100644 --- a/src/etc/di.xml +++ b/src/etc/di.xml @@ -3,39 +3,6 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd" > - - - - - OpenForgeProject\MageForge\Console\Command\System\VersionCommand - - OpenForgeProject\MageForge\Console\Command\System\CheckCommand - - OpenForgeProject\MageForge\Console\Command\Theme\ListCommand - - OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand - - OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand - - OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand - - - OpenForgeProject\MageForge\Console\Command\Static\CleanCommand + OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand