Migrated to symfony/console

This commit is contained in:
Dan Brown 2023-03-05 15:28:02 +00:00
parent 4d9d591792
commit 0be1bf7499
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
5 changed files with 546 additions and 95 deletions

View File

@ -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("<info>Dumping the database via mysqldump...</info>");
$dumpTempFile = $this->createDatabaseDump();
echo "Adding database dump to backup archive...\n";
$output->writeln("<info>Adding database dump to backup archive...</info>");
$zip->addFile($dumpTempFile, 'db.sql');
}
if ($handleUploads) {
echo "Adding BookStack upload folders to backup archive...\n";
$output->writeln("<info>Adding BookStack upload folders to backup archive...</info>");
$this->addUploadFoldersToZip($zip);
}
if ($handleThemes) {
echo "Adding BookStack theme folders to backup archive...\n";
$output->writeln("<info>Adding BookStack theme folders to backup archive...</info>");
$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("<info>Backup finished.</info>");
$output->writeln("Output ZIP saved to: {$zipOutFile}");
return Command::SUCCESS;
}
/**

View File

@ -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("<info>Checking system requirements...</info>");
$this->ensureRequirementsMet();
$suggestedOutPath = $input->subcommand;
if ($suggestedOutPath === 'default') {
$suggestedOutPath = '';
}
$suggestedOutPath = $input->getArgument('target-directory');
echo "Locating and checking install directory...\n";
$output->writeln("<info>Locating and checking install directory...</info>");
$installDir = $this->getInstallDir($suggestedOutPath);
$this->ensureInstallDirEmptyAndWritable($installDir);
echo "Cloning down BookStack project to install directory...\n";
$output->writeln("<info>Cloning down BookStack project to install directory...</info>");
$this->cloneBookStackViaGit($installDir);
echo "Checking composer exists...\n";
$output->writeln("<info>Checking composer exists...</info>");
$composer = $this->getComposerProgram($installDir);
try {
$composer->ensureFound();
} catch (\Exception $exception) {
echo "Composer does not exist, downloading a local copy...\n";
$output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
$this->downloadComposerToInstall($installDir);
}
echo "Installing application dependencies using composer...\n";
$output->writeln("<info>Installing application dependencies using composer...</info>");
$this->installComposerDependencies($composer, $installDir);
echo "Creating .env file from .env.example...\n";
$output->writeln("<info>Creating .env file from .env.example...</info>");
copy($installDir . DIRECTORY_SEPARATOR . '.env.example', $installDir . DIRECTORY_SEPARATOR . '.env');
sleep(1);
echo "Generating app key...\n";
$output->writeln("<info>Generating app key...</info>");
$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("<info>A BookStack install has been initialized at: {$installDir}\n</info>");
$output->writeln("<info>You will still need to:</info>");
$output->writeln("<info>- Update the .env file in the install with correct URL, database and email details.</info>");
$output->writeln("<info>- Run 'php artisan migrate' to set-up the database.</info>");
$output->writeln("<info>- Configure your webserver for use with BookStack.</info>");
$output->writeln("<info>- Ensure the required directories (storage/ bootstrap/cache public/uploads) are web-server writable.</info>");
return Command::SUCCESS;
}
/**

View File

@ -1,6 +1,6 @@
{
"require": {
"minicli/minicli": "^3.2",
"symfony/console": "^6.0",
"symfony/process": "^6.0",
"vlucas/phpdotenv": "^5.5"
},

533
scripts/composer.lock generated
View File

@ -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",

View File

@ -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");