Skip to content

Commit 8d92503

Browse files
dermatzCopilotCopilot
authored
Feature hyva compat checker (#66)
* Add Hyvä compatibility checker command - 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 * 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 * 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 * Docs: Add interactive mode documentation * 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 * Remove pre-selection from 'incompatible only' option * Update src/Service/Hyva/ModuleScanner.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update CHANGELOG for Hyvä compatibility checker * ✨ feat: update CHANGELOG and improve di.xml formatting * Update src/Service/Hyva/IncompatibilityDetector.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add actual execution tests for Hyvä compatibility checker in CI (#60) * 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 <m.elle@dermatz.de> * Clarify exit code behavior in Hyvä compatibility checker documentation (#61) * 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 <m.elle@dermatz.de> * Update src/Console/Command/Hyva/CompatibilityCheckCommand.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Initial plan (#64) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Mathias Elle <m.elle@dermatz.de> * Update src/Service/Hyva/IncompatibilityDetector.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * 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> * Revert "Address code review feedback: fix duplicate config, improve regex patterns, enhance security (#65)" This reverts commit f77c7ac. * Update docs/commands.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * ✨ feat: register Hyvä compatibility check command in di.xml * ♻️ refactor: improve compatibility check command options and logic --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
1 parent 2a05707 commit 8d92503

5 files changed

Lines changed: 82 additions & 119 deletions

File tree

docs/commands.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ bin/magento mageforge:hyva:compatibility:check [options]
205205
**Options**:
206206

207207
- `--show-all` / `-a` - Show all modules including compatible ones
208-
- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento_* modules)
208+
- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento\_\* modules)
209209
- `--include-vendor` - Include Magento core modules in scan (default: third-party only)
210210
- `--detailed` / `-d` - Show detailed file-level issues for incompatible modules
211211

@@ -273,7 +273,7 @@ bin/magento mageforge:hyva:compatibility:check --detailed
273273
- Critical issues and warnings count
274274
- Shows detailed file paths and line numbers with `--detailed` flag
275275
- Provides helpful recommendations for resolving issues
276-
- Returns exit code 1 if critical issues are found, 0 otherwise (even if warnings exist)
276+
- Returns exit code 1 if any critical issues are found. If only warnings (and no critical issues) are detected, the command returns exit code 0 so CI/CD pipelines do not fail on warnings alone.
277277

278278
**Detected Patterns**:
279279

src/Console/Command/Hyva/CompatibilityCheckCommand.php

Lines changed: 26 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ protected function configure(): void
5656
self::OPTION_INCLUDE_VENDOR,
5757
null,
5858
InputOption::VALUE_NONE,
59-
'Include vendor modules in scan (default: excluded, but included with --third-party-only)'
59+
'Include Magento core modules (default: third-party modules only)'
6060
)
6161
->addOption(
6262
self::OPTION_DETAILED,
@@ -96,7 +96,8 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp
9696
label: 'Select scan options',
9797
options: [
9898
'show-all' => 'Show all modules including compatible ones',
99-
'include-vendor' => 'Include vendor modules (default: excluded)',
99+
'incompatible-only' => 'Show only incompatible modules (default behavior)',
100+
'include-vendor' => 'Include Magento core modules (default: third-party only)',
100101
'detailed' => 'Show detailed file-level issues with line numbers',
101102
],
102103
default: [],
@@ -110,6 +111,7 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp
110111

111112
// Apply selected options to input
112113
$showAll = in_array('show-all', $selectedOptions);
114+
$incompatibleOnly = in_array('incompatible-only', $selectedOptions);
113115
$includeVendor = in_array('include-vendor', $selectedOptions);
114116
$detailed = in_array('detailed', $selectedOptions);
115117
$thirdPartyOnly = false; // Not needed in interactive mode
@@ -119,11 +121,13 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp
119121
$config = [];
120122
if ($showAll) {
121123
$config[] = 'Show all modules';
124+
} elseif ($incompatibleOnly) {
125+
$config[] = 'Show incompatible only';
122126
} else {
123127
$config[] = 'Show modules with issues';
124128
}
125129
if ($includeVendor) {
126-
$config[] = 'Include vendor modules';
130+
$config[] = 'Include Magento core';
127131
} else {
128132
$config[] = 'Third-party modules only';
129133
}
@@ -133,8 +137,8 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp
133137
$this->io->comment('Configuration: ' . implode(', ', $config));
134138
$this->io->newLine();
135139

136-
// Run scan with selected options
137-
return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, false, $output);
140+
// Run scan with selected options (pass incompatibleOnly flag)
141+
return $this->runScan($showAll, $thirdPartyOnly, $includeVendor, $detailed, $incompatibleOnly, $output);
138142
} catch (\Exception $e) {
139143
$this->resetPromptEnvironment();
140144
$this->io->error('Interactive mode failed: ' . $e->getMessage());
@@ -171,12 +175,12 @@ private function runScan(
171175
OutputInterface $output
172176
): int {
173177

174-
// Determine filter logic for vendor and third-party modules:
175-
// - By default (no flags): scan third-party modules excluding those in vendor/
176-
// - With --include-vendor: include vendor modules in the scan
177-
// - With --third-party-only: explicitly scan only third-party modules
178-
$scanThirdPartyOnly = $thirdPartyOnly || (!$includeVendor && !$thirdPartyOnly);
179-
$excludeVendor = !$includeVendor;
178+
// Determine filter logic:
179+
// - thirdPartyOnly: Only scan non-Magento_* modules (default behavior)
180+
// - includeVendor: Also scan Magento_* core modules
181+
// - excludeVendor: Whether to exclude vendor/ directory (always false for now)
182+
$scanThirdPartyOnly = !$includeVendor;
183+
$excludeVendor = false;
180184

181185
// Run the compatibility check
182186
$results = $this->compatibilityChecker->check(
@@ -187,17 +191,14 @@ private function runScan(
187191
$excludeVendor
188192
);
189193

190-
// Determine display mode based on flags
191-
// If incompatibleOnly is set, only show modules with issues
192-
// If showAll is set, show everything
193-
// Otherwise, show default (incompatible only)
194-
$displayShowAll = $showAll;
195-
if ($incompatibleOnly && !$showAll) {
196-
$displayShowAll = false; // Only show incompatible
197-
}
194+
// Determine display mode:
195+
// showAll = show all modules including compatible ones
196+
// incompatibleOnly = show only modules with critical issues
197+
// default = show modules with any issues (critical or warnings)
198+
$displayShowAll = $showAll && !$incompatibleOnly;
198199

199200
// Display results
200-
$this->displayResults($results, $displayShowAll || $detailed);
201+
$this->displayResults($results, $displayShowAll);
201202

202203
// Display detailed issues if requested
203204
if ($detailed && $results['hasIncompatibilities']) {
@@ -364,25 +365,9 @@ private function isInteractiveTerminal(OutputInterface $output): bool
364365
}
365366
}
366367

367-
// Additional check: detect if running in a proper TTY using safer methods
368-
if (\function_exists('stream_isatty') && \defined('STDIN')) {
369-
try {
370-
return \stream_isatty(\STDIN);
371-
} catch (\Throwable $e) {
372-
// Fall through to next check
373-
}
374-
}
375-
376-
if (\function_exists('posix_isatty') && \defined('STDIN')) {
377-
try {
378-
return \posix_isatty(\STDIN);
379-
} catch (\Throwable $e) {
380-
// Fall through to default
381-
}
382-
}
383-
384-
// Conservative default if no TTY-detection functions are available
385-
return false;
368+
// Additional check: try to detect if running in a proper TTY
369+
$sttyOutput = shell_exec('stty -g 2>/dev/null');
370+
return !empty($sttyOutput);
386371
}
387372

388373
/**
@@ -425,11 +410,7 @@ private function resetPromptEnvironment(): void
425410
*/
426411
private function removeSecureEnvironmentValue(string $name): void
427412
{
428-
// Remove the specific variable from our secure storage
429413
unset($this->secureEnvStorage[$name]);
430-
431-
// Clear the static cache to force refresh on next access
432-
$this->clearEnvironmentCache();
433414
}
434415

435416
/**
@@ -461,19 +442,8 @@ private function getServerVar(string $name): ?string
461442
private function setEnvVar(string $name, string $value): void
462443
{
463444
$this->secureEnvStorage[$name] = $value;
464-
$_SERVER[$name] = $value;
445+
putenv("$name=$value");
465446
}
466447

467-
/**
468-
* Clear environment cache
469-
*/
470-
private function clearEnvironmentCache(): void
471-
{
472-
// Force refresh on next access by clearing our storage
473-
$this->secureEnvStorage = array_filter(
474-
$this->secureEnvStorage,
475-
fn($key) => in_array($key, ['COLUMNS', 'LINES', 'TERM']),
476-
ARRAY_FILTER_USE_KEY
477-
);
478-
}
448+
479449
}

src/Service/Hyva/IncompatibilityDetector.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class IncompatibilityDetector
4343
'severity' => self::SEVERITY_WARNING,
4444
],
4545
[
46-
'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']\s*[^\]]*\]/',
46+
'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']/',
4747
'description' => 'Magento RequireJS module reference',
4848
'severity' => self::SEVERITY_CRITICAL,
4949
],
@@ -65,7 +65,7 @@ class IncompatibilityDetector
6565
'severity' => self::SEVERITY_CRITICAL,
6666
],
6767
[
68-
'pattern' => '/<referenceBlock\b[^>]*\bremove\s*=\s*"true"[^>]*>/s',
68+
'pattern' => '/<referenceBlock.*remove="true">/',
6969
'description' => 'Block removal (review for Hyvä compatibility)',
7070
'severity' => self::SEVERITY_WARNING,
7171
],
@@ -82,7 +82,7 @@ class IncompatibilityDetector
8282
'severity' => self::SEVERITY_CRITICAL,
8383
],
8484
[
85-
'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*\(/',
85+
'pattern' => '/\$\(.*\)\..*\(/',
8686
'description' => 'jQuery DOM manipulation',
8787
'severity' => self::SEVERITY_WARNING,
8888
],

src/Service/Hyva/ModuleScanner.php

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,31 @@ private function findRelevantFiles(string $directory): array
107107
}
108108

109109
/**
110-
* Check if module has Hyvä compatibility package
110+
* Check if module has Hyvä compatibility package based on composer data
111+
*
112+
* @param array $composerData Parsed composer.json data
113+
*/
114+
private function isHyvaCompatibilityPackage(array $composerData): bool
115+
{
116+
// Check if this IS a Hyvä compatibility package
117+
$packageName = $composerData['name'] ?? '';
118+
if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) {
119+
return true;
120+
}
121+
122+
// Check dependencies for Hyvä packages
123+
$requires = $composerData['require'] ?? [];
124+
foreach ($requires as $package => $version) {
125+
if (str_starts_with($package, 'hyva-themes/')) {
126+
return true;
127+
}
128+
}
129+
130+
return false;
131+
}
132+
133+
/**
134+
* Check if module has Hyvä compatibility package (public wrapper)
111135
*/
112136
public function hasHyvaCompatibilityPackage(string $modulePath): bool
113137
{
@@ -125,21 +149,7 @@ public function hasHyvaCompatibilityPackage(string $modulePath): bool
125149
return false;
126150
}
127151

128-
// Check if this IS a Hyvä compatibility package
129-
$packageName = $composerData['name'] ?? '';
130-
if (str_starts_with($packageName, 'hyva-themes/') && str_contains($packageName, '-compat')) {
131-
return true;
132-
}
133-
134-
// Check dependencies for Hyvä packages
135-
$requires = $composerData['require'] ?? [];
136-
foreach ($requires as $package => $version) {
137-
if (str_starts_with($package, 'hyva-themes/')) {
138-
return true;
139-
}
140-
}
141-
142-
return false;
152+
return $this->isHyvaCompatibilityPackage($composerData);
143153
} catch (\Exception $e) {
144154
// Log error when debugging to help identify JSON or file read issues
145155
if (getenv('MAGEFORGE_DEBUG')) {

src/etc/di.xml

Lines changed: 25 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,54 +3,37 @@
33
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
44
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"
55
>
6-
<type name="Magento\Framework\Console\CommandList">
7-
<arguments>
8-
<argument
9-
name="commands"
10-
xsi:type="array"
11-
>
12-
<item name="mageforge_system_version"
13-
xsi:type="object"
14-
>OpenForgeProject\MageForge\Console\Command\System\VersionCommand</item>
15-
<item name="mageforge_system_check"
16-
xsi:type="object"
17-
>OpenForgeProject\MageForge\Console\Command\System\CheckCommand</item>
18-
<item name="mageforge_theme_list"
19-
xsi:type="object"
20-
>OpenForgeProject\MageForge\Console\Command\Theme\ListCommand</item>
21-
<item name="mageforge_theme_build"
22-
xsi:type="object"
23-
>OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand</item>
24-
<item name="mageforge_theme_watch"
25-
xsi:type="object"
26-
>OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand</item>
27-
<item name="mageforge_static_clean"
28-
xsi:type="object"
29-
>OpenForgeProject\MageForge\Console\Command\Static\CleanCommand</item>
30-
<item name="mageforge_hyva_compatibility_check"
31-
xsi:type="object"
32-
>OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand</item>
33-
</argument>
34-
</arguments>
35-
</type>
6+
<!-- Register CLI Commands -->
7+
<type name="Magento\Framework\Console\CommandList">
8+
<arguments>
9+
<argument name="commands" xsi:type="array">
10+
<item name="mageforge_system_version" xsi:type="object">
11+
OpenForgeProject\MageForge\Console\Command\System\VersionCommand</item>
12+
<item name="mageforge_system_check" xsi:type="object">
13+
OpenForgeProject\MageForge\Console\Command\System\CheckCommand</item>
14+
<item name="mageforge_theme_list" xsi:type="object">
15+
OpenForgeProject\MageForge\Console\Command\Theme\ListCommand</item>
16+
<item name="mageforge_theme_build" xsi:type="object">
17+
OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand</item>
18+
<item name="mageforge_theme_watch" xsi:type="object">
19+
OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand</item>
20+
<item name="mageforge_static_clean" xsi:type="object">
21+
OpenForgeProject\MageForge\Console\Command\Static\CleanCommand</item>
22+
<item name="mageforge_hyva_compatibility_check" xsi:type="object">
23+
OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand</item>
24+
</argument>
25+
</arguments>
26+
</type>
3627

3728
<!-- Configure Theme Builder Pool -->
3829
<type name="OpenForgeProject\MageForge\Service\ThemeBuilder\BuilderPool">
3930
<arguments>
40-
<argument name="builders"
41-
xsi:type="array"
42-
>
43-
<item name="hyva"
44-
xsi:type="object"
45-
>
31+
<argument name="builders" xsi:type="array">
32+
<item name="hyva" xsi:type="object">
4633
OpenForgeProject\MageForge\Service\ThemeBuilder\HyvaThemes\Builder</item>
47-
<item name="standard"
48-
xsi:type="object"
49-
>
34+
<item name="standard" xsi:type="object">
5035
OpenForgeProject\MageForge\Service\ThemeBuilder\MagentoStandard\Builder</item>
51-
<item name="tailwindcss"
52-
xsi:type="object"
53-
>
36+
<item name="tailwindcss" xsi:type="object">
5437
OpenForgeProject\MageForge\Service\ThemeBuilder\TailwindCSS\Builder</item>
5538
</argument>
5639
</arguments>

0 commit comments

Comments
 (0)