diff --git a/modules/system/ServiceProvider.php b/modules/system/ServiceProvider.php index 774694d0e0..ad047d792a 100644 --- a/modules/system/ServiceProvider.php +++ b/modules/system/ServiceProvider.php @@ -301,6 +301,11 @@ protected function registerConsole() $this->registerConsoleCommand('create.settings', \System\Console\CreateSettings::class); $this->registerConsoleCommand('create.test', \System\Console\CreateTest::class); + $this->app->singleton(Console\VendorPublish::class, function ($app) { + return new Console\VendorPublish($app['files']); + }); + $this->commands(Console\VendorPublish::class); + $this->registerConsoleCommand('winter.up', \System\Console\WinterUp::class); $this->registerConsoleCommand('winter.down', \System\Console\WinterDown::class); $this->registerConsoleCommand('winter.update', \System\Console\WinterUpdate::class); diff --git a/modules/system/console/PluginList.php b/modules/system/console/PluginList.php index f7883c34d4..db6d985774 100644 --- a/modules/system/console/PluginList.php +++ b/modules/system/console/PluginList.php @@ -1,7 +1,5 @@ sortBy('code') as $plugin) { $rows[] = [ $plugin->code, $plugin->version, diff --git a/modules/system/console/VendorPublish.php b/modules/system/console/VendorPublish.php new file mode 100644 index 0000000000..6f6d826ec8 --- /dev/null +++ b/modules/system/console/VendorPublish.php @@ -0,0 +1,80 @@ +(eg: Winter.Blog)} + {--existing : Publish and overwrite only the files that have already been published} + {--force : Overwrite any existing files} + {--all : Publish assets for all service providers without prompt} + {--provider= : The service provider that has assets you want to publish} + {--tag=* : One or many tags that have assets you want to publish}'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Publish any publishable assets from vendor packages to the specified plugin.'; + + /** + * Execute the console command. + * + * @return void + */ + public function handle() + { + $this->validateProvidedPlugin(); + + parent::handle(); + } + + /** + * Publishes the assets for a tag. + * + * @param string $tag + * @return mixed + */ + protected function publishTag($tag) + { + /** + * @TODO: Continue here: + * - Temporarily override the internal paths for: + * - config_path() -> plugin/config + * - resource_path() -> plugin/resources (May want to do some magic rewriting of resources/views to plugin/views or just add support for the resources folder in a plugin) + * - database_path() -> plugin/updates/$tag/ (May want to add support for integrating the migrations in order to the version.yaml file, same as create:migration) + */ + $pathsToPublish = $this->pathsToPublish($tag); + + if ($publishing = count($pathsToPublish) > 0) { + $this->components->info(sprintf( + 'Publishing %sassets', + $tag ? "[$tag] " : '', + )); + } + + foreach ($pathsToPublish as $from => $to) { + $this->publishItem($from, $to); + } + + if ($publishing === false) { + $this->components->info('No publishable resources for tag ['.$tag.'].'); + } else { + $this->laravel['events']->dispatch(new VendorTagPublished($tag, $pathsToPublish)); + + $this->newLine(); + } + } +} diff --git a/modules/system/console/traits/HasPluginArgument.php b/modules/system/console/traits/HasPluginArgument.php index 39d5d669d1..0766a50f95 100644 --- a/modules/system/console/traits/HasPluginArgument.php +++ b/modules/system/console/traits/HasPluginArgument.php @@ -3,6 +3,11 @@ use InvalidArgumentException; use System\Classes\PluginBase; use System\Classes\PluginManager; +use Throwable; +use Winter\Storm\Support\Arr; + +use function Laravel\Prompts\search; +use function Laravel\Prompts\select; /** * Console Command Trait that provides autocompletion for the "plugin" argument @@ -22,6 +27,8 @@ trait HasPluginArgument */ // protected $validatePluginInput = true; + protected ?PluginBase $plugin = null; + /** * Return available plugins for autocompletion of the "plugin" argument */ @@ -45,7 +52,33 @@ public function suggestPluginValues() } } - return $plugins; + return Arr::sort($plugins); + } + + /** + * Prompt for which provider or tag to publish. + */ + protected function promptForPlugin(): ?string + { + $choices = $this->suggestPluginValues(); + + $choice = windows_os() + ? select( + "Which plugin would you like to select?", + $choices, + scroll: 15, + ) + : search( + label: "Which plugin would you like to select?", + placeholder: 'Search...', + options: fn ($search) => array_values(array_filter( + $choices, + fn ($choice) => str_contains(strtolower($choice), strtolower($search)) + )), + scroll: 15, + ); + + return $choice; } /** @@ -55,7 +88,7 @@ public function suggestPluginValues() public function getPluginIdentifier($identifier = null): string { $pluginManager = PluginManager::instance(); - $pluginName = $identifier ?? $this->argument('plugin'); + $pluginName = $identifier ?? $this->argument('plugin') ?? $this->promptForPlugin(); $pluginName = $pluginManager->normalizeIdentifier($pluginName); if ( @@ -76,4 +109,24 @@ public function getPlugin($identifier = null): ?PluginBase { return PluginManager::instance()->findByIdentifier($this->getPluginIdentifier($identifier)); } + + /** + * Validates that the provided plugin can be accessed + * + * @throws InvalidArgumentException if it cannot be + */ + public function validateProvidedPlugin() + { + if (!$this->plugin) { + try { + $this->plugin = $this->getPlugin(); + } catch (Throwable $e) { + throw new InvalidArgumentException(sprintf( + 'Provided plugin "%s" failed to load: %s', + $this->argument('plugin'), + $e->getMessage() + )); + } + } + } }