3 namespace Cli\Commands;
5 use Cli\Services\ComposerLocator;
6 use Cli\Services\EnvironmentLoader;
7 use Cli\Services\ProgramRunner;
8 use Cli\Services\RequirementsValidator;
9 use Symfony\Component\Console\Command\Command;
10 use Symfony\Component\Console\Input\InputInterface;
11 use Symfony\Component\Console\Output\OutputInterface;
13 class UpdateCommand extends Command
16 public function __construct(
17 protected string $appDir
19 parent::__construct();
22 protected function configure(): void
24 $this->setName('update');
25 $this->setDescription('Update an existing BookStack instance.');
29 * @throws CommandError
31 protected function execute(InputInterface $input, OutputInterface $output): int
33 $output->writeln("<info>Checking system requirements...</info>");
34 RequirementsValidator::validate();
36 $output->writeln("<info>Checking composer exists...</info>");
37 $composerLocator = new ComposerLocator($this->appDir);
38 $composer = $composerLocator->getProgram();
39 if (!$composer->isFound()) {
40 $output->writeln("<info>Composer does not exist, downloading a local copy...</info>");
41 $composerLocator->download();
44 $output->writeln("<info>Fetching latest code via Git...</info>");
45 $this->updateCodeUsingGit();
47 $output->writeln("<info>Installing PHP dependencies via composer...</info>");
48 $this->installComposerDependencies($composer);
50 $output->writeln("<info>Running database migrations...</info>");
51 $this->runArtisanCommand(['migrate', '--force']);
53 $output->writeln("<info>Clearing app caches...</info>");
54 $this->runArtisanCommand(['cache:clear']);
55 $this->runArtisanCommand(['config:clear']);
56 $this->runArtisanCommand(['view:clear']);
58 return Command::SUCCESS;
62 * @throws CommandError
64 protected function updateCodeUsingGit(): void
66 $errors = (new ProgramRunner('git', '/usr/bin/git'))
69 ->runCapturingStdErr([
71 'pull', '-q', 'origin', 'release',
75 throw new CommandError("Failed git pull with errors:\n" . $errors);
80 * @throws CommandError
82 protected function installComposerDependencies(ProgramRunner $composer): void
84 $errors = $composer->runCapturingStdErr([
86 '--no-dev', '-n', '-q', '--no-progress',
91 throw new CommandError("Failed composer install with errors:\n" . $errors);
95 protected function runArtisanCommand(array $commandArgs): void
97 $errors = (new ProgramRunner('php', '/usr/bin/php'))
100 ->withEnvironment(EnvironmentLoader::load($this->appDir))
101 ->runCapturingAllOutput([
102 $this->appDir . DIRECTORY_SEPARATOR . 'artisan',
108 $cmdString = implode(' ', $commandArgs);
109 throw new CommandError("Failed 'php artisan {$cmdString}' with errors:\n" . $errors);