]> BookStack Code Mirror - system-cli/blob - src/Commands/UpdateCommand.php
Added strict types, started phpunit testing
[system-cli] / src / Commands / UpdateCommand.php
1 <?php declare(strict_types=1);
2
3 namespace Cli\Commands;
4
5 use Cli\Services\AppLocator;
6 use Cli\Services\ArtisanRunner;
7 use Cli\Services\ComposerLocator;
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;
14
15 class UpdateCommand extends Command
16 {
17     protected function configure(): void
18     {
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', '');
22     }
23
24     /**
25      * @throws CommandError
26      */
27     protected function execute(InputInterface $input, OutputInterface $output): int
28     {
29         $appDir = AppLocator::require($input->getOption('app-directory'));
30         $output->writeln("<info>Checking system requirements...</info>");
31         RequirementsValidator::validate();
32
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();
39         }
40
41         $output->writeln("<info>Fetching latest code via Git...</info>");
42         $this->updateCodeUsingGit($appDir);
43
44         $output->writeln("<info>Installing PHP dependencies via composer...</info>");
45         $this->installComposerDependencies($composer, $appDir);
46
47         $output->writeln("<info>Running database migrations...</info>");
48         $artisan = (new ArtisanRunner($appDir));
49         $artisan->run(['migrate', '--force']);
50
51         $output->writeln("<info>Clearing app caches...</info>");
52         $artisan->run(['cache:clear']);
53         $artisan->run(['config:clear']);
54         $artisan->run(['view:clear']);
55
56         return Command::SUCCESS;
57     }
58
59     /**
60      * @throws CommandError
61      */
62     protected function updateCodeUsingGit(string $appDir): void
63     {
64         $errors = (new ProgramRunner('git', '/usr/bin/git'))
65             ->withTimeout(240)
66             ->withIdleTimeout(15)
67             ->runCapturingStdErr([
68                 '-C', $appDir,
69                 'pull', '-q', 'origin', 'release',
70             ]);
71
72         if ($errors) {
73             throw new CommandError("Failed git pull with errors:\n" . $errors);
74         }
75     }
76
77     /**
78      * @throws CommandError
79      */
80     protected function installComposerDependencies(ProgramRunner $composer, string $appDir): void
81     {
82         $errors = $composer->runCapturingStdErr([
83             'install',
84             '--no-dev', '-n', '-q', '--no-progress',
85             '-d', $appDir,
86         ]);
87
88         if ($errors) {
89             throw new CommandError("Failed composer install with errors:\n" . $errors);
90         }
91     }
92 }