]> BookStack Code Mirror - system-cli/blob - scripts/Commands/UpdateCommand.php
Added update command
[system-cli] / scripts / Commands / UpdateCommand.php
1 <?php
2
3 namespace Cli\Commands;
4
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;
12
13 class UpdateCommand extends Command
14 {
15
16     public function __construct(
17         protected string $appDir
18     ) {
19         parent::__construct();
20     }
21
22     protected function configure(): void
23     {
24         $this->setName('update');
25         $this->setDescription('Update an existing BookStack instance.');
26     }
27
28     /**
29      * @throws CommandError
30      */
31     protected function execute(InputInterface $input, OutputInterface $output): int
32     {
33         $output->writeln("<info>Checking system requirements...</info>");
34         RequirementsValidator::validate();
35
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();
42         }
43
44         $output->writeln("<info>Fetching latest code via Git...</info>");
45         $this->updateCodeUsingGit();
46
47         $output->writeln("<info>Installing PHP dependencies via composer...</info>");
48         $this->installComposerDependencies($composer);
49
50         $output->writeln("<info>Running database migrations...</info>");
51         $this->runArtisanCommand(['migrate', '--force']);
52
53         $output->writeln("<info>Clearing app caches...</info>");
54         $this->runArtisanCommand(['cache:clear']);
55         $this->runArtisanCommand(['config:clear']);
56         $this->runArtisanCommand(['view:clear']);
57
58         return Command::SUCCESS;
59     }
60
61     /**
62      * @throws CommandError
63      */
64     protected function updateCodeUsingGit(): void
65     {
66         $errors = (new ProgramRunner('git', '/usr/bin/git'))
67             ->withTimeout(240)
68             ->withIdleTimeout(15)
69             ->runCapturingStdErr([
70                 '-C', $this->appDir,
71                 'pull', '-q', 'origin', 'release',
72             ]);
73
74         if ($errors) {
75             throw new CommandError("Failed git pull with errors:\n" . $errors);
76         }
77     }
78
79     /**
80      * @throws CommandError
81      */
82     protected function installComposerDependencies(ProgramRunner $composer): void
83     {
84         $errors = $composer->runCapturingStdErr([
85             'install',
86             '--no-dev', '-n', '-q', '--no-progress',
87             '-d', $this->appDir,
88         ]);
89
90         if ($errors) {
91             throw new CommandError("Failed composer install with errors:\n" . $errors);
92         }
93     }
94
95     protected function runArtisanCommand(array $commandArgs): void
96     {
97         $errors = (new ProgramRunner('php', '/usr/bin/php'))
98             ->withTimeout(60)
99             ->withIdleTimeout(5)
100             ->withEnvironment(EnvironmentLoader::load($this->appDir))
101             ->runCapturingAllOutput([
102                 $this->appDir . DIRECTORY_SEPARATOR . 'artisan',
103                 '-n', '-q',
104                 ...$commandArgs
105             ]);
106
107         if ($errors) {
108             $cmdString = implode(' ', $commandArgs);
109             throw new CommandError("Failed 'php artisan {$cmdString}' with errors:\n" . $errors);
110         }
111     }
112 }