3 namespace Cli\Commands;
5 use Cli\Services\AppLocator;
6 use Cli\Services\ComposerLocator;
7 use Cli\Services\EnvironmentLoader;
8 use Cli\Services\ProgramRunner;
9 use Cli\Services\RequirementsValidator;
10 use Symfony\Component\Console\Command\Command;
11 use Symfony\Component\Console\Input\InputInterface;
12 use Symfony\Component\Console\Input\InputOption;
13 use Symfony\Component\Console\Output\OutputInterface;
15 class UpdateCommand extends Command
17 protected function configure(): void
19 $this->setName('update');
20 $this->setDescription('Update an existing BookStack instance.');
21 $this->addOption('app-directory', null, InputOption::VALUE_OPTIONAL, 'BookStack install directory to update', '');
25 * @throws CommandError
27 protected function execute(InputInterface $input, OutputInterface $output): int
29 $appDir = AppLocator::require($input->getOption('app-directory'));
30 $output->writeln("<info>Checking system requirements...</info>");
31 RequirementsValidator::validate();
33 $output->writeln("<info>Checking composer exists...</info>");
34 $composerLocator = new ComposerLocator($appDir);
35 $composer = $composerLocator->getProgram();
36 if (!$composer->isFound()) {
37 $output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
38 $composerLocator->download();
41 $output->writeln("<info>Fetching latest code via Git...</info>");
42 $this->updateCodeUsingGit($appDir);
44 $output->writeln("<info>Installing PHP dependencies via composer...</info>");
45 $this->installComposerDependencies($composer, $appDir);
47 $output->writeln("<info>Running database migrations...</info>");
48 $this->runArtisanCommand(['migrate', '--force'], $appDir);
50 $output->writeln("<info>Clearing app caches...</info>");
51 $this->runArtisanCommand(['cache:clear'], $appDir);
52 $this->runArtisanCommand(['config:clear'], $appDir);
53 $this->runArtisanCommand(['view:clear'], $appDir);
55 return Command::SUCCESS;
59 * @throws CommandError
61 protected function updateCodeUsingGit(string $appDir): void
63 $errors = (new ProgramRunner('git', '/usr/bin/git'))
66 ->runCapturingStdErr([
68 'pull', '-q', 'origin', 'release',
72 throw new CommandError("Failed git pull with errors:\n" . $errors);
77 * @throws CommandError
79 protected function installComposerDependencies(ProgramRunner $composer, string $appDir): void
81 $errors = $composer->runCapturingStdErr([
83 '--no-dev', '-n', '-q', '--no-progress',
88 throw new CommandError("Failed composer install with errors:\n" . $errors);
92 protected function runArtisanCommand(array $commandArgs, string $appDir): void
94 $errors = (new ProgramRunner('php', '/usr/bin/php'))
97 ->withEnvironment(EnvironmentLoader::load($appDir))
98 ->runCapturingAllOutput([
99 $appDir . DIRECTORY_SEPARATOR . 'artisan',
105 $cmdString = implode(' ', $commandArgs);
106 throw new CommandError("Failed 'php artisan {$cmdString}' with errors:\n" . $errors);