diff --git a/src/Commands/InstallComponentCommand.php b/src/Commands/InstallComponentCommand.php index 475d4bb..5b6c3e2 100644 --- a/src/Commands/InstallComponentCommand.php +++ b/src/Commands/InstallComponentCommand.php @@ -58,6 +58,7 @@ public function handle() $result = (new ComponentInstaller($this, $this->components, $installationConfig))->install($name); if ($result === Command::SUCCESS) { + $this->info(" ✓ Updated sheaf.json and sheaf-lock.json"); $this->components->info("Full documentation: https://sheafui.dev/docs/components/{$name}"); } } @@ -91,6 +92,7 @@ public function getBannerTitle(string $title) { $formattedTitle = Str::of($title)->headline(); + return match (true) { $this->option('only-deps') => "Installing {$formattedTitle} Dependencies Only", $this->option('dry-run') => "Preview: Installing {$formattedTitle} (Dry Run)", diff --git a/src/Commands/RemoveComponentCommand.php b/src/Commands/RemoveComponentCommand.php new file mode 100644 index 0000000..a9de8f7 --- /dev/null +++ b/src/Commands/RemoveComponentCommand.php @@ -0,0 +1,76 @@ +getComponentName(); + + foreach ($componentNames as $name) { + + $this->banner("Removing all $name files"); + + $this->componentRemover->remove($name); + } + $this->info("+ updated sheaf-lock.json and sheaf.json files"); + return Command::SUCCESS; + } + + private function getComponentName() + { + $componentName = $this->argument('name'); + + + if (!$componentName) { + $componentName = text(label: 'What are the component(s) you would like to remove?', placeholder: 'button', required: true); + } + + + return Arr::wrap($componentName); + } + + public function banner(string $title): void + { + $length = strlen(" {$title}") + 4; + + $this->newLine(); + $this->line(str_repeat("═", $length)); + $this->line(" {$title}"); + $this->line(str_repeat("═", $length)); + $this->newLine(); + } +} diff --git a/src/Commands/UpdateCommand.php b/src/Commands/UpdateComponentCommand.php similarity index 96% rename from src/Commands/UpdateCommand.php rename to src/Commands/UpdateComponentCommand.php index c029e8b..242708b 100644 --- a/src/Commands/UpdateCommand.php +++ b/src/Commands/UpdateComponentCommand.php @@ -5,7 +5,7 @@ use Illuminate\Console\Command; use Sheaf\Cli\Services\ComponentUpdater; -class UpdateCommand extends Command +class UpdateComponentCommand extends Command { /** * The name and signature of the console command. diff --git a/src/Commands/WhoAmICommand.php b/src/Commands/WhoAmICommand.php index 4af14cf..2f24954 100644 --- a/src/Commands/WhoAmICommand.php +++ b/src/Commands/WhoAmICommand.php @@ -28,7 +28,7 @@ class WhoAmICommand extends Command */ public function handle() { - $currentUser = SheafConfig::getConfigFile(); + $currentUser = SheafConfig::getCurrentUser(); if(!$currentUser) { $this->components->warn("You're not log in. please login first with your sheaf account."); diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 7bca8ec..3664459 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -9,7 +9,8 @@ use Sheaf\Cli\Commands\LogoutCommand; use Sheaf\Cli\Commands\WhoAmICommand; use Illuminate\Support\ServiceProvider as SupportServiceProvider; -use Sheaf\Cli\Commands\UpdateCommand; +use Sheaf\Cli\Commands\RemoveComponentCommand; +use Sheaf\Cli\Commands\UpdateComponentCommand; class ServiceProvider extends SupportServiceProvider { @@ -22,7 +23,8 @@ public function boot() $this->commands([InstallComponentCommand::class]); $this->commands([LogoutCommand::class]); $this->commands([WhoAmICommand::class]); - $this->commands([UpdateCommand::class]); + $this->commands([UpdateComponentCommand::class]); + $this->commands([RemoveComponentCommand::class]); } $this->publishes( diff --git a/src/Services/ComponentHttpClient.php b/src/Services/ComponentHttpClient.php index 0ac1874..114fe01 100644 --- a/src/Services/ComponentHttpClient.php +++ b/src/Services/ComponentHttpClient.php @@ -16,6 +16,25 @@ public function __construct() $this->baseUrl = config('sheaf.cli.server_url'); $this->token = SheafConfig::getUserToken(); } + + public function fetchComponentFilesPath(string $name) { + $url = "{$this->baseUrl}/api/cli/components/$name/files"; + + $response = Http::asJson()->get($url); + + if ($response->failed()) { + $component = Str::of($name)->headline(); + $message = array_key_exists('message', $response->json() ?? []) ? $response->json()['message'] : ""; + + throw new Exception("Failed to install the component '$component'. \n $message"); + } + + return [ + 'success' => true, + 'data' => $response->collect() + ]; + + } public function fetchResources(string $componentName) { diff --git a/src/Services/ComponentRemover.php b/src/Services/ComponentRemover.php new file mode 100644 index 0000000..21da607 --- /dev/null +++ b/src/Services/ComponentRemover.php @@ -0,0 +1,141 @@ +output = new ConsoleOutput(); + } + + public function remove($name) + { + $this->componentName = $name; + + $this->deleteComponentFiles(); + + $this->cleaningSheafLock($name); + } + + protected function deleteComponentFiles() + { + $componentDirectory = resource_path("views/components/ui/{$this->componentName}"); + + if (File::isDirectory($componentDirectory)) { + File::deleteDirectory($componentDirectory); + $this->message("+ Deleted directory: $componentDirectory"); + } + + $componentFile = resource_path("views/components/ui/{$this->componentName}.blade.php"); + + if (File::exists($componentFile)) { + File::delete($componentFile); + } + } + + protected function cleaningSheafLock($name) + { + $sheafLock = SheafConfig::loadSheafLock(); + + $this->cleaningFiles($sheafLock); + + $this->cleaningDependencies($sheafLock); + + $this->cleaningHelpers($sheafLock); + + $this->updateSheafFile(); + + SheafConfig::saveSheafLock($sheafLock); + } + + protected function cleaningFiles(&$sheafLock) + { + foreach ($sheafLock['files'] as $path => $components) { + $remainingComponents = $this->removeComponentFromList($components); + + if (empty($remainingComponents)) { + $this->deleteFile($path); + unset($sheafLock['files'][$path]); + $this->message("+ Removed File: $path"); + } else { + $sheafLock['files'][$path] = $remainingComponents; + } + } + } + + protected function cleaningDependencies(&$sheafLock) + { + foreach ($sheafLock['internalDependencies'] as $dep => $components) { + $remainingComponents = $this->removeComponentFromList($components); + + if (empty($remainingComponents)) { + $remover = new self(); + + $remover->remove($dep); + unset($sheafLock['internalDependencies'][$dep]); + $this->message("+ Removed internal dependency: $dep (no longer used.)"); + } else { + $sheafLock['internalDependencies'][$dep] = $remainingComponents; + } + } + } + + protected function cleaningHelpers(&$sheafLock) + { + foreach ($sheafLock['helpers'] as $helper => $components) { + $remainingComponents = $this->removeComponentFromList($components); + + if (empty($remainingComponents)) { + $this->deleteHelperFile($helper); + unset($sheafLock['helpers'][$helper]); + $this->message("+ Removed helper: $helper (no longer used.)"); + } else { + $sheafLock['helpers'][$helper] = $remainingComponents; + } + } + } + + protected function updateSheafFile() + { + $sheafFile = SheafConfig::getSheafFile(); + + unset($sheafFile['components'][$this->componentName]); + + SheafConfig::saveSheafFile($sheafFile); + } + + protected function removeComponentFromList($components) + { + return array_values(array_filter($components, fn($c) => $c !== $this->componentName)); + } + + protected function deleteFile($path) + { + if (File::exists($path)) { + File::delete($path); + } + } + + protected function deleteHelperFile($helper) + { + $path = resource_path("views/components/ui/$helper.blade.php"); + + if (File::exists($path)) { + File::delete($path); + } + } + + protected function message(string $message) + { + $this->output->writeln("$message"); + } +} diff --git a/src/Services/ComponentUpdater.php b/src/Services/ComponentUpdater.php index 5c3bdfb..ddc8ec2 100644 --- a/src/Services/ComponentUpdater.php +++ b/src/Services/ComponentUpdater.php @@ -108,17 +108,17 @@ public function installComponent() public function needsUpdate($lastModified) { - $installedComponents = SheafConfig::getInstalledComponents(); + $sheafFile = SheafConfig::getSheafFile(); - if (!$installedComponents) { + if (!$sheafFile) { return true; } - if (!array_key_exists($this->component, $installedComponents['components'])) { + if (!array_key_exists($this->component, $sheafFile['components'])) { return true; } - return $installedComponents['components'][$this->component]['installationTime'] < $lastModified; + return $sheafFile['components'][$this->component]['installationTime'] < $lastModified; } diff --git a/src/Services/JavaScriptAssetService.php b/src/Services/JavaScriptAssetService.php index fac7eab..6576f3c 100644 --- a/src/Services/JavaScriptAssetService.php +++ b/src/Services/JavaScriptAssetService.php @@ -124,6 +124,7 @@ public function SetupLivewire() protected function createUtilsFile(): bool { $path = resource_path("js/utils.js"); + $this->updateSheafLock("resources/js/utils.js", "dark-theme"); if (File::exists($path) && !$this->forceOverwrite) { $this->command->warn("File already exists: utils.js"); @@ -301,4 +302,26 @@ protected function hasLivewire3(): bool return version_compare($version, 'v3.0.0', '>='); } + + protected function updateSheafLock($path, $name) { + + $sheafLockPath = base_path('sheaf-lock.json'); + + $sheafLock = []; + + if(File::exists($sheafLockPath)) { + $sheafLock = json_decode(File::get($sheafLockPath), true) ?: []; + } + + $sheafLock['files'] ??= []; + $sheafLock['files'][$path] ??= []; + + if(!in_array($name, $sheafLock['files'][$path], true)) { + $sheafLock['files'][$path][] = $name; + } + + File::put($sheafLockPath, json_encode($sheafLock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + + } + } diff --git a/src/Services/SheafConfig.php b/src/Services/SheafConfig.php index 25a7976..066a71d 100644 --- a/src/Services/SheafConfig.php +++ b/src/Services/SheafConfig.php @@ -9,9 +9,9 @@ class SheafConfig { public static function saveLoggedInUserCredentials(string $email, string $token) { - $configFile = self::configFile(); + $configFile = self::configFilePath(); - if(File::exists($configFile)) { + if (File::exists($configFile)) { $data = File::get($configFile); $data = json_decode($data, true); } @@ -27,7 +27,7 @@ public static function saveLoggedInUserCredentials(string $email, string $token) public static function saveProjectHash() { - $configFile = self::configFile(); + $configFile = self::configFilePath(); $data = []; @@ -51,9 +51,9 @@ public static function saveProjectHash() public static function getProjectHash() { try { - $configFile = self::configFile(); + $configFile = self::configFilePath(); - if(!File::exists($configFile)) { + if (!File::exists($configFile)) { return self::saveProjectHash(); } @@ -71,7 +71,7 @@ public static function getProjectHash() } } - public static function configFile() + public static function configFilePath() { return base_path('sheaf.json'); } @@ -79,9 +79,9 @@ public static function configFile() public static function getUserToken() { try { - $configFile = self::configFile(); + $configFile = self::configFilePath(); - if(!File::exists($configFile)) { + if (!File::exists($configFile)) { return null; } @@ -93,12 +93,12 @@ public static function getUserToken() } } - public static function getConfigFile() + public static function getCurrentUser() { try { - $configFile = self::configFile(); + $configFile = self::configFilePath(); - if(!File::exists($configFile)) { + if (!File::exists($configFile)) { return null; } @@ -112,23 +112,46 @@ public static function getConfigFile() public static function saveInstalledComponent(string $componentName) { - $installedComponents = self::getInstalledComponents(); + $sheafFile = self::getSheafFile(); - $installedComponents['components'][$componentName] = [ + $sheafFile['components'][$componentName] = [ 'installationTime' => time() ]; - File::put(base_path('sheaf.json'), json_encode($installedComponents, true)); + self::saveSheafFile($sheafFile); + } + + public static function saveSheafFile($sheafFile) { + File::put(self::configFilePath(), json_encode($sheafFile, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); + } + + public static function getSheafFile() + { + $sheafFile = []; + + if (File::exists(self::configFilePath())) { + $sheafFile = json_decode(File::get(self::configFilePath()), true); + } + + return $sheafFile; } - public static function getInstalledComponents() + public static function loadSheafLock(): array { - $installedComponents = []; + $sheafLockPath = base_path('sheaf-lock.json'); - if (File::exists(base_path('sheaf.json'))) { - $installedComponents = json_decode(File::get(base_path('sheaf.json')), true); + if (!File::exists($sheafLockPath)) { + return []; } - return $installedComponents; + return json_decode(File::get($sheafLockPath), true) ?: []; + } + + public static function saveSheafLock(array $sheafLock): void + { + File::put( + base_path('sheaf-lock.json'), + json_encode($sheafLock, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) + ); } } diff --git a/src/Strategies/Installation/DependencyOnlyStrategy.php b/src/Strategies/Installation/DependencyOnlyStrategy.php index 50801c8..a3dc11f 100644 --- a/src/Strategies/Installation/DependencyOnlyStrategy.php +++ b/src/Strategies/Installation/DependencyOnlyStrategy.php @@ -6,10 +6,12 @@ use Sheaf\Cli\Contracts\BaseInstallationStrategy; use Sheaf\Cli\Traits\CanHandleDependenciesInstallation; use Illuminate\Console\Command; +use Sheaf\Cli\Traits\CanUpdateSheafLock; class DependencyOnlyStrategy extends BaseInstallationStrategy { use CanHandleDependenciesInstallation; + use CanUpdateSheafLock; public function execute($componentResources): int { @@ -28,6 +30,9 @@ public function execute($componentResources): int $this->installDependencies($dependencies); + $this->updateSheafLock(null, $dependencies, $this->componentName); + + return Command::SUCCESS; } diff --git a/src/Strategies/Installation/FullInstallationStrategy.php b/src/Strategies/Installation/FullInstallationStrategy.php index 2982f71..b5a6826 100644 --- a/src/Strategies/Installation/FullInstallationStrategy.php +++ b/src/Strategies/Installation/FullInstallationStrategy.php @@ -8,12 +8,16 @@ use Sheaf\Cli\Services\SheafConfig; use Sheaf\Cli\Traits\CanHandleDependenciesInstallation; use Illuminate\Console\Command; +use Illuminate\Support\Arr; +use Illuminate\Support\Facades\File; +use Sheaf\Cli\Traits\CanUpdateSheafLock; class FullInstallationStrategy extends BaseInstallationStrategy { use CanHandleDependenciesInstallation; use CanHandleFilesInstallation; + use CanUpdateSheafLock; public function execute($componentResources): int { @@ -24,10 +28,12 @@ public function execute($componentResources): int $this->reportInstallation($createdFiles); + $this->runInitialization(); $this->installDependencies($componentResources->get('dependencies')); + $this->updateSheafLock($createdFiles, $componentResources->get('dependencies'), $this->componentName); return Command::SUCCESS; } @@ -39,7 +45,7 @@ public function runInitialization() $this->initInstallationConfigForFilesInstallation($this->installationConfig); } - private function reportInstallation(array $createdFiles): void + protected function reportInstallation(array $createdFiles): void { $this->reportSuccess(); @@ -48,4 +54,5 @@ private function reportInstallation(array $createdFiles): void } } + } diff --git a/src/Strategies/Installation/SkipDependenciesStrategy.php b/src/Strategies/Installation/SkipDependenciesStrategy.php index 4ab5556..449c1a8 100644 --- a/src/Strategies/Installation/SkipDependenciesStrategy.php +++ b/src/Strategies/Installation/SkipDependenciesStrategy.php @@ -8,6 +8,7 @@ use Sheaf\Cli\Services\SheafConfig; use Illuminate\Console\Command; use Illuminate\Support\Facades\File; +use Sheaf\Cli\Traits\CanUpdateSheafLock; use function Laravel\Prompts\confirm; use function Laravel\Prompts\select; @@ -15,6 +16,7 @@ class SkipDependenciesStrategy extends BaseInstallationStrategy { use CanHandleFilesInstallation; + use CanUpdateSheafLock; public function execute($componentResources): int { @@ -29,6 +31,7 @@ public function execute($componentResources): int $createdFiles = $this->installFiles($componentResources->get('files')); SheafConfig::saveInstalledComponent($this->componentName); + $this->updateSheafLock($createdFiles, null, $this->componentName); $this->reportInstallation($createdFiles); diff --git a/src/Traits/CanHandleDependenciesInstallation.php b/src/Traits/CanHandleDependenciesInstallation.php index c1c2623..a518e22 100644 --- a/src/Traits/CanHandleDependenciesInstallation.php +++ b/src/Traits/CanHandleDependenciesInstallation.php @@ -163,20 +163,20 @@ public function installExternalDeps(array $deps) public function shouldInstallDependency($dependency, $info) { - $installedComponents = SheafConfig::getInstalledComponents(); + $sheafFile = SheafConfig::getSheafFile(); if(!File::exists(resource_path("views/components/ui/$dependency"))) { return true; } - if (!$installedComponents) { + if (!$sheafFile) { return true; } - if (!array_key_exists($dependency, $installedComponents['components'])) { + if (!array_key_exists($dependency, $sheafFile['components'])) { return true; } - return $installedComponents['components'][$dependency]['installationTime'] < $info['lastModified']; + return $sheafFile['components'][$dependency]['installationTime'] < $info['lastModified']; } } diff --git a/src/Traits/CanUpdateSheafLock.php b/src/Traits/CanUpdateSheafLock.php new file mode 100644 index 0000000..2e5b092 --- /dev/null +++ b/src/Traits/CanUpdateSheafLock.php @@ -0,0 +1,71 @@ +name = $name; + + $this->updateFilesInLock($sheafLock, $files); + $this->updateDependenciesInLock($sheafLock, $dependencies); + + SheafConfig::saveSheafLock($sheafLock); + } + + + protected function updateFilesInLock(&$sheafLock, $files) + { + if(!$files) return; + + $sheafLock['files'] ??= []; + + foreach ($files as $file) { + if ($this->shouldSkipFile($file['path'])) continue; + + $this->addComponentToLockEntry($sheafLock['files'], $file['path']); + } + } + + protected function updateDependenciesInLock(&$sheafLock, $dependencies) + { + if(!$dependencies) return; + + if (isset($dependencies['helpers'])) { + $this->addComponentToLockSection($sheafLock, 'helpers', $dependencies['helpers']); + } + + if (isset($dependencies['internal'])) { + $this->addComponentToLockSection($sheafLock, 'internalDependencies', Arr::wrap($dependencies['internal'])); + } + } + + protected function shouldSkipFile($path) + { + return str_contains($path, "resources/views/components/ui/{$this->name}"); + } + + protected function addComponentToLockSection(&$sheafLock, $section, $items) + { + $sheafLock[$section] ??= []; + + foreach ($items as $item => $value) { + $this->addComponentToLockEntry($sheafLock[$section], $item); + } + } + + protected function addComponentToLockEntry(&$lockSection, $key) + { + $lockSection[$key] ??= []; + if (!in_array($this->name, $lockSection[$key], true)) { + $lockSection[$key][] = $this->name; + } + } +} \ No newline at end of file