From 0be1bf7499bfe5ab6d6b701e768e05944c706863 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 5 Mar 2023 15:28:02 +0000 Subject: [PATCH] Migrated to symfony/console --- scripts/Commands/BackupCommand.php | 44 ++- scripts/Commands/InitCommand.php | 51 +-- scripts/composer.json | 2 +- scripts/composer.lock | 533 ++++++++++++++++++++++++++--- scripts/run | 11 +- 5 files changed, 546 insertions(+), 95 deletions(-) diff --git a/scripts/Commands/BackupCommand.php b/scripts/Commands/BackupCommand.php index 8dcc3fa25..cfef12ed7 100644 --- a/scripts/Commands/BackupCommand.php +++ b/scripts/Commands/BackupCommand.php @@ -4,33 +4,44 @@ namespace Cli\Commands; use Cli\Services\EnvironmentLoader; use Cli\Services\ProgramRunner; -use Minicli\Command\CommandCall; use RecursiveDirectoryIterator; use SplFileInfo; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Process\Exception\ProcessTimedOutException; use ZipArchive; -final class BackupCommand +final class BackupCommand extends Command { public function __construct( protected string $appDir ) { + parent::__construct(); + } + + protected function configure(): void + { + $this->setName('backup'); + $this->setDescription('Backup a BookStack installation to a single compressed ZIP file.'); + $this->addArgument('backup-path', InputArgument::OPTIONAL, 'Outfile file or directory to store the resulting backup file.', ''); + $this->addOption('no-database', null, null, "Skip adding a database dump to the backup"); + $this->addOption('no-uploads', null, null, "Skip adding uploaded files to the backup"); + $this->addOption('no-themes', null, null, "Skip adding the themes folder to the backup"); } /** * @throws CommandError */ - public function handle(CommandCall $input) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->ensureRequiredExtensionInstalled(); - $handleDatabase = !$input->hasFlag('no-database'); - $handleUploads = !$input->hasFlag('no-uploads'); - $handleThemes = !$input->hasFlag('no-themes'); - $suggestedOutPath = $input->subcommand; - if ($suggestedOutPath === 'default') { - $suggestedOutPath = ''; - } + $handleDatabase = !$input->getOption('no-database'); + $handleUploads = !$input->getOption('no-uploads'); + $handleThemes = !$input->getOption('no-themes'); + $suggestedOutPath = $input->getArgument('backup-path'); $zipOutFile = $this->buildZipFilePath($suggestedOutPath); @@ -45,19 +56,19 @@ final class BackupCommand $zip->addFile($this->appDir . DIRECTORY_SEPARATOR . 'scripts' . DIRECTORY_SEPARATOR . 'run', 'run'); if ($handleDatabase) { - echo "Dumping the database via mysqldump...\n"; + $output->writeln("Dumping the database via mysqldump..."); $dumpTempFile = $this->createDatabaseDump(); - echo "Adding database dump to backup archive...\n"; + $output->writeln("Adding database dump to backup archive..."); $zip->addFile($dumpTempFile, 'db.sql'); } if ($handleUploads) { - echo "Adding BookStack upload folders to backup archive...\n"; + $output->writeln("Adding BookStack upload folders to backup archive..."); $this->addUploadFoldersToZip($zip); } if ($handleThemes) { - echo "Adding BookStack theme folders to backup archive...\n"; + $output->writeln("Adding BookStack theme folders to backup archive..."); $this->addFolderToZipRecursive($zip, implode(DIRECTORY_SEPARATOR, [$this->appDir, 'themes']), 'themes'); } @@ -71,7 +82,10 @@ final class BackupCommand rename($zipTempFile, $zipOutFile); // Announce end - echo "Backup finished.\nOutput ZIP saved to: {$zipOutFile}\n"; + $output->writeln("Backup finished."); + $output->writeln("Output ZIP saved to: {$zipOutFile}"); + + return Command::SUCCESS; } /** diff --git a/scripts/Commands/InitCommand.php b/scripts/Commands/InitCommand.php index 262806569..6021d482d 100644 --- a/scripts/Commands/InitCommand.php +++ b/scripts/Commands/InitCommand.php @@ -4,56 +4,65 @@ namespace Cli\Commands; use Cli\Services\EnvironmentLoader; use Cli\Services\ProgramRunner; -use Minicli\Command\CommandCall; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; -class InitCommand +class InitCommand extends Command { + protected function configure(): void + { + $this->setName('init'); + $this->setDescription('Initialise a new BookStack install. Does not configure the webserver or database.'); + $this->addArgument('target-directory', InputArgument::OPTIONAL, 'The directory to create the BookStack install within. Must be empty.', ''); + } + /** * @throws CommandError */ - public function handle(CommandCall $input) + protected function execute(InputInterface $input, OutputInterface $output): int { - echo "Checking system requirements...\n"; + $output->writeln("Checking system requirements..."); $this->ensureRequirementsMet(); - $suggestedOutPath = $input->subcommand; - if ($suggestedOutPath === 'default') { - $suggestedOutPath = ''; - } + $suggestedOutPath = $input->getArgument('target-directory'); - echo "Locating and checking install directory...\n"; + $output->writeln("Locating and checking install directory..."); $installDir = $this->getInstallDir($suggestedOutPath); $this->ensureInstallDirEmptyAndWritable($installDir); - echo "Cloning down BookStack project to install directory...\n"; + $output->writeln("Cloning down BookStack project to install directory..."); $this->cloneBookStackViaGit($installDir); - echo "Checking composer exists...\n"; + $output->writeln("Checking composer exists..."); $composer = $this->getComposerProgram($installDir); try { $composer->ensureFound(); } catch (\Exception $exception) { - echo "Composer does not exist, downloading a local copy...\n"; + $output->writeln("Composer does not exist, downloading a local copy..."); $this->downloadComposerToInstall($installDir); } - echo "Installing application dependencies using composer...\n"; + $output->writeln("Installing application dependencies using composer..."); $this->installComposerDependencies($composer, $installDir); - echo "Creating .env file from .env.example...\n"; + $output->writeln("Creating .env file from .env.example..."); copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env'); sleep(1); - echo "Generating app key...\n"; + $output->writeln("Generating app key..."); $this->generateAppKey($installDir); // Announce end - echo "A BookStack install has been initialized at: {$installDir}\n\n"; - echo "You will still need to:\n"; - echo "- Update the .env file in the install with correct URL, database and email details.\n"; - echo "- Run 'php artisan migrate' to set-up the database.\n"; - echo "- Configure your webserver for use with BookStack.\n"; - echo "- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable.\n"; + $output->writeln("A BookStack install has been initialized at: {$installDir}\n"); + $output->writeln("You will still need to:"); + $output->writeln("- Update the .env file in the install with correct URL, database and email details."); + $output->writeln("- Run 'php artisan migrate' to set-up the database."); + $output->writeln("- Configure your webserver for use with BookStack."); + $output->writeln("- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable."); + + return Command::SUCCESS; } /** diff --git a/scripts/composer.json b/scripts/composer.json index b79df7763..50c971fda 100644 --- a/scripts/composer.json +++ b/scripts/composer.json @@ -1,6 +1,6 @@ { "require": { - "minicli/minicli": "^3.2", + "symfony/console": "^6.0", "symfony/process": "^6.0", "vlucas/phpdotenv": "^5.5" }, diff --git a/scripts/composer.lock b/scripts/composer.lock index 3789592f2..03ae28280 100644 --- a/scripts/composer.lock +++ b/scripts/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b56ccc782d009482249ce5e3a4b62147", + "content-hash": "40bb5069cf724f3ce0428185a73841e7", "packages": [ { "name": "graham-campbell/result-type", @@ -68,57 +68,6 @@ ], "time": "2023-02-25T20:23:15+00:00" }, - { - "name": "minicli/minicli", - "version": "3.2.1", - "source": { - "type": "git", - "url": "https://github.com/minicli/minicli.git", - "reference": "2d31b303461d0ec1f8c6d0be00f8b26be8766fbe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/minicli/minicli/zipball/2d31b303461d0ec1f8c6d0be00f8b26be8766fbe", - "reference": "2d31b303461d0ec1f8c6d0be00f8b26be8766fbe", - "shasum": "" - }, - "require": { - "ext-readline": "*", - "php": ">=8" - }, - "require-dev": { - "friendsofphp/php-cs-fixer": "^3.5", - "pestphp/pest": "^1.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "Minicli\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Experimental micro CLI framework for PHP", - "homepage": "https://github.com/minicli/minicli", - "keywords": [ - "cli", - "command-line" - ], - "support": { - "issues": "https://github.com/minicli/minicli/issues", - "source": "https://github.com/minicli/minicli/tree/3.2.1" - }, - "funding": [ - { - "url": "https://github.com/erikaheidi", - "type": "github" - } - ], - "time": "2022-09-23T09:04:22+00:00" - }, { "name": "phpoption/phpoption", "version": "1.9.1", @@ -194,6 +143,154 @@ ], "time": "2023-02-25T19:38:58+00:00" }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "symfony/console", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "c3ebc83d031b71c39da318ca8b7a07ecc67507ed" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/c3ebc83d031b71c39da318ca8b7a07ecc67507ed", + "reference": "c3ebc83d031b71c39da318ca8b7a07ecc67507ed", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.4|^6.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0", + "symfony/dependency-injection": "^5.4|^6.0", + "symfony/event-dispatcher": "^5.4|^6.0", + "symfony/lock": "^5.4|^6.0", + "symfony/process": "^5.4|^6.0", + "symfony/var-dumper": "^5.4|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.0.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-01T08:36:10+00:00" + }, { "name": "symfony/polyfill-ctype", "version": "v1.27.0", @@ -276,6 +373,171 @@ ], "time": "2022-11-03T14:55:06+00:00" }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/511a08c03c1960e08a883f4cffcacd219b758354", + "reference": "511a08c03c1960e08a883f4cffcacd219b758354", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "reference": "19bd1e4fcd5b91116f14d8533c57831ed00571b6", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, { "name": "symfony/polyfill-mbstring", "version": "v1.27.0", @@ -503,6 +765,173 @@ ], "time": "2023-01-01T08:36:10+00:00" }, + { + "name": "symfony/service-contracts", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "reference": "d78d39c1599bd1188b8e26bb341da52c3c6d8a66", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "psr/container": "^2.0" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.0-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.0.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:58+00:00" + }, + { + "name": "symfony/string", + "version": "v6.0.19", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "d9e72497367c23e08bf94176d2be45b00a9d232a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/d9e72497367c23e08bf94176d2be45b00a9d232a", + "reference": "d9e72497367c23e08bf94176d2be45b00a9d232a", + "shasum": "" + }, + "require": { + "php": ">=8.0.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.0" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0", + "symfony/http-client": "^5.4|^6.0", + "symfony/translation-contracts": "^2.0|^3.0", + "symfony/var-exporter": "^5.4|^6.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v6.0.19" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-01-01T08:36:10+00:00" + }, { "name": "vlucas/phpdotenv", "version": "v5.5.0", diff --git a/scripts/run b/scripts/run index 758d707ae..31bebbf13 100644 --- a/scripts/run +++ b/scripts/run @@ -7,9 +7,9 @@ if (php_sapi_name() !== 'cli') { require __DIR__ . '/vendor/autoload.php'; +use Symfony\Component\Console\Application; use Cli\Commands\BackupCommand; use Cli\Commands\InitCommand; -use Minicli\App; // Get the directory of the CLI "entrypoint", adjusted to be the real // location where running via a phar. @@ -20,14 +20,13 @@ if (str_starts_with($scriptDir, 'phar://')) { $bsDir = dirname($scriptDir); // Setup our CLI -$app = new App(); -$app->setSignature('./run'); +$app = new Application('bookstack-system'); -$app->registerCommand('backup', [new BackupCommand($bsDir), 'handle']); -$app->registerCommand('init', [new InitCommand(), 'handle']); +$app->add(new BackupCommand($bsDir)); +$app->add(new InitCommand()); try { - $app->runCommand($argv); + $app->run(); } catch (Exception $error) { fwrite(STDERR, "An error occurred when attempting to run a command:\n"); fwrite(STDERR, $error->getMessage() . "\n");