diff --git a/docs/commands.md b/docs/commands.md index 07c2606..00925e0 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -56,6 +56,7 @@ bin/magento mageforge:theme:build [...] **Implementation Details**: +- `themeCodes` accepts single themes (`Vendor/theme`) or just the vendor name (`Vendor`) to target all themes of a specific vendor. - If no theme codes are provided, displays an interactive prompt to select themes - For each selected theme: 1. Resolves the theme path @@ -109,11 +110,13 @@ bin/magento mageforge:theme:watch [--theme=THEME] **Usage**: ```bash -bin/magento mageforge:theme:clean [] +bin/magento mageforge:theme:clean [...] ``` **Implementation Details**: +- Can accept multiple themes like `Vendor/theme1 Vendor/theme2`. +- Accepts simply the vendor name `Vendor` to clean all registered themes for a vendor. - 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 diff --git a/release-please-config.json b/release-please-config.json index 37aa103..f84408e 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -12,7 +12,7 @@ "include-v-in-tag": false, "include-component-in-tag": false, "pull-request-title-pattern": "chore: Next Release ${version}", - "pull-request-header": "## Release ${version}", + "pull-request-header": "## Upcoming Release", "changelog-sections": [ { "type": "feat", diff --git a/src/Console/Command/AbstractCommand.php b/src/Console/Command/AbstractCommand.php index 152ee54..561402d 100644 --- a/src/Console/Command/AbstractCommand.php +++ b/src/Console/Command/AbstractCommand.php @@ -7,6 +7,7 @@ use Laravel\Prompts\SelectPrompt; use Magento\Framework\Console\Cli; use OpenForgeProject\MageForge\Service\ThemeSuggester; +use OpenForgeProject\MageForge\Model\ThemeList; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -478,4 +479,72 @@ private function removeSecureEnvironmentValue(string $name): void unset($this->secureEnvStorage[$name]); $this->clearEnvironmentCache(); } + + /** + * Resolve vendor theme codes (e.g., Vendor to all underlying vendor themes) + * + * @param array $themeCodes + * @param ThemeList $themeList + * @return array + */ + protected function resolveVendorThemes( + array $themeCodes, + ThemeList $themeList + ): array { + $resolved = []; + $availableThemes = null; + + foreach ($themeCodes as $code) { + // Check if it's explicitly a wildcard OR just a vendor name without a slash + $isExplicitWildcard = \str_ends_with($code, '/*'); + $isVendorOnly = !\str_contains($code, '/'); + + if ($isExplicitWildcard || $isVendorOnly) { + // Lazy-load themes only when needed + if ($availableThemes === null) { + $availableThemes = array_map( + fn($theme) => $theme->getCode(), + $themeList->getAllThemes() + ); + } + + if ($isExplicitWildcard) { + $prefix = substr($code, 0, -1); // Keeps the trailing slash, e.g. "Vendor/" + } else { + $prefix = $code . '/'; // e.g. "Vendor" -> "Vendor/" + } + + $matched = array_filter( + $availableThemes, + fn(string $availableCode) => \str_starts_with($availableCode, $prefix) + ); + + if (empty($matched)) { + $this->io->warning(sprintf("No themes found for vendor/prefix '%s'", $prefix)); + + // If they typed just a word and it wasn't a vendor, + // we still add it so standard Magento validation kicks in later. + if ($isVendorOnly) { + $resolved[] = $code; + } + } else { + $this->io->note(sprintf( + "Resolved vendor '%s' to %d theme(s): %s", + $code, + count($matched), + implode(', ', $matched) + )); + + foreach ($matched as $match) { + $resolved[] = $match; + } + } + } else { + $resolved[] = $code; + } + } + + // Return a fresh list without duplicates + return array_values(array_unique($resolved)); + } } diff --git a/src/Console/Command/Theme/BuildCommand.php b/src/Console/Command/Theme/BuildCommand.php index f70e506..8d89536 100644 --- a/src/Console/Command/Theme/BuildCommand.php +++ b/src/Console/Command/Theme/BuildCommand.php @@ -51,7 +51,7 @@ protected function configure(): void ->addArgument( 'themeCodes', InputArgument::IS_ARRAY, - 'Theme codes to build (format: Vendor/theme, Vendor/theme 2, ...)', + 'Theme codes to build (format: Vendor/theme, Vendor, ...)', ) ->setAliases(['frontend:build']); } @@ -66,6 +66,17 @@ protected function configure(): void protected function executeCommand(InputInterface $input, OutputInterface $output): int { $themeCodes = $input->getArgument('themeCodes'); + + // Allow wildcards using the AbstractCommand helper + if (!empty($themeCodes)) { + $themeCodes = $this->resolveVendorThemes($themeCodes, $this->themeList); + + // If wildcards matched nothing and no other explicit themes remain + if (empty($themeCodes)) { + return Command::SUCCESS; + } + } + $isVerbose = $this->isVerbose($output); if (empty($themeCodes)) { @@ -337,12 +348,22 @@ private function processTheme( private function displayBuildSummary(SymfonyStyle $io, array $successList, float $duration): void { $io->newLine(); - $io->success(sprintf('🚀 Build process completed in %.2f seconds with the following results:', $duration)); - $io->writeln('Summary:'); - $io->newLine(); - if (empty($successList)) { - $io->warning('No themes were built successfully.'); + $successCount = count($successList); + + if ($successCount > 0) { + $io->success(sprintf( + '🚀 Successfully built %d theme(s). Build process completed in %.2f seconds.', + $successCount, + $duration + )); + $io->writeln('Summary:'); + $io->newLine(); + } else { + $io->warning(sprintf( + 'Build process completed in %.2f seconds, but no themes were built successfully.', + $duration + )); return; } diff --git a/src/Console/Command/Theme/CleanCommand.php b/src/Console/Command/Theme/CleanCommand.php index 3bc29d9..053ac26 100644 --- a/src/Console/Command/Theme/CleanCommand.php +++ b/src/Console/Command/Theme/CleanCommand.php @@ -49,7 +49,7 @@ protected function configure(): void ->addArgument( 'themeCodes', InputArgument::IS_ARRAY, - 'Theme codes to clean (format: Vendor/theme, Vendor/theme 2, ...)', + 'Theme codes to clean (format: Vendor/theme, Vendor, ...)', ) ->addOption('all', 'a', InputOption::VALUE_NONE, 'Clean all themes') ->addOption( @@ -105,6 +105,15 @@ private function resolveThemeCodes(InputInterface $input, OutputInterface $outpu return $this->getAllThemeCodes(); } + if (!empty($themeCodes)) { + $themeCodes = $this->resolveVendorThemes($themeCodes, $this->themeList); + + // If wildcards matched nothing and no other explicit themes remain + if (empty($themeCodes)) { + return null; + } + } + if (empty($themeCodes)) { return $this->selectThemesInteractively($output); }