From 74325fd86e269e986960b07d09a5b3ca6b6f9ace Mon Sep 17 00:00:00 2001 From: Alex Pott <alex.a.pott@googlemail.com> Date: Mon, 29 Apr 2024 16:30:07 +0100 Subject: [PATCH] Issue #3427558 by srishtiiee, alexpott, phenaproxima, thejimbirch: Output information from the recipe application process (cherry picked from commit 300b8e0fb982675b53f635715c4c06b9d96747be) --- core/lib/Drupal/Core/Recipe/RecipeCommand.php | 55 ++++++++++++++++++- core/lib/Drupal/Core/Recipe/RecipeRunner.php | 5 ++ .../Core/Recipe/RecipeCommandTest.php | 6 +- phpstan-level9-baseline.neon | 2 +- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/core/lib/Drupal/Core/Recipe/RecipeCommand.php b/core/lib/Drupal/Core/Recipe/RecipeCommand.php index d0fa0706469f..9bfd555b950a 100644 --- a/core/lib/Drupal/Core/Recipe/RecipeCommand.php +++ b/core/lib/Drupal/Core/Recipe/RecipeCommand.php @@ -4,6 +4,7 @@ namespace Drupal\Core\Recipe; +use Drupal\Component\Render\PlainTextOutput; use Drupal\Core\Config\Checkpoint\Checkpoint; use Drupal\Core\Config\ConfigImporter; use Drupal\Core\Config\ConfigImporterException; @@ -83,8 +84,45 @@ protected function execute(InputInterface $input, OutputInterface $output): int $backup_checkpoint = $checkpoint_storage ->checkpoint("Backup before the '$recipe->name' recipe."); try { - RecipeRunner::processRecipe($recipe); - $io->success(sprintf('%s applied successfully', $recipe->name)); + $steps = RecipeRunner::toBatchOperations($recipe); + $progress_bar = $io->createProgressBar(); + $progress_bar->setFormat("%current%/%max% [%bar%]\n%message%\n"); + $progress_bar->setMessage($this->toPlainString(t('Applying recipe'))); + $progress_bar->start(count($steps)); + + /** @var array{message?: \Stringable|string, results: array{module?: string[], theme?: string[], content?: string[], recipe?: string[]}} $context */ + $context = ['results' => []]; + foreach ($steps as $step) { + call_user_func_array($step[0], array_merge($step[1], [&$context])); + if (isset($context['message'])) { + $progress_bar->setMessage($this->toPlainString($context['message'])); + } + unset($context['message']); + $progress_bar->advance(); + } + if ($io->isVerbose()) { + if (!empty($context['results']['module'])) { + $io->section($this->toPlainString(t('Modules installed'))); + $modules = array_map(fn ($module) => \Drupal::service('extension.list.module')->getName($module), $context['results']['module']); + sort($modules, SORT_NATURAL); + $io->listing($modules); + } + if (!empty($context['results']['theme'])) { + $io->section($this->toPlainString(t('Themes installed'))); + $themes = array_map(fn ($theme) => \Drupal::service('extension.list.theme')->getName($theme), $context['results']['theme']); + sort($themes, SORT_NATURAL); + $io->listing($themes); + } + if (!empty($context['results']['content'])) { + $io->section($this->toPlainString(t('Content created for recipes'))); + $io->listing($context['results']['content']); + } + if (!empty($context['results']['recipe'])) { + $io->section($this->toPlainString(t('Recipes applied'))); + $io->listing($context['results']['recipe']); + } + } + $io->success($this->toPlainString(t('%recipe applied successfully', ['%recipe' => $recipe->name]))); return 0; } catch (\Throwable $e) { @@ -98,6 +136,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + /** + * Converts a stringable like TranslatableMarkup to a plain text string. + * + * @param \Stringable|string $text + * The string to convert. + * + * @return string + * The plain text string. + */ + private function toPlainString(\Stringable|string $text): string { + return PlainTextOutput::renderFromHtml((string) $text); + } + /** * Rolls config back to a particular checkpoint. * diff --git a/core/lib/Drupal/Core/Recipe/RecipeRunner.php b/core/lib/Drupal/Core/Recipe/RecipeRunner.php index 2a9a0b2caece..0e08d2df16bf 100644 --- a/core/lib/Drupal/Core/Recipe/RecipeRunner.php +++ b/core/lib/Drupal/Core/Recipe/RecipeRunner.php @@ -51,6 +51,7 @@ public static function triggerEvent(Recipe $recipe, ?array &$context = NULL): vo $event = new RecipeAppliedEvent($recipe); \Drupal::service(EventDispatcherInterface::class)->dispatch($event); $context['message'] = t('Applied %recipe recipe.', ['%recipe' => $recipe->name]); + $context['results']['recipe'][] = $recipe->name; } /** @@ -248,6 +249,7 @@ public static function installModule(string $module, StorageInterface|Recipe $re \Drupal::service('module_installer')->install([$module]); \Drupal::service('config.installer')->setSyncing(FALSE); $context['message'] = t('Installed %module module.', ['%module' => \Drupal::service('extension.list.module')->getName($module)]); + $context['results']['module'][] = $module; } /** @@ -277,6 +279,7 @@ public static function installTheme(string $theme, StorageInterface|Recipe $reci \Drupal::service('theme_installer')->install([$theme]); \Drupal::service('config.installer')->setSyncing(FALSE); $context['message'] = t('Installed %theme theme.', ['%theme' => \Drupal::service('extension.list.theme')->getName($theme)]); + $context['results']['theme'][] = $theme; } /** @@ -290,6 +293,7 @@ public static function installTheme(string $theme, StorageInterface|Recipe $reci public static function installConfig(Recipe $recipe, ?array &$context = NULL): void { static::processConfiguration($recipe->config); $context['message'] = t('Installed configuration for %recipe recipe.', ['%recipe' => $recipe->name]); + $context['results']['config'][] = $recipe->name; } /** @@ -303,6 +307,7 @@ public static function installConfig(Recipe $recipe, ?array &$context = NULL): v public static function installContent(Recipe $recipe, ?array &$context = NULL): void { static::processContent($recipe->content); $context['message'] = t('Created content for %recipe recipe.', ['%recipe' => $recipe->name]); + $context['results']['content'][] = $recipe->name; } } diff --git a/core/tests/Drupal/FunctionalTests/Core/Recipe/RecipeCommandTest.php b/core/tests/Drupal/FunctionalTests/Core/Recipe/RecipeCommandTest.php index 75862c08b307..229d4acfdacf 100644 --- a/core/tests/Drupal/FunctionalTests/Core/Recipe/RecipeCommandTest.php +++ b/core/tests/Drupal/FunctionalTests/Core/Recipe/RecipeCommandTest.php @@ -37,7 +37,7 @@ public function testRecipeCommand(): void { $process = $this->applyRecipe('core/tests/fixtures/recipes/install_node_with_config'); $this->assertSame(0, $process->getExitCode()); - $this->assertSame('', $process->getErrorOutput()); + $this->assertStringContainsString("6/6 [â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“]\nApplied Install node with config recipe.", $process->getErrorOutput()); $this->assertStringContainsString('Install node with config applied successfully', $process->getOutput()); $this->assertTrue(\Drupal::moduleHandler()->moduleExists('node'), 'The node module is installed'); $this->assertCheckpointsExist(["Backup before the 'Install node with config' recipe."]); @@ -45,7 +45,7 @@ public function testRecipeCommand(): void { // Ensure recipes can be applied without affecting pre-existing checkpoints. $process = $this->applyRecipe('core/tests/fixtures/recipes/install_two_modules'); $this->assertSame(0, $process->getExitCode()); - $this->assertSame('', $process->getErrorOutput()); + $this->assertStringContainsString("1/1 [â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“]\nApplied Install two modules recipe.", $process->getErrorOutput()); $this->assertStringContainsString('Install two modules applied successfully', $process->getOutput()); $this->assertTrue(\Drupal::moduleHandler()->moduleExists('node'), 'The node module is installed'); $this->assertCheckpointsExist([ @@ -68,7 +68,7 @@ public function testRecipeCommand(): void { \Drupal::service('config.storage.checkpoint')->checkpoint('Test log message'); $process = $this->applyRecipe('core/tests/fixtures/recipes/no_extensions'); $this->assertSame(0, $process->getExitCode()); - $this->assertSame('', $process->getErrorOutput()); + $this->assertStringContainsString("1/1 [â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“â–“]\nApplied No extensions recipe.", $process->getErrorOutput()); $this->assertCheckpointsExist([ "Backup before the 'Install node with config' recipe.", "Backup before the 'Install two modules' recipe.", diff --git a/phpstan-level9-baseline.neon b/phpstan-level9-baseline.neon index 407f70f8bdaa..82f59183f735 100644 --- a/phpstan-level9-baseline.neon +++ b/phpstan-level9-baseline.neon @@ -242,7 +242,7 @@ parameters: - message: "#^\\\\Drupal calls should be avoided in classes, use dependency injection instead$#" - count: 1 + count: 3 path: core/lib/Drupal/Core/Recipe/RecipeCommand.php - -- GitLab