Skip to content

Extend vendor:publish so it will write manifest about what was published #2402

Description

@elcreator

Laravel has no built-in vendor:unpublish or rollback command. But here it may be useful during the package development or using packages which are in very raw active development state.
So vendor:publish should be extended to unambiguously track what was written and where, which folders and files were created, deleted, renamed etc by which exact version. And write manifest about it i.e. somewhere to EVO_STORAGE_PATH.

So a custom vendor:unpublish command will be possible. The main issue is not discovering the destinations. The issue is deleting them safely.

<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Illuminate\Support\ServiceProvider;

class VendorUnpublishCommand extends Command
{
    protected $signature = 'vendor:unpublish
        {--provider= : The service provider class}
        {--tag= : The publish tag}
        {--force : Delete without confirmation}';

    protected $description = 'Delete files published by a package service provider';

    public function handle(Filesystem $files): int
    {
        $provider = $this->option('provider');
        $tag = $this->option('tag');

        $paths = ServiceProvider::pathsToPublish($provider, $tag);

        if ($paths === []) {
            $this->error('No publishable paths were found.');

            return self::FAILURE;
        }

        foreach ($paths as $source => $destination) {
            $this->line($destination);
        }

        if (
            ! $this->option('force')
            && ! $this->confirm('Delete all listed destinations?')
        ) {
            return self::SUCCESS;
        }

        foreach ($paths as $source => $destination) {
            if ($files->isDirectory($source)) {
                $this->deletePublishedDirectory($files, $source, $destination);
            } elseif ($files->exists($destination)) {
                $files->delete($destination);
            }
        }

        return self::SUCCESS;
    }

    private function deletePublishedDirectory(
        Filesystem $files,
        string $source,
        string $destination
    ): void {
        foreach ($files->allFiles($source) as $sourceFile) {
            $relativePath = $sourceFile->getRelativePathname();
            $publishedPath = $destination.DIRECTORY_SEPARATOR.$relativePath;

            if ($files->exists($publishedPath)) {
                $files->delete($publishedPath);
            }
        }

        $directories = $files->allDirectories($destination);

        usort(
            $directories,
            static fn (string $a, string $b): int => strlen($b) <=> strlen($a)
        );

        foreach ($directories as $directory) {
            if ($files->isDirectory($directory) && $files->files($directory) === []) {
                $files->deleteDirectory($directory);
            }
        }

        if (
            $files->isDirectory($destination)
            && $files->files($destination) === []
            && $files->directories($destination) === []
        ) {
            $files->deleteDirectory($destination);
        }
    }
}

Usage:

php artisan vendor:unpublish \
    --provider="Vendor\Package\PackageServiceProvider"

Or by tag:

php artisan vendor:unpublish --tag=package-config

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions