]> BookStack Code Mirror - system-cli/blob - scripts/Commands/UpdateCommand.php
Added central way to resolve app path, improved ouput formatting
[system-cli] / scripts / Commands / UpdateCommand.php
1 <?php
2
3 namespace Cli\Commands;
4
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;
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         $this->runArtisanCommand(['migrate', '--force'], $appDir);
49
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);
54
55         return Command::SUCCESS;
56     }
57
58     /**
59      * @throws CommandError
60      */
61     protected function updateCodeUsingGit(string $appDir): void
62     {
63         $errors = (new ProgramRunner('git', '/usr/bin/git'))
64             ->withTimeout(240)
65             ->withIdleTimeout(15)
66             ->runCapturingStdErr([
67                 '-C', $appDir,
68                 'pull', '-q', 'origin', 'release',
69             ]);
70
71         if ($errors) {
72             throw new CommandError("Failed git pull with errors:\n" . $errors);
73         }
74     }
75
76     /**
77      * @throws CommandError
78      */
79     protected function installComposerDependencies(ProgramRunner $composer, string $appDir): void
80     {
81         $errors = $composer->runCapturingStdErr([
82             'install',
83             '--no-dev', '-n', '-q', '--no-progress',
84             '-d', $appDir,
85         ]);
86
87         if ($errors) {
88             throw new CommandError("Failed composer install with errors:\n" . $errors);
89         }
90     }
91
92     protected function runArtisanCommand(array $commandArgs, string $appDir): void
93     {
94         $errors = (new ProgramRunner('php', '/usr/bin/php'))
95             ->withTimeout(60)
96             ->withIdleTimeout(5)
97             ->withEnvironment(EnvironmentLoader::load($appDir))
98             ->runCapturingAllOutput([
99                 $appDir . DIRECTORY_SEPARATOR . 'artisan',
100                 '-n', '-q',
101                 ...$commandArgs
102             ]);
103
104         if ($errors) {
105             $cmdString = implode(' ', $commandArgs);
106             throw new CommandError("Failed 'php artisan {$cmdString}' with errors:\n" . $errors);
107         }
108     }
109 }