From c8505a8428e4fc4f8ca51a11481ee1600ec905f3 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Mon, 8 Apr 2019 18:14:11 +0200 Subject: [PATCH 01/22] WIP on migrating --- src/Console/InitCommand.php | 44 ++----- src/Console/InstallCommand.php | 209 ++++++++++++++++++++----------- src/Console/UpdateCommand.php | 6 +- src/Installer/ThemeInstaller.php | 3 +- src/Util/ConfigMaker.php | 4 +- src/Util/ManageDirectory.php | 72 ++++++++++- 6 files changed, 215 insertions(+), 123 deletions(-) diff --git a/src/Console/InitCommand.php b/src/Console/InitCommand.php index 32aa5f8..f945672 100644 --- a/src/Console/InitCommand.php +++ b/src/Console/InitCommand.php @@ -3,6 +3,7 @@ namespace OFFLINE\Bootstrapper\October\Console; use OFFLINE\Bootstrapper\October\Util\UsesTemplate; +use OFFLINE\Bootstrapper\October\Util\ManageDirectory; use RuntimeException; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; @@ -16,7 +17,7 @@ */ class InitCommand extends Command { - use UsesTemplate; + use UsesTemplate, ManageDirectory; /** * Configure the command options. @@ -45,9 +46,9 @@ protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('Creating project directory...'); - $dir = getcwd() . DS . $input->getArgument('directory'); + $dir = $this->pwd() . $input->getArgument('directory'); - $this->createWorkingDirectory($dir); + $this->mkdir($dir); $output->writeln('Updating template files...'); $this->updateTemplateFiles(); @@ -57,45 +58,14 @@ protected function execute(InputInterface $input, OutputInterface $output) $output->writeln('Creating default october.yaml...'); - if (file_exists($target)) { + if ($this->fileExists($target)) { return $output->writeln('october.yaml already exists: ' . $target . ''); } - $this->copyYamlTemplate($template, $target); + $this->copy($template, $target); $output->writeln('Done! Now edit your october.yaml and run october install.'); return true; } - - /** - * @param $dir - * - * @throws \RuntimeException - */ - protected function createWorkingDirectory($dir) - { - if ( ! @mkdir($dir) && ! is_dir($dir)) { - throw new RuntimeException('Cannot create target directory: ' . $dir); - } - } - - /** - * @param $template - * @param $target - * - * @throws \RuntimeException - */ - protected function copyYamlTemplate($template, $target) - { - if ( ! file_exists($template)) { - throw new RuntimeException('Cannot find october.yaml template: ' . $template); - } - - copy($template, $target); - - if ( ! file_exists($target)) { - throw new RuntimeException('october.yaml could not be created'); - } - } -} \ No newline at end of file +} diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index d34d192..a9bf717 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -2,16 +2,18 @@ namespace OFFLINE\Bootstrapper\October\Console; +use OFFLINE\Bootstrapper\October\Manager\PluginManager; +use OFFLINE\Bootstrapper\October\Manager\ThemeManager; use OFFLINE\Bootstrapper\October\Config\Setup; -use OFFLINE\Bootstrapper\October\Config\Yaml; use OFFLINE\Bootstrapper\October\Downloader\OctoberCms; use OFFLINE\Bootstrapper\October\Installer\DeploymentInstaller; -use OFFLINE\Bootstrapper\October\Installer\PluginInstaller; -use OFFLINE\Bootstrapper\October\Installer\ThemeInstaller; +use OFFLINE\Bootstrapper\October\Util\Artisan; use OFFLINE\Bootstrapper\October\Util\Composer; use OFFLINE\Bootstrapper\October\Util\Gitignore; -use OFFLINE\Bootstrapper\October\Util\RunsProcess; use OFFLINE\Bootstrapper\October\Util\UsesTemplate; +use OFFLINE\Bootstrapper\October\Util\ManageDirectory; +use OFFLINE\Bootstrapper\October\Util\ConfigMaker; +use OFFLINE\Bootstrapper\October\Util\CliIO; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Exception\InvalidArgumentException; use Symfony\Component\Console\Exception\RuntimeException; @@ -26,29 +28,75 @@ */ class InstallCommand extends Command { - use UsesTemplate, RunsProcess; + use ConfigMaker, UsesTemplate, CliIO, ManageDirectory; + + /** + * @var Gitignore + */ + protected $gitignore; + + /** + * @var bool + */ + protected $firstRun; /** - * @var + * @var bool */ - public $config; + protected $force; + /** - * @var OutputInterface + * @var PluginManager */ - protected $output; + protected $pluginManager; + /** - * @var Gitignore + * @var ThemeManager */ - protected $gitignore; + protected $themeManager; + /** - * @var bool + * @var Artisan */ - protected $firstRun; + protected $artisan; + + /** + * @var Composer + */ + protected $composer; + /** * @var string */ protected $php; + /** + * @inheritdoc + */ + public function __construct($name = null) + { + $this->pluginManager = new PluginManager(); + $this->themeManager = new ThemeManager(); + $this->artisan = new Artisan(); + $this->composer = new Composer(); + + $this->setPhp(); + + parent::__construct($name); + } + + /** + * Set PHP version to be used in console commands + */ + public function setPhp(string $php = 'php') + { + //IDEA: simple observer for changing the php version + $this->php = $php; + $this->artisan->setPhp($php); + $this->pluginManager->setPhp($php); + $this->themeManager->setPhp($php); + } + /** * Configure the command options. * @@ -90,87 +138,75 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - if ( ! class_exists('ZipArchive')) { - throw new RuntimeException('The Zip PHP extension is not installed. Please install it and try again.'); - } - - $force = $input->getOption('force'); - $this->php = $input->getOption('php') ?: 'php'; + $this->prepareEnv($input, $output); - $this->firstRun = ! is_dir(getcwd() . DS . 'bootstrap') || $force; + $this->makeConfig(); - $this->output = $output; - - $configFile = getcwd() . DS . 'october.yaml'; - if ( ! file_exists($configFile)) { - return $output->writeln('october.yaml not found. Run october init first.'); + if (!empty($php = $input->getOption('php'))) { + $this->setPhp($php); } - $this->config = new Yaml($configFile); $this->gitignore = new Gitignore($this->getGitignore()); - $output->writeln('Downloading latest October CMS...'); + $this->output->writeln('Downloading latest October CMS...'); try { - (new OctoberCms())->download($force); + (new OctoberCms())->download($this->force); } catch (\LogicException $e) { - $output->writeln('' . $e->getMessage() . ''); + $this->output->writeln('' . $e->getMessage() . ''); } - $output->writeln('Installing composer dependencies...'); - (new Composer())->install(); - (new Composer())->addDependency('offline/oc-bootstrapper'); + $this->output->writeln('Installing composer dependencies...'); + $this->composer->install(); + $this->composer->addDependency('offline/oc-bootstrapper'); - $output->writeln('Setting up config files...'); - $this->writeConfig($force); + $this->output->writeln('Setting up config files...'); + $this->writeConfig($this->force); $this->prepareDatabase(); - $output->writeln('Migrating database...'); - $this->runProcess($this->php . ' artisan october:up', 'Migrations failed!'); + $this->output->writeln('Migrating database...'); + $this->artisan->call('october:up'); - $output->writeln('Installing Theme...'); - try { - (new ThemeInstaller($this->config, $this->gitignore, $this->output, $this->php))->install(); - } catch (\RuntimeException $e) { - $output->writeln('' . $e->getMessage() . ''); - } + $this->output->writeln('Installing Theme...'); + $themeConfig = $this->config->cms['theme']; + $this->themeManager->install($themeConfig); - $output->writeln('Installing Plugins...'); - try { - (new PluginInstaller($this->config, $this->gitignore, $this->output, $this->php))->install(); - } catch (\RuntimeException $e) { - $output->writeln('' . $e->getMessage() . ''); + $this->output->writeln('Installing Plugins...'); + $pluginsConfigs = $this->config->plugins; + + foreach ($pluginsConfigs as $pluginConfig) { + $this->pluginManager->install($pluginConfig); } - $output->writeln('Migrating plugin tables...'); - $this->runProcess($this->php . ' artisan october:up', 'Plugin migrations failed!'); + $this->output->writeln('Migrating plugin tables...'); + $this->artisan->call('october:up'); - $output->writeln('Setting up deployments...'); + $this->output->writeln('Setting up deployments...'); try { - (new DeploymentInstaller($this->config, $this->gitignore, $this->output, $this->php))->install($force); + (new DeploymentInstaller($this->config, $this->gitignore, $this->output, $this->php))->install($this->force); } catch (\RuntimeException $e) { - $output->writeln("${e}"); + $this->output->writeln("${e}"); } - $output->writeln('Creating .gitignore...'); + $this->output->writeln('Creating .gitignore...'); $this->gitignore->write(); if ($this->firstRun) { - $output->writeln('Removing demo data...'); - $this->runProcess($this->php . ' artisan october:fresh', 'Failed to remove demo data!'); + $this->output->writeln('Removing demo data...'); + $this->artisan->call('october:fresh'); - $output->writeln('Creating README...'); - $this->readme(); + $this->output->writeln('Creating README...'); + $this->copyReadme(); - $output->writeln('Cleaning up...'); + $this->output->writeln('Cleaning up...'); $this->cleanup(); } - $output->writeln('Clearing cache...'); - $this->runProcess($this->php . ' artisan clear-compiled', 'Failed to clear compiled files!'); - $this->runProcess($this->php . ' artisan cache:clear', 'Failed to clear cache!'); + $this->output->writeln('Clearing cache...'); + $this->artisan->call('clear-compiled'); + $this->artisan->call('cache:clear'); - $output->writeln('Application ready! Build something amazing.'); + $this->output->writeln('Application ready! Build something amazing.'); return true; } @@ -191,54 +227,54 @@ protected function writeConfig($force = false) return; } - if ((file_exists(getcwd() . DS . '.env') && $force === false)) { + if ($this->fileExists('.env') && $force === false) { return $this->output->writeln('-> Configuration already set up. Use --force to regenerate.'); } $setup->env(); - } /** - * Get the .gitignore template. + * Get the .gitignore or create it using template. * * @return string */ protected function getGitignore() { - $target = getcwd() . DS . '.gitignore'; - if (file_exists($target)) { + $target = $this->path('.gitignore'); + + if ($this->fileExists($target)) { return $target; } $file = $this->config->git['bareRepo'] ? 'gitignore.bare' : 'gitignore'; $template = $this->getTemplate($file); - copy($template, $target); + $this->copy($template, $target); return $target; } - + /** * Copy the README template. * * @return void */ - protected function readme() + protected function copyReadme() { $template = $this->getTemplate('README.md'); - copy($template, getcwd() . DS . 'README.md'); + $this->copy($template, 'README.md'); } protected function cleanup() { - if ( ! $this->firstRun) { + if (! $this->firstRun) { return; } $remove = ['CONTRIBUTING.md', 'CHANGELOG.md', 'ISSUE_TEMPLATE.md']; foreach ($remove as $file) { - @unlink(getcwd() . DS . $file); + $this->unlink(($this->path($file))); } } @@ -250,10 +286,33 @@ public function prepareDatabase() // If SQLite database does not exist, create it if ($this->config->database['connection'] === 'sqlite') { $path = $this->config->database['database']; - if ( ! file_exists($path) && is_dir(dirname($path))) { + if (! $this->fileExists($path) && is_dir(dirname($path))) { $this->output->writeln("Creating $path ..."); - touch($path); + $this->touchFile($path); } } } + + /** + * Prepare the environment + * + * @param InputInterface $input + * @param OutputInterface $output + * @return void + */ + protected function prepareEnv(InputInterface $input, OutputInterface $output) + { + if (! class_exists('ZipArchive')) { + throw new RuntimeException('The Zip PHP extension is not installed. Please install it and try again.'); + } + + $this->setOutput($output); + + $this->pluginManager->setOutput($output); + $this->themeManager->setOutput($output); + + $this->force = $input->getOption('force'); + + $this->firstRun = ! is_dir($this->path('bootstrap')) || $this->force; + } } diff --git a/src/Console/UpdateCommand.php b/src/Console/UpdateCommand.php index 67083f7..77285ab 100644 --- a/src/Console/UpdateCommand.php +++ b/src/Console/UpdateCommand.php @@ -106,11 +106,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { $this->prepareEnv($input, $output); - try { - $this->makeConfig(); - } catch (RuntimeException $e) { - return $this->write($e->message); - } + $this->makeConfig(); if (!empty($php = $input->getOption('php'))) { $this->setPhp($php); diff --git a/src/Installer/ThemeInstaller.php b/src/Installer/ThemeInstaller.php index e6bb8d8..e58e304 100644 --- a/src/Installer/ThemeInstaller.php +++ b/src/Installer/ThemeInstaller.php @@ -2,7 +2,6 @@ namespace OFFLINE\Bootstrapper\October\Installer; - use OFFLINE\Bootstrapper\October\Util\Git; use Symfony\Component\Process\Exception\InvalidArgumentException; use Symfony\Component\Process\Exception\LogicException; @@ -41,7 +40,7 @@ public function install() $themeDir = getcwd() . DS . implode(DS, ['themes', $theme]); $this->mkdir($themeDir); - if ( ! $this->isEmpty($themeDir)) { + if (! $this->isEmpty($themeDir)) { $this->write(sprintf('-> Theme "%s" is already installed. Skipping.', $theme)); return; diff --git a/src/Util/ConfigMaker.php b/src/Util/ConfigMaker.php index d761f74..fcfe912 100644 --- a/src/Util/ConfigMaker.php +++ b/src/Util/ConfigMaker.php @@ -10,7 +10,7 @@ */ trait ConfigMaker { - use ManageDirectory; + // use ManageDirectory; /** * @var @@ -20,7 +20,7 @@ trait ConfigMaker protected function makeConfig() { $configFile = $this->pwd() . 'october.yaml'; - if ( ! file_exists($configFile)) { + if (! file_exists($configFile)) { throw new RuntimeException("october.yaml not found. Run october init first.", 1); } diff --git a/src/Util/ManageDirectory.php b/src/Util/ManageDirectory.php index 9a5d43a..a3984b3 100644 --- a/src/Util/ManageDirectory.php +++ b/src/Util/ManageDirectory.php @@ -10,7 +10,75 @@ */ trait ManageDirectory { + /** + * Copy file from sourceFile to targetFile + * + * @param string $sourceFile + * @param string $targetFile + */ + public function copy($sourceFile, $targetFile) + { + $sourceFile = $this->path($sourceFile); + $targetFile = $this->path($targetFile); + + copy($sourceFile, $targetFile); + if (!$this->fileExists($targetFile)) { + throw new RuntimeException('File ' . $targetFile . ' could not be created'); + } + + return true; + } + + /** + * Touch file + * + * @param string $file relative or absolute path of the file to create + */ + public function touch($file) + { + return touch($this->path($file)); + } + + /** + * Check if file exists + * + * @param string $file relative or absolute path of the file to check existence + * + * @return bool + */ + public function fileExists($file) + { + return file_exists($this->path($file)); + } + + /** + * Get absolute path of the file + * + * @param string $file relative or absolute path of the file + * + * @return string path of the file + */ + public function path($file) + { + $relative = false; + + $file = trim($file); + + $windows = strpos($this->pwd(), '/', 0) === false; + + if (!$windows && $file[0] !== '/') { + $relative = true; + } elseif ($windows && !preg_match('/^[^*?"<>|:]*$/', $file)) { + $relative = true; + } + + if ($relative) { + $file = $this->pwd() . $file; + } + + return $file; + } /** * Delete a file. Fallback to OS native rm command if the file to be deleted is write protected. @@ -64,7 +132,7 @@ public function rmdir($dir) */ public function mkdir($dir) { - if ( ! @mkdir($dir) && ! is_dir($dir)) { + if (! @mkdir($dir) && ! is_dir($dir)) { throw new RuntimeException('Could not create directory: ' . $dir); } @@ -85,7 +153,7 @@ public function isEmpty($themeDir) /** * Returns current working directory, mimic of `pwd` console command - * + * * @return string current path */ public function pwd() From 774e9b6aab42a22e96633a4dde4934f461305c82 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Tue, 9 Apr 2019 17:53:49 +0200 Subject: [PATCH 02/22] Added project's repo section --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ea5aac0..9611d68 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Bootstrapper for October CMS `oc-bootstrapper` is a simple script that enables you to bootstrap an October CMS installation -with custom plugins and custom themes. You simply describe your setup in a config file and run +with custom plugins and custom theme. You simply describe your setup in a config file and run the install command. `oc-bootstrapper` enables you to install plugins and themes from your own git repo. @@ -32,10 +32,11 @@ The following steps will be taken care of: Works on Windows via Ubuntu Bash or Git Bash. -## Example project +## Project's repo -Take a look at the [OFFLINE-GmbH/octobertricks.com](https://github.com/OFFLINE-GmbH/octobertricks.com) repo to see an example setup of oc-bootstrapper. +While using `oc-bootstrapper` it is a good idea to keep `october.yaml`, project's theme and project's plugins (those that are not shared among other projects) in project's repo. +Take a look at the [OFFLINE-GmbH/octobertricks.com](https://github.com/OFFLINE-GmbH/octobertricks.com) repo to see an example setup of `oc-bootstrapper`. ## Installation From fcd45193c5b3dad08de05ad8395ce3ece27a2d06 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Tue, 9 Apr 2019 17:54:01 +0200 Subject: [PATCH 03/22] Removed plugin and theme installers --- src/Installer/PluginInstaller.php | 193 ------------------------------ src/Installer/ThemeInstaller.php | 103 ---------------- 2 files changed, 296 deletions(-) delete mode 100644 src/Installer/PluginInstaller.php delete mode 100644 src/Installer/ThemeInstaller.php diff --git a/src/Installer/PluginInstaller.php b/src/Installer/PluginInstaller.php deleted file mode 100644 index c86cfd4..0000000 --- a/src/Installer/PluginInstaller.php +++ /dev/null @@ -1,193 +0,0 @@ -config->plugins; - } catch (\RuntimeException $e) { - $this->write(' - Nothing to install'); - - // No plugin set - return false; - } - - $privatePluginInstalled = false; - $isBare = isset($this->config->git['bareRepo']) - ? (bool)$this->config->git['bareRepo'] - : false; - - $excludePlugins = isset($this->config->git['excludePlugins']) - ? (bool)$this->config->git['excludePlugins'] - : false; - - foreach ($config as $plugin) { - - $this->write(' - ' . $plugin . ''); - - list($vendor, $plugin, $remote, $branch) = $this->parse($plugin); - $vendor = strtolower($vendor); - $plugin = strtolower($plugin); - - $vendorDir = $this->createVendorDir($vendor); - $pluginDir = $vendorDir . DS . $plugin; - - $this->mkdir($pluginDir); - - if ( ! $this->isEmpty($pluginDir)) { - - if ($this->handleExistingPlugin($excludePlugins, $remote, $vendor, $plugin, $pluginDir, - $isBare) === false - ) { - continue; - } - } - - if ($remote === false) { - $this->installViaArtisan($vendor, $plugin); - continue; - } - - $repo = Git::repo($pluginDir); - try { - $repo->cloneFrom($remote, $pluginDir); - if ($branch !== false) { - $this->write(' -> ' . sprintf('Checkout "%s" ...', $branch) . ''); - $repo->checkout($branch); - } - } catch (RuntimeException $e) { - $this->write(' - ' . 'Error while cloning plugin repo: ' . $e->getMessage() . ''); - continue; - } - - if ($excludePlugins === false) { - (new Process($this->php . " artisan plugin:refresh {$vendor}.{$plugin}"))->run(); - - if ($isBare) { - $this->gitignore->addPlugin($vendor, $plugin); - } - } - - $this->cleanup($pluginDir); - $privatePluginInstalled = true; - } - - if ($privatePluginInstalled) { - $this->write('Installing dependencies of private plugins...'); - (new Composer())->updateLock(); - } - - return true; - } - - protected function handleExistingPlugin($excludePlugins, $remote, $vendor, $plugin, $pluginDir, $isBare) - { - if ($excludePlugins === false || $remote === false || $isBare === false) { - $this->write(' -> ' . sprintf('Plugin "%s.%s" already installed. Skipping.', - $vendor, $plugin) . ''); - - return false; - } - - // Remove any existing local version of the private plugin so it can be checked out via git again - if ($this->gitignore->hasPluginHeader($vendor, $plugin)) { - $this->write(' -> ' . sprintf('Plugin "%s.%s" found in .gitignore. Skipping redownload of newest version ...', - $vendor, $plugin) . ''); - - return false; - } - - $this->write(' -> ' . sprintf('Removing "%s" to redownload the newest version ...', - $pluginDir) . ''); - - $this->rmdir($pluginDir); - $this->mkdir($pluginDir); - - return true; - } - - /** - * Parse the Vendor, Plugin and Remote values out of the - * given plugin declaration. - * - * @param $plugin - * - * @return mixed - */ - protected function parse($plugin) - { - // Vendor.Plugin (Remote) - preg_match("/([^\.]+)\.([^ #]+)(?: ?\(([^\#)]+)(?:#([^\)]+)?)?)?/", $plugin, $matches); - - array_shift($matches); - - if (count($matches) < 3) { - $matches[2] = false; - } - - if (count($matches) < 4) { - $matches[3] = false; - } - - return $matches; - } - - /** - * Create the plugin's vendor directory. - * - * @param $vendor - * - * @return string - * @throws \RuntimeException - */ - protected function createVendorDir($vendor) - { - $pluginDir = getcwd() . DS . implode(DS, ['plugins', $vendor]); - - return $this->mkdir($pluginDir); - } - - /** - * Installs a plugin via artisan command. - * - * @param $vendor - * @param $plugin - * - * @throws LogicException - * @throws RuntimeException - */ - protected function installViaArtisan($vendor, $plugin) - { - $exitCode = (new Process($this->php . " artisan plugin:install {$vendor}.{$plugin}"))->run(); - - if ($exitCode !== $this::EXIT_CODE_OK) { - throw new RuntimeException( - sprintf('Error while installing plugin %s via artisan. Is your database set up correctly?', - $vendor . '.' . $plugin - ) - ); - } - } -} \ No newline at end of file diff --git a/src/Installer/ThemeInstaller.php b/src/Installer/ThemeInstaller.php deleted file mode 100644 index e58e304..0000000 --- a/src/Installer/ThemeInstaller.php +++ /dev/null @@ -1,103 +0,0 @@ -config->cms['theme']; - } catch (\RuntimeException $e) { - // No theme set - return false; - } - - list($theme, $remote) = $this->parse($config); - if ($remote === false) { - return $this->installViaArtisan($theme); - } - - $themeDir = getcwd() . DS . implode(DS, ['themes', $theme]); - $this->mkdir($themeDir); - - if (! $this->isEmpty($themeDir)) { - $this->write(sprintf('-> Theme "%s" is already installed. Skipping.', $theme)); - - return; - } - - $repo = Git::repo($themeDir); - try { - $repo->cloneFrom($remote, $themeDir); - } catch (RuntimeException $e) { - throw new RuntimeException('Error while cloning theme repo: ' . $e->getMessage()); - } - - $this->cleanup($themeDir); - - return true; - } - - /** - * Parse the theme's name and remote path out of the - * given theme declaration. - * - * @param $theme - * - * @return mixed - */ - protected function parse($theme) - { - // Theme (Remote) - preg_match("/([^ ]+)(?: ?\(([^\)]+))?/", $theme, $matches); - - array_shift($matches); - - if (count($matches) < 2) { - $matches[1] = false; - } - - return $matches; - } - - /** - * Installs a theme via artisan command. - * - * @param $theme - * - * @return bool - * - * @throws LogicException - * @throws RuntimeException - */ - protected function installViaArtisan($theme) - { - $exitCode = (new Process($this->php . " artisan theme:install {$theme}"))->run(); - - if ($exitCode !== $this::EXIT_CODE_OK) { - throw new RuntimeException(sprintf('Error while installing theme "%s" via artisan.', $theme)); - } - - return true; - } -} From e531565fef3ba76c35d8d8a98348e01f3b48e624 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Wed, 10 Apr 2019 10:51:41 +0200 Subject: [PATCH 04/22] Deployments moved to deployments --- src/Console/InstallCommand.php | 32 +++++++++++++++----------- src/Deployment/DeploymentBase.php | 15 ++++++++++++ src/Deployment/DeploymentFactory.php | 23 ++++++++++++++++++ src/Deployment/DeploymentInterface.php | 25 ++++++++++++++++++++ src/Deployment/Gitlab.php | 26 +++++++++++++++++++++ 5 files changed, 108 insertions(+), 13 deletions(-) create mode 100644 src/Deployment/DeploymentBase.php create mode 100644 src/Deployment/DeploymentFactory.php create mode 100644 src/Deployment/DeploymentInterface.php create mode 100644 src/Deployment/Gitlab.php diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index a9bf717..1c22729 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -2,25 +2,26 @@ namespace OFFLINE\Bootstrapper\October\Console; -use OFFLINE\Bootstrapper\October\Manager\PluginManager; -use OFFLINE\Bootstrapper\October\Manager\ThemeManager; +use OFFLINE\Bootstrapper\October\Util\CliIO; use OFFLINE\Bootstrapper\October\Config\Setup; -use OFFLINE\Bootstrapper\October\Downloader\OctoberCms; -use OFFLINE\Bootstrapper\October\Installer\DeploymentInstaller; use OFFLINE\Bootstrapper\October\Util\Artisan; +use Symfony\Component\Console\Command\Command; use OFFLINE\Bootstrapper\October\Util\Composer; use OFFLINE\Bootstrapper\October\Util\Gitignore; -use OFFLINE\Bootstrapper\October\Util\UsesTemplate; -use OFFLINE\Bootstrapper\October\Util\ManageDirectory; +use Symfony\Component\Console\Input\InputOption; use OFFLINE\Bootstrapper\October\Util\ConfigMaker; -use OFFLINE\Bootstrapper\October\Util\CliIO; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\RuntimeException; +use OFFLINE\Bootstrapper\October\Util\UsesTemplate; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use OFFLINE\Bootstrapper\October\Manager\ThemeManager; +use OFFLINE\Bootstrapper\October\Util\ManageDirectory; +use OFFLINE\Bootstrapper\October\Downloader\OctoberCms; +use OFFLINE\Bootstrapper\October\Manager\PluginManager; use Symfony\Component\Process\Exception\LogicException; +use Symfony\Component\Console\Exception\RuntimeException; +use OFFLINE\Bootstrapper\October\Deployment\DeploymentFactory; +use OFFLINE\Bootstrapper\October\Installer\DeploymentInstaller; +use Symfony\Component\Console\Exception\InvalidArgumentException; /** * Class InstallCommand @@ -182,10 +183,15 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->artisan->call('october:up'); $this->output->writeln('Setting up deployments...'); + $deployment = false; try { - (new DeploymentInstaller($this->config, $this->gitignore, $this->output, $this->php))->install($this->force); + $deployment = $this->config->git['deployment']; } catch (\RuntimeException $e) { - $this->output->writeln("${e}"); + } + + if ($deployment) { + $deploymentObj = DeploymentFactory::createDeployment($deployment); + $deploymentObj->install($this->force); } $this->output->writeln('Creating .gitignore...'); diff --git a/src/Deployment/DeploymentBase.php b/src/Deployment/DeploymentBase.php new file mode 100644 index 0000000..9b95d90 --- /dev/null +++ b/src/Deployment/DeploymentBase.php @@ -0,0 +1,15 @@ +force && $this->fileExists('.gitlab-ci.yml')) { + return $this->write('-> Deployment is already set up. Use --force to overwrite'); + } + + $this->copy($this->getTemplate('gitlab-ci.yml'), '.gitlab-ci.yml'); + $this->copy($this->getTemplate('Envoy.blade.php'), 'Envoy.blade.php'); + $this->copy($this->getTemplate('git.cron.sh'), 'git.cron.sh'); + } +} From 1f36b889459a38806f3867b92a7ec89477187ef1 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Thu, 11 Apr 2019 16:24:04 +0200 Subject: [PATCH 05/22] Pretty nice version --- README.md | 186 ++++++++++++---------------- october | 2 +- src/Console/InstallCommand.php | 128 +++++++++++++------- src/Console/UpdateCommand.php | 18 +-- src/Downloader/OctoberCms.php | 41 +++++-- src/Installer/PluginInstaller.php | 195 ++++++++++++++++++++++++++++++ src/Installer/ThemeInstaller.php | 104 ++++++++++++++++ src/Manager/BaseManager.php | 10 -- src/Manager/PluginManager.php | 39 ++++-- src/Manager/ThemeManager.php | 3 +- src/Util/CliIO.php | 8 +- src/Util/ConfigMaker.php | 2 +- src/Util/ManageDirectory.php | 16 ++- templates/Envoy.blade.php | 68 +++++++++-- templates/gitlab-ci.yml | 6 +- 15 files changed, 601 insertions(+), 225 deletions(-) create mode 100644 src/Installer/PluginInstaller.php create mode 100644 src/Installer/ThemeInstaller.php diff --git a/README.md b/README.md index 9611d68..af7798f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,19 @@ # Bootstrapper for October CMS -`oc-bootstrapper` is a simple script that enables you to bootstrap an October CMS installation -with custom plugins and custom theme. You simply describe your setup in a config file and run -the install command. - - `oc-bootstrapper` enables you to install plugins and themes from your own git repo. +`oc-bootstrapper` is a command line tool that enables you to reconstruct an October CMS installation +from a single configuration file. + +It can be used to quickly bootstrap a local development environment for a project or +to build and update a production installation during a deployment. -The following steps will be taken care of: -1. The latest October CMS gets downloaded from github and gets installed -2. All composer dependencies are installed -3. Relevant config entries are moved to a `.env` file for easy customization -4. Sensible configuration defaults for your `prod` environment get pre-set -5. Your database gets migrated -6. All demo data gets removed -7. Your selected theme gets downloaded and installed -8. All your plugins get downloaded and installed -9. A .gitignore file gets created -10. A push to deploy setup gets initialized for you +## Features + +* Installs and updates private and public plugins (via Git or Marketplace) +* Makes sure only necessary files are in your git repo by intelligently managing your `.gitignore` file +* Built in support for GitLab CI deployments +* Built in support for shared configuration file templates +* Sets sensible configuration defaults using `.env` files for production and development environments ## Dependencies @@ -28,9 +24,10 @@ The following steps will be taken care of: * Ubuntu 15.10 * Ubuntu 16.04 +* Ubuntu 18.04 * OSX 10.11 (El Capitan) -Works on Windows via Ubuntu Bash or Git Bash. +Works on Windows via `Ubuntu Bash` or `Git Bash`. ## Project's repo @@ -40,13 +37,15 @@ Take a look at the [OFFLINE-GmbH/octobertricks.com](https://github.com/OFFLINE-G ## Installation -```composer global require offline/oc-bootstrapper``` +```bash +composer global require offline/oc-bootstrapper +``` You can now run `october` from your command line. ```bash -$ october -October CMS Bootstrapper version 0.2.0 +$ october -v +October CMS Bootstrapper version 0.5.0 ``` ### Docker image @@ -63,7 +62,7 @@ docker run offlinegmbh/oc-bootstrapper composer -v ### Initialize your project -Use the `october init` command to create a new project with a config file: +Use the `october init` command to create a new empty project with a config file: ```sh october init myproject.com @@ -88,18 +87,13 @@ cms: database: connection: mysql - username: homestead - password: secret + username: root + password: database: bootstrapper - host: 192.168.10.10 + host: localhost git: - deployment: false - - bareRepo: true # Exclude everything except themes and custom plugins in git - excludePlugins: false # Even exclude plugins from your repo. Private plugins will be - # checked out again during each "install" run. Be careful! - # Manual changes to these plugins will be overwritten. + deployment: gitlab plugins: - Rainlab.Pages @@ -107,7 +101,8 @@ plugins: - Indikator.Backend - OFFLINE.SiteSearch - OFFLINE.ResponsiveImages - - OFFLINE.Indirect (https://github.com/OFFLINE-GmbH/oc-indirect-plugin.git) + - OFFLINE.GDPR (https://github.com/OFFLINE-GmbH/oc-gdpr-plugin.git) + - ^OFFLINE.Mall (https://github.com/OFFLINE-GmbH/oc-mall-plugin.git#develop) # - Vendor.Private (user@remote.git) # - Vendor.PrivateCustomBranch (user@remote.git#branch) @@ -125,10 +120,34 @@ mail: append your repo's address in `()` to tell `oc-bootstrapper` to check it out for you. If no repo is defined the plugins are loaded from the October Marketplace. +##### Examples + +```yaml +# Install a plugin from the official October Marketplace +- OFFLINE.Mall + +# Install a plugin from a git repository. The plugin will be cloned +# into your local repository and become part of it. You can change the +# plugin and modify it to your needs. It won't be checked out again (no updates). +- OFFLINE.Mall (https://github.com/OFFLINE-GmbH/oc-mall-plugin.git) + +# The ^ marks this plugin as updateable. It will be removed and checked out again +# during each call to `october install`. Local changes will be overwritten. +# This plugin will stay up to date with the changes of your original plugin repo. +- ^OFFLINE.Mall (https://github.com/OFFLINE-GmbH/oc-mall-plugin.git) + +# Install a specific branch of a plugin. Keep it up-to-date. +- ^OFFLINE.Mall (https://github.com/OFFLINE-GmbH/oc-mall-plugin.git#develop) +``` + ### Install October CMS When you are done editing your configuration file, simply run `october install` to install October. +`oc-bootstrapper` will take care of setting everything up for you. You can run this command locally +after checking out a project repository or during deployment. + +This command is *idempotent*, it will only install what is missing on subsequent calls. ``` october install @@ -140,23 +159,13 @@ Use the `--help` flag to see all available options. october install --help ``` -### Update October CMS - -If you want to update the installation you can run +#### Install additional plugins -``` -october update -``` +If at any point in time you need to install additional plugins, simply add them to your `october.yaml` and re-run +`october install`. Missing plugins will be installed. -The command will: + -1. Run `october install` command, which will install all plugins and themes that are not installed yet -2. Remove every plugin that has git repo specified in october.yaml, for `october:update` command not to try update them -3. Run `php artisan october:update`, which updates core and marketplace plugins -4. Git clone all plugins again -5. Run `php artisan october:up` to migrate all versions of plugins -6. Run `composer update` to update all composer packages - #### Use a custom php binary Via the `--php` flag you can specify a custom php binary to be used for the installation commands: @@ -164,67 +173,45 @@ Via the `--php` flag you can specify a custom php binary to be used for the inst ``` october install --php=/usr/local/bin/php72 ``` +### Update October CMS -#### Install additional plugins - -If at any point in time you need to install additional plugins, simply add them to your `october.yaml` and rerun `october install`. Missing plugins will be installed. - -### Change config - -To change your installation's configuration, simply edit the `.env` file in your project root. -When deploying to production, make sure to edit your `.env.production` template file and rename it to `.env`. - -### Bare repos - -If you don't want to have the complete October source code in your repository set the `bareRepo` - option to `true`. - - This will set up a `.gitignore` file that excludes everything except your `theme` directory and all the **manually installed** plugins in your `plugins` directory. - - > If you want to deploy a bare repo please read the section `SSH deployments with bare repos` below. - -#### `excludePlugins` - -By default every private plugin will be cloned only once and is then added to your `.gitignore` file. In the end your bare repo includes your theme and all your custom and private plugins. If you wish to only include your theme and no plugin data at all you can set `excludePlugins` to true. - -If you run `october install` in an existing project (let's say during deployment) all private plugin directories will get remove from your local disk and are checked out via git again so you'll get the latest version. - -If you don't want a plugin to be checked out on every `october install` run you can add the following line to your `.gitignore` file. If such a line is found the plugin will not be touched after the first checkout. +If you want to update the installation you can run ``` -# vendor.plugin -# offline.sitesearch +october update ``` -#### Get up and running after `git clone` - -After cloning a bare repo for the first time, simply run `october install`. October CMS and all missing plugins will get installed locally on your machine. - +The command will update all plugins, the October CMS core and all composer dependencies. + ### SSH deployments Set the `deployment` option to `false` if you don't want to setup deployments. -Currently `oc-bootstrapper` supports a simple setup to deploy a project on push via GitLab CI. To automatically create all needed files, simply set the `deployment` option to `gitlab`. +#### Setup -Support for other CI systems is added on request. +You can use `oc-bootstrapper` with any kind of deployment software. You need to setup the following steps: - #### SSH deployments with bare repos - - If you use SSH deployments with a bare repo, make sure to run `./vendor/bin/october install` in your deployment script to install the October source code and all of your plugins . If the October source code is already available it won't be downloaded again. +1. Connect to the target server (via SSH) +1. Install composer and oc-bootstrapper +1. Run `october install` -If you use the provided GitLab deployment via Envoy make sure to simply uncomment [this line](https://github.com/OFFLINE-GmbH/oc-bootstrapper/blob/fd45b66580f4b1af24880a3b331635a7654cf4ed/templates/Envoy.blade.php#L17). - - It is important that you list every installed plugin in your `october.yaml` file. Otherwise the plugins won't be available after deployment. - -#### GitLab CI with Envoy +You can run this "script" for each push to your repository. The `october install` command will +only install what is missing from the target server. + + +#### Example setup for GitLab CI -If you use the gitlab deployment option the `.gitlab-ci.yml` and `Envoy.blade.php` files are created for you. +To initialize a project with GitLab CI support set the `deployment` option in your config file to `gitlab`. -Change the variables inside the `Envoy.blade.php` to fit your needs. You have to create a Pipeline Variable in GitLab called `SSH_PRIVATE_KEY` that contains your private key. Add your public key to the target server. +This will setup a [`.gitlab-ci.yml`](templates/gitlab-ci.yml) and a [`Envoy.blade.php`](templates/Envoy.blade.php). -If you push to your GitLab server and CI builds are enabled, your private key is added to the `ssh-agent` inside the Docker container and the tasks from `Envoy.blade.php` will be executed on your target server. +1. Create a SSH key pair to log in to your deployment target server +1. Create a `SSH_PRIVATE_KEY` variable in your GitLab CI settings that contains the created private key +1. Edit the `Envoy.blade.php` script to fit your needs +1. Push to your repository. GitLab will run the example `.gitlab-ci.yml` configuration -##### Cronjob to commit changes from prod into git + +### Cronjob to commit changes from prod into git If a deployed website is edited by a customer directly on the prod server you might want to commit those changes back to your git repository. @@ -255,22 +242,3 @@ git clone your-central-templates-repo.git . git branch --set-upstream-to=origin/master master git pull # Make sure this works without any user interaction ``` - -## Troubleshooting - -### Fix cURL error 60 on Windows using XAMPP - -If you are working with XAMPP on Windows you will most likely get the following error during `october install`: - - cURL error 60: SSL certificate problem: unable to get local issuer certificate - -You can fix this error by executing the following steps. - -1. Download the `cacert.pem` file from [VersatilityWerks](https://gist.github.com/VersatilityWerks/5719158/download) -2. Extract the `cacert.pem` to the `php` folder in your xampp directory (eg. `c:\xampp\php`) -3. Edit your `php.ini` (`C:\xampp\php\php.ini`). Add the following line. - - `curl.cainfo = "\xampp\php\cacert.pem"` - -`october install` should work now as expected. - diff --git a/october b/october index cb7c55e..3c82cc5 100755 --- a/october +++ b/october @@ -8,7 +8,7 @@ if (file_exists(__DIR__.'/../../autoload.php')) { require __DIR__.'/vendor/autoload.php'; } -$app = new Symfony\Component\Console\Application('October CMS Bootstrapper', '0.4.4'); +$app = new Symfony\Component\Console\Application('October CMS Bootstrapper', '0.5.0'); $app->add(new \OFFLINE\Bootstrapper\October\Console\InitCommand); $app->add(new \OFFLINE\Bootstrapper\October\Console\InstallCommand); $app->add(new \OFFLINE\Bootstrapper\October\Console\UpdateCommand); diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 1c22729..3ec5d3a 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -139,7 +139,17 @@ protected function configure() */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->prepareEnv($input, $output); + if (! class_exists('ZipArchive')) { + throw new RuntimeException('The Zip PHP extension is not installed. Please install it and try again.'); + } + + $this->setOutput($output); + $this->pluginManager->setOutput($output); + $this->themeManager->setOutput($output); + + $this->force = $input->getOption('force'); + + $this->firstRun = ! is_dir($this->path('bootstrap')) || $this->force; $this->makeConfig(); @@ -149,40 +159,53 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->gitignore = new Gitignore($this->getGitignore()); - $this->output->writeln('Downloading latest October CMS...'); + $this->write('Downloading latest October CMS...'); try { (new OctoberCms())->download($this->force); } catch (\LogicException $e) { - $this->output->writeln('' . $e->getMessage() . ''); + $this->write($e->getMessage(), 'error'); } - $this->output->writeln('Installing composer dependencies...'); + $this->write('Installing composer dependencies...'); $this->composer->install(); $this->composer->addDependency('offline/oc-bootstrapper'); - $this->output->writeln('Setting up config files...'); + $this->write('Setting up config files...'); $this->writeConfig($this->force); $this->prepareDatabase(); - $this->output->writeln('Migrating database...'); + $this->write('Migrating database...'); $this->artisan->call('october:up'); - $this->output->writeln('Installing Theme...'); - $themeConfig = $this->config->cms['theme']; - $this->themeManager->install($themeConfig); + $this->write('Installing Theme...'); + $themeDeclaration = false; + try { + $themeDeclaration = $this->config->cms['theme']; + } catch (\RuntimeException $e) { + $this->write(' - No theme to install'); + } - $this->output->writeln('Installing Plugins...'); - $pluginsConfigs = $this->config->plugins; + if ($themeDeclaration) { + $this->installTheme($themeDeclaration); + } - foreach ($pluginsConfigs as $pluginConfig) { - $this->pluginManager->install($pluginConfig); + $this->write('Installing Plugins...'); + $pluginsConfigs = false; + try { + $pluginsConfig = $this->config->plugins; + } catch (\RuntimeException $e) { + $this->write(' - No plugins to install'); } - $this->output->writeln('Migrating plugin tables...'); + if ($pluginsConfigs) { + $this->installPlugins($pluginsConfigs); + } + + $this->write('Migrating plugin tables...'); $this->artisan->call('october:up'); - $this->output->writeln('Setting up deployments...'); + $this->write('Setting up deployments...'); $deployment = false; try { $deployment = $this->config->git['deployment']; @@ -194,29 +217,65 @@ protected function execute(InputInterface $input, OutputInterface $output) $deploymentObj->install($this->force); } - $this->output->writeln('Creating .gitignore...'); + $this->write('Creating .gitignore...'); $this->gitignore->write(); if ($this->firstRun) { - $this->output->writeln('Removing demo data...'); + $this->write('Removing demo data...'); $this->artisan->call('october:fresh'); - $this->output->writeln('Creating README...'); + $this->write('Creating README...'); $this->copyReadme(); - $this->output->writeln('Cleaning up...'); + $this->write('Cleaning up...'); $this->cleanup(); } - $this->output->writeln('Clearing cache...'); + $this->write('Clearing cache...'); $this->artisan->call('clear-compiled'); $this->artisan->call('cache:clear'); - $this->output->writeln('Application ready! Build something amazing.'); + $this->write('Application ready! Build something amazing.', 'comment'); return true; } + public function installPlugins($pluginsDeclarations) + { + foreach ($pluginsDeclarations as $pluginDeclaration) { + $pluginInstalled = $this->pluginManager->isInstalled($pluginDeclaration); + $installPlugin = !$pluginInstalled; + + list($update, $vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginDeclaration); + + if ($update || !$this->gitignore->hasPluginHeader($vendor, $plugin)) { + $this->write(sprintf('Removing "%s.%s" directory to re-download the newest version ...', $vendor, $plugin), 'comment'); + + $this->pluginManager->removeDir($pluginDeclaration); + $installPlugin = true; + } else { + $this->write(sprintf('Skipping re-downloading of plugin "%s.%s"', $vendor, $plugin), 'comment'); + } + + if ($installPlugin) { + try { + $this->pluginManager->install($pluginDeclaration); + } catch (RuntimeException $e) { + $this->write($e->getMessage(), 'error'); + } + } + } + } + + public function installTheme($themeDeclaration) + { + try { + $this->themeManager->install($themeDeclaration); + } catch (RuntimeException $e) { + $this->write($e->getMessage(), 'error'); + } + } + /** * Create the .env and config files. * @@ -234,7 +293,7 @@ protected function writeConfig($force = false) } if ($this->fileExists('.env') && $force === false) { - return $this->output->writeln('-> Configuration already set up. Use --force to regenerate.'); + return $this->write('-> Configuration already set up. Use --force to regenerate.', 'comment'); } $setup->env(); @@ -293,32 +352,9 @@ public function prepareDatabase() if ($this->config->database['connection'] === 'sqlite') { $path = $this->config->database['database']; if (! $this->fileExists($path) && is_dir(dirname($path))) { - $this->output->writeln("Creating $path ..."); + $this->write("Creating $path ..."); $this->touchFile($path); } } } - - /** - * Prepare the environment - * - * @param InputInterface $input - * @param OutputInterface $output - * @return void - */ - protected function prepareEnv(InputInterface $input, OutputInterface $output) - { - if (! class_exists('ZipArchive')) { - throw new RuntimeException('The Zip PHP extension is not installed. Please install it and try again.'); - } - - $this->setOutput($output); - - $this->pluginManager->setOutput($output); - $this->themeManager->setOutput($output); - - $this->force = $input->getOption('force'); - - $this->firstRun = ! is_dir($this->path('bootstrap')) || $this->force; - } } diff --git a/src/Console/UpdateCommand.php b/src/Console/UpdateCommand.php index 77285ab..989d27e 100644 --- a/src/Console/UpdateCommand.php +++ b/src/Console/UpdateCommand.php @@ -112,13 +112,10 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->setPhp($php); } - // 1. Run `october install` command, which will install all plugins and themes that are not installed yet $this->write("Installing new plugins"); $this->runProcess($this->php . ' october install', 'Installation failed!'); - // 2. Remove every plugin that has git repo specified in october.yaml, for `october:update` command not to try update them - $pluginsConfigs = $this->config->plugins; $this->write("Removing private plugins"); @@ -131,15 +128,12 @@ protected function execute(InputInterface $input, OutputInterface $output) } $this->write("Cleared private plugins"); - - // 3. Run `php artisan october:update`, which updates core and marketplace plugins - $this->write("Running artisan october:update"); $this->artisan->call('october:update'); // 4. Git clone all plugins again - $this->write("Reinstalling plugins:"); + $this->write('Reinstalling plugins:'); foreach ($pluginsConfigs as $pluginConfig) { list($vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginConfig); @@ -149,19 +143,13 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - // 5. Run `php artisan october:up` to migrate all versions of plugins - - $this->write("Migrating all unmigrated versions"); + $this->write('Migrating all unmigrated versions'); $this->artisan->call('october:up'); - // 6. Run `composer update` to update all composer packages - - $this->write("Running composer update"); + $this->write('Running composer update'); $this->composer->updateLock(); - // 7. IDEA: Optionally commit and push to git repo - return true; } diff --git a/src/Downloader/OctoberCms.php b/src/Downloader/OctoberCms.php index 74008be..a38fd7c 100644 --- a/src/Downloader/OctoberCms.php +++ b/src/Downloader/OctoberCms.php @@ -33,14 +33,15 @@ public function __construct() */ public function download($force = false) { - if($this->alreadyInstalled($force)) { + if ($this->alreadyInstalled($force)) { throw new \LogicException('-> October is already installed. Use --force to reinstall.'); } - + $this->fetchZip() ->extract() ->fetchHtaccess() - ->cleanUp(); + ->cleanUp() + ->setMaster(); return $this; } @@ -48,9 +49,9 @@ public function download($force = false) /** * Download the temporary Zip to the given file. * - * @throws LogicException - * @throws RuntimeException * @return $this + * @throws RuntimeException + * @throws LogicException */ protected function fetchZip() { @@ -89,12 +90,38 @@ protected function fetchHtaccess() return $this; } + + /** + * Since we don't want any unstable updates we fix + * the libraries to the master branch. + * + * @return $this + */ + protected function setMaster() + { + $json = getcwd() . DS . 'composer.json'; + + $contents = file_get_contents($json); + + $contents = preg_replace_callback( + '/october\/(?:rain|system|backend|cms)":\s"([^"]+)"/m', + function ($treffer) { + return str_replace($treffer[1], 'dev-master', $treffer[0]); + }, + $contents + ); + + file_put_contents($json, $contents); + + return $this; + } + /** * Remove the Zip file, move folder contents one level up. * - * @throws LogicException - * @throws \Symfony\Component\Process\Exception\RuntimeException * @return $this + * @throws \Symfony\Component\Process\Exception\RuntimeException + * @throws LogicException */ protected function cleanUp() { diff --git a/src/Installer/PluginInstaller.php b/src/Installer/PluginInstaller.php new file mode 100644 index 0000000..4fd352f --- /dev/null +++ b/src/Installer/PluginInstaller.php @@ -0,0 +1,195 @@ +config->plugins; + } catch (\RuntimeException $e) { + $this->write(' - Nothing to install'); + + // No plugin set + return false; + } + + $privatePluginInstalled = false; + + foreach ($config as $plugin) { + + $this->write(' - ' . $plugin . ''); + + list($update, $vendor, $plugin, $remote, $branch) = $this->parse($plugin); + + $vendorDir = $this->createVendorDir($vendor); + $pluginDir = $vendorDir . DS . $plugin; + + $this->mkdir($pluginDir); + + if ( ! $this->isEmpty($pluginDir)) { + if ($this->handleExistingPlugin( + $vendor, + $plugin, + $pluginDir, + $update + ) === false + ) { + continue; + } + } + + if ( ! $remote) { + $this->installViaArtisan($vendor, $plugin); + continue; + } + + $repo = Git::repo($pluginDir); + try { + $repo->cloneFrom($remote, $pluginDir); + if ($branch) { + $this->write(' -> ' . sprintf('Checkout "%s" ...', $branch) . ''); + $repo->checkout($branch); + } + } catch (RuntimeException $e) { + $this->write(' - ' . 'Error while cloning plugin repo: ' . $e->getMessage() . ''); + continue; + } + + (new Process($this->php . " artisan plugin:refresh {$vendor}.{$plugin}"))->run(); + + if ($update === false) { + $this->gitignore->addPlugin($vendor, $plugin); + } + + $this->cleanup($pluginDir); + $privatePluginInstalled = true; + } + + if ($privatePluginInstalled) { + $this->write('Installing dependencies of private plugins...'); + (new Composer())->updateLock(); + } + + return true; + } + + protected function handleExistingPlugin($vendor, $plugin, $pluginDir, $update) + { + if ($update === false) { + $this->write(' -> ' . sprintf('Plugin "%s.%s" already installed. Skipping.', + $vendor, $plugin) . ''); + + return false; + } + + // Remove any existing local version of the private plugin so it can be checked out via git again + if ($this->gitignore->hasPluginHeader($vendor, $plugin)) { + $this->write(' -> ' . sprintf('Plugin "%s.%s" found in .gitignore. Skipping re-download of newest version ...', + $vendor, $plugin) . ''); + + return false; + } + + $this->write(' -> ' . sprintf('Removing "%s" to re-download the newest version ...', + $pluginDir) . ''); + + $this->rmdir($pluginDir); + $this->mkdir($pluginDir); + + return true; + } + + /** + * Parse the Vendor, Plugin and Remote values out of the + * given plugin declaration. + * + * @param $plugin + * + * @return mixed + */ + protected function parse($plugin) + { + // ^Vendor.Plugin (Remote) + preg_match( + "/(?\^)?(?[^\.]+)\.(?[^ #]+)(?: ?\((?[^\#)]+)(?:#(?[^\)]+)?)?)?/", + $plugin, + $matches + ); + + array_shift($matches); + + $matches = array_map('strtolower', $matches); + + if ($matches['update']) { + $matches['update'] = true; + } else { + $matches['update'] = false; + } + + return [ + $matches['update'] ?? false, + $matches['vendor'] ?? '', + $matches['plugin'] ?? '', + $matches['remote'] ?? '', + $matches['branch'] ?? '', + ]; + } + + /** + * Create the plugin's vendor directory. + * + * @param $vendor + * + * @return string + * @throws \RuntimeException + */ + protected function createVendorDir($vendor) + { + $pluginDir = getcwd() . DS . implode(DS, ['plugins', $vendor]); + + return $this->mkdir($pluginDir); + } + + /** + * Installs a plugin via artisan command. + * + * @param $vendor + * @param $plugin + * + * @throws LogicException + * @throws RuntimeException + */ + protected function installViaArtisan($vendor, $plugin) + { + $exitCode = (new Process($this->php . " artisan plugin:install {$vendor}.{$plugin}"))->run(); + + if ($exitCode !== $this::EXIT_CODE_OK) { + throw new RuntimeException( + sprintf('Error while installing plugin %s via artisan. Is your database set up correctly?', + $vendor . '.' . $plugin + ) + ); + } + } +} \ No newline at end of file diff --git a/src/Installer/ThemeInstaller.php b/src/Installer/ThemeInstaller.php new file mode 100644 index 0000000..e6bb8d8 --- /dev/null +++ b/src/Installer/ThemeInstaller.php @@ -0,0 +1,104 @@ +config->cms['theme']; + } catch (\RuntimeException $e) { + // No theme set + return false; + } + + list($theme, $remote) = $this->parse($config); + if ($remote === false) { + return $this->installViaArtisan($theme); + } + + $themeDir = getcwd() . DS . implode(DS, ['themes', $theme]); + $this->mkdir($themeDir); + + if ( ! $this->isEmpty($themeDir)) { + $this->write(sprintf('-> Theme "%s" is already installed. Skipping.', $theme)); + + return; + } + + $repo = Git::repo($themeDir); + try { + $repo->cloneFrom($remote, $themeDir); + } catch (RuntimeException $e) { + throw new RuntimeException('Error while cloning theme repo: ' . $e->getMessage()); + } + + $this->cleanup($themeDir); + + return true; + } + + /** + * Parse the theme's name and remote path out of the + * given theme declaration. + * + * @param $theme + * + * @return mixed + */ + protected function parse($theme) + { + // Theme (Remote) + preg_match("/([^ ]+)(?: ?\(([^\)]+))?/", $theme, $matches); + + array_shift($matches); + + if (count($matches) < 2) { + $matches[1] = false; + } + + return $matches; + } + + /** + * Installs a theme via artisan command. + * + * @param $theme + * + * @return bool + * + * @throws LogicException + * @throws RuntimeException + */ + protected function installViaArtisan($theme) + { + $exitCode = (new Process($this->php . " artisan theme:install {$theme}"))->run(); + + if ($exitCode !== $this::EXIT_CODE_OK) { + throw new RuntimeException(sprintf('Error while installing theme "%s" via artisan.', $theme)); + } + + return true; + } +} diff --git a/src/Manager/BaseManager.php b/src/Manager/BaseManager.php index cb943f7..f6fd222 100644 --- a/src/Manager/BaseManager.php +++ b/src/Manager/BaseManager.php @@ -46,14 +46,4 @@ public function setPhp(string $php = 'php') $this->php = $php; $this->artisan->setPhp($php); } - - /** - * Removes .git Directories. - * - * @param $path - */ - public function removeGitRepo($path) - { - $this->rmdir($path . DS . '.git'); - } } diff --git a/src/Manager/PluginManager.php b/src/Manager/PluginManager.php index d28cfb3..a137e4f 100644 --- a/src/Manager/PluginManager.php +++ b/src/Manager/PluginManager.php @@ -9,7 +9,7 @@ * Plugin manager class */ class PluginManager extends BaseManager -{ +{ /** * Parse the Vendor, Plugin and Remote values out of the @@ -59,7 +59,7 @@ public function createVendorDir($vendor) public function createDir(string $pluginDeclaration) { - list($vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); + list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); $vendor = strtolower($vendor); $plugin = strtolower($plugin); @@ -77,7 +77,7 @@ public function createDir(string $pluginDeclaration) public function removeDir(string $pluginDeclaration) { - list($vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); + list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); $vendor = strtolower($vendor); $plugin = strtolower($plugin); @@ -88,7 +88,7 @@ public function removeDir(string $pluginDeclaration) public function getDirPath(string $pluginDeclaration) { - list($vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); + list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); $vendor = strtolower($vendor); $plugin = strtolower($plugin); @@ -98,16 +98,23 @@ public function getDirPath(string $pluginDeclaration) return $pluginDir; } + public function isInstalled(string $pluginDeclaration) + { + $pluginDir = $this->getDirPath($pluginDeclaration); + + return $this->isEmpty($pluginDir); + } + public function install(string $pluginDeclaration) { - list($vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); + list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); - $this->write(' - ' . $vendor . '.' . $plugin . ''); + $this->write('- ' . $vendor . '.' . $plugin); $pluginDir = $this->createDir($pluginDeclaration); if (!$this->isEmpty($pluginDir)) { - throw new RuntimeException(" - Plugin directory not empty. Aborting. "); + throw new RuntimeException("Plugin directory not empty. Aborting."); } if ($remote === false) { @@ -118,14 +125,20 @@ public function install(string $pluginDeclaration) try { $repo->cloneFrom($remote, $pluginDir); if ($branch !== false) { - $this->write(' -> ' . sprintf('Checkout "%s" ...', $branch) . ''); + $this->write(' -> ' . sprintf('Checkout "%s" ...', $branch), 'comment'); $repo->checkout($branch); } } catch (RuntimeException $e) { - throw new RuntimeException(' - ' . 'Error while cloning plugin repo: ' . $e->getMessage() . ''); + throw new RuntimeException('Error while cloning plugin repo: ' . $e->getMessage()); } - $this->removeGitRepo($pluginDir); + $this->artisan->call("plugin:refresh {$vendor}.{$plugin}"); + + if ($update === false) { + $this->gitignore->addPlugin($vendor, $plugin); + } + + $this->removeGitRepo($this->getDirPath($pluginDeclaration)); } /** @@ -138,13 +151,14 @@ public function install(string $pluginDeclaration) */ public function installViaArtisan(string $pluginDeclaration) { - list($vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); + list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); try { $this->artisan->call("plugin:install {$vendor}.{$plugin}"); } catch (RuntimeException $e) { throw new RuntimeException( - sprintf('Error while installing plugin %s via artisan. Is your database set up correctly?', + sprintf( + 'Error while installing plugin %s via artisan. Is your database set up correctly?', $vendor . '.' . $plugin ) ); @@ -152,5 +166,4 @@ public function installViaArtisan(string $pluginDeclaration) return "${vendor}.${plugin} plugin installed"; } - } diff --git a/src/Manager/ThemeManager.php b/src/Manager/ThemeManager.php index 8ca5df7..da1c0bb 100644 --- a/src/Manager/ThemeManager.php +++ b/src/Manager/ThemeManager.php @@ -75,7 +75,7 @@ public function install(string $themeDeclaration) $themeDir = $this->createDir($themeDeclaration); if (!$this->isEmpty($themeDir)) { - throw new RuntimeException(" - Theme directory not empty. Aborting. "); + throw new RuntimeException("Theme directory not empty. Aborting."); } if ($remote === false) { @@ -114,5 +114,4 @@ public function installViaArtisan(string $themeDeclaration) return "${theme} theme installed"; } - } diff --git a/src/Util/CliIO.php b/src/Util/CliIO.php index e684e8c..454a860 100644 --- a/src/Util/CliIO.php +++ b/src/Util/CliIO.php @@ -31,7 +31,7 @@ trait CliIO * @param OutputInterface $output * * @return self - */ + */ public function setOutput(OutputInterface $output) { $this->output = $output; @@ -45,7 +45,7 @@ public function setOutput(OutputInterface $output) * @param InputInterface $input * * @return self - */ + */ public function setInput(InputInterface $input) { $this->input = $input; @@ -59,8 +59,8 @@ public function setInput(InputInterface $input) * @param string $line * @return void */ - protected function write($line) + protected function write($line, $surround = "info") { - $this->output->writeln($line); + $this->output->writeln("<${surround}> ${line} rmdir($path . DS . '.git'); + } } diff --git a/templates/Envoy.blade.php b/templates/Envoy.blade.php index d9777fd..6df5148 100644 --- a/templates/Envoy.blade.php +++ b/templates/Envoy.blade.php @@ -1,25 +1,67 @@ @setup - $project = 'project-title'; - $user = 'username'; - $server = 'servername'; + // Deployment configuration ---------- + $project = 'example.com'; + $user = 'hostinguser'; + $server = 'servername'; + $directory = 'public_html/example.com/'; + $slackWebhook = 'https://hooks.slack.com/services/your/slack/webhook/url'; + // ----------------------------------- - $directory = 'public_html/'; + $author = isset($author) ? $author : "someone"; + $branch = isset($branch) ? $branch : "unknown branch"; + $commit = isset($commit) ? $commit : "no message"; @endsetup -@servers(['web' => $user . '@' . $server]) +@servers(['web' => $user . '@' . $server, 'localhost' => '127.0.0.1']) -@task('deploy', ['on' => 'web']) +@story('deploy') + update +@endstory + +@task('update', ['on' => 'web']) cd {{ $directory }} git pull + [ ! -f "composer.phar" ] && wget https://getcomposer.org/composer.phar - php composer.phar install --no-interaction --no-dev --prefer-dist - ## Enable this line when using bare repos - # ./vendor/bin/october install + php composer.phar install --no-interaction --no-dev --prefer-dist --ignore-platform-reqs + php ./vendor/bin/october install php artisan -v october:up + + ## START UPDATE CHECK + LOCK_FILE=".last-update-check" + NOW=$(date +%s) + LAST_CHECK=$( [ -f $LOCK_FILE ] && cat $LOCK_FILE || echo 0 ) + SECONDS_SINCE=$(expr $NOW - $LAST_CHECK) + + if [ "$SECONDS_SINCE" -gt "86400" ]; then + HOSTNAME=$( hostname ) + GIT=$( which git ) + + $PHP composer.phar self-update + php ./vendor/bin/october update + + if [[ -n $(git status -s) ]]; then + $GIT add --all . + $GIT commit -m "[ci skip] oc-bootstrapper updated October CMS ({{ $project }})" + $GIT push origin master + fi + + echo $NOW > $LOCK_FILE + else + echo "Skipping update check (last check was $SECONDS_SINCE seconds ago)" + fi + ## END UPDATE CHECK + git status -s @endtask -@after - $message = 'Deployed project ' . $project; - // @slack('webhook', 'deployments', $message) -@endafter \ No newline at end of file +@finished + $message = sprintf( + "`%s` deployed `%s` via `%s`:\n\n> %s", + ucfirst($author), + $project, + $branch, + $commit + ); + @slack($slackWebhook, 'deployments', $message) +@endfinished \ No newline at end of file diff --git a/templates/gitlab-ci.yml b/templates/gitlab-ci.yml index c760e5d..3612b3a 100644 --- a/templates/gitlab-ci.yml +++ b/templates/gitlab-ci.yml @@ -6,4 +6,8 @@ deploy: - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" > /tmp/id_rsa && chmod 400 /tmp/id_rsa && ssh-add /tmp/id_rsa && mkdir -p ~/.ssh - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' - - envoy run deploy \ No newline at end of file + - | + envoy run deploy \ + --author="$GITLAB_USER_LOGIN" \ + --branch="$CI_COMMIT_REF_NAME" \ + --commit="$CI_COMMIT_MESSAGE" From f7bc9dd92487ef2bcab86efb5ee96889ff07d31d Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Thu, 11 Apr 2019 22:26:05 +0200 Subject: [PATCH 06/22] WIP --- src/Console/InstallCommand.php | 49 ++++++++++++++++++++++++---------- src/Manager/PluginManager.php | 31 +++++++++++++-------- src/Util/CliIO.php | 5 ++-- src/Util/ManageDirectory.php | 12 +++++++++ 4 files changed, 69 insertions(+), 28 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 3ec5d3a..3b5597f 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -98,6 +98,16 @@ public function setPhp(string $php = 'php') $this->themeManager->setPhp($php); } + /** + * Set output for all components + */ + public function setOutput(OutputInterface $output) + { + $this->output = $output; + $this->pluginManager->setOutput($output); + $this->themeManager->setOutput($output); + } + /** * Configure the command options. * @@ -144,12 +154,10 @@ protected function execute(InputInterface $input, OutputInterface $output) } $this->setOutput($output); - $this->pluginManager->setOutput($output); - $this->themeManager->setOutput($output); $this->force = $input->getOption('force'); - $this->firstRun = ! is_dir($this->path('bootstrap')) || $this->force; + $this->firstRun = ! $this->dirExists($this->path('bootstrap')) || $this->force; $this->makeConfig(); @@ -162,7 +170,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->write('Downloading latest October CMS...'); try { (new OctoberCms())->download($this->force); - } catch (\LogicException $e) { + } catch (LogicException $e) { $this->write($e->getMessage(), 'error'); } @@ -178,7 +186,6 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->write('Migrating database...'); $this->artisan->call('october:up'); - $this->write('Installing Theme...'); $themeDeclaration = false; try { $themeDeclaration = $this->config->cms['theme']; @@ -187,32 +194,34 @@ protected function execute(InputInterface $input, OutputInterface $output) } if ($themeDeclaration) { + $this->write('Installing Theme...'); $this->installTheme($themeDeclaration); } - $this->write('Installing Plugins...'); - $pluginsConfigs = false; + $pluginsDeclarations = false; try { - $pluginsConfig = $this->config->plugins; + $pluginsDeclarations = $this->config->plugins; } catch (\RuntimeException $e) { $this->write(' - No plugins to install'); } - if ($pluginsConfigs) { - $this->installPlugins($pluginsConfigs); + if ($pluginsDeclarations) { + $this->write('Installing Plugins...'); + $this->installPlugins($pluginsDeclarations); } $this->write('Migrating plugin tables...'); $this->artisan->call('october:up'); - $this->write('Setting up deployments...'); $deployment = false; try { $deployment = $this->config->git['deployment']; } catch (\RuntimeException $e) { + $this->write(' - No deployments to install'); } if ($deployment) { + $this->write("Setting up ${deployment} deployment."); $deploymentObj = DeploymentFactory::createDeployment($deployment); $deploymentObj->install($this->force); } @@ -240,6 +249,12 @@ protected function execute(InputInterface $input, OutputInterface $output) return true; } + /** + * Handle installing plugins and updating them if possible + * + * @param array $pluginsDeclarations + * @return void + */ public function installPlugins($pluginsDeclarations) { foreach ($pluginsDeclarations as $pluginDeclaration) { @@ -249,12 +264,12 @@ public function installPlugins($pluginsDeclarations) list($update, $vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginDeclaration); if ($update || !$this->gitignore->hasPluginHeader($vendor, $plugin)) { - $this->write(sprintf('Removing "%s.%s" directory to re-download the newest version ...', $vendor, $plugin), 'comment'); + $this->write("Removing ${vendor}.${plugin} directory to re-download the newest version...", 'comment'); $this->pluginManager->removeDir($pluginDeclaration); $installPlugin = true; } else { - $this->write(sprintf('Skipping re-downloading of plugin "%s.%s"', $vendor, $plugin), 'comment'); + $this->write("Skipping re-downloading of ${vendor}.${plugin}", 'comment'); } if ($installPlugin) { @@ -267,7 +282,13 @@ public function installPlugins($pluginsDeclarations) } } - public function installTheme($themeDeclaration) + /** + * Install theme + * + * @param string $themeDeclaration + * @return void + */ + public function installTheme(string $themeDeclaration) { try { $this->themeManager->install($themeDeclaration); diff --git a/src/Manager/PluginManager.php b/src/Manager/PluginManager.php index a137e4f..542878f 100644 --- a/src/Manager/PluginManager.php +++ b/src/Manager/PluginManager.php @@ -12,28 +12,37 @@ class PluginManager extends BaseManager { /** - * Parse the Vendor, Plugin and Remote values out of the - * given plugin declaration. + * Parse the plugin declaration values out of the given plugin declaration string * - * @param string $pluginDeclaration like Vendor.Plugin (Remote) + * @param string $pluginDeclaration like ^Vendor.Plugin (Remote) * - * @return array array containing $vendor, $pluginName[, $remote[, $branch]] + * @return array array [bool $update, string $vendor, string $pluginName, string $remote, string $branch] */ public function parseDeclaration(string $pluginDeclaration): array { - preg_match("/([^\.]+)\.([^ #]+)(?: ?\(([^\#)]+)(?:#([^\)]+)?)?)?/", $pluginDeclaration, $matches); + preg_match( + "/(?\^)?(?[^\.]+)\.(?[^ #]+)(?: ?\((?[^\#)]+)(?:#(?[^\)]+)?)?)?/", + $pluginDeclaration, + $matches + ); array_shift($matches); - if (count($matches) < 3) { - $matches[2] = false; - } + $matches = array_map('strtolower', $matches); - if (count($matches) < 4) { - $matches[3] = false; + if ($matches['update']) { + $matches['update'] = true; + } else { + $matches['update'] = false; } - return $matches; + return [ + $matches['update'] ?? false, + $matches['vendor'] ?? '', + $matches['plugin'] ?? '', + $matches['remote'] ?? '', + $matches['branch'] ?? '', + ]; } /** diff --git a/src/Util/CliIO.php b/src/Util/CliIO.php index 454a860..e177f3a 100644 --- a/src/Util/CliIO.php +++ b/src/Util/CliIO.php @@ -29,7 +29,6 @@ trait CliIO * Set the value of output * * @param OutputInterface $output - * * @return self */ public function setOutput(OutputInterface $output) @@ -43,7 +42,6 @@ public function setOutput(OutputInterface $output) * Set the value of input * * @param InputInterface $input - * * @return self */ public function setInput(InputInterface $input) @@ -57,10 +55,11 @@ public function setInput(InputInterface $input) * Writes new line to output * * @param string $line + * @param string $surround html tag to surround the message * @return void */ protected function write($line, $surround = "info") { - $this->output->writeln("<${surround}> ${line} output->writeln("<${surround}>${line}"); } } diff --git a/src/Util/ManageDirectory.php b/src/Util/ManageDirectory.php index ad320a7..b0744a0 100644 --- a/src/Util/ManageDirectory.php +++ b/src/Util/ManageDirectory.php @@ -52,6 +52,18 @@ public function fileExists($file) return file_exists($this->path($file)); } + /** + * Check if directory exists + * + * @param string $dir relative or absolute path of the directory to check existence + * + * @return bool + */ + public function dirExists($dir) + { + return is_dir($this->path($dir)); + } + /** * Get absolute path of the file * From 2898ff940e241f658ef3b9ba9a3d4e4588231b11 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Mon, 29 Apr 2019 16:47:19 +0200 Subject: [PATCH 07/22] Almost done --- src/Console/InstallCommand.php | 16 +++++++++------- src/Manager/PluginManager.php | 2 -- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 3b5597f..ef8bb46 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -190,7 +190,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $themeDeclaration = $this->config->cms['theme']; } catch (\RuntimeException $e) { - $this->write(' - No theme to install'); + $this->write('No theme to install'); } if ($themeDeclaration) { @@ -202,7 +202,7 @@ protected function execute(InputInterface $input, OutputInterface $output) try { $pluginsDeclarations = $this->config->plugins; } catch (\RuntimeException $e) { - $this->write(' - No plugins to install'); + $this->write('No plugins to install'); } if ($pluginsDeclarations) { @@ -210,14 +210,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->installPlugins($pluginsDeclarations); } - $this->write('Migrating plugin tables...'); - $this->artisan->call('october:up'); - $deployment = false; try { $deployment = $this->config->git['deployment']; } catch (\RuntimeException $e) { - $this->write(' - No deployments to install'); + $this->write('No deployments to install'); } if ($deployment) { @@ -263,7 +260,9 @@ public function installPlugins($pluginsDeclarations) list($update, $vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginDeclaration); - if ($update || !$this->gitignore->hasPluginHeader($vendor, $plugin)) { + $this->write((string) $update); + $this->write((string) $this->gitignore->hasPluginHeader($vendor, $plugin)); + if ($pluginInstalled && ($update || !$this->gitignore->hasPluginHeader($vendor, $plugin))) { $this->write("Removing ${vendor}.${plugin} directory to re-download the newest version...", 'comment'); $this->pluginManager->removeDir($pluginDeclaration); @@ -280,6 +279,9 @@ public function installPlugins($pluginsDeclarations) } } } + + $this->write('Migrating plugin tables...'); + $this->artisan->call('october:up'); } /** diff --git a/src/Manager/PluginManager.php b/src/Manager/PluginManager.php index 542878f..ed4e6d4 100644 --- a/src/Manager/PluginManager.php +++ b/src/Manager/PluginManager.php @@ -141,8 +141,6 @@ public function install(string $pluginDeclaration) throw new RuntimeException('Error while cloning plugin repo: ' . $e->getMessage()); } - $this->artisan->call("plugin:refresh {$vendor}.{$plugin}"); - if ($update === false) { $this->gitignore->addPlugin($vendor, $plugin); } From 9609ba24aea20aa8cb21cd1282051fdb03c47977 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Wed, 1 May 2019 10:30:03 +0200 Subject: [PATCH 08/22] Removed installers --- src/Installer/PluginInstaller.php | 195 ------------------------------ src/Installer/ThemeInstaller.php | 104 ---------------- 2 files changed, 299 deletions(-) delete mode 100644 src/Installer/PluginInstaller.php delete mode 100644 src/Installer/ThemeInstaller.php diff --git a/src/Installer/PluginInstaller.php b/src/Installer/PluginInstaller.php deleted file mode 100644 index 4fd352f..0000000 --- a/src/Installer/PluginInstaller.php +++ /dev/null @@ -1,195 +0,0 @@ -config->plugins; - } catch (\RuntimeException $e) { - $this->write(' - Nothing to install'); - - // No plugin set - return false; - } - - $privatePluginInstalled = false; - - foreach ($config as $plugin) { - - $this->write(' - ' . $plugin . ''); - - list($update, $vendor, $plugin, $remote, $branch) = $this->parse($plugin); - - $vendorDir = $this->createVendorDir($vendor); - $pluginDir = $vendorDir . DS . $plugin; - - $this->mkdir($pluginDir); - - if ( ! $this->isEmpty($pluginDir)) { - if ($this->handleExistingPlugin( - $vendor, - $plugin, - $pluginDir, - $update - ) === false - ) { - continue; - } - } - - if ( ! $remote) { - $this->installViaArtisan($vendor, $plugin); - continue; - } - - $repo = Git::repo($pluginDir); - try { - $repo->cloneFrom($remote, $pluginDir); - if ($branch) { - $this->write(' -> ' . sprintf('Checkout "%s" ...', $branch) . ''); - $repo->checkout($branch); - } - } catch (RuntimeException $e) { - $this->write(' - ' . 'Error while cloning plugin repo: ' . $e->getMessage() . ''); - continue; - } - - (new Process($this->php . " artisan plugin:refresh {$vendor}.{$plugin}"))->run(); - - if ($update === false) { - $this->gitignore->addPlugin($vendor, $plugin); - } - - $this->cleanup($pluginDir); - $privatePluginInstalled = true; - } - - if ($privatePluginInstalled) { - $this->write('Installing dependencies of private plugins...'); - (new Composer())->updateLock(); - } - - return true; - } - - protected function handleExistingPlugin($vendor, $plugin, $pluginDir, $update) - { - if ($update === false) { - $this->write(' -> ' . sprintf('Plugin "%s.%s" already installed. Skipping.', - $vendor, $plugin) . ''); - - return false; - } - - // Remove any existing local version of the private plugin so it can be checked out via git again - if ($this->gitignore->hasPluginHeader($vendor, $plugin)) { - $this->write(' -> ' . sprintf('Plugin "%s.%s" found in .gitignore. Skipping re-download of newest version ...', - $vendor, $plugin) . ''); - - return false; - } - - $this->write(' -> ' . sprintf('Removing "%s" to re-download the newest version ...', - $pluginDir) . ''); - - $this->rmdir($pluginDir); - $this->mkdir($pluginDir); - - return true; - } - - /** - * Parse the Vendor, Plugin and Remote values out of the - * given plugin declaration. - * - * @param $plugin - * - * @return mixed - */ - protected function parse($plugin) - { - // ^Vendor.Plugin (Remote) - preg_match( - "/(?\^)?(?[^\.]+)\.(?[^ #]+)(?: ?\((?[^\#)]+)(?:#(?[^\)]+)?)?)?/", - $plugin, - $matches - ); - - array_shift($matches); - - $matches = array_map('strtolower', $matches); - - if ($matches['update']) { - $matches['update'] = true; - } else { - $matches['update'] = false; - } - - return [ - $matches['update'] ?? false, - $matches['vendor'] ?? '', - $matches['plugin'] ?? '', - $matches['remote'] ?? '', - $matches['branch'] ?? '', - ]; - } - - /** - * Create the plugin's vendor directory. - * - * @param $vendor - * - * @return string - * @throws \RuntimeException - */ - protected function createVendorDir($vendor) - { - $pluginDir = getcwd() . DS . implode(DS, ['plugins', $vendor]); - - return $this->mkdir($pluginDir); - } - - /** - * Installs a plugin via artisan command. - * - * @param $vendor - * @param $plugin - * - * @throws LogicException - * @throws RuntimeException - */ - protected function installViaArtisan($vendor, $plugin) - { - $exitCode = (new Process($this->php . " artisan plugin:install {$vendor}.{$plugin}"))->run(); - - if ($exitCode !== $this::EXIT_CODE_OK) { - throw new RuntimeException( - sprintf('Error while installing plugin %s via artisan. Is your database set up correctly?', - $vendor . '.' . $plugin - ) - ); - } - } -} \ No newline at end of file diff --git a/src/Installer/ThemeInstaller.php b/src/Installer/ThemeInstaller.php deleted file mode 100644 index e6bb8d8..0000000 --- a/src/Installer/ThemeInstaller.php +++ /dev/null @@ -1,104 +0,0 @@ -config->cms['theme']; - } catch (\RuntimeException $e) { - // No theme set - return false; - } - - list($theme, $remote) = $this->parse($config); - if ($remote === false) { - return $this->installViaArtisan($theme); - } - - $themeDir = getcwd() . DS . implode(DS, ['themes', $theme]); - $this->mkdir($themeDir); - - if ( ! $this->isEmpty($themeDir)) { - $this->write(sprintf('-> Theme "%s" is already installed. Skipping.', $theme)); - - return; - } - - $repo = Git::repo($themeDir); - try { - $repo->cloneFrom($remote, $themeDir); - } catch (RuntimeException $e) { - throw new RuntimeException('Error while cloning theme repo: ' . $e->getMessage()); - } - - $this->cleanup($themeDir); - - return true; - } - - /** - * Parse the theme's name and remote path out of the - * given theme declaration. - * - * @param $theme - * - * @return mixed - */ - protected function parse($theme) - { - // Theme (Remote) - preg_match("/([^ ]+)(?: ?\(([^\)]+))?/", $theme, $matches); - - array_shift($matches); - - if (count($matches) < 2) { - $matches[1] = false; - } - - return $matches; - } - - /** - * Installs a theme via artisan command. - * - * @param $theme - * - * @return bool - * - * @throws LogicException - * @throws RuntimeException - */ - protected function installViaArtisan($theme) - { - $exitCode = (new Process($this->php . " artisan theme:install {$theme}"))->run(); - - if ($exitCode !== $this::EXIT_CODE_OK) { - throw new RuntimeException(sprintf('Error while installing theme "%s" via artisan.', $theme)); - } - - return true; - } -} From e921ab21a3b5b410d8c7d490fc9cb3449c94cf28 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Wed, 1 May 2019 10:51:47 +0200 Subject: [PATCH 09/22] Removed installers --- src/Console/InstallCommand.php | 1 - src/Deployment/DeploymentFactory.php | 1 - src/Deployment/DeploymentInterface.php | 1 - src/Installer/BaseInstaller.php | 138 ------------------------- src/Installer/DeploymentInstaller.php | 66 ------------ 5 files changed, 207 deletions(-) delete mode 100644 src/Installer/BaseInstaller.php delete mode 100644 src/Installer/DeploymentInstaller.php diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 9696767..ebea696 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -20,7 +20,6 @@ use Symfony\Component\Process\Exception\LogicException; use Symfony\Component\Console\Exception\RuntimeException; use OFFLINE\Bootstrapper\October\Deployment\DeploymentFactory; -use OFFLINE\Bootstrapper\October\Installer\DeploymentInstaller; use Symfony\Component\Console\Exception\InvalidArgumentException; /** diff --git a/src/Deployment/DeploymentFactory.php b/src/Deployment/DeploymentFactory.php index e319c60..3a03eea 100644 --- a/src/Deployment/DeploymentFactory.php +++ b/src/Deployment/DeploymentFactory.php @@ -8,7 +8,6 @@ /** * Static factory to create deployments - * @package OFFLINE\Bootstrapper\October\BaseInstaller */ final class DeploymentFactory { diff --git a/src/Deployment/DeploymentInterface.php b/src/Deployment/DeploymentInterface.php index 785635b..56a1190 100644 --- a/src/Deployment/DeploymentInterface.php +++ b/src/Deployment/DeploymentInterface.php @@ -8,7 +8,6 @@ /** * Deployment interface - * @package OFFLINE\Bootstrapper\October\BaseInstaller */ interface Deployment { diff --git a/src/Installer/BaseInstaller.php b/src/Installer/BaseInstaller.php deleted file mode 100644 index de0f77b..0000000 --- a/src/Installer/BaseInstaller.php +++ /dev/null @@ -1,138 +0,0 @@ -config = $config; - $this->gitignore = $gitignore; - $this->output = $output; - $this->php = $php; - } - - /** - * Creates a directory. - * - * @param $dir - * - * @return mixed - * @throws \RuntimeException - */ - protected function mkdir($dir) - { - if ( ! @mkdir($dir) && ! is_dir($dir)) { - throw new RuntimeException('Could not create directory: ' . $dir); - } - - return $dir; - } - - /** - * Checks if a directory is empty. - * - * @param $themeDir - * - * @return bool - */ - protected function isEmpty($themeDir) - { - return count(glob($themeDir . '/*')) === 0; - } - - /** - * Removes .git Directories. - * - * @param $path - */ - protected function cleanup($path) - { - $this->rmdir($path . DS . '.git'); - } - - /** - * Removes a directory recursively. - * - * @param $dir - * - * @return mixed - */ - public function rmdir($dir) - { - $entries = array_diff(scandir($dir), ['.', '..']); - foreach ($entries as $entry) { - $path = $dir . DS . $entry; - is_dir($path) ? $this->rmdir($path) : $this->unlink($path); - } - - return rmdir($dir); - } - - /** - * Delete a file. Fallback to OS native rm command if the file to be deleted is write protected. - * - * @param string $file - */ - public function unlink($file) - { - if (is_writable($file)) { - unlink($file); - } else { - // Just to be sure that we don't delete "too much" by accident... - if (\in_array($file, ['*', '**', '.', '..', '/', '/*'])) { - return; - } - - // If there are write-protected files present (primarily on Windows) we can use - // the force mode of rm to remove it. PHP itself won't delete write-protected files. - $command = stripos(PHP_OS, 'WIN') === 0 ? 'rm /f' : 'rm -f'; - $file = escapeshellarg($file); - - (new Process($command . ' ' . $file))->setTimeout(60)->run(); - } - } - - protected function write($line) - { - $this->output->writeln($line); - } -} \ No newline at end of file diff --git a/src/Installer/DeploymentInstaller.php b/src/Installer/DeploymentInstaller.php deleted file mode 100644 index 43c3a33..0000000 --- a/src/Installer/DeploymentInstaller.php +++ /dev/null @@ -1,66 +0,0 @@ -config->git['deployment']; - } catch (\RuntimeException $e) { - // Config entry is not set. - return false; - } - - // Deployments are disabled - if ($deployment === false) { - return true; - } - - if ( ! method_exists($this, $deployment)) { - $this->write('-> Unknown deployment option "' . $deployment . '"'); - return false; - } - - $this->force = $force; - - return $this->{$deployment}(); - } - - /** - * Copy the neccessary tempalte files. - * - * @return void - * @throws \LogicException - */ - public function gitlab() - { - $base = getcwd() . DS; - - if(! $this->force && file_exists($base . '.gitlab-ci.yml')) { - return $this->write('-> Deployment is already set up. Use --force to overwrite'); - } - - copy($this->getTemplate('gitlab-ci.yml'), $base . '.gitlab-ci.yml'); - copy($this->getTemplate('Envoy.blade.php'), $base . 'Envoy.blade.php'); - copy($this->getTemplate('git.cron.sh'), $base . 'git.cron.sh'); - } -} \ No newline at end of file From 8980bfbf71b7cd55c13c40c928c339ff78c10486 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Thu, 2 May 2019 12:29:17 +0200 Subject: [PATCH 10/22] Added managers --- src/Manager/PluginManager.php | 3 ++- src/Manager/ThemeManager.php | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Manager/PluginManager.php b/src/Manager/PluginManager.php index 43a4e0e..9432b4a 100644 --- a/src/Manager/PluginManager.php +++ b/src/Manager/PluginManager.php @@ -2,8 +2,9 @@ namespace OFFLINE\Bootstrapper\October\Manager; -use OFFLINE\Bootstrapper\October\Util\Artisan; use OFFLINE\Bootstrapper\October\Util\Git; +use OFFLINE\Bootstrapper\October\Util\Artisan; +use Symfony\Component\Console\Exception\RuntimeException; /** * Plugin manager class diff --git a/src/Manager/ThemeManager.php b/src/Manager/ThemeManager.php index da1c0bb..1797f06 100644 --- a/src/Manager/ThemeManager.php +++ b/src/Manager/ThemeManager.php @@ -3,6 +3,7 @@ namespace OFFLINE\Bootstrapper\October\Manager; use OFFLINE\Bootstrapper\October\Util\Git; +use Symfony\Component\Console\Exception\RuntimeException; /** * Plugin manager class From bc0c32b225af715eb46ede0c00e1a03f54e523ba Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Thu, 2 May 2019 13:01:00 +0200 Subject: [PATCH 11/22] Small but important fixes --- src/Console/InstallCommand.php | 3 +-- src/Manager/PluginManager.php | 4 +--- src/Util/Artisan.php | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index ebea696..7eebbb3 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -264,12 +264,11 @@ public function installPlugins($pluginsDeclarations) $this->pluginManager->removeDir($pluginDeclaration); $installPlugin = true; - } else { - $this->write("Skipping re-downloading of ${vendor}.${plugin}", 'comment'); } if ($installPlugin) { try { + $this->write('- ' . $vendor . '.' . $plugin); $this->pluginManager->install($pluginDeclaration); } catch (RuntimeException $e) { $this->write($e->getMessage(), 'error'); diff --git a/src/Manager/PluginManager.php b/src/Manager/PluginManager.php index 9432b4a..ee4ebcf 100644 --- a/src/Manager/PluginManager.php +++ b/src/Manager/PluginManager.php @@ -112,15 +112,13 @@ public function isInstalled(string $pluginDeclaration) { $pluginDir = $this->getDirPath($pluginDeclaration); - return $this->isEmpty($pluginDir); + return !$this->isEmpty($pluginDir); } public function install(string $pluginDeclaration) { list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); - $this->write('- ' . $vendor . '.' . $plugin); - $pluginDir = $this->createDir($pluginDeclaration); if (!$this->isEmpty($pluginDir)) { diff --git a/src/Util/Artisan.php b/src/Util/Artisan.php index 093e987..f704de7 100644 --- a/src/Util/Artisan.php +++ b/src/Util/Artisan.php @@ -34,7 +34,7 @@ public function setPhp(string $php = 'php') public function call(string $command) { - $exitCode = (new Process($this->php . " artisan " . $command))->run(); + $exitCode = (new Process($this->php . " artisan " . $command))->setTimeout(3600)->run(); if ($exitCode !== $this->exitCodeOk) { throw new RuntimeException("Error running \"{$this->php} artisan {$command}\" command"); From 66a77c0d9e19b742df9b04cd60e1a64efa5b8a03 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Thu, 2 May 2019 13:27:54 +0200 Subject: [PATCH 12/22] Getting proper gitignore template fixed --- src/Console/InstallCommand.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 7eebbb3..4ace391 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -334,8 +334,14 @@ protected function getGitignore() if ($this->fileExists($target)) { return $target; } + + $templateName = 'gitignore'; - $template = $this->getTemplate('gitignore'); + if ($this->config->git['bareRepo']) { + $templateName .= '.bare'; + } + + $template = $this->getTemplate($templateName); $this->copy($template, $target); From 7a3292c3be8333570e8c192ef07d0f46f1f1a509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Fri, 3 May 2019 08:38:21 +0200 Subject: [PATCH 13/22] Code cleanup --- src/Config/Config.php | 1 + src/Console/InitCommand.php | 12 +- src/Console/InstallCommand.php | 132 ++++++++++--------- src/Console/UpdateCommand.php | 30 ++--- src/Deployment/DeploymentBase.php | 3 +- src/Deployment/DeploymentFactory.php | 2 - src/Deployment/DeploymentInterface.php | 11 +- src/Deployment/Gitlab.php | 7 +- src/Downloader/OctoberCms.php | 6 +- src/Exceptions/DeploymentExistsException.php | 11 ++ src/Exceptions/PluginExistsException.php | 11 ++ src/Exceptions/ThemeExistsException.php | 11 ++ src/Manager/BaseManager.php | 2 +- src/Manager/PluginManager.php | 19 +-- src/Manager/ThemeManager.php | 8 +- src/Util/Artisan.php | 11 +- src/Util/CliIO.php | 7 +- src/Util/Composer.php | 63 +++++---- src/Util/ConfigMaker.php | 4 +- src/Util/KeyGenerator.php | 14 +- src/Util/ManageDirectory.php | 10 +- src/Util/RunsProcess.php | 19 +-- 22 files changed, 237 insertions(+), 157 deletions(-) create mode 100644 src/Exceptions/DeploymentExistsException.php create mode 100644 src/Exceptions/PluginExistsException.php create mode 100644 src/Exceptions/ThemeExistsException.php diff --git a/src/Config/Config.php b/src/Config/Config.php index 1a7ca0a..65563ac 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -1,4 +1,5 @@ pluginManager = new PluginManager(); - $this->themeManager = new ThemeManager(); - $this->artisan = new Artisan(); - $this->composer = new Composer(); + $this->themeManager = new ThemeManager(); + $this->artisan = new Artisan(); + $this->composer = new Composer(); $this->setPhp(); @@ -110,8 +112,8 @@ public function setOutput(OutputInterface $output) /** * Configure the command options. * - * @throws InvalidArgumentException * @return void + * @throws InvalidArgumentException */ protected function configure() { @@ -136,19 +138,19 @@ protected function configure() /** * Execute the command. * - * @param InputInterface $input - * @param OutputInterface $output + * @param InputInterface $input + * @param OutputInterface $output * * @return mixed - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\InvalidArgumentException + * @throws RuntimeException + * @throws InvalidArgumentException * @throws LogicException * @throws RuntimeException * @throws InvalidArgumentException */ protected function execute(InputInterface $input, OutputInterface $output) { - if (! class_exists('ZipArchive')) { + if ( ! class_exists('ZipArchive')) { throw new RuntimeException('The Zip PHP extension is not installed. Please install it and try again.'); } @@ -160,7 +162,7 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->makeConfig(); - if (!empty($php = $input->getOption('php'))) { + if ( ! empty($php = $input->getOption('php'))) { $this->setPhp($php); } @@ -169,8 +171,12 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->write('Downloading latest October CMS...'); try { (new OctoberCms())->download($this->force); - } catch (LogicException $e) { + } catch (\LogicException $e) { + $this->write($e->getMessage(), 'comment'); + } catch (Throwable $e) { $this->write($e->getMessage(), 'error'); + + return false; } $this->write('Installing composer dependencies...'); @@ -188,19 +194,27 @@ protected function execute(InputInterface $input, OutputInterface $output) $themeDeclaration = false; try { $themeDeclaration = $this->config->cms['theme']; - } catch (\RuntimeException $e) { - $this->write('No theme to install'); + } catch (RuntimeException $e) { + $this->write('No theme to install', 'comment'); } if ($themeDeclaration) { $this->write('Installing Theme...'); - $this->installTheme($themeDeclaration); + try { + $this->themeManager->install($themeDeclaration); + } catch (ThemeExistsException $e) { + $this->write($e->getMessage(), 'comment'); + } catch (Throwable $e) { + $this->write('Failed to install theme: ' . $e->getMessage(), 'error'); + + return false; + } } - $pluginsDeclarations = false; + $pluginsDeclarations = []; try { $pluginsDeclarations = $this->config->plugins; - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $this->write('No plugins to install'); } @@ -212,14 +226,22 @@ protected function execute(InputInterface $input, OutputInterface $output) $deployment = false; try { $deployment = $this->config->git['deployment']; - } catch (\RuntimeException $e) { + } catch (RuntimeException $e) { $this->write('No deployments to install'); } if ($deployment) { $this->write("Setting up ${deployment} deployment."); - $deploymentObj = DeploymentFactory::createDeployment($deployment); - $deploymentObj->install($this->force); + try { + $deploymentObj = DeploymentFactory::createDeployment($deployment); + $deploymentObj->install($this->force); + } catch (DeploymentExistsException $e) { + $this->write($e->getMessage(), 'comment'); + } catch (Throwable $e) { + $this->write($e->getMessage(), 'error'); + + return false; + } } $this->write('Creating .gitignore...'); @@ -249,29 +271,36 @@ protected function execute(InputInterface $input, OutputInterface $output) * Handle installing plugins and updating them if possible * * @param array $pluginsDeclarations + * * @return void */ public function installPlugins($pluginsDeclarations) { foreach ($pluginsDeclarations as $pluginDeclaration) { $pluginInstalled = $this->pluginManager->isInstalled($pluginDeclaration); - $installPlugin = !$pluginInstalled; + $installPlugin = ! $pluginInstalled; list($update, $vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginDeclaration); - if ($pluginInstalled && ($update || !$this->gitignore->hasPluginHeader($vendor, $plugin))) { - $this->write("Removing ${vendor}.${plugin} directory to re-download the newest version...", 'comment'); + if ($pluginInstalled && ($update || ! $this->gitignore->hasPluginHeader($vendor, $plugin))) { + if ($pluginInstalled && $remote) { + $this->write("Removing ${vendor}.${plugin} directory to re-download the newest version...", + 'comment'); + } $this->pluginManager->removeDir($pluginDeclaration); $installPlugin = true; + } else { + $installPlugin = false; + $this->write("-> Skipping re-downloading of ${vendor}.${plugin}", 'comment'); } if ($installPlugin) { try { - $this->write('- ' . $vendor . '.' . $plugin); $this->pluginManager->install($pluginDeclaration); - } catch (RuntimeException $e) { + } catch (Throwable $e) { $this->write($e->getMessage(), 'error'); + continue; } } @@ -284,21 +313,6 @@ public function installPlugins($pluginsDeclarations) $this->artisan->call('october:up'); } - /** - * Install theme - * - * @param string $themeDeclaration - * @return void - */ - public function installTheme(string $themeDeclaration) - { - try { - $this->themeManager->install($themeDeclaration); - } catch (RuntimeException $e) { - $this->write($e->getMessage(), 'error'); - } - } - /** * Create the .env and config files. * @@ -334,7 +348,7 @@ protected function getGitignore() if ($this->fileExists($target)) { return $target; } - + $templateName = 'gitignore'; if ($this->config->git['bareRepo']) { @@ -347,7 +361,7 @@ protected function getGitignore() return $target; } - + /** * Copy the README template. * @@ -361,7 +375,7 @@ protected function copyReadme() protected function cleanup() { - if (! $this->firstRun) { + if ( ! $this->firstRun) { return; } @@ -379,7 +393,7 @@ public function prepareDatabase() // If SQLite database does not exist, create it if ($this->config->database['connection'] === 'sqlite') { $path = $this->config->database['database']; - if (! $this->fileExists($path) && is_dir(dirname($path))) { + if ( ! $this->fileExists($path) && is_dir(dirname($path))) { $this->write("Creating $path ..."); $this->touchFile($path); } diff --git a/src/Console/UpdateCommand.php b/src/Console/UpdateCommand.php index 0f1cf7f..0080a0f 100644 --- a/src/Console/UpdateCommand.php +++ b/src/Console/UpdateCommand.php @@ -2,20 +2,20 @@ namespace OFFLINE\Bootstrapper\October\Console; +use InvalidArgumentException; +use LogicException; +use OFFLINE\Bootstrapper\October\Exceptions\PluginExistsException; use OFFLINE\Bootstrapper\October\Manager\PluginManager; use OFFLINE\Bootstrapper\October\Util\Artisan; +use OFFLINE\Bootstrapper\October\Util\CliIO; use OFFLINE\Bootstrapper\October\Util\Composer; -use OFFLINE\Bootstrapper\October\Util\Gitignore; -use OFFLINE\Bootstrapper\October\Util\RunsProcess; use OFFLINE\Bootstrapper\October\Util\ConfigMaker; -use OFFLINE\Bootstrapper\October\Util\CliIO; +use OFFLINE\Bootstrapper\October\Util\RunsProcess; +use RuntimeException; use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Exception\InvalidArgumentException; -use Symfony\Component\Console\Exception\RuntimeException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\Exception\LogicException; /** * Class UpdateCommand @@ -51,8 +51,8 @@ class UpdateCommand extends Command public function __construct($name = null) { $this->pluginManager = new PluginManager(); - $this->artisan = new Artisan(); - $this->composer = new Composer(); + $this->artisan = new Artisan(); + $this->composer = new Composer(); $this->setPhp(); @@ -97,11 +97,12 @@ protected function configure() * @param OutputInterface $output * * @return mixed - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\InvalidArgumentException + * @throws RuntimeException + * @throws InvalidArgumentException * @throws LogicException * @throws RuntimeException * @throws InvalidArgumentException + * @throws PluginExistsException */ protected function execute(InputInterface $input, OutputInterface $output) { @@ -109,13 +110,11 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->makeConfig(); - if (!empty($php = $input->getOption('php'))) { + if ( ! empty($php = $input->getOption('php'))) { $this->setPhp($php); } - $this->write("Installing new plugins"); - $this->runProcess($this->php . ' october install', 'Installation failed!'); $pluginsConfigs = $this->config->plugins; @@ -123,7 +122,7 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($pluginsConfigs as $pluginConfig) { list($vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginConfig); - if (!empty($remote)) { + if ( ! empty($remote)) { $this->pluginManager->removeDir($pluginConfig); } } @@ -139,7 +138,7 @@ protected function execute(InputInterface $input, OutputInterface $output) foreach ($pluginsConfigs as $pluginConfig) { list($vendor, $plugin, $remote, $branch) = $this->pluginManager->parseDeclaration($pluginConfig); - if (!empty($remote)) { + if ( ! empty($remote)) { $this->pluginManager->install($pluginConfig); } } @@ -159,6 +158,7 @@ protected function execute(InputInterface $input, OutputInterface $output) * * @param InputInterface $input * @param OutputInterface $output + * * @return void */ protected function prepareEnv(InputInterface $input, OutputInterface $output) diff --git a/src/Deployment/DeploymentBase.php b/src/Deployment/DeploymentBase.php index 9b95d90..5c54f78 100644 --- a/src/Deployment/DeploymentBase.php +++ b/src/Deployment/DeploymentBase.php @@ -2,9 +2,8 @@ namespace OFFLINE\Bootstrapper\October\Deployment; -use OFFLINE\Bootstrapper\October\Util\UsesTemplate; use OFFLINE\Bootstrapper\October\Util\ManageDirectory; -use OFFLINE\Bootstrapper\October\Deployment\DeploymentInterface; +use OFFLINE\Bootstrapper\October\Util\UsesTemplate; /** * Deployment base class diff --git a/src/Deployment/DeploymentFactory.php b/src/Deployment/DeploymentFactory.php index 3a03eea..9d33025 100644 --- a/src/Deployment/DeploymentFactory.php +++ b/src/Deployment/DeploymentFactory.php @@ -2,9 +2,7 @@ namespace OFFLINE\Bootstrapper\October\Deployment; -use OFFLINE\Bootstrapper\October\Deployment\Gitlab; use Symfony\Component\Console\Exception\RuntimeException; -use OFFLINE\Bootstrapper\October\Deployment\DeploymentInterface; /** * Static factory to create deployments diff --git a/src/Deployment/DeploymentInterface.php b/src/Deployment/DeploymentInterface.php index 56a1190..119c12e 100644 --- a/src/Deployment/DeploymentInterface.php +++ b/src/Deployment/DeploymentInterface.php @@ -2,23 +2,26 @@ namespace OFFLINE\Bootstrapper\October\Deployment; +use OFFLINE\Bootstrapper\October\Exceptions\DeploymentExistsException; +use Symfony\Component\Process\Exception\InvalidArgumentException; use Symfony\Component\Process\Exception\LogicException; use Symfony\Component\Process\Exception\RuntimeException; -use Symfony\Component\Process\Exception\InvalidArgumentException; /** * Deployment interface */ -interface Deployment +interface DeploymentInterface { /** * Install the deployment setup * * @param boolean $force parameter to enforce installing even if installed - * @throws LogicException + * + * @return void * @throws RuntimeException * @throws InvalidArgumentException - * @return void + * @throws DeploymentExistsException + * @throws LogicException */ public function install($force = false); } diff --git a/src/Deployment/Gitlab.php b/src/Deployment/Gitlab.php index bd7ec80..b3b6b35 100644 --- a/src/Deployment/Gitlab.php +++ b/src/Deployment/Gitlab.php @@ -2,8 +2,7 @@ namespace OFFLINE\Bootstrapper\October\Deployment; -use OFFLINE\Bootstrapper\October\Deployment\DeploymentBase; -use OFFLINE\Bootstrapper\October\Deployment\DeploymentInterface; +use OFFLINE\Bootstrapper\October\Exceptions\DeploymentExistsException; /** * GitLab deployment @@ -15,8 +14,8 @@ class Gitlab extends DeploymentBase implements DeploymentInterface */ public function install($force = false) { - if (! $this->force && $this->fileExists('.gitlab-ci.yml')) { - return $this->write('-> Deployment is already set up. Use --force to overwrite'); + if ( ! $force && $this->fileExists('.gitlab-ci.yml')) { + throw new DeploymentExistsException('-> Deployment is already set up. Use --force to overwrite'); } $this->copy($this->getTemplate('gitlab-ci.yml'), '.gitlab-ci.yml'); diff --git a/src/Downloader/OctoberCms.php b/src/Downloader/OctoberCms.php index a38fd7c..9637d7c 100644 --- a/src/Downloader/OctoberCms.php +++ b/src/Downloader/OctoberCms.php @@ -28,8 +28,8 @@ public function __construct() * @param bool $force * * @return $this - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\LogicException + * @throws RuntimeException + * @throws LogicException */ public function download($force = false) { @@ -120,7 +120,7 @@ function ($treffer) { * Remove the Zip file, move folder contents one level up. * * @return $this - * @throws \Symfony\Component\Process\Exception\RuntimeException + * @throws RuntimeException * @throws LogicException */ protected function cleanUp() diff --git a/src/Exceptions/DeploymentExistsException.php b/src/Exceptions/DeploymentExistsException.php new file mode 100644 index 0000000..6ffc925 --- /dev/null +++ b/src/Exceptions/DeploymentExistsException.php @@ -0,0 +1,11 @@ +getDirPath($pluginDeclaration); - return !$this->isEmpty($pluginDir); + return $this->isEmpty($pluginDir); } public function install(string $pluginDeclaration) { list($update, $vendor, $plugin, $remote, $branch) = $this->parseDeclaration($pluginDeclaration); + $this->write('- ' . $vendor . '.' . $plugin); + $pluginDir = $this->createDir($pluginDeclaration); - if (!$this->isEmpty($pluginDir)) { - throw new RuntimeException("Plugin directory not empty. Aborting."); + if ( ! $this->isEmpty($pluginDir)) { + throw new PluginExistsException("Plugin %s is already installed."); } if ($remote === '') { @@ -160,8 +162,9 @@ public function installViaArtisan(string $pluginDeclaration) } catch (RuntimeException $e) { throw new RuntimeException( sprintf( - 'Error while installing plugin %s via artisan. Is your database set up correctly?', - $vendor . '.' . $plugin + "Error while installing plugin %s via artisan:\n\n%s", + $vendor . '.' . $plugin, + $e->getMessage() ) ); } diff --git a/src/Manager/ThemeManager.php b/src/Manager/ThemeManager.php index 1797f06..b6cb067 100644 --- a/src/Manager/ThemeManager.php +++ b/src/Manager/ThemeManager.php @@ -2,8 +2,9 @@ namespace OFFLINE\Bootstrapper\October\Manager; +use OFFLINE\Bootstrapper\October\Exceptions\ThemeExistsException; use OFFLINE\Bootstrapper\October\Util\Git; -use Symfony\Component\Console\Exception\RuntimeException; +use RuntimeException; /** * Plugin manager class @@ -68,6 +69,7 @@ public function getDirPath(string $themeDeclaration) * @throws InvalidArgumentException * @throws \RuntimeException * @throws \LogicException + * @throws ThemeExistsException */ public function install(string $themeDeclaration) { @@ -75,8 +77,8 @@ public function install(string $themeDeclaration) $themeDir = $this->createDir($themeDeclaration); - if (!$this->isEmpty($themeDir)) { - throw new RuntimeException("Theme directory not empty. Aborting."); + if ( ! $this->isEmpty($themeDir)) { + throw new ThemeExistsException("-> Theme is already installed. Skipping."); } if ($remote === false) { diff --git a/src/Util/Artisan.php b/src/Util/Artisan.php index f704de7..e7cd3d9 100644 --- a/src/Util/Artisan.php +++ b/src/Util/Artisan.php @@ -2,9 +2,8 @@ namespace OFFLINE\Bootstrapper\October\Util; +use RuntimeException; use Symfony\Component\Process\Process; -use Symfony\Component\Process\Exception\RuntimeException; -use OFFLINE\Bootstrapper\October\Util\CliIO; /** * Class Artisan @@ -34,10 +33,14 @@ public function setPhp(string $php = 'php') public function call(string $command) { - $exitCode = (new Process($this->php . " artisan " . $command))->setTimeout(3600)->run(); + $proc = new Process($this->php . " artisan " . $command); + $proc->enableOutput(); + $exitCode = $proc->run(); if ($exitCode !== $this->exitCodeOk) { - throw new RuntimeException("Error running \"{$this->php} artisan {$command}\" command"); + throw new RuntimeException( + sprintf("Error running \"{$this->php} artisan {$command}\" command: %s", $proc->getOutput()) + ); } } } diff --git a/src/Util/CliIO.php b/src/Util/CliIO.php index e177f3a..99e32e1 100644 --- a/src/Util/CliIO.php +++ b/src/Util/CliIO.php @@ -28,7 +28,8 @@ trait CliIO /** * Set the value of output * - * @param OutputInterface $output + * @param OutputInterface $output + * * @return self */ public function setOutput(OutputInterface $output) @@ -41,7 +42,8 @@ public function setOutput(OutputInterface $output) /** * Set the value of input * - * @param InputInterface $input + * @param InputInterface $input + * * @return self */ public function setInput(InputInterface $input) @@ -56,6 +58,7 @@ public function setInput(InputInterface $input) * * @param string $line * @param string $surround html tag to surround the message + * * @return void */ protected function write($line, $surround = "info") diff --git a/src/Util/Composer.php b/src/Util/Composer.php index 6f45413..2554377 100644 --- a/src/Util/Composer.php +++ b/src/Util/Composer.php @@ -3,7 +3,9 @@ namespace OFFLINE\Bootstrapper\October\Util; -use Symfony\Component\Process\Process; +use InvalidArgumentException; +use LogicException; +use RuntimeException; /** * Class Composer @@ -11,6 +13,7 @@ */ class Composer { + use RunsProcess; /** * @var string @@ -43,70 +46,78 @@ protected function findComposer() * Composer install * * @return void - * @throws \Symfony\Component\Process\Exception\InvalidArgumentException - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\LogicException + * @throws InvalidArgumentException + * @throws RuntimeException + * @throws LogicException */ public function install() { - (new Process($this->composer . ' install --no-scripts --no-interaction --prefer-dist')) - ->setTimeout(3600) - ->run(); + $this->runProcess( + $this->composer . ' install --no-scripts --no-interaction --prefer-dist', + 'Failed to run composer install', + 3600 + ); } /** * Composer update --lock * * @return void - * @throws \Symfony\Component\Process\Exception\InvalidArgumentException - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\LogicException + * @throws InvalidArgumentException + * @throws RuntimeException + * @throws LogicException */ public function updateLock() { - (new Process($this->composer . ' update --no-scripts --no-interaction --prefer-dist --lock')) - ->setTimeout(3600) - ->run(); + $this->runProcess( + $this->composer . ' update --no-scripts --no-interaction --prefer-dist --lock', + 'Failed to run composer update', + 3600 + ); } /** * Composer require (if not already there) * * @return void - * @throws \Symfony\Component\Process\Exception\LogicException - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\InvalidArgumentException + * @throws LogicException + * @throws RuntimeException + * @throws InvalidArgumentException */ public function addDependency($package) { // If the package is already installed don't add it again $slashed = str_replace('/', '\/', $package); - if(preg_grep('/' . $slashed . '/', file(getcwd() . DS . 'composer.json'))) { + if (preg_grep('/' . $slashed . '/', file(getcwd() . DS . 'composer.json'))) { return true; } $package = escapeshellarg($package); - (new Process($this->composer . ' require ' . $package . ' --no-interaction')) - ->setTimeout(3600) - ->run(); + $this->runProcess( + $this->composer . ' require ' . $package . ' --no-interaction', + 'Failed to add composer dependency', + 3600 + ); } /** * Composer require * * @return void - * @throws \Symfony\Component\Process\Exception\LogicException - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\InvalidArgumentException + * @throws LogicException + * @throws RuntimeException + * @throws InvalidArgumentException */ public function requireVersion($package, $version) { $package = escapeshellarg($package); $version = escapeshellarg($version); - (new Process($this->composer . ' require ' . $package . ' ' . $version . ' --no-interaction')) - ->setTimeout(3600) - ->run(); + $this->runProcess( + $this->composer . ' require ' . $package . ' ' . $version . ' --no-interaction', + 'Failed to add specific composer dependency', + 3600 + ); } } \ No newline at end of file diff --git a/src/Util/ConfigMaker.php b/src/Util/ConfigMaker.php index 7c4b702..2291e9b 100644 --- a/src/Util/ConfigMaker.php +++ b/src/Util/ConfigMaker.php @@ -3,7 +3,7 @@ namespace OFFLINE\Bootstrapper\October\Util; use OFFLINE\Bootstrapper\October\Config\Yaml; -use Symfony\Component\Console\Exception\RuntimeException; +use RuntimeException; /** * Config maker trait @@ -20,7 +20,7 @@ trait ConfigMaker protected function makeConfig() { $configFile = $this->pwd() . 'october.yaml'; - if (! file_exists($configFile)) { + if ( ! file_exists($configFile)) { throw new RuntimeException("october.yaml not found. Run october init first.", 1); } diff --git a/src/Util/KeyGenerator.php b/src/Util/KeyGenerator.php index e0aa4a7..aba8d27 100644 --- a/src/Util/KeyGenerator.php +++ b/src/Util/KeyGenerator.php @@ -3,6 +3,8 @@ namespace OFFLINE\Bootstrapper\October\Util; +use RuntimeException; + /** * Class KeyGenerator * @package OFFLINE\Bootstrapper\October\Util @@ -11,17 +13,18 @@ class KeyGenerator { /** * Generate the application's key. - * + * * @param int $length * * @return string + * @throws \Exception */ public function generate($length = 32) { $string = ''; while (($len = strlen($string)) < $length) { - $size = $length - $len; - $bytes = $this->randomBytes($size); + $size = $length - $len; + $bytes = $this->randomBytes($size); $string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); } @@ -34,6 +37,7 @@ public function generate($length = 32) * @param int $length * * @return string + * @throws \Exception */ private function randomBytes($length = 32) { @@ -45,6 +49,10 @@ private function randomBytes($length = 32) $bytes = openssl_random_pseudo_bytes($length, $strong); } + if ($bytes === false) { + throw new RuntimeException('Failed to generate random bytes.'); + } + return $bytes; } diff --git a/src/Util/ManageDirectory.php b/src/Util/ManageDirectory.php index 27bb500..a896fd0 100644 --- a/src/Util/ManageDirectory.php +++ b/src/Util/ManageDirectory.php @@ -23,7 +23,7 @@ public function copy($sourceFile, $targetFile) copy($sourceFile, $targetFile); - if (!$this->fileExists($targetFile)) { + if ( ! $this->fileExists($targetFile)) { throw new RuntimeException('File ' . $targetFile . ' could not be created'); } @@ -79,9 +79,9 @@ public function path($file) $windows = strpos($this->pwd(), '/', 0) === false; - if (!$windows && $file[0] !== '/') { + if ( ! $windows && $file[0] !== '/') { $relative = true; - } elseif ($windows && !preg_match('/^[^*?"<>|:]*$/', $file)) { + } elseif ($windows && ! preg_match('/^[^*?"<>|:]*$/', $file)) { $relative = true; } @@ -125,7 +125,7 @@ public function unlink($file) */ public function rmdir($dir) { - if (!$this->dirExists($dir)) { + if ( ! $this->dirExists($dir)) { return true; } @@ -148,7 +148,7 @@ public function rmdir($dir) */ public function mkdir($dir) { - if (! @mkdir($dir) && ! is_dir($dir)) { + if ( ! @mkdir($dir) && ! is_dir($dir)) { throw new RuntimeException('Could not create directory: ' . $dir); } diff --git a/src/Util/RunsProcess.php b/src/Util/RunsProcess.php index 44a60e4..0c5d751 100644 --- a/src/Util/RunsProcess.php +++ b/src/Util/RunsProcess.php @@ -2,6 +2,8 @@ namespace OFFLINE\Bootstrapper\October\Util; +use LogicException; +use RuntimeException; use Symfony\Component\Process\Process; trait RunsProcess @@ -14,16 +16,17 @@ trait RunsProcess * @param $errorMessage * * @return bool - * @throws \Symfony\Component\Process\Exception\RuntimeException - * @throws \Symfony\Component\Process\Exception\LogicException + * @throws RuntimeException + * @throws LogicException */ - protected function runProcess($command, $errorMessage) + protected function runProcess($command, $errorMessage, $timeout = 30) { - - $process = (new Process($command)); + $process = new Process($command); + $process->setTimeout($timeout); + $process->enableOutput(); $exitCode = $process->run(); - return $this->checkProcessResult($exitCode, $errorMessage); + return $this->checkProcessResult($exitCode, $errorMessage, $process->getOutput()); } /** @@ -34,10 +37,10 @@ protected function runProcess($command, $errorMessage) * * @return bool */ - protected function checkProcessResult($exitCode, $message) + protected function checkProcessResult($exitCode, $message, $output) { if ($exitCode !== 0) { - $this->output->writeln('' . $message . ''); + $this->output->writeln('' . $message . ': ' . $output . ''); return false; } From ee921cdbff922c390e41267f76f20a204c0797d2 Mon Sep 17 00:00:00 2001 From: Tomasz Strojny Date: Fri, 3 May 2019 11:30:24 +0200 Subject: [PATCH 14/22] Push command added --- README.md | 4 +- october | 1 + src/Console/PushCommand.php | 75 +++++++++++++++++++++++++++++++++++++ src/Deployment/Gitlab.php | 1 - templates/git.cron.sh | 10 ----- templates/gitignore.bare | 1 - 6 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 src/Console/PushCommand.php delete mode 100755 templates/git.cron.sh diff --git a/README.md b/README.md index 6c55190..d3ea498 100644 --- a/README.md +++ b/README.md @@ -216,8 +216,8 @@ This will setup a [`.gitlab-ci.yml`](templates/gitlab-ci.yml) and a [`Envoy.blad If a deployed website is edited by a customer directly on the prod server you might want to commit those changes back to your git repository. -To do this, simply create a cronjob that executes `git.cron.sh` every X minutes. This script will commit all changes -to your git repo automatically. +To do this, simply create a cronjob that executes `october push` every X minutes. This command will commit all changes +to your git repo automatically with message `[ci skip] Added changes from $hostname`. ### File templates diff --git a/october b/october index 3c82cc5..b8ea84d 100755 --- a/october +++ b/october @@ -12,4 +12,5 @@ $app = new Symfony\Component\Console\Application('October CMS Bootstrapper', '0. $app->add(new \OFFLINE\Bootstrapper\October\Console\InitCommand); $app->add(new \OFFLINE\Bootstrapper\October\Console\InstallCommand); $app->add(new \OFFLINE\Bootstrapper\October\Console\UpdateCommand); +$app->add(new \OFFLINE\Bootstrapper\October\Console\PushCommand); $app->run(); diff --git a/src/Console/PushCommand.php b/src/Console/PushCommand.php new file mode 100644 index 0000000..7900ce3 --- /dev/null +++ b/src/Console/PushCommand.php @@ -0,0 +1,75 @@ +setName('push') + ->setDescription('Push project repository to origin master') + ->addArgument('directory', InputArgument::OPTIONAL, 'Name of the working directory', '.'); + } + + /** + * Execute the command. + * + * @param InputInterface $input + * @param OutputInterface $output + * + * @throws RuntimeException + * @return mixed + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $this->setOutput($output); + + $this->write('Pushing the project repo to origin master...'); + + try { + $repo = Git::repo($this->pwd()); + $status = $repo->getStatus(); + } catch (Throwable $e) { + $this->write($e->getMessage(), 'error'); + return false; + } + + if (count($status->all()) === 0) { + $this->write('Nothing to push'); + return true; + } + + $repo->stage(); + + $repo->commit('[ci skip] Added changes from ' . gethostname()); + + $repo->push(); + + $this->write('Pushed'); + + return true; + } +} diff --git a/src/Deployment/Gitlab.php b/src/Deployment/Gitlab.php index b3b6b35..feffded 100644 --- a/src/Deployment/Gitlab.php +++ b/src/Deployment/Gitlab.php @@ -20,6 +20,5 @@ public function install($force = false) $this->copy($this->getTemplate('gitlab-ci.yml'), '.gitlab-ci.yml'); $this->copy($this->getTemplate('Envoy.blade.php'), 'Envoy.blade.php'); - $this->copy($this->getTemplate('git.cron.sh'), 'git.cron.sh'); } } diff --git a/templates/git.cron.sh b/templates/git.cron.sh deleted file mode 100755 index d9bb2e7..0000000 --- a/templates/git.cron.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -HOSTNAME=$( hostname ) -GIT=$( which git ) - -# Commit changes from prod server back to git -if [[ -n $(git status -s) ]]; then - $GIT add --all . - $GIT commit -m "[ci skip] Added changes from $HOSTNAME" - $GIT push origin master -fi diff --git a/templates/gitignore.bare b/templates/gitignore.bare index 930f1eb..8a6e2f6 100644 --- a/templates/gitignore.bare +++ b/templates/gitignore.bare @@ -4,7 +4,6 @@ composer.phar !.env.* !october.yaml -!git.cron.sh !Envoy.blade.php !.gitlab-ci.yml !themes From 15c4a8001c72f55626239d0fad11526924406968 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Fri, 3 May 2019 15:12:03 +0200 Subject: [PATCH 15/22] Fixed isInstalled method --- src/Manager/PluginManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Manager/PluginManager.php b/src/Manager/PluginManager.php index aec187a..bae9ba6 100644 --- a/src/Manager/PluginManager.php +++ b/src/Manager/PluginManager.php @@ -112,7 +112,7 @@ public function isInstalled(string $pluginDeclaration) { $pluginDir = $this->getDirPath($pluginDeclaration); - return $this->isEmpty($pluginDir); + return ! $this->isEmpty($pluginDir); } public function install(string $pluginDeclaration) From 497a1c6d99b927582dc4b7d8922edef13ddc874d Mon Sep 17 00:00:00 2001 From: Oliver Buchmann Date: Fri, 3 May 2019 16:07:35 +0200 Subject: [PATCH 16/22] Fixes Plugin Installation, adds OutputInterface to Composer --- src/Console/InstallCommand.php | 13 ++++++++----- src/Util/Composer.php | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index a02782a..2e1a6c9 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -107,6 +107,7 @@ public function setOutput(OutputInterface $output) $this->output = $output; $this->pluginManager->setOutput($output); $this->themeManager->setOutput($output); + $this->composer->setOutput($output); } /** @@ -286,13 +287,15 @@ public function installPlugins($pluginsDeclarations) if ($pluginInstalled && $remote) { $this->write("Removing ${vendor}.${plugin} directory to re-download the newest version...", 'comment'); + + $this->pluginManager->removeDir($pluginDeclaration); + $installPlugin = true; + } + else { + $installPlugin = false; + $this->write("-> Skipping re-downloading of ${vendor}.${plugin}", 'comment'); } - $this->pluginManager->removeDir($pluginDeclaration); - $installPlugin = true; - } else { - $installPlugin = false; - $this->write("-> Skipping re-downloading of ${vendor}.${plugin}", 'comment'); } if ($installPlugin) { diff --git a/src/Util/Composer.php b/src/Util/Composer.php index 2554377..6d25771 100644 --- a/src/Util/Composer.php +++ b/src/Util/Composer.php @@ -6,6 +6,7 @@ use InvalidArgumentException; use LogicException; use RuntimeException; +use Symfony\Component\Console\Output\OutputInterface; /** * Class Composer @@ -20,6 +21,12 @@ class Composer */ protected $composer; + /** + * @var OutputInterface + */ + protected $output; + + /** * Run Composer commands. */ @@ -28,6 +35,15 @@ public function __construct() $this->composer = $this->findComposer(); } + /** + * @param OutputInterface $output + */ + public function setOutput($output) + { + $this->output = $output; + } + + /** * Get the composer command for the environment. * From e9da2f5273bc75d1c5437c15925a757583d02068 Mon Sep 17 00:00:00 2001 From: Oliver Buchmann Date: Fri, 3 May 2019 16:20:54 +0200 Subject: [PATCH 17/22] Adds Set Project Id to installation --- src/Console/InstallCommand.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 2e1a6c9..aceeb3a 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -212,6 +212,11 @@ protected function execute(InputInterface $input, OutputInterface $output) } } + if($this->config->projectID){ + $this->write('Setting Project ID...'); + $this->artisan->call('october:util set project --projectId='.$this->config->projectID); + } + $pluginsDeclarations = []; try { $pluginsDeclarations = $this->config->plugins; From 28cb89a629a310f57348b8d6934f33becd2c3124 Mon Sep 17 00:00:00 2001 From: Oliver Buchmann Date: Fri, 3 May 2019 16:23:34 +0200 Subject: [PATCH 18/22] Renames projectID to project in yaml --- src/Console/InstallCommand.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index aceeb3a..94fd34e 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -212,9 +212,9 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - if($this->config->projectID){ + if($this->config->project){ $this->write('Setting Project ID...'); - $this->artisan->call('october:util set project --projectId='.$this->config->projectID); + $this->artisan->call('october:util set project --projectId='.$this->config->project); } $pluginsDeclarations = []; From 8076585ffa6599f9ded4969b06599114464cebf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Mon, 6 May 2019 08:50:38 +0200 Subject: [PATCH 19/22] Fixed format --- src/Console/InstallCommand.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 94fd34e..80c700a 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -212,9 +212,9 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - if($this->config->project){ + if ($this->config->project) { $this->write('Setting Project ID...'); - $this->artisan->call('october:util set project --projectId='.$this->config->project); + $this->artisan->call('october:util set project --projectId=' . $this->config->project); } $pluginsDeclarations = []; @@ -295,12 +295,10 @@ public function installPlugins($pluginsDeclarations) $this->pluginManager->removeDir($pluginDeclaration); $installPlugin = true; - } - else { + } else { $installPlugin = false; $this->write("-> Skipping re-downloading of ${vendor}.${plugin}", 'comment'); } - } if ($installPlugin) { From 00e2151ec6caa70c6d486dfe38d66d37cbd6e2a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Mon, 6 May 2019 08:55:54 +0200 Subject: [PATCH 20/22] Moved project config entry to cms.project --- README.md | 1 + src/Console/InstallCommand.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6c55190..7dd50f0 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ cms: theme: name (user@remote.git) edgeUpdates: false enableSafeMode: false + # project: XXXX # Marketplace project ID database: connection: mysql diff --git a/src/Console/InstallCommand.php b/src/Console/InstallCommand.php index 80c700a..e61917a 100644 --- a/src/Console/InstallCommand.php +++ b/src/Console/InstallCommand.php @@ -212,9 +212,9 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - if ($this->config->project) { + if (array_key_exists('project', $this->config->cms)) { $this->write('Setting Project ID...'); - $this->artisan->call('october:util set project --projectId=' . $this->config->project); + $this->artisan->call('october:util set project --projectId=' . $this->config->cms['project']); } $pluginsDeclarations = []; @@ -296,8 +296,8 @@ public function installPlugins($pluginsDeclarations) $this->pluginManager->removeDir($pluginDeclaration); $installPlugin = true; } else { - $installPlugin = false; $this->write("-> Skipping re-downloading of ${vendor}.${plugin}", 'comment'); + $installPlugin = false; } } From 6934710a3c075ce5bdc2f30a62ad2deb5e899b81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Mon, 6 May 2019 09:04:38 +0200 Subject: [PATCH 21/22] Added october push to readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4e6ce66..08ca939 100644 --- a/README.md +++ b/README.md @@ -181,8 +181,15 @@ If you want to update the installation you can run ``` october update ``` +### Push changes to remote git repo -The command will update all plugins, the October CMS core and all composer dependencies. +To push local changes to the current git remote run + +``` +october push +``` + +This command can be run as cron job to keep your git repo up-to-date with changes on production. ### SSH deployments From 045f6ac807a64ab3cf0d68ac544a362b7fc254af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Mon, 6 May 2019 09:04:48 +0200 Subject: [PATCH 22/22] Version bump --- october | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/october b/october index b8ea84d..5a45e1e 100755 --- a/october +++ b/october @@ -8,7 +8,7 @@ if (file_exists(__DIR__.'/../../autoload.php')) { require __DIR__.'/vendor/autoload.php'; } -$app = new Symfony\Component\Console\Application('October CMS Bootstrapper', '0.5.0'); +$app = new Symfony\Component\Console\Application('October CMS Bootstrapper', '0.5.1'); $app->add(new \OFFLINE\Bootstrapper\October\Console\InitCommand); $app->add(new \OFFLINE\Bootstrapper\October\Console\InstallCommand); $app->add(new \OFFLINE\Bootstrapper\October\Console\UpdateCommand);