diff --git a/src/Core/Console/VendorPublishCommand.php b/src/Core/Console/VendorPublishCommand.php index 048346b4..f32a4aa5 100644 --- a/src/Core/Console/VendorPublishCommand.php +++ b/src/Core/Console/VendorPublishCommand.php @@ -6,13 +6,21 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Arr; use Illuminate\Support\ServiceProvider; -use League\Flysystem\Adapter\Local; +use Illuminate\Support\Str; +use League\Flysystem\Filesystem as Flysystem; +use League\Flysystem\Local\LocalFilesystemAdapter as LocalAdapter; use League\Flysystem\MountManager; +use League\Flysystem\UnixVisibility\PortableVisibilityConverter; +use League\Flysystem\Visibility; +use Symfony\Component\Console\Attribute\AsCommand; +#[AsCommand(name: 'vendor:publish')] class VendorPublishCommand extends Command { /** - * @var Filesystem + * The filesystem instance. + * + * @var \Illuminate\Filesystem\Filesystem */ protected $files; @@ -21,28 +29,51 @@ class VendorPublishCommand extends Command * * @var string */ - protected $provider; + protected ?string $provider = null; /** * The tags to publish. * * @var array */ - protected $tags = []; + protected array $tags = []; /** + * The console command signature. + * * @var string */ - protected $signature = 'vendor:publish {--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.}'; + protected $signature = 'vendor:publish + {--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 name of the console command. + * + * This name is used to identify the command during lazy loading. + * + * @var string|null + * + * @deprecated + */ + protected static $defaultName = 'vendor:publish'; + + /** + * The console command description. + * * @var string */ protected $description = 'Publish any publishable assets from vendor packages'; + /** + * Create a new command instance. + * + * @param \Illuminate\Filesystem\Filesystem $files + * @return void + */ public function __construct(Filesystem $files) { parent::__construct(); @@ -52,30 +83,31 @@ public function __construct(Filesystem $files) /** * Execute the console command. + * + * @return void */ - public function handle() + public function handle(): void { $this->determineWhatShouldBePublished(); - foreach ($this->tags ?: [null] as $tag) { + foreach ($this->tags as $tag) { $this->publishTag($tag); } - - $this->info('Publishing complete.'); } /** - * Determine the provider of tag(s) to publish. + * Determine the provider or tag(s) to publish. + * + * @return void */ - protected function determineWhatShouldBePublished() + protected function determineWhatShouldBePublished(): void { if ($this->option('all')) { return; } - list($this->provider, $this->tags) = [ - $this->option('provider'), - (array) $this->option('tag'), + [$this->provider, $this->tags] = [ + $this->option('provider'), (array) $this->option('tag'), ]; if (! $this->provider && ! $this->tags) { @@ -85,12 +117,14 @@ protected function determineWhatShouldBePublished() /** * Prompt for which provider or tag to publish. + * + * @return void */ - protected function promptForProviderOrTag() + protected function promptForProviderOrTag(): void { - $choice = $this->choice( + $choice = $this->components->choice( "Which provider or tag's files would you like to publish?", - $choices = $this->publishableChoices(), + $choices = $this->publishableChoices() ); if ($choice == $choices[0] || is_null($choice)) { @@ -101,16 +135,16 @@ protected function promptForProviderOrTag() } /** - * return the choices available via the prompt. + * The choices available via the prompt. * * @return array */ - protected function publishableChoices() + protected function publishableChoices(): array { return array_merge( ['Publish files from all providers and tags listed below'], - preg_filter('/^/', 'Provider: ', Arr::sort(ServiceProvider::publishableProviders())), - preg_filter('/^/', 'Tag: ', Arr::sort(ServiceProvider::publishableGroups())), + preg_filter('/^/', 'Provider: ', Arr::sort(ServiceProvider::publishableProviders())), + preg_filter('/^/', 'Tag: ', Arr::sort(ServiceProvider::publishableGroups())) ); } @@ -118,10 +152,11 @@ protected function publishableChoices() * Parse the answer that was given via the prompt. * * @param string $choice + * @return void */ - protected function parseChoice($choice) + protected function parseChoice(string $choice): void { - list($type, $value) = explode(': ', strip_tags($choice)); + [$type, $value] = explode(': ', strip_tags($choice)); if ($type === 'Provider') { $this->provider = $value; @@ -131,27 +166,46 @@ protected function parseChoice($choice) } /** - * Publish the assets for a tag. + * Publishes the assets for a tag. * * @param string $tag + * @return void */ - protected function publishTag($tag) + protected function publishTag(string $tag): void { - foreach ($this->pathsToPublish($tag) as $from => $to) { + $published = false; + + $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->newLine(); + } } /** - * Get all the paths to publish. + * Get all of the paths to publish. * * @param string $tag - * * @return array */ - protected function pathsToPublish($tag) + protected function pathsToPublish(string $tag): array { - return ServiceProvider::pathsToPublish($this->provider, $tag); + return ServiceProvider::pathsToPublish( + $this->provider, $tag + ); } /** @@ -159,20 +213,19 @@ protected function pathsToPublish($tag) * * @param string $from * @param string $to + * @return void */ - protected function publishItem($from, $to) + protected function publishItem(string $from, string $to): void { if ($this->files->isFile($from)) { $this->publishFile($from, $to); - return; } elseif ($this->files->isDirectory($from)) { $this->publishDirectory($from, $to); - return; } - $this->error("Can't locate path: <{$from}>"); + $this->components->error("Can't locate path: <{$from}>"); } /** @@ -180,46 +233,70 @@ protected function publishItem($from, $to) * * @param string $from * @param string $to + * @return void */ - protected function publishFile($from, $to) + protected function publishFile(string $from, string $to): void { - if (! $this->files->exists($to) || $this->option('force')) { + if ((! $this->option('existing') && (! $this->files->exists($to) || $this->option('force'))) + || ($this->option('existing') && $this->files->exists($to))) { $this->createParentDirectory(dirname($to)); + $this->files->copy($from, $to); - $this->status($from, $to, 'File'); + + $this->status($from, $to, 'file'); + } else { + if ($this->option('existing')) { + $this->components->twoColumnDetail(sprintf( + 'File [%s] does not exist', + str_replace(base_path().'/', '', $to), + ), 'SKIPPED'); + } else { + $this->components->twoColumnDetail(sprintf( + 'File [%s] already exists', + str_replace(base_path().'/', '', realpath($to)), + ), 'SKIPPED'); + } } } /** * Publish the directory to the given directory. * - * @param string $from - * @param string $to - * - * @throws \League\Flysystem\FileNotFoundException + * @param string $from + * @param string $to + * @return void */ - protected function publishDirectory($from, $to) + protected function publishDirectory($from, $to): void { + $visibility = PortableVisibilityConverter::fromArray([], Visibility::PUBLIC); + $this->moveManagedFiles(new MountManager([ - 'from' => new \League\Flysystem\Filesystem(new Local($from)), - 'to' => new \League\Flysystem\Filesystem(new Local($to)), + 'from' => new Flysystem(new LocalAdapter($from)), + 'to' => new Flysystem(new LocalAdapter($to, $visibility)), ])); - $this->status($from, $to, 'Directory'); + $this->status($from, $to, 'directory'); } /** * Move all the files in the given MountManager. * * @param \League\Flysystem\MountManager $manager - * - * @throws \League\Flysystem\FileNotFoundException + * @return void */ - protected function moveManagedFiles($manager) + protected function moveManagedFiles(MountManager $manager): void { foreach ($manager->listContents('from://', true) as $file) { - if ($file['type'] === 'file' && (! $manager->has('to://' . $file['path']) || $this->option('force'))) { - $manager->put('to://' . $file['path'], $manager->read('from://' . $file['path'])); + $path = Str::after($file['path'], 'from://'); + + if ( + $file['type'] === 'file' + && ( + (! $this->option('existing') && (! $manager->fileExists('to://'.$path) || $this->option('force'))) + || ($this->option('existing') && $manager->fileExists('to://'.$path)) + ) + ) { + $manager->write('to://'.$path, $manager->read($file['path'])); } } } @@ -228,8 +305,9 @@ protected function moveManagedFiles($manager) * Create the directory to house the published files if needed. * * @param string $directory + * @return void */ - protected function createParentDirectory($directory) + protected function createParentDirectory(string $directory): void { if (! $this->files->isDirectory($directory)) { $this->files->makeDirectory($directory, 0755, true); @@ -242,14 +320,19 @@ protected function createParentDirectory($directory) * @param string $from * @param string $to * @param string $type + * @return void */ - protected function status($from, $to, $type) + protected function status(string $from, string $to, string $type): void { - $from = str_replace(base_path(), '', realpath($from)); - $to = str_replace(base_path(), '', realpath($to)); + $from = str_replace(base_path().'/', '', realpath($from)); - $this->line( - "Copied {$type} [{$from}] To [{$to}]", - ); + $to = str_replace(base_path().'/', '', realpath($to)); + + $this->components->task(sprintf( + 'Copying %s [%s] to [%s]', + $type, + $from, + $to, + )); } }