From a29ea3607307d1bfc062cfa41ee246987b84c555 Mon Sep 17 00:00:00 2001 From: Jesse Leite Date: Tue, 26 Nov 2024 15:01:00 -0500 Subject: [PATCH] [5.x] Add `default` config for select starter kit modules (#11045) --- src/StarterKits/Installer.php | 26 ++-- tests/StarterKits/InstallTest.php | 194 ++++++++++++++++++++++++++++-- 2 files changed, 203 insertions(+), 17 deletions(-) diff --git a/src/StarterKits/Installer.php b/src/StarterKits/Installer.php index 0f95af244a..650ffcdb8d 100644 --- a/src/StarterKits/Installer.php +++ b/src/StarterKits/Installer.php @@ -324,9 +324,11 @@ protected function instantiateModule(array $config, string $key): InstallableMod $name = str_replace('_', ' ', $key); - if ($shouldPrompt && $this->isInteractive && ! confirm(Arr::get($config, 'prompt', "Would you like to install the [{$name}] module?"), false)) { + $default = Arr::get($config, 'default', false); + + if ($shouldPrompt && $this->isInteractive && ! confirm(Arr::get($config, 'prompt', "Would you like to install the [{$name}] module?"), $default)) { return false; - } elseif ($shouldPrompt && ! $this->isInteractive) { + } elseif ($shouldPrompt && ! $this->isInteractive && ! $default) { return false; } @@ -338,19 +340,27 @@ protected function instantiateModule(array $config, string $key): InstallableMod */ protected function instantiateSelectModule(array $config, string $key): InstallableModule|array|bool { + $skipOptionLabel = Arr::get($config, 'skip_option', 'No'); + $skipModuleValue = 'skip_module'; + $options = collect($config['options']) ->map(fn ($option, $optionKey) => Arr::get($option, 'label', ucfirst($optionKey))) - ->prepend(Arr::get($config, 'skip_option', 'No'), $skipModule = 'skip_module') + ->when($skipOptionLabel !== false, fn ($c) => $c->prepend($skipOptionLabel, $skipModuleValue)) ->all(); $name = str_replace('_', ' ', $key); - $choice = select( - label: Arr::get($config, 'prompt', "Would you like to install one of the following [{$name}] modules?"), - options: $options, - ); + if ($this->isInteractive) { + $choice = select( + label: Arr::get($config, 'prompt', "Would you like to install one of the following [{$name}] modules?"), + options: $options, + default: Arr::get($config, 'default'), + ); + } elseif (! $this->isInteractive && ! $choice = Arr::get($config, 'default')) { + return false; + } - if ($choice === $skipModule) { + if ($choice === $skipModuleValue) { return false; } diff --git a/tests/StarterKits/InstallTest.php b/tests/StarterKits/InstallTest.php index 10c85e1c2e..8303443d7c 100644 --- a/tests/StarterKits/InstallTest.php +++ b/tests/StarterKits/InstallTest.php @@ -772,7 +772,7 @@ public function it_installs_no_modules_by_default_when_running_non_interactively } #[Test] - public function it_installs_modules_with_prompt_false_config_by_default_when_running_non_interactively() + public function it_can_still_install_modules_with_prompt_false_or_default_config() { $this->setConfig([ 'export_paths' => [ @@ -780,7 +780,7 @@ public function it_installs_modules_with_prompt_false_config_by_default_when_run ], 'modules' => [ 'seo' => [ - 'prompt' => false, // Setting prompt to false skips confirmation, so this module should still get installed non-interactively + 'prompt' => false, // Setting `prompt: false` normally skips confirmation and ensures it always gets installed 'export_paths' => [ 'resources/css/seo.css', ], @@ -788,6 +788,12 @@ public function it_installs_modules_with_prompt_false_config_by_default_when_run 'statamic/seo-pro' => '^0.2.0', ], ], + 'hockey' => [ + 'default' => true, // Setting `default: true` will still ask user for confirmation, but should still get installed non-interactively + 'export_paths' => [ + 'resources/css/hockey.css', + ], + ], 'bobsled' => [ 'export_paths' => [ 'resources/css/bobsled.css', @@ -797,29 +803,64 @@ public function it_installs_modules_with_prompt_false_config_by_default_when_run ], ], 'jamaica' => [ - 'prompt' => false, // Setting prompt to false skips confirmation, so this module should still get installed non-interactively + 'prompt' => false, // Setting `prompt: false` normally skips confirmation and ensures it always gets installed 'export_as' => [ 'resources/css/theme.css' => 'resources/css/jamaica.css', ], ], + 'js' => [ + 'default' => 'vue', // Setting a `default` option will still ask user for confirmation, but should still get installed non-interactively + 'options' => [ + 'react' => [ + 'label' => 'React JS', + 'export_paths' => [ + 'resources/js/react.js', + ], + ], + 'vue' => [ + 'label' => 'Vue JS', + 'export_paths' => [ + 'resources/js/vue.js', + ], + ], + ], + ], + 'js_invalid' => [ + 'prompt' => false, // Setting `prompt: false` doesn't do anything for select modules, should use `default` like above + 'options' => [ + 'svelte' => [ + 'export_paths' => [ + 'resources/js/svelte.js', + ], + ], + ], + ], ], ]); $this->assertFileDoesNotExist(base_path('copied.md')); $this->assertFileDoesNotExist(base_path('resources/css/seo.css')); + $this->assertFileDoesNotExist(base_path('resources/css/hockey.css')); $this->assertFileDoesNotExist(base_path('resources/css/bobsled.css')); $this->assertFileDoesNotExist(base_path('resources/css/theme.css')); $this->assertComposerJsonDoesntHave('statamic/seo-pro'); $this->assertComposerJsonDoesntHave('bobsled/speed-calculator'); + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileDoesNotExist(base_path('resources/js/vue.js')); + $this->assertFileDoesNotExist(base_path('resources/js/svelte.js')); $this->installCoolRunnings(); $this->assertFileExists(base_path('copied.md')); $this->assertFileExists(base_path('resources/css/seo.css')); + $this->assertFileExists(base_path('resources/css/hockey.css')); $this->assertFileDoesNotExist(base_path('resources/css/bobsled.css')); $this->assertFileExists(base_path('resources/css/theme.css')); $this->assertComposerJsonHasPackageVersion('require', 'statamic/seo-pro', '^0.2.0'); $this->assertComposerJsonDoesntHave('bobsled/speed-calculator'); + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileExists(base_path('resources/js/vue.js')); + $this->assertFileDoesNotExist(base_path('resources/js/svelte.js')); } #[Test] @@ -926,7 +967,120 @@ public function it_installs_only_the_modules_confirmed_interactively_via_prompt( } #[Test] - public function it_display_custom_module_prompts() + public function it_allows_user_to_skip_in_select_module_prompts() + { + $this->setConfig([ + 'modules' => [ + 'js' => [ + 'prompt' => 'Want one of these fancy JS options?', + 'options' => [ + 'react' => [ + 'label' => 'React JS', + 'export_paths' => [ + 'resources/js/react.js', + ], + ], + 'vue' => [ + 'label' => 'Vue JS', + 'export_paths' => [ + 'resources/js/vue.js', + ], + ], + 'svelte' => [ + 'export_paths' => [ + 'resources/js/svelte.js', + ], + ], + ], + ], + ], + ]); + + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileDoesNotExist(base_path('resources/js/vue.js')); + $this->assertFileDoesNotExist(base_path('resources/js/svelte.js')); + + $command = $this->installCoolRunningsModules(); + + // Some fixes to `expectsChoice()` were merged for us, but are not available on 11.20.0 and below + // See: https://github.com/laravel/framework/pull/52408 + if (version_compare(app()->version(), '11.20.0', '>')) { + $command->expectsChoice('Want one of these fancy JS options?', 'skip_module', [ + 'skip_module' => 'No', + 'react' => 'React JS', + 'vue' => 'Vue JS', + 'svelte' => 'Svelte', + ]); + } else { + $command->expectsQuestion('Want one of these fancy JS options?', 'skip_module'); + } + + $command->run(); + + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileDoesNotExist(base_path('resources/js/vue.js')); + $this->assertFileDoesNotExist(base_path('resources/js/svelte.js')); + } + + #[Test] + public function it_can_disable_skip_option_in_select_module_prompts() + { + $this->setConfig([ + 'modules' => [ + 'js' => [ + 'prompt' => 'Want one of these fancy JS options?', + 'skip_option' => false, + 'options' => [ + 'react' => [ + 'label' => 'React JS', + 'export_paths' => [ + 'resources/js/react.js', + ], + ], + 'vue' => [ + 'label' => 'Vue JS', + 'export_paths' => [ + 'resources/js/vue.js', + ], + ], + 'svelte' => [ + 'export_paths' => [ + 'resources/js/svelte.js', + ], + ], + ], + ], + ], + ]); + + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileDoesNotExist(base_path('resources/js/vue.js')); + $this->assertFileDoesNotExist(base_path('resources/js/svelte.js')); + + $command = $this->installCoolRunningsModules(); + + // Some fixes to `expectsChoice()` were merged for us, but are not available on 11.20.0 and below + // See: https://github.com/laravel/framework/pull/52408 + if (version_compare(app()->version(), '11.20.0', '>')) { + $command->expectsChoice('Want one of these fancy JS options?', 'svelte', [ + // 'skip_module' => 'No', // This should not be here anymore, because of `skip_option: false` + 'react' => 'React JS', + 'vue' => 'Vue JS', + 'svelte' => 'Svelte', + ]); + } else { + $command->expectsQuestion('Want one of these fancy JS options?', 'svelte'); + } + + $command->run(); + + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileDoesNotExist(base_path('resources/js/vue.js')); + $this->assertFileExists(base_path('resources/js/svelte.js')); + } + + #[Test] + public function it_display_custom_module_prompts_and_option_labels() { $this->setConfig([ 'modules' => [ @@ -938,6 +1092,7 @@ public function it_display_custom_module_prompts() ], 'js' => [ 'prompt' => 'Want one of these fancy JS options?', + 'skip_option' => 'No, thank you!', 'options' => [ 'react' => [ 'label' => 'React JS', @@ -974,7 +1129,7 @@ public function it_display_custom_module_prompts() // See: https://github.com/laravel/framework/pull/52408 if (version_compare(app()->version(), '11.20.0', '>')) { $command->expectsChoice('Want one of these fancy JS options?', 'svelte', [ - 'skip_module' => 'No', + 'skip_module' => 'No, thank you!', 'react' => 'React JS', 'vue' => 'Vue JS', 'svelte' => 'Svelte', @@ -1146,7 +1301,7 @@ public static function validModuleConfigs() } #[Test] - public function it_installs_nested_modules_with_prompt_false_config_by_default_when_running_non_interactively() + public function it_can_still_install_nested_modules_with_prompt_false_or_default_config() { $this->setConfig([ 'export_paths' => [ @@ -1154,13 +1309,13 @@ public function it_installs_nested_modules_with_prompt_false_config_by_default_w ], 'modules' => [ 'canada' => [ - 'prompt' => false, // Setting prompt to false skips confirmation, so this module should still get installed non-interactively + 'prompt' => false, // Setting `prompt: false` skips confirmation, so this module should still get installed 'export_paths' => [ 'resources/css/hockey.css', ], 'modules' => [ 'hockey_players' => [ - 'prompt' => false, // Setting prompt to false skips confirmation, so this module should still get installed non-interactively + 'prompt' => false, // Setting `prompt: false` skips confirmation, so this module should still get installed 'export_paths' => [ 'resources/dictionaries/players.yaml', ], @@ -1174,11 +1329,28 @@ public function it_installs_nested_modules_with_prompt_false_config_by_default_w ], ], 'hockey_night_in_canada' => [ - 'prompt' => false, // Setting prompt to false skips confirmation, so this module should still get installed non-interactively + 'prompt' => false, // Setting `prompt: false` skips confirmation, so this module should still get installed 'export_paths' => [ 'resources/dictionaries/canadian_players.yaml', ], ], + 'js' => [ + 'default' => 'vue', // Setting a `default` option, so this module should still get installed + 'options' => [ + 'react' => [ + 'label' => 'React JS', + 'export_paths' => [ + 'resources/js/react.js', + ], + ], + 'vue' => [ + 'label' => 'Vue JS', + 'export_paths' => [ + 'resources/js/vue.js', + ], + ], + ], + ], ], ], ], @@ -1192,6 +1364,8 @@ public function it_installs_nested_modules_with_prompt_false_config_by_default_w $this->assertFileDoesNotExist(base_path('resources/dictionaries/players.yaml')); $this->assertFileDoesNotExist(base_path('resources/dictionaries/american_players.yaml')); $this->assertFileDoesNotExist(base_path('resources/dictionaries/canadian_players.yaml')); + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileDoesNotExist(base_path('resources/js/vue.js')); $this->installCoolRunnings(); @@ -1201,6 +1375,8 @@ public function it_installs_nested_modules_with_prompt_false_config_by_default_w $this->assertFileExists(base_path('resources/dictionaries/players.yaml')); $this->assertFileDoesNotExist(base_path('resources/dictionaries/american_players.yaml')); $this->assertFileExists(base_path('resources/dictionaries/canadian_players.yaml')); + $this->assertFileDoesNotExist(base_path('resources/js/react.js')); + $this->assertFileExists(base_path('resources/js/vue.js')); } #[Test]