Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ All notable changes to this project will be documented in this file.

## UNRELEASED


### Added

- feat: add `mageforge:theme:tokens` command to generate Hyvä design tokens from design.tokens.json or hyva.config.json
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.

Inconsistent command name in changelog. The changelog mentions 'mageforge:theme:tokens' which contradicts the actual implementation that uses 'mageforge:hyva:tokens'. While this might be the intended correct name (matching the di.xml and PR title), the actual code implementation needs to be updated to match.

Suggested change
- feat: add `mageforge:theme:tokens` command to generate Hyvä design tokens from design.tokens.json or hyva.config.json
- feat: add `mageforge:hyva:tokens` command to generate Hyvä design tokens from design.tokens.json or hyva.config.json

Copilot uses AI. Check for mistakes.
- feat: add `mageforge:hyva:compatibility:check` command to add a Hyvä compatibility checker
- Scans Magento modules for Hyvä theme compatibility issues
- Detects RequireJS, Knockout.js, jQuery, and UI Components usage
Expand Down
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Please ensure that your Magento installation meets this requirement before insta
| `mageforge:theme:list` | Lists all available themes | `m:t:l` |
| `mageforge:theme:build` | Builds selected themes (CSS/TailwindCSS) | `m:t:b`, `frontend:build` |
| `mageforge:theme:watch` | Starts watch mode for theme development | `m:t:w`, `frontend:watch` |
| `mageforge:static:clean` | Clean static files, cache and generated files for a theme | `frontend:clean` |
| `mageforge:hyva:tokens` | Generate Hyvä design tokens (Hyvä themes only) | `m:h:t` |
| `mageforge:static:clean` | Clean static files, cache and generated files for a theme | `m:st:c`,`frontend:clean` |

---

Expand Down Expand Up @@ -79,7 +80,15 @@ Please ensure that your Magento installation meets this requirement before insta
bin/magento mageforge:theme:watch <theme-code>
```

4. Enjoy automatic CSS rebuilding you work on your theme files!
4. Generate Hyvä design tokens (for Hyvä themes):

```bash
bin/magento mageforge:hyva:tokens <theme-code>
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.

Inconsistent command name in usage example. The command name should match the actual implementation and the project structure. If this command is in the Theme/ directory, it should use 'mageforge:theme:tokens' not 'mageforge:hyva:tokens'.

Copilot uses AI. Check for mistakes.
```

This creates a `generated/hyva-tokens.css` file from your design tokens configuration.

5. Enjoy automatic CSS rebuilding as you work on your theme files!

## Additional Documentation

Expand Down
79 changes: 78 additions & 1 deletion docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,19 @@ bin/magento mageforge:theme:watch [--theme=THEME]
**File**: `/src/Console/Command/Static/CleanCommand.php`

**Dependencies**:

- `Filesystem` - Magento filesystem component for file operations
- `ThemeList` - Service to retrieve theme information
- `ThemePath` - Service to resolve theme paths

**Usage**:

```bash
bin/magento mageforge:static:clean [<themename>]
```

**Implementation Details**:

- If no theme name is provided:
- In interactive terminals, displays an interactive prompt to select the theme to clean
- In non-interactive environments, prints the list of available themes and exits, requiring an explicit theme name
Expand Down Expand Up @@ -157,7 +160,7 @@ bin/magento mageforge:system:check

---

### 6. VersionCommand (`mageforge:version`)
### 5. VersionCommand (`mageforge:version`)

**Purpose**: Displays the current and latest version of the MageForge module.

Expand Down Expand Up @@ -308,6 +311,80 @@ _PHTML Files (.phtml)_:

---

### 7. TokensCommand (`mageforge:hyva:tokens`)

**Purpose**: Generates Hyvä design tokens from design.tokens.json or hyva.config.json configuration files.

**File**: `/src/Console/Command/Theme/TokensCommand.php`

**Dependencies**:

- `ThemeList` - Service to retrieve theme information
- `ThemePath` - Service to resolve theme paths
- `BuilderPool` - Service to verify Hyvä theme type
- `File` - Filesystem driver for directory checks
- `Shell` - Shell command executor

**Usage**:

```bash
bin/magento mageforge:hyva:tokens [<themeCode>]
```

**Aliases**:

- `m:h:t`

**Arguments**:

- `themeCode` (optional) - Theme code in format Vendor/theme

**Examples**:

```bash
# Interactive mode - select theme from list
bin/magento m:h:t

# Direct execution
bin/magento mageforge:hyva:tokens Hyva/default
```

**Implementation Details**:

- If no theme code is provided, displays an interactive prompt to select a Hyvä theme
- Validates that the theme is installed and is a Hyvä theme
- Checks if the theme has been built (node_modules exists)
- Changes to the theme's `web/tailwind` directory
- Executes `npx hyva-tokens` to generate design tokens
- For themes in `app/design/frontend/`:
- Generates tokens in `web/tailwind/generated/hyva-tokens.css`
- For vendor themes (in `vendor/` directory):
- Generates tokens in `web/tailwind/generated/hyva-tokens.css` first
- Copies the generated file to `var/generated/hyva-token/{ThemeCode}/hyva-tokens.css`
- Displays a note informing that tokens were saved to var/generated location
- Returns success status code or error message

**Requirements**:

- Theme must be a Hyvä theme
- Theme must be built first (`mageforge:theme:build`)
- Node.js and npm must be available
- `hyva-tokens` package must be installed (via theme build)

**Output Locations**:

- **Custom themes** (app/design): `{theme-path}/web/tailwind/generated/hyva-tokens.css`
- **Vendor themes** (vendor/): `var/generated/hyva-token/{ThemeCode}/hyva-tokens.css`

**Error Handling**:

- Returns error if theme is not installed
- Returns error if theme is not a Hyvä theme
- Returns warning if node_modules not found (build required)
- Returns error if tailwind directory doesn't exist

---

## Command Services

The commands rely on several services for their functionality:
Expand Down
164 changes: 164 additions & 0 deletions src/Console/Command/Theme/TokensCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<?php

declare(strict_types=1);

namespace OpenForgeProject\MageForge\Console\Command\Theme;

use Laravel\Prompts\SelectPrompt;
use Magento\Framework\Console\Cli;
use Magento\Framework\Filesystem\Driver\File;
use Magento\Framework\Shell;
use OpenForgeProject\MageForge\Console\Command\AbstractCommand;
use OpenForgeProject\MageForge\Model\ThemeList;
use OpenForgeProject\MageForge\Model\ThemePath;
use OpenForgeProject\MageForge\Service\ThemeBuilder\BuilderPool;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* Command for generating Hyvä design tokens
*/
class TokensCommand extends AbstractCommand
{
/**
* @param ThemeList $themeList
* @param ThemePath $themePath
* @param BuilderPool $builderPool
* @param File $fileDriver
* @param Shell $shell
*/
public function __construct(
private readonly ThemeList $themeList,
private readonly ThemePath $themePath,
private readonly BuilderPool $builderPool,
private readonly File $fileDriver,
private readonly Shell $shell,
) {
parent::__construct();
}

/**
* {@inheritdoc}
*/
protected function configure(): void
{
$this->setName($this->getCommandName('hyva', 'tokens'))
->setAliases(['m:h:t'])
Comment on lines +46 to +47
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.

Alias inconsistency. The alias 'm:h:t' suggests this is a hyva command, but if the command is corrected to 'mageforge:theme:tokens' (as indicated by its location in the Theme/ directory and the di.xml registration), the alias should be 'm:t:t' to follow the pattern of other theme commands (m:t:l, m:t:b, m:t:w).

Suggested change
$this->setName($this->getCommandName('hyva', 'tokens'))
->setAliases(['m:h:t'])
$this->setName($this->getCommandName('theme', 'tokens'))
->setAliases(['m:t:t'])

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +47
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.

Inconsistency between command name and file location. The command is located in the Theme/ directory but uses 'hyva' as the group name. According to the project structure, commands in src/Console/Command/Theme/ should use 'theme' as the group (e.g., 'mageforge:theme:tokens'), while commands in src/Console/Command/Hyva/ use 'hyva'. This creates confusion with the di.xml registration name 'mageforge_theme_tokens' and the PR title which mentions 'mageforge:theme:tokens'. Either the command should be moved to the Hyva directory or the group name should be changed to 'theme'.

Suggested change
$this->setName($this->getCommandName('hyva', 'tokens'))
->setAliases(['m:h:t'])
$this->setName($this->getCommandName('theme', 'tokens'))
->setAliases(['m:t:t'])

Copilot uses AI. Check for mistakes.
->setDescription('Generate Hyvä design tokens from design.tokens.json or hyva.config.json')
->addArgument(
'themeCode',
InputArgument::OPTIONAL,
'Theme code to generate tokens for (format: Vendor/theme)'
);
}

/**
* {@inheritdoc}
*/
protected function executeCommand(InputInterface $input, OutputInterface $output): int
{
$themeCode = $input->getArgument('themeCode');
$isVerbose = $this->isVerbose($output);

if (empty($themeCode)) {
$themes = $this->themeList->getAllThemes();
$options = array_map(fn($theme) => $theme->getCode(), $themes);

$themeCodePrompt = new SelectPrompt(
label: 'Select theme to generate tokens for',
options: $options,
scroll: 10,
hint: 'Arrow keys to navigate, Enter to confirm',
);

try {
$themeCode = $themeCodePrompt->prompt();
\Laravel\Prompts\Prompt::terminal()->restoreTty();
} catch (\Exception $e) {
$this->io->error('Interactive mode failed: ' . $e->getMessage());
return Cli::RETURN_FAILURE;
Comment on lines +68 to +80
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.

Missing environment protection pattern for Laravel Prompts. Other commands in the codebase (BuildCommand, CompatibilityCheckCommand) use setPromptEnvironment() and resetPromptEnvironment() methods to safely manage environment variables around Laravel Prompts usage. This command directly uses SelectPrompt without this protection, which could lead to environment pollution or interference with other processes.

Suggested change
$themeCodePrompt = new SelectPrompt(
label: 'Select theme to generate tokens for',
options: $options,
scroll: 10,
hint: 'Arrow keys to navigate, Enter to confirm',
);
try {
$themeCode = $themeCodePrompt->prompt();
\Laravel\Prompts\Prompt::terminal()->restoreTty();
} catch (\Exception $e) {
$this->io->error('Interactive mode failed: ' . $e->getMessage());
return Cli::RETURN_FAILURE;
$this->setPromptEnvironment();
try {
$themeCodePrompt = new SelectPrompt(
label: 'Select theme to generate tokens for',
options: $options,
scroll: 10,
hint: 'Arrow keys to navigate, Enter to confirm',
);
$themeCode = $themeCodePrompt->prompt();
\Laravel\Prompts\Prompt::terminal()->restoreTty();
} catch (\Exception $e) {
$this->io->error('Interactive mode failed: ' . $e->getMessage());
return Cli::RETURN_FAILURE;
} finally {
$this->resetPromptEnvironment();

Copilot uses AI. Check for mistakes.
}
}
Comment on lines +64 to +82
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.

The interactive theme selection does not check if the environment supports interactive terminals, unlike BuildCommand which uses isInteractiveTerminal() to handle non-interactive environments gracefully. In non-interactive CI/CD environments, this will fail with an exception. Consider adding a check similar to BuildCommand's implementation to handle non-interactive environments by displaying available themes and returning gracefully instead of attempting to show an interactive prompt.

Copilot uses AI. Check for mistakes.

$themePath = $this->themePath->getPath($themeCode);
if ($themePath === null) {
$this->io->error("Theme $themeCode is not installed.");
return Cli::RETURN_FAILURE;
}

// Check if this is a Hyvä theme
$builder = $this->builderPool->getBuilder($themePath);
if ($builder === null || $builder->getName() !== 'HyvaThemes') {
$this->io->error("Theme $themeCode is not a Hyvä theme. This command only works with Hyvä themes.");
return Cli::RETURN_FAILURE;
}

$tailwindPath = rtrim($themePath, '/') . '/web/tailwind';
if (!$this->fileDriver->isDirectory($tailwindPath)) {
$this->io->error("Tailwind directory not found in: $tailwindPath");
return Cli::RETURN_FAILURE;
}

// Check if node_modules exists
if (!$this->fileDriver->isDirectory($tailwindPath . '/node_modules')) {
$this->io->warning('Node modules not found. Please run: bin/magento mageforge:theme:build ' . $themeCode);
return Cli::RETURN_FAILURE;
}

if ($isVerbose) {
$this->io->section("Generating Hyvä design tokens for theme: $themeCode");
$this->io->text("Working directory: $tailwindPath");
}

// Change to tailwind directory and run npx hyva-tokens
$currentDir = getcwd();
chdir($tailwindPath);

try {
if ($isVerbose) {
$this->io->text('Running npx hyva-tokens...');
}

$this->shell->execute('npx hyva-tokens');

chdir($currentDir);

// Determine output path based on theme location
$isVendorTheme = str_contains($themePath, '/vendor/');
$sourceFilePath = $tailwindPath . '/generated/hyva-tokens.css';

if ($isVendorTheme) {
// Store in var/generated/hyva-token/{ThemeCode}/ for vendor themes
$varGeneratedPath = $currentDir . '/var/generated/hyva-token/' . str_replace('/', '/', $themeCode);
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.

The str_replace call is replacing '/' with '/', which is a no-op and doesn't change the string. This appears to be a mistake. If the intention is to keep the slash, this line can be simplified to just concatenate themeCode. If the intention is to replace slashes with a different character (like '-' or '_'), the second parameter should be updated accordingly.

Suggested change
$varGeneratedPath = $currentDir . '/var/generated/hyva-token/' . str_replace('/', '/', $themeCode);
$varGeneratedPath = $currentDir . '/var/generated/hyva-token/' . $themeCode;

Copilot uses AI. Check for mistakes.

if (!$this->fileDriver->isDirectory($varGeneratedPath)) {
$this->fileDriver->createDirectory($varGeneratedPath, 0755);
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.

Potential directory creation failure not handled. The createDirectory call can throw exceptions, but they're not caught separately from the main try-catch block. If directory creation fails in the vendor theme path, the error message will say "Failed to generate Hyvä design tokens" which is misleading since the tokens were already generated but the copy operation failed. Consider adding specific error handling for directory operations.

Suggested change
$this->fileDriver->createDirectory($varGeneratedPath, 0755);
try {
$this->fileDriver->createDirectory($varGeneratedPath, 0755);
} catch (\Exception $e) {
chdir($currentDir);
$this->io->error(
'Failed to prepare vendor directory for Hyvä design tokens: '
. $e->getMessage()
);
return Cli::RETURN_FAILURE;
}

Copilot uses AI. Check for mistakes.
}

$generatedFilePath = $varGeneratedPath . '/hyva-tokens.css';

// Copy file to var/generated location
if ($this->fileDriver->isExists($sourceFilePath)) {
$this->fileDriver->copy($sourceFilePath, $generatedFilePath);
}

$this->io->success('Hyvä design tokens generated successfully.');
$this->io->note('This is a vendor theme. Tokens have been saved to var/generated/hyva-token/ instead.');
$this->io->text('Generated file: ' . $generatedFilePath);
} else {
$generatedFilePath = $sourceFilePath;
$this->io->success('Hyvä design tokens generated successfully.');
$this->io->text('Generated file: ' . $generatedFilePath);
}
Comment on lines +129 to +153
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.

Missing error handling if the source file doesn't exist after npx hyva-tokens execution. The code only checks if the file exists before copying for vendor themes but doesn't handle the case where the token generation succeeds but doesn't create the expected file. For non-vendor themes, the code assumes the file exists and displays it as the generated file path without verification. Consider adding explicit validation that the file was created before reporting success.

Copilot uses AI. Check for mistakes.

$this->io->newLine();

return Cli::RETURN_SUCCESS;
} catch (\Exception $e) {
chdir($currentDir);
$this->io->error('Failed to generate Hyvä design tokens: ' . $e->getMessage());
return Cli::RETURN_FAILURE;
}
}
}
2 changes: 2 additions & 0 deletions src/etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
OpenForgeProject\MageForge\Console\Command\Static\CleanCommand</item>
<item name="mageforge_hyva_compatibility_check" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Hyva\CompatibilityCheckCommand</item>
<item name="mageforge_theme_tokens" xsi:type="object">
OpenForgeProject\MageForge\Console\Command\Theme\TokensCommand</item>
</argument>
</arguments>
</type>
Expand Down