├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── RoboFile.php ├── composer.json ├── src ├── DrushStack.php └── loadTasks.php └── tests └── DrushStackTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.idea/ 3 | composer.lock 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - 7.2 4 | - 7.1 5 | - 7.0 6 | - 5.6 7 | install: 8 | - composer self-update 9 | - composer install 10 | script: 11 | - vendor/bin/robo test 12 | 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased][unreleased] 9 | 10 | ## [4.2.0] - 2018-12-11 11 | 12 | ### Added 13 | 14 | - `existingConfig` method for site installs (which sets `--existing-config` in the drush call) 15 | 16 | ## [4.1.0] - 2018-09-11 17 | 18 | ### Added 19 | 20 | - possibility to test multiple drush versions 21 | 22 | ## [4.0.0] - 2018-09-11 23 | 24 | Since people might have shell escaped these parameters already, 25 | this version is a new major as it is potentially backwards incompatible. 26 | 27 | ### Changed 28 | 29 | - `accountPass` and `dbSuPw` in site install command are now shell escaped 30 | 31 | ## [3.1.1] - 2017-08-21 32 | 33 | ### Added 34 | 35 | - support for the upcoming Drush 9 (only use `drush version` to get the version) 36 | 37 | ## [3.1.0] - 2017-07-05 38 | 39 | ### Added 40 | 41 | - support for `config-dir` property 42 | 43 | ## [3.0.2] - 2017-02-01 44 | 45 | ### Changed 46 | 47 | - consolidation/robo dependency to `~1` 48 | 49 | ## [2.2.1] - 2015-11-27 50 | 51 | ### Changed 52 | 53 | - Robo dependency to `>=0.5.2`, which means all future versions. As Robo is in pre-1.0 stage, this seems to make sense. 54 | 55 | ## [2.2.0] - 2015-09-20 56 | 57 | ### Added 58 | 59 | - second parameter `assumeYes` (default `true`) to `exec()` 60 | 61 | ## [2.1.0] - 2015-02-27 62 | 63 | This version is the first one working without strict errors using Robo >=0.5.2. 64 | 65 | ### Added 66 | 67 | - Robo dependency to `~0.5.2` (and removed `conflict` section) 68 | 69 | ### Fixed 70 | 71 | - PSR-4 autoloading (renamed `Drush.php` to `DrushStack.php` 72 | - PHPUnit deprecation warning 73 | 74 | ## [2.0.2] - 2015-02-21 75 | 76 | You have to use `"codegyre/robo": "dev-master"` in your composer.json, 77 | since there is no new release which includes https://github.com/Codegyre/Robo/pull/114. 78 | 79 | ### Fixed 80 | 81 | - strict warning about 'same property' 82 | 83 | ## [2.0.1] - 2015-01-25 84 | 85 | ### Fixed 86 | 87 | - Robo version in `conflict` section of composer.json 88 | 89 | ## [2.0.0] - 2015-01-25 90 | 91 | Release for Robo >=0.5 (not compatible with 0.4.*!). 92 | 93 | ### Changed 94 | 95 | - Trait name to `\Boedah\Robo\Task\Drush\loadTasks` in line with Robo tasks 96 | - Task name from `DrushStackTask` to `DrushStack` 97 | 98 | ## [1.0.3] - 2015-01-25 99 | 100 | ### Added 101 | 102 | - `conflict` section in composer.json 103 | 104 | ## [1.0.2] - 2015-01-25 105 | 106 | ### Added 107 | - This [change log](CHANGELOG.md) following [keepachangelog.com](http://keepachangelog.com/). 108 | - Installation instructions to [README](README.md). 109 | 110 | ### Changed 111 | - Robo version in composer dev dependencies is now 0.4.5, as 0.4.6. introduced a BC break.
112 | Code will be updated soon to be compatible with Robo 0.5. 113 | 114 | ### Fixed 115 | - phpdoc of `getVersion()` 116 | 117 | ## [1.0.1] - 2014-06-20 118 | 119 | ### Fixed 120 | - Drush version is now fetched correctly in `updateDb()` 121 | 122 | ## 1.0.0 - 2014-06-06 123 | 124 | ### Added 125 | - Initial commit 126 | 127 | [unreleased]: https://github.com/boedah/robo-drush/compare/4.2.0...HEAD 128 | [1.0.1]: https://github.com/boedah/robo-drush/compare/1.0.0...1.0.1 129 | [1.0.2]: https://github.com/boedah/robo-drush/compare/1.0.1...1.0.2 130 | [1.0.3]: https://github.com/boedah/robo-drush/compare/1.0.2...1.0.3 131 | [2.0.0]: https://github.com/boedah/robo-drush/compare/1.0.3...2.0.0 132 | [2.0.1]: https://github.com/boedah/robo-drush/compare/2.0.0...2.0.1 133 | [2.0.2]: https://github.com/boedah/robo-drush/compare/2.0.1...2.0.2 134 | [2.1.0]: https://github.com/boedah/robo-drush/compare/2.0.2...2.1.0 135 | [2.2.0]: https://github.com/boedah/robo-drush/compare/2.1.0...2.2.0 136 | [2.2.1]: https://github.com/boedah/robo-drush/compare/2.2.0...2.2.1 137 | [3.0.2]: https://github.com/boedah/robo-drush/compare/2.2.1...3.0.2 138 | [3.1.0]: https://github.com/boedah/robo-drush/compare/3.0.2...3.1.0 139 | [3.1.1]: https://github.com/boedah/robo-drush/compare/3.1.0...3.1.1 140 | [4.0.0]: https://github.com/boedah/robo-drush/compare/3.1.1...4.0.0 141 | [4.1.0]: https://github.com/boedah/robo-drush/compare/4.0.0...4.1.0 142 | [4.2.0]: https://github.com/boedah/robo-drush/compare/4.1.0...4.2.0 143 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Robo Drush Extension 2 | 3 | Extension to execute Drush commands in [Robo](https://github.com/Codegyre/Robo). 4 | 5 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/1d842f01-b2c4-415a-b372-12af7a5516e0/mini.png)](https://insight.sensiolabs.com/projects/1d842f01-b2c4-415a-b372-12af7a5516e0) [![Build Status](https://travis-ci.org/boedah/robo-drush.svg?branch=master)](https://travis-ci.org/boedah/robo-drush) [![Latest Stable Version](https://poser.pugx.org/boedah/robo-drush/v/stable)](https://packagist.org/packages/boedah/robo-drush) [![Total Downloads](https://poser.pugx.org/boedah/robo-drush/downloads)](https://packagist.org/packages/boedah/robo-drush) [![Latest Unstable Version](https://poser.pugx.org/boedah/robo-drush/v/unstable)](https://packagist.org/packages/boedah/robo-drush) [![License](https://poser.pugx.org/boedah/robo-drush/license)](https://packagist.org/packages/boedah/robo-drush) 6 | 7 | Runs Drush commands in stack. You can define global options for all commands (like Drupal root and uri). 8 | 9 | The option -y assumed by default but can be overridden on calls to `exec()` by passing `false` as the second parameter. 10 | 11 | ## Table of contents 12 | 13 | - [Versions](#versions) 14 | - [Installation](#installation) 15 | - [Testing](#testing) 16 | - [Usage](#usage) 17 | - [Examples](#examples) 18 | 19 | ## Installation 20 | 21 | For new projects (and Robo >= 1.0.0-RC1), just do: 22 | 23 | composer require --dev boedah/robo-drush 24 | 25 | For older versions of Robo, use: 26 | 27 | - `~1.0`: Robo <= 0.4.5 28 | - `~2.1`: Robo >= 0.5.2 29 | 30 | ## Testing 31 | 32 | `composer test` 33 | 34 | ## Usage 35 | 36 | Use the trait (according to your used version) in your RoboFile: 37 | 38 | ```php 39 | class RoboFile extends \Robo\Tasks 40 | { 41 | // if you use robo-drush ~2.1 for Robo >=0.5.2, or robo-drush >3 for Robo >=1.0.0-RC1 42 | use \Boedah\Robo\Task\Drush\loadTasks; 43 | 44 | // if you use ~1.0 for Robo ~0.4 45 | use \Boedah\Robo\Task\Drush; 46 | 47 | //... 48 | } 49 | ``` 50 | 51 | ## Examples 52 | 53 | ### Site update 54 | 55 | This executes pending database updates and reverts all features (from code to database): 56 | 57 | ```php 58 | $this->taskDrushStack() 59 | ->drupalRootDirectory('/var/www/html/some-site') 60 | ->uri('sub.example.com') 61 | ->maintenanceOn() 62 | ->updateDb() 63 | ->revertAllFeatures() 64 | ->maintenanceOff() 65 | ->run(); 66 | ``` 67 | 68 | ### Site install 69 | 70 | ```php 71 | $this->taskDrushStack() 72 | ->siteName('Site Name') 73 | ->siteMail('site-mail@example.com') 74 | ->locale('de') 75 | ->accountMail('mail@example.com') 76 | ->accountName('admin') 77 | ->accountPass('pw') 78 | ->dbPrefix('drupal_') 79 | ->sqliteDbUrl('sites/default/.ht.sqlite') 80 | ->disableUpdateStatusModule() 81 | ->siteInstall('minimal') 82 | ->run(); 83 | ``` 84 | -------------------------------------------------------------------------------- /RoboFile.php: -------------------------------------------------------------------------------- 1 | stopOnFail(true); 10 | $this->taskPHPUnit() 11 | ->option('disallow-test-output') 12 | ->option('report-useless-tests') 13 | ->option('strict-coverage') 14 | ->option('-v') 15 | ->option('-d error_reporting=-1') 16 | ->bootstrap('vendor/autoload.php') 17 | ->arg('tests') 18 | ->run(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boedah/robo-drush", 3 | "description": "Drush CommandStack for Robo Task Runner", 4 | "type": "robo-tasks", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Peter Gasser", 9 | "email": "dev@boedah.de" 10 | } 11 | ], 12 | "scripts": { 13 | "test": "robo --ansi test" 14 | }, 15 | "autoload": { 16 | "psr-4": { 17 | "Boedah\\Robo\\Task\\Drush\\": "src" 18 | } 19 | }, 20 | "require": { 21 | "php": ">=5.5.0", 22 | "consolidation/robo": "~1" 23 | }, 24 | "require-dev": { 25 | "phpunit/phpunit": "~4.4", 26 | "drush/drush": "^8.0 | ^9.0" 27 | }, 28 | "suggest": { 29 | "drush/drush": "robo-drush needs a global or local Drush to use." 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/DrushStack.php: -------------------------------------------------------------------------------- 1 | taskDrushStack() 15 | * ->drupalRootDirectory('/var/www/html/some-site') 16 | * ->uri('sub.example.com') 17 | * ->maintenanceOn() 18 | * ->updateDb() 19 | * ->revertAllFeatures() 20 | * ->maintenanceOff() 21 | * ->run(); 22 | * ``` 23 | * 24 | * Example site install command: 25 | * 26 | * ``` php 27 | * $this->taskDrushStack() 28 | * ->siteName('Site Name') 29 | * ->siteMail('site-mail@example.com') 30 | * ->locale('de') 31 | * ->accountMail('mail@example.com') 32 | * ->accountName('admin') 33 | * ->accountPass('pw') 34 | * ->dbPrefix('drupal_') 35 | * ->sqliteDbUrl('sites/default/.ht.sqlite') 36 | * ->disableUpdateStatusModule() 37 | * ->siteInstall('minimal') 38 | * ->run(); 39 | * ``` 40 | */ 41 | class DrushStack extends CommandStack 42 | { 43 | use CommandArguments; 44 | 45 | protected $argumentsForNextCommand; 46 | 47 | /** 48 | * Drush site alias. 49 | * We need to save this, since it needs to be the first argument. 50 | * 51 | * @var string 52 | */ 53 | protected $siteAlias; 54 | 55 | /** 56 | * @var string 57 | */ 58 | protected $drushVersion; 59 | 60 | public function __construct($pathToDrush = 'drush') 61 | { 62 | $this->executable = $pathToDrush; 63 | } 64 | 65 | public function drupalRootDirectory($drupalRootDirectory) 66 | { 67 | $this->printTaskInfo('Drupal root: ' . $drupalRootDirectory . ''); 68 | $this->option('-r', $drupalRootDirectory); 69 | 70 | return $this; 71 | } 72 | 73 | public function uri($uri) 74 | { 75 | $this->printTaskInfo('URI: ' . $uri . ''); 76 | $this->option('-l', $uri); 77 | 78 | return $this; 79 | } 80 | 81 | public function siteAlias($alias) 82 | { 83 | $this->printTaskInfo('Site Alias: ' . $alias . ''); 84 | $this->siteAlias = $alias; 85 | 86 | return $this; 87 | } 88 | 89 | public function debug() 90 | { 91 | $this->option('-d'); 92 | 93 | return $this; 94 | } 95 | 96 | public function verbose() 97 | { 98 | $this->option('-v'); 99 | 100 | return $this; 101 | } 102 | 103 | public function simulate() 104 | { 105 | $this->option('-s'); 106 | 107 | return $this; 108 | } 109 | 110 | public function siteName($siteName) 111 | { 112 | $this->argForNextCommand('--site-name=' . escapeshellarg($siteName)); 113 | 114 | return $this; 115 | } 116 | 117 | /** 118 | * Add an argument used in the next invocation of drush. 119 | * 120 | * @param string $arg 121 | * 122 | * @return $this 123 | */ 124 | protected function argForNextCommand($arg) 125 | { 126 | return $this->argsForNextCommand($arg); 127 | } 128 | 129 | /** 130 | * Add multiple arguments used in the next invocation of drush. 131 | * 132 | * @param array|string $args can also be multiple parameters 133 | * 134 | * @return $this 135 | */ 136 | protected function argsForNextCommand($args) 137 | { 138 | if (!is_array($args)) { 139 | $args = func_get_args(); 140 | } 141 | $this->argumentsForNextCommand .= ' ' . implode(' ', $args); 142 | 143 | return $this; 144 | } 145 | 146 | public function siteMail($siteMail) 147 | { 148 | $this->argForNextCommand('--site-mail=' . $siteMail); 149 | 150 | return $this; 151 | } 152 | 153 | public function sitesSubdir($sitesSubdir) 154 | { 155 | $this->argForNextCommand('--sites-subdir=' . $sitesSubdir); 156 | 157 | return $this; 158 | } 159 | 160 | public function locale($locale) 161 | { 162 | $this->argForNextCommand('--locale=' . $locale); 163 | 164 | return $this; 165 | } 166 | 167 | public function accountMail($accountMail) 168 | { 169 | $this->argForNextCommand('--account-mail=' . $accountMail); 170 | 171 | return $this; 172 | } 173 | 174 | public function accountName($accountName) 175 | { 176 | $this->argForNextCommand('--account-name=' . escapeshellarg($accountName)); 177 | 178 | return $this; 179 | } 180 | 181 | public function accountPass($accountPass) 182 | { 183 | $this->argForNextCommand('--account-pass=' . escapeshellarg($accountPass)); 184 | 185 | return $this; 186 | } 187 | 188 | public function dbPrefix($dbPrefix) 189 | { 190 | $this->argForNextCommand('--db-prefix=' . $dbPrefix); 191 | 192 | return $this; 193 | } 194 | 195 | public function dbSu($dbSu) 196 | { 197 | $this->argForNextCommand('--db-su=' . $dbSu); 198 | 199 | return $this; 200 | } 201 | 202 | public function dbSuPw($dbSuPw) 203 | { 204 | $this->argForNextCommand('--db-su-pw=' . escapeshellarg($dbSuPw)); 205 | 206 | return $this; 207 | } 208 | 209 | public function sqliteDbUrl($relativePath) 210 | { 211 | return $this->dbUrl('sqlite://' . $relativePath); 212 | } 213 | 214 | public function dbUrl($dbUrl) 215 | { 216 | $this->argForNextCommand('--db-url=' . escapeshellarg($dbUrl)); 217 | 218 | return $this; 219 | } 220 | 221 | public function mysqlDbUrl($dsn) 222 | { 223 | return $this->dbUrl('mysql://' . $dsn); 224 | } 225 | 226 | public function disableUpdateStatusModule() 227 | { 228 | $this->argForNextCommand('install_configure_form.update_status_module=0'); 229 | 230 | return $this; 231 | } 232 | 233 | public function configDir($configDir) 234 | { 235 | $this->argForNextCommand('--config-dir=' . $configDir); 236 | 237 | return $this; 238 | } 239 | 240 | public function existingConfig($existingConfig = true) 241 | { 242 | if ($existingConfig) { 243 | $this->argForNextCommand('--existing-config'); 244 | } 245 | 246 | return $this; 247 | } 248 | 249 | /** 250 | * Executes `drush status` 251 | * 252 | * @return $this 253 | */ 254 | public function status() 255 | { 256 | return $this->drush('status'); 257 | } 258 | 259 | /** 260 | * Runs the given drush command. 261 | * 262 | * @param string $command 263 | * @param bool $assumeYes 264 | * 265 | * @return $this 266 | */ 267 | public function drush($command, $assumeYes = true) 268 | { 269 | if (is_array($command)) { 270 | $command = implode(' ', array_filter($command)); 271 | } 272 | 273 | return $this->exec($this->injectArguments($command, $assumeYes)); 274 | } 275 | 276 | /** 277 | * Prepends site-alias and appends arguments to the command. 278 | * 279 | * @param string $command 280 | * @param bool $assumeYes 281 | * 282 | * @return string the modified command string 283 | */ 284 | protected function injectArguments($command, $assumeYes) 285 | { 286 | $cmd = 287 | $this->siteAlias . ' ' 288 | . $command 289 | . ($assumeYes ? ' -y': '') 290 | . $this->arguments 291 | . $this->argumentsForNextCommand; 292 | $this->argumentsForNextCommand = ''; 293 | 294 | return $cmd; 295 | } 296 | 297 | /** 298 | * Runs pending database updates. 299 | * 300 | * @return $this 301 | */ 302 | public function updateDb() 303 | { 304 | $this->printTaskInfo('Do database updates'); 305 | $this->drush('updb'); 306 | $drushVersion = $this->getVersion(); 307 | if (-1 === version_compare($drushVersion, '6.0')) { 308 | $this->printTaskInfo('Will clear cache after db updates for drush ' 309 | . $drushVersion); 310 | $this->clearCache(); 311 | } else { 312 | $this->printTaskInfo('Will not clear cache after db updates, since drush ' 313 | . $drushVersion . ' should do it automatically'); 314 | } 315 | 316 | return $this; 317 | } 318 | 319 | /** 320 | * Returns the drush version. 321 | * 322 | * @return string 323 | */ 324 | public function getVersion() 325 | { 326 | if (empty($this->drushVersion)) { 327 | $isPrinted = $this->isPrinted; 328 | $this->isPrinted = false; 329 | $result = $this->executeCommand($this->executable . ' version'); 330 | $output = $result->getMessage(); 331 | $this->drushVersion = 'unknown'; 332 | if (preg_match('#[0-9.]+#', $output, $matches)) { 333 | $this->drushVersion = $matches[0]; 334 | } 335 | $this->isPrinted = $isPrinted; 336 | } 337 | 338 | return $this->drushVersion; 339 | } 340 | 341 | /** 342 | * Clears the given cache. 343 | * 344 | * @param string $name cache name 345 | * 346 | * @return $this 347 | */ 348 | public function clearCache($name = 'all') 349 | { 350 | $this->printTaskInfo('Clear cache'); 351 | 352 | return $this->drush('cc ' . $name); 353 | } 354 | 355 | /** 356 | * @param bool $force force revert even if Features assumes components' state are default 357 | * @param string $excludedFeatures space-delimited list of features to exclude from being reverted 358 | * 359 | * @return $this 360 | */ 361 | public function revertAllFeatures($force = false, $excludedFeatures = '') 362 | { 363 | $this->printTaskInfo('Revert all features'); 364 | $args = $excludedFeatures . ($force ? ' --force' : ''); 365 | 366 | return $this->drush('fra ' . $args); 367 | } 368 | 369 | /** 370 | * Enables the maintenance mode. 371 | * 372 | * @return $this 373 | */ 374 | public function maintenanceOn() 375 | { 376 | $this->printTaskInfo('Turn maintenance mode on'); 377 | 378 | return $this->drush('vset --exact maintenance_mode 1'); 379 | } 380 | 381 | /** 382 | * Disables the maintenance mode. 383 | * 384 | * @return $this 385 | */ 386 | public function maintenanceOff() 387 | { 388 | $this->printTaskInfo('Turn maintenance mode off'); 389 | 390 | return $this->drush('vdel --exact maintenance_mode'); 391 | } 392 | 393 | /** 394 | * @param string $installationProfile 395 | * 396 | * @return $this 397 | */ 398 | public function siteInstall($installationProfile) 399 | { 400 | return $this->drush('site-install ' . $installationProfile); 401 | } 402 | } 403 | -------------------------------------------------------------------------------- /src/loadTasks.php: -------------------------------------------------------------------------------- 1 | task(DrushStack::class, $pathToDrush); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/DrushStackTest.php: -------------------------------------------------------------------------------- 1 | setContainer($container); 31 | 32 | // Prepare temp directory. 33 | $this->fs = new Filesystem(); 34 | $this->tmpDir = realpath(sys_get_temp_dir()) . DIRECTORY_SEPARATOR . 'robo-drush'; 35 | } 36 | 37 | // Scaffold the collection builder 38 | public function collectionBuilder() 39 | { 40 | $emptyRobofile = new \Robo\Tasks; 41 | 42 | return $this->getContainer()->get('collectionBuilder', [$emptyRobofile]); 43 | } 44 | 45 | public function testYesIsAssumed() 46 | { 47 | $command = $this->taskDrushStack() 48 | ->drush('command') 49 | ->getCommand(); 50 | $this->assertEquals('drush command -y', $command); 51 | } 52 | 53 | public function testAbsenceofYes() 54 | { 55 | $command = $this->taskDrushStack() 56 | ->drush('command', false) 57 | ->getCommand(); 58 | $this->assertEquals('drush command', $command); 59 | } 60 | 61 | public function testOptionsArePrependedBeforeEachCommand() 62 | { 63 | $command = $this->taskDrushStack() 64 | ->drupalRootDirectory('/var/www/html/app') 65 | ->drush('command-1') 66 | ->drush('command-2') 67 | ->getCommand(); 68 | $this->assertEquals(2, preg_match_all('#-r /var/www/html/app#', $command)); 69 | } 70 | 71 | public function testSiteInstallCommand() 72 | { 73 | $pw = 'p"|&w'; 74 | $command = $this->taskDrushStack() 75 | ->siteName('Site Name') 76 | ->siteMail('site-mail@example.com') 77 | ->locale('de') 78 | ->accountMail('mail@example.com') 79 | ->accountName('admin') 80 | ->accountPass($pw) 81 | ->dbPrefix('drupal_') 82 | ->dbSu('su_account') 83 | ->dbSuPw($pw) 84 | ->sqliteDbUrl('sit"es/default/.ht.sqlite') 85 | ->disableUpdateStatusModule() 86 | ->siteInstall('minimal') 87 | ->getCommand(); 88 | $expected = 'drush site-install minimal -y --site-name=' . escapeshellarg('Site Name') 89 | . ' --site-mail=site-mail@example.com' 90 | . ' --locale=de --account-mail=mail@example.com --account-name=' . escapeshellarg('admin') 91 | . ' --account-pass=' . escapeshellarg($pw) 92 | . ' --db-prefix=drupal_ --db-su=su_account --db-su-pw=' . escapeshellarg($pw) . ' --db-url=' . escapeshellarg('sqlite://sit"es/default/.ht.sqlite') 93 | . ' install_configure_form.update_status_module=0'; 94 | $this->assertEquals($expected, $command); 95 | } 96 | 97 | public function testExistingConfigDefaultsToTrue() 98 | { 99 | $command = $this->taskDrushStack() 100 | ->existingConfig() 101 | ->siteInstall('minimal') 102 | ->getCommand(); 103 | $expected = 'drush site-install minimal -y --existing-config'; 104 | $this->assertEquals($expected, $command); 105 | } 106 | 107 | /** 108 | * @dataProvider existingConfigWithBooleanParamIsRespectedProvider 109 | * 110 | * @param mixed $existingConfigParam 111 | * @param string $commandParam 112 | */ 113 | public function testExistingConfigWithBooleanParamIsRespected( 114 | $existingConfigParam, 115 | $commandParam = ' --existing-config' 116 | ) { 117 | $command = $this->taskDrushStack() 118 | ->existingConfig($existingConfigParam) 119 | ->siteInstall('minimal') 120 | ->getCommand(); 121 | $expected = 'drush site-install minimal -y' . $commandParam; 122 | $this->assertEquals($expected, $command); 123 | } 124 | 125 | public function existingConfigWithBooleanParamIsRespectedProvider() 126 | { 127 | return [ 128 | // trueish 129 | 'true' => [true], 130 | '1' => [1], 131 | '"1"' => ['1'], 132 | // falsish 133 | 'false' => [false, ''], 134 | '0' => [0, ''], 135 | '"0"' => ['0', ''], 136 | 'null' => [null, ''], 137 | 'empty string' => ['', ''], 138 | ]; 139 | } 140 | 141 | public function testSiteAliasIsFirstOption() 142 | { 143 | $command = $this->taskDrushStack() 144 | ->drupalRootDirectory('/var/www/html/app') 145 | ->siteAlias('@qa') 146 | ->drush('command-1') 147 | ->drush('command-2') 148 | ->getCommand(); 149 | $this->assertEquals(2, preg_match_all('#drush @qa comm#', $command)); 150 | } 151 | 152 | public function testDrushStatus() 153 | { 154 | $result = $this->taskDrushStack(__DIR__ . '/../vendor/bin/drush') 155 | ->printed(false) 156 | ->status() 157 | ->run(); 158 | $this->assertTrue($result->wasSuccessful(), 'Exit code was: ' . $result->getExitCode()); 159 | } 160 | 161 | /** 162 | * @dataProvider drushVersionProvider 163 | * 164 | * @param string $composerDrushVersion version to require with composer (can be different e.g. for RC versions) 165 | * @param string $expectedVersion version to compare 166 | */ 167 | public function testDrushVersion($composerDrushVersion, $expectedVersion = null) 168 | { 169 | if (null === $expectedVersion) { 170 | $expectedVersion = $composerDrushVersion; 171 | } 172 | if (version_compare('5.6', phpversion()) > 0 && version_compare($expectedVersion, '9.0') > 0) { 173 | $this->markTestSkipped(phpversion() . ' too low for drush ' . $expectedVersion); 174 | } 175 | 176 | $cwd = getcwd(); 177 | $this->ensureDirectoryExistsAndClear($this->tmpDir); 178 | chdir($this->tmpDir); 179 | $this->writeComposerJSON(); 180 | $this->composer('require --no-progress --no-suggest --update-with-dependencies drush/drush:"' . $composerDrushVersion . '"'); 181 | $actualVersion = $this->taskDrushStack($this->tmpDir . '/vendor/bin/drush') 182 | ->getVersion(); 183 | $this->assertEquals($expectedVersion, $actualVersion); 184 | chdir($cwd); 185 | } 186 | 187 | /** 188 | * Should return an array of arrays with the following values: 189 | * 0: $composerDrushVersion (can be different e.g. for RC versions) 190 | * 1: $expectedVersion 191 | * 192 | * @return array 193 | */ 194 | public function drushVersionProvider() 195 | { 196 | return [ 197 | '8' => ['8.1.15'], 198 | '9-rc1' => ['9.0.0-rc1', '9.0.0'], 199 | '9' => ['9.4.0'], 200 | ]; 201 | } 202 | 203 | /** 204 | * Writes the default composer json to the temp directory. 205 | */ 206 | protected function writeComposerJSON() 207 | { 208 | $json = json_encode($this->composerJSONDefaults(), JSON_PRETTY_PRINT); 209 | file_put_contents($this->tmpDir . '/composer.json', $json); 210 | } 211 | 212 | /** 213 | * Provides the default composer.json data. 214 | * 215 | * @return array 216 | */ 217 | protected function composerJSONDefaults() 218 | { 219 | return array( 220 | 'minimum-stability' => 'beta' 221 | ); 222 | } 223 | 224 | /** 225 | * Wrapper for the composer command. 226 | * 227 | * @param string $command composer command name, arguments and/or options 228 | * 229 | * @throws RuntimeException 230 | */ 231 | protected function composer($command) 232 | { 233 | exec(escapeshellcmd('composer -q ' . $command), $output, $exitCode); 234 | if ($exitCode !== 0) { 235 | throw new \RuntimeException('Composer returned a non-zero exit code.'); 236 | } 237 | } 238 | 239 | /** 240 | * Makes sure the given directory exists and has no content. 241 | * 242 | * @param string $directory 243 | */ 244 | protected function ensureDirectoryExistsAndClear($directory) 245 | { 246 | if (is_dir($directory)) { 247 | $this->fs->remove($directory); 248 | } 249 | $this->fs->mkdir($directory, 0777); 250 | } 251 | } 252 | --------------------------------------------------------------------------------