From 0952e08beb1a4f8b01b72661bf4adbbcebfdc2a0 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Fri, 21 Feb 2025 07:06:03 +0000 Subject: [PATCH] Allow "winter:util compile" command to download Font Awesome if required --- modules/system/console/WinterUtil.php | 100 ++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/modules/system/console/WinterUtil.php b/modules/system/console/WinterUtil.php index 28f6244c48..c26b483ff3 100644 --- a/modules/system/console/WinterUtil.php +++ b/modules/system/console/WinterUtil.php @@ -3,7 +3,9 @@ use Lang; use File; use Config; +use DirectoryIterator; use Illuminate\Console\Command; +use Illuminate\Support\Facades\File as Filesystem; use Illuminate\Support\Facades\Storage; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; @@ -11,6 +13,9 @@ use System\Classes\CombineAssets; use System\Models\Parameter; use System\Models\File as FileModel; +use Winter\Storm\Filesystem\Zip; +use Winter\Storm\Network\Http as NetworkHttp; +use Winter\Storm\Support\Facades\Http; /** * Console command for other utility commands. @@ -114,6 +119,7 @@ protected function getOptions() ['debug', null, InputOption::VALUE_NONE, 'Run the operation in debug / development mode.'], ['projectId', null, InputOption::VALUE_REQUIRED, 'Specify a projectId for set project'], ['missing-files', null, InputOption::VALUE_NONE, 'Purge system_files records for missing storage files'], + ['include-icons', null, InputOption::VALUE_NONE, 'Include icons.less when compiling LESS files'], ]; } @@ -146,6 +152,67 @@ protected function utilCompileScss() protected function utilCompileAssets($type = null) { + // Download Font Awesome icons if they are missing and LESS files are being compiled + if ( + ( + !is_dir(base_path('node_modules/@fortawesome/fontawesome-free')) + || !is_file(base_path('node_modules/@fortawesome/fontawesome-free/less/_variables.less')) + ) + && ($type === 'less' || $type === null) + ) { + $this->comment('Downloading Font Awesome icons...'); + + $releases = Http::get('https://api.github.com/repos/FortAwesome/Font-Awesome/releases/latest', function (NetworkHttp $http) { + $http->header('Accept', 'application/json'); + $http->header('User-Agent', 'Winter CMS'); + }); + + if (!$releases->ok) { + $this->error('Failed to download Font Awesome icons'); + return; + } + + $releases = json_decode($releases->body, true); + $releaseName = null; + $zipUrl = null; + + foreach ($releases['assets'] as $asset) { + if ( + str_starts_with($asset['name'], 'fontawesome-free-') + && str_ends_with($asset['name'], 'web.zip') + ) { + $zipUrl = $asset['browser_download_url']; + $releaseName = pathinfo($asset['name'], PATHINFO_FILENAME); + } + } + + if (is_null($zipUrl)) { + $this->error('Failed to find Font Awesome icons download URL'); + return; + } + + Http::get($zipUrl, function (NetworkHttp $http) { + $http->header('User-Agent', 'Winter CMS'); + $http->toFile(storage_path('temp/fontawesome.zip')); + }); + + // Extract Font Awesome files + if (is_dir(storage_path('temp/fontawesome'))) { + $this->rimraf(storage_path('temp/fontawesome')); + } + + Zip::extract(storage_path('temp/fontawesome.zip'), storage_path('temp/fontawesome')); + Filesystem::delete(storage_path('temp/fontawesome.zip')); + + // Move Font Awesome LESS and font files into place + Filesystem::makeDirectory(base_path('node_modules/@fortawesome/fontawesome-free/less'), 0755, true); + Filesystem::moveDirectory(storage_path('temp/fontawesome/' . $releaseName . '/less'), base_path('node_modules/@fortawesome/fontawesome-free/less')); + Filesystem::copyDirectory(storage_path('temp/fontawesome/' . $releaseName . '/webfonts'), base_path('modules/system/assets/ui/font')); + + // Remove remaining files + $this->rimraf(storage_path('temp/fontawesome')); + } + $this->comment('Compiling registered asset bundles...'); Config::set('cms.enableAssetMinify', !$this->option('debug')); @@ -458,4 +525,37 @@ protected function utilSetProject() 'system::project.owner' => $result['owner'], ]); } + + /** + * PHP-based "rm -rf" command. + * + * Recursively removes a directory and all files and subdirectories within. + */ + protected function rimraf(string $path): void + { + if (!file_exists($path)) { + return; + } + + if (is_file($path)) { + @unlink($path); + return; + } + + $dir = new DirectoryIterator($path); + + foreach ($dir as $item) { + if ($item->isDot()) { + continue; + } + + if ($item->isDir()) { + $this->rimraf($item->getPathname()); + } + + @unlink($item->getPathname()); + } + + @rmdir($path); + } }