Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
11634f2
Add Hyvä compatibility checker command
dermatz Jan 9, 2026
44d98e1
Fix: Default scan now includes third-party modules
dermatz Jan 9, 2026
c959f74
Add interactive menu with Laravel Prompts
dermatz Jan 10, 2026
0b999fe
Docs: Add interactive mode documentation
dermatz Jan 10, 2026
73f63f8
Add 'incompatible only' option to interactive menu
dermatz Jan 10, 2026
a6a3769
Remove pre-selection from 'incompatible only' option
dermatz Jan 10, 2026
73850e0
Update src/Service/Hyva/ModuleScanner.php
dermatz Jan 10, 2026
ee13cc1
Update CHANGELOG for Hyvä compatibility checker
dermatz Jan 10, 2026
3520dc8
✨ feat: update CHANGELOG and improve di.xml formatting
dermatz Jan 10, 2026
262968b
Update src/Service/Hyva/IncompatibilityDetector.php
dermatz Jan 10, 2026
d8952bd
Add actual execution tests for Hyvä compatibility checker in CI (#60)
Copilot Jan 10, 2026
96ceecf
Clarify exit code behavior in Hyvä compatibility checker documentatio…
Copilot Jan 10, 2026
7e3ba24
Update src/Console/Command/Hyva/CompatibilityCheckCommand.php
dermatz Jan 12, 2026
2abf4e0
Merge branch 'main' into feature-hyva-compat-checker
dermatz Jan 12, 2026
20d92d6
Initial plan (#64)
Copilot Jan 12, 2026
2f4a1c1
Update src/Service/Hyva/IncompatibilityDetector.php
dermatz Jan 12, 2026
f77c7ac
Address code review feedback: fix duplicate config, improve regex pat…
Copilot Jan 12, 2026
4bf1568
Revert "Address code review feedback: fix duplicate config, improve r…
dermatz Jan 12, 2026
4c07b5d
Merge branch 'main' into feature-hyva-compat-checker
dermatz Jan 12, 2026
58a2414
Update docs/commands.md
dermatz Jan 12, 2026
6c7fd76
✨ feat: register Hyvä compatibility check command in di.xml
dermatz Jan 12, 2026
df9f58c
♻️ refactor: improve compatibility check command options and logic
dermatz Jan 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling inconsistency: "Magento_*" at line 208 uses an escaped underscore with backslash, which is correct for some Markdown renderers, but the same pattern at line 230 uses "Magento_*" without escaping. For consistency, both instances should use the same escaping pattern. Most modern Markdown renderers don't require escaping underscores when they're part of literal text, so consider removing the backslash escape for consistency.

Suggested change
- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento\_\* modules)
- `--third-party-only` / `-t` - Check only third-party modules (exclude Magento_* modules)

Copilot uses AI. Check for mistakes.
- `--include-vendor` - Include Magento core modules in scan (default: third-party only)
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Documentation inconsistency: The description states "Include Magento core modules (default: third-party only)" at line 209, but looking at the actual command option definition and implementation, the --include-vendor flag controls whether to include vendor directory modules, not specifically Magento core modules. The Magento_* core modules are filtered by the --third-party-only flag. This distinction should be clarified in the documentation to accurately reflect the command's behavior.

Suggested change
- `--include-vendor` - Include Magento core modules in scan (default: third-party only)
- `--include-vendor` - Include modules from the `vendor/` directory in the scan (by default only non-vendor modules are scanned; Magento\_\* core modules are still controlled by `--third-party-only`)

Copilot uses AI. Check for mistakes.
- `--detailed` / `-d` - Show detailed file-level issues for incompatible modules

Expand Down Expand Up @@ -273,7 +273,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 are found, 0 otherwise (even if warnings exist)
- 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.

**Detected Patterns**:

Expand Down
82 changes: 26 additions & 56 deletions src/Console/Command/Hyva/CompatibilityCheckCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ protected function configure(): void
self::OPTION_INCLUDE_VENDOR,
null,
InputOption::VALUE_NONE,
'Include vendor modules in scan (default: excluded, but included with --third-party-only)'
'Include Magento core modules (default: third-party modules only)'
)
->addOption(
self::OPTION_DETAILED,
Expand Down Expand Up @@ -96,7 +96,8 @@ private function runInteractiveMode(InputInterface $input, OutputInterface $outp
label: 'Select scan options',
options: [
'show-all' => 'Show all modules including compatible ones',
'include-vendor' => 'Include vendor modules (default: excluded)',
'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: [],
Expand All @@ -110,6 +111,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
Expand All @@ -119,11 +121,13 @@ 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 vendor modules';
$config[] = 'Include Magento core';
} else {
$config[] = 'Third-party modules only';
}
Expand All @@ -133,8 +137,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, false, $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());
Expand Down Expand Up @@ -171,12 +175,12 @@ private function runScan(
OutputInterface $output
): int {

// Determine filter logic for vendor and third-party 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 = !$includeVendor;
// Determine filter logic:
// - thirdPartyOnly: Only scan non-Magento_* modules (default behavior)
// - includeVendor: Also scan Magento_* core modules
// - excludeVendor: Whether to exclude vendor/ directory (always false for now)
$scanThirdPartyOnly = !$includeVendor;
$excludeVendor = false;

// Run the compatibility check
$results = $this->compatibilityChecker->check(
Expand All @@ -187,17 +191,14 @@ 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
}
// Determine display mode:
// showAll = show all modules including compatible ones
// incompatibleOnly = show only modules with critical issues
// default = show modules with any issues (critical or warnings)
$displayShowAll = $showAll && !$incompatibleOnly;

// Display results
$this->displayResults($results, $displayShowAll || $detailed);
$this->displayResults($results, $displayShowAll);

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

// 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;
// Additional check: try to detect if running in a proper TTY
$sttyOutput = shell_exec('stty -g 2>/dev/null');
return !empty($sttyOutput);
Comment on lines +369 to +370
Copy link

Copilot AI Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: The shell_exec() call at line 373 executes a shell command without proper sanitization. While stty is a standard command, using shell_exec in production code can be risky. Consider using PHP's posix_isatty() function instead, which provides similar functionality without invoking a shell. Example: posix_isatty(STDIN) would be safer and more portable.

Suggested change
$sttyOutput = shell_exec('stty -g 2>/dev/null');
return !empty($sttyOutput);
if (function_exists('posix_isatty')) {
return posix_isatty(STDIN);
}
// Fallback: we've already passed other interactive checks above
return true;

Copilot uses AI. Check for mistakes.
}

/**
Expand Down Expand Up @@ -425,11 +410,7 @@ private function resetPromptEnvironment(): void
*/
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();
}

/**
Expand Down Expand Up @@ -461,19 +442,8 @@ private function getServerVar(string $name): ?string
private function setEnvVar(string $name, string $value): void
{
$this->secureEnvStorage[$name] = $value;
$_SERVER[$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
);
}

}
6 changes: 3 additions & 3 deletions src/Service/Hyva/IncompatibilityDetector.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class IncompatibilityDetector
'severity' => self::SEVERITY_WARNING,
],
[
'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']\s*[^\]]*\]/',
'pattern' => '/(?:define|require)\s*\(\s*\[[^\]]*["\']mage\/[^"\']*["\']/',
'description' => 'Magento RequireJS module reference',
'severity' => self::SEVERITY_CRITICAL,
],
Expand All @@ -65,7 +65,7 @@ class IncompatibilityDetector
'severity' => self::SEVERITY_CRITICAL,
],
[
'pattern' => '/<referenceBlock\b[^>]*\bremove\s*=\s*"true"[^>]*>/s',
'pattern' => '/<referenceBlock.*remove="true">/',
'description' => 'Block removal (review for Hyvä compatibility)',
'severity' => self::SEVERITY_WARNING,
],
Expand All @@ -82,7 +82,7 @@ class IncompatibilityDetector
'severity' => self::SEVERITY_CRITICAL,
],
[
'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*\(/',
'pattern' => '/\$\(.*\)\..*\(/',
'description' => 'jQuery DOM manipulation',
'severity' => self::SEVERITY_WARNING,
],
Expand Down
42 changes: 26 additions & 16 deletions src/Service/Hyva/ModuleScanner.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,31 @@ private function findRelevantFiles(string $directory): array
}

/**
* 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
{
Expand All @@ -125,21 +149,7 @@ 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 false;
return $this->isHyvaCompatibilityPackage($composerData);
} catch (\Exception $e) {
// Log error when debugging to help identify JSON or file read issues
if (getenv('MAGEFORGE_DEBUG')) {
Expand Down
67 changes: 25 additions & 42 deletions src/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,37 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"
>
<type name="Magento\Framework\Console\CommandList">
<arguments>
<argument
name="commands"
xsi:type="array"
>
<item name="mageforge_system_version"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\System\VersionCommand</item>
<item name="mageforge_system_check"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\System\CheckCommand</item>
<item name="mageforge_theme_list"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\Theme\ListCommand</item>
<item name="mageforge_theme_build"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand</item>
<item name="mageforge_theme_watch"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand</item>
<item name="mageforge_static_clean"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\Static\CleanCommand</item>
<item name="mageforge_hyva_compatibility_check"
xsi:type="object"
>OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand</item>
</argument>
</arguments>
</type>
<!-- Register CLI Commands -->
<type name="Magento\Framework\Console\CommandList">
<arguments>
<argument name="commands" xsi:type="array">
<item name="mageforge_system_version" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\System\VersionCommand</item>
<item name="mageforge_system_check" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\System\CheckCommand</item>
<item name="mageforge_theme_list" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Theme\ListCommand</item>
<item name="mageforge_theme_build" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Theme\BuildCommand</item>
<item name="mageforge_theme_watch" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Theme\WatchCommand</item>
<item name="mageforge_static_clean" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Static\CleanCommand</item>
<item name="mageforge_hyva_compatibility_check" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand</item>
</argument>
</arguments>
</type>

<!-- Configure Theme Builder Pool -->
<type name="OpenForgeProject\MageForge\Service\ThemeBuilder\BuilderPool">
<arguments>
<argument name="builders"
xsi:type="array"
>
<item name="hyva"
xsi:type="object"
>
<argument name="builders" xsi:type="array">
<item name="hyva" xsi:type="object">
OpenForgeProject\MageForge\Service\ThemeBuilder\HyvaThemes\Builder</item>
<item name="standard"
xsi:type="object"
>
<item name="standard" xsi:type="object">
OpenForgeProject\MageForge\Service\ThemeBuilder\MagentoStandard\Builder</item>
<item name="tailwindcss"
xsi:type="object"
>
<item name="tailwindcss" xsi:type="object">
OpenForgeProject\MageForge\Service\ThemeBuilder\TailwindCSS\Builder</item>
</argument>
</arguments>
Expand Down