From e6ac9aafecbc781162fd543dd33a1719e9e87020 Mon Sep 17 00:00:00 2001 From: YoussefACHCHIRAJ Date: Wed, 15 Oct 2025 21:25:42 +0100 Subject: [PATCH] fix: ask for confirmation before deleting a dependency --- src/Commands/RemoveComponentCommand.php | 21 ++++++-- src/Services/ComponentRemover.php | 50 +++++++++---------- tests/Feature/Commands/InstallCommandTest.php | 27 ++++++---- tests/Feature/Commands/RemoveCommandTest.php | 47 +++++++++++++++-- 4 files changed, 102 insertions(+), 43 deletions(-) diff --git a/src/Commands/RemoveComponentCommand.php b/src/Commands/RemoveComponentCommand.php index 5fd9619..1d5f4a2 100644 --- a/src/Commands/RemoveComponentCommand.php +++ b/src/Commands/RemoveComponentCommand.php @@ -4,6 +4,7 @@ use Illuminate\Console\Command; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\File; use Sheaf\Cli\Services\ComponentRemover; use function Laravel\Prompts\text; @@ -33,18 +34,25 @@ public function handle() { $componentNames = $this->getComponentName(); - $success = Command::SUCCESS; + $sheafFileEdited = false; $componentRemover = new ComponentRemover($this); foreach ($componentNames as $name) { + $isExists = $this->checkComponentExistence($name); + + if (!$isExists) { + $this->info("Component is not installed in this project."); + continue; + } $this->banner("Removing all $name files"); - $success = $componentRemover->remove($name); + $componentRemover->remove($name); + $sheafFileEdited = true; } - if($success === Command::SUCCESS) { + if ($sheafFileEdited) { $this->info("+ updated sheaf-lock.json and sheaf.json files"); } return Command::SUCCESS; @@ -73,4 +81,11 @@ public function banner(string $title): void $this->line(str_repeat("═", $length)); $this->newLine(); } + + protected function checkComponentExistence(string $name) + { + + return File::exists(resource_path("views/components/ui/$name")) || + File::exists(resource_path("views/components/ui/$name.blade.php")); + } } diff --git a/src/Services/ComponentRemover.php b/src/Services/ComponentRemover.php index 13aa063..b511dec 100644 --- a/src/Services/ComponentRemover.php +++ b/src/Services/ComponentRemover.php @@ -6,38 +6,25 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\File; +use function Laravel\Prompts\confirm; + class ComponentRemover { protected $output; protected $componentName; - public function __construct(protected $command) - { - } + public function __construct(protected $command) {} public function remove($name) { $this->componentName = $name; - $isExists = $this->checkComponentExistence(); - - if(!$isExists) { - $this->message("Component is not installed in this project."); - return Command::FAILURE; - } $this->deleteComponentFiles(); $this->cleaningSheafLock($name); } - protected function checkComponentExistence() - { - - return File::exists(resource_path("views/components/ui/{$this->componentName}")) || - File::exists(resource_path("views/components/ui/{$this->componentName}.blade.php")); - } - protected function deleteComponentFiles() { $componentDirectory = resource_path("views/components/ui/{$this->componentName}"); @@ -54,7 +41,7 @@ protected function deleteComponentFiles() } } - protected function cleaningSheafLock($name) + protected function cleaningSheafLock() { $sheafLock = SheafConfig::loadSheafLock(); @@ -86,28 +73,39 @@ protected function cleaningFiles(&$sheafLock) protected function cleaningDependencies(&$sheafLock) { - if(!isset($sheafLock['internalDependencies'])) { + if (!isset($sheafLock['internalDependencies'])) { return; } foreach ($sheafLock['internalDependencies'] as $dep => $components) { + + if(!in_array($this->componentName, $components, true)) { + continue; + } $remainingComponents = $this->removeComponentFromList($components); - if (empty($remainingComponents)) { - $remover = new self($this->command); + $sheafLock['internalDependencies'][$dep] = $remainingComponents; + + if (!empty($remainingComponents)) { + continue; + } - $remover->remove($dep); - unset($sheafLock['internalDependencies'][$dep]); - $this->message("+ Removed internal dependency: $dep (no longer used.)"); - } else { - $sheafLock['internalDependencies'][$dep] = $remainingComponents; + $confirm = confirm(label: "$dep is no longer used as a dependency, would you like to remove it?", default: false, hint: "If you use $dep somewhere else in your project, select no."); + + if (!$confirm) { + continue; } + + $remover = new self($this->command); + $remover->remove($dep); + $this->message("+ Removed internal dependency: $dep (no longer used.)"); + unset($sheafLock['internalDependencies'][$dep]); } } protected function cleaningHelpers(&$sheafLock) { - if(!isset($sheafLock['helpers'])) { + if (!isset($sheafLock['helpers'])) { return; } diff --git a/tests/Feature/Commands/InstallCommandTest.php b/tests/Feature/Commands/InstallCommandTest.php index 415aaaf..cde8c67 100644 --- a/tests/Feature/Commands/InstallCommandTest.php +++ b/tests/Feature/Commands/InstallCommandTest.php @@ -1,5 +1,6 @@ assertExitCode(0) ->run(); - $this->view('components.ui.separator.index'); - $this->artisan("sheaf:remove separator"); + expect(view()->exists('components.ui.separator.index'))->toBeTrue(); + $this->artisan("sheaf:remove separator")->run(); }); it("installs a component along with its dependencies when confirmed", function () { + $sheafLock = json_decode(File::get(base_path("sheaf-lock.json")), true); + + if(isset($sheafLock['internalDependencies']['icon'])) { + $this->artisan("sheaf:remove icon radio")->run(); + } + $this->artisan("sheaf:install radio") - ->expectsQuestion('Install required dependencies?', 'yes') + ->expectsQuestion('Install required dependencies?', true) ->assertExitCode(0) ->run(); - $this->view("components.ui.radio.group", ['slot' => 'default']); - $this->artisan("sheaf:remove radio"); + expect(view()->exists("components.ui.radio.group"))->toBeTrue(); + + $this->artisan("sheaf:remove radio")->expectsQuestion("icon is no longer used as a dependency, would you like to remove it?", true)->run(); + }); @@ -37,9 +46,9 @@ ->assertExitCode(0) ->run(); - $this->view("components.ui.separator.index"); + expect(view()->exists("components.ui.separator.index"))->toBeTrue(); - $this->artisan("sheaf:remove separator"); + $this->artisan("sheaf:remove separator")->run(); }); it("installs only dependencies when the component already exists and that option is chosen", function () { @@ -56,9 +65,9 @@ ->assertExitCode(0) ->run(); - $this->view("components.ui.separator.index"); + expect(view()->exists("components.ui.separator.index"))->toBeTrue(); - $this->artisan("sheaf:remove separator"); + $this->artisan("sheaf:remove separator")->run(); }); diff --git a/tests/Feature/Commands/RemoveCommandTest.php b/tests/Feature/Commands/RemoveCommandTest.php index 026e038..2612ce5 100644 --- a/tests/Feature/Commands/RemoveCommandTest.php +++ b/tests/Feature/Commands/RemoveCommandTest.php @@ -1,7 +1,5 @@ exists("components.ui.$dependency.index"))->toBeTrue(); expect(view()->exists("components.ui.$helper"))->toBeTrue(); - $this->artisan("sheaf:remove $component") ->assertExitCode(0) + ->expectsQuestion("$dependency is no longer used as a dependency, would you like to remove it?", true) ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$dependency") ->expectsOutputToContain("Removed internal dependency: $dependency (no longer used.)") @@ -86,3 +84,42 @@ expect($sheafLock)->not->toContain("$component"); expect($sheafLock)->not->toContain("$dependency"); }); + +it("removes an installed component without dependencies after not confirming to remove dependencies", function () { + + $component = 'autocomplete'; + $dependency = 'icon'; + $helper = 'popup'; + $baseDirectory = resource_path("views/components/ui/"); + + //* using force option to ensure the command runs + $this->artisan("sheaf:install $component --force --internal-deps") + ->assertExitCode(0) + ->run(); + + expect(File::isDirectory("$baseDirectory/$component"))->toBeTrue(); + expect(File::isDirectory("$baseDirectory/$dependency"))->toBeTrue(); + expect(File::exists("$baseDirectory/$helper.blade.php"))->toBeTrue(); + + expect(view()->exists("components.ui.$component.index"))->toBeTrue(); + expect(view()->exists("components.ui.$dependency.index"))->toBeTrue(); + expect(view()->exists("components.ui.$helper"))->toBeTrue(); + + + $this->artisan("sheaf:remove $component") + ->assertExitCode(0) + ->expectsQuestion("$dependency is no longer used as a dependency, would you like to remove it?", false) + ->doesntExpectOutputToContain("Deleted directory: resources/views/components/ui/$dependency") + ->doesntExpectOutputToContain("Removed internal dependency: $dependency (no longer used.)") + ->expectsOutputToContain("Deleted directory: resources/views/components/ui/$component") + ->expectsOutputToContain("Removed helper: $helper (no longer used.)") + ->run(); + + expect(File::isDirectory("$baseDirectory/$component"))->toBeFalse(); + expect(File::isDirectory("$baseDirectory/$dependency"))->toBeTrue(); + + $sheafLock = File::get(base_path("sheaf-lock.json")); + + expect($sheafLock)->not->toContain("$component"); + expect($sheafLock)->toContain("$dependency"); +});